├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .nvmrc ├── .wp-env.json ├── README.md ├── classes ├── Hooks.php ├── Plugin.php ├── Rest │ ├── Api.php │ └── Controllers │ │ ├── Controller.php │ │ └── Settings.php ├── Settings.php ├── SettingsPage.php └── index.php ├── composer.json ├── composer.lock ├── makeZip.js ├── package-lock.json ├── package.json ├── phpcs.xml.dist ├── phpunit.xml.dist ├── pm2-modern-plugin.php ├── src ├── api │ └── useSettings.js ├── block │ ├── block.json │ ├── edit.js │ ├── editor.scss │ ├── index.js │ ├── save.js │ └── style.scss ├── components │ ├── ApiKeyField.js │ └── RunOnce.js ├── editor.scss ├── index.php └── settings │ ├── index.js │ ├── otherTab.js │ ├── settingsTab.js │ └── style.css ├── tests ├── WordPress │ ├── SettingsTest.php │ ├── SetupTest.php │ └── TestCase.php ├── bootstrap.php ├── index.php └── wp-config.php └── webpack.config.js /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | # Run on 4 | on: 5 | push: 6 | # any branch 7 | branches: 8 | - '*' 9 | jobs: 10 | tests: 11 | name: Install And Test 12 | runs-on: ubuntu-20.04 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Node 18 | uses: actions/setup-node@v3 19 | with: 20 | cache: 'npm' 21 | node-version-file: '.nvmrc' 22 | 23 | - name: Setup PHP and Composer 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: '7.4' 27 | tools: composer:v2 28 | 29 | - name: Install NPM dependencies 30 | run: npm install 31 | 32 | - name: Start the Docker testing environment 33 | run: npm run env start --xdebug=coverage 34 | 35 | - name: Ensure uploads dir exists first 36 | run: mkdir -p wordpress/wp-content/uploads 37 | 38 | - name: Test 39 | run: npm run test:php 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | /npm-debug.log 4 | /vendor/ 5 | /dist/ 6 | /tests/logs/ 7 | /wordpress/ 8 | build 9 | .phpunit.result.cache 10 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 17 2 | -------------------------------------------------------------------------------- /.wp-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": "WordPress/WordPress", 3 | "plugins": [ 4 | "." 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PLUGIN_NAME 2 | 3 | Put Your Description Here 4 | 5 | [![Built With Plugin Machine (v2)](https://img.shields.io/badge/Built%20With-Plugin%20Machine-lightgrey)](https://pluginmachine.com) 6 | 7 | 8 | ## Developopment 9 | 10 | - Clone 11 | - git@github.com:GITHUB_ORG/GITHUB_REPO.git 12 | - Install 13 | - `npm i` 14 | - Installs with npm and composer 15 | - Start environment 16 | - `npm run env start` 17 | - Uses [@wordpres/env](https://www.npmjs.com/package/@wordpress/env) 18 | - [Requires Docker](https://www.docker.com/products/docker-desktop/) 19 | - Test PHP 20 | - `npm run test:php` 21 | - Format JS, CSS and PHP 22 | - `npm run format` 23 | - Run linter, without fixing code 24 | - `npm run lint` 25 | - Build JS/CSS 26 | - `npm run build` 27 | - Create zip 28 | - `npm run zip` 29 | - Installs with composer optimized 30 | - Builds CSS/JS/Blocks 31 | - Makes a new zip 32 | -------------------------------------------------------------------------------- /classes/Hooks.php: -------------------------------------------------------------------------------- 1 | plugin = $plugin; 23 | $this->settingsPage = new SettingsPage($plugin); 24 | } 25 | 26 | /** 27 | * Register all hooks 28 | */ 29 | public function addHooks() { 30 | add_action( 'plugins_loaded', [$this->plugin, 'pluginLoaded']); 31 | add_action( 'admin_menu', [$this->settingsPage, 'addPage' ]); 32 | add_action( 'rest_api_init', [$this->plugin->getRestApi(), 'registerRoutes']); 33 | add_action( 'admin_enqueue_scripts', [$this->settingsPage, 'registerAssets']); 34 | } 35 | 36 | /** 37 | * Remove Hooks 38 | */ 39 | public function removeHooks() { 40 | remove_action( 'plugins_loaded', [$this->plugin, 'pluginLoaded']); 41 | remove_action( 'admin_menu', [$this->settingsPage, 'addPage' ]); 42 | remove_action( 'rest_api_init', [$this->plugin->getRestApi(), 'registerRoutes']); 43 | remove_action( 'admin_enqueue_scripts', [$this->settingsPage, 'registerAssets'] ); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /classes/Plugin.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 44 | } 45 | 46 | /** 47 | * Initialize the plugin 48 | * 49 | * @since 0.0.1 50 | * 51 | * @uses "ACTION_PREFIX_init" action 52 | * 53 | * @return void 54 | */ 55 | public function init(){ 56 | if( ! isset($this->api) ){ 57 | $this->api = new Api( $this ); 58 | } 59 | $this->hooks = new Hooks( $this ); 60 | $this->hooks->addHooks(); 61 | } 62 | 63 | 64 | /** 65 | * When the plugin is loaded: 66 | * - Load the plugin's text domain. 67 | * 68 | * @uses "plugins_loaded" action 69 | * 70 | */ 71 | public function pluginLoaded(){ 72 | load_plugin_textdomain( 'pm2-modern-plugin' ); 73 | } 74 | 75 | /** 76 | * Get Settings 77 | * 78 | * @since 0.0.1 79 | * 80 | * @return Settings 81 | */ 82 | public function getSettings() { 83 | return $this->settings; 84 | } 85 | 86 | /** 87 | * Get API 88 | * 89 | * @since 0.0.1 90 | * 91 | * @return Api 92 | */ 93 | public function getRestApi() { 94 | return $this->api; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /classes/Rest/Api.php: -------------------------------------------------------------------------------- 1 | plugin = $plugin; 39 | } 40 | 41 | /** 42 | * Register all routes 43 | * 44 | * @since 1.0.0 45 | * 46 | * @uses "rest_api_init" action 47 | * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/ 48 | * @return void 49 | */ 50 | public function registerRoutes() { 51 | $controller = new SettingsController( $this->plugin ); 52 | register_rest_route( $this->namespace, '/settings', [ 53 | [ 54 | 'methods' => 'GET', 55 | 'callback' => [ $controller, 'get' ], 56 | 'permission_callback' => [ $controller, 'authorize' ], 57 | ], 58 | [ 59 | 'methods' => 'POST', 60 | 'callback' => [ $controller, 'update' ], 61 | 'permission_callback' => [ $controller, 'authorize' ], 62 | 'args' => [ 63 | 'apiKey' => [ 64 | 'required' => true, 65 | 'sanitize_callback' => 'sanitize_text_field', 66 | 'type' => 'string', 67 | ] 68 | ] 69 | ], 70 | ] ); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /classes/Rest/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | plugin = $plugin; 28 | } 29 | 30 | /** 31 | * Default permission_callback 32 | * 33 | * @param \WP_REST_Request $request 34 | * @return bool 35 | */ 36 | public function authorize( $request ) { 37 | $capability = is_multisite() ? 'delete_sites' : 'manage_options'; 38 | /** 39 | * Filter the capability required to access the endpoint. 40 | * 41 | * @param string $capability 42 | * @param Controller $this 43 | * @param \WP_REST_Request $request 44 | */ 45 | $capability = apply_filters( 'ACTION_PREFIX_rest_authorize_capability', $capability, $this, $request ); 46 | return current_user_can( $capability ); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /classes/Rest/Controllers/Settings.php: -------------------------------------------------------------------------------- 1 | plugin->getSettings()->getAll(); 15 | return [ 16 | 'settings' => $settings 17 | ]; 18 | } 19 | 20 | /** 21 | * Update settings via API 22 | * 23 | * @param \WP_REST_Request $request 24 | * @return array 25 | */ 26 | public function update($request ){ 27 | $key = $request->get_param('apiKey'); 28 | $this->plugin->getSettings()->save([ 29 | 'apiKey' => $key 30 | ]); 31 | $settings = $this->plugin->getSettings()->getAll(); 32 | return [ 33 | 'settings' => $settings 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /classes/Settings.php: -------------------------------------------------------------------------------- 1 | '', 27 | ]; 28 | 29 | /** 30 | * Save settings 31 | * 32 | * @since 0.0.1 33 | * 34 | * @param array $data 35 | * @return void 36 | */ 37 | public function save(array $data) 38 | { 39 | $data = array_merge($this->defaults, $data); 40 | /** 41 | * Filter settings before saving 42 | * 43 | * @since 0.0.1 44 | * @param array $data Data to save 45 | */ 46 | $data = apply_filters('ACTION_PREFIX_save_settings', $data); 47 | update_option($this->optionName,$data); 48 | } 49 | /** 50 | * Get all settings 51 | * 52 | * @since 0.0.1 53 | * 54 | * @return array 55 | */ 56 | public function getAll() 57 | { 58 | $values = get_option($this->optionName, $this->defaults); 59 | /** 60 | * Filter settings before returning 61 | * 62 | * @since 0.0.1 63 | * @param array $values Settings 64 | */ 65 | return apply_filters('ACTION_PREFIX_get_settings', $values); 66 | } 67 | 68 | /** 69 | * Get defaults for settings 70 | * @return array 71 | */ 72 | public function getDefaults() 73 | { 74 | return $this->defaults; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /classes/SettingsPage.php: -------------------------------------------------------------------------------- 1 | plugin = $plugin; 24 | } 25 | 26 | /** 27 | * Register assets 28 | * 29 | * @since 0.0.1 30 | * 31 | * @uses "admin_enqueue_scripts" action 32 | */ 33 | public function registerAssets(){ 34 | $dependencies = []; 35 | $version = PM2_MODERN_VERSION; 36 | 37 | // Use asset file if it exists 38 | if ( file_exists( PM2_MODERN_PLUGIN_DIR . 'build/settings.asset.php' ) ) { 39 | $asset_file = include PM2_MODERN_PLUGIN_DIR . 'build/settings.asset.php'; 40 | $dependencies = $asset_file['dependencies']; 41 | $version = $asset_file['version']; 42 | 43 | } 44 | 45 | wp_register_script( 46 | SettingsPage::SCREEN, 47 | plugins_url( 'build/settings.js', PM2_MODERN_MAIN_FILE ), 48 | $dependencies, 49 | $version, 50 | ); 51 | } 52 | /** 53 | * Adds the settings page to the Settings menu. 54 | * 55 | * @since 0.0.1 56 | * 57 | * @return string 58 | */ 59 | public function addPage() 60 | { 61 | 62 | // Add the page 63 | $suffix = add_options_page( 64 | __('PLUGIN_NAME', 'pm2-modern-plugin'), 65 | __('PLUGIN_NAME', 'pm2-modern-plugin'), 66 | 'manage_options', 67 | self::SCREEN, 68 | [ 69 | $this, 70 | 'renderPage', 71 | ] 72 | ); 73 | 74 | // This adds a link in the plugins list table 75 | add_action( 76 | 'plugin_action_links_' . plugin_basename(PM2_MODERN_MAIN_FILE), 77 | [ 78 | $this, 79 | 'addLinks', 80 | ] 81 | ); 82 | 83 | return $suffix; 84 | } 85 | 86 | /** 87 | * Adds a link to the setting page to the plugin's entry in the plugins list table. 88 | * 89 | * @since 1.0.0 90 | * 91 | * @param array $links List of plugin action links HTML. 92 | * @return array Modified list of plugin action links HTML. 93 | */ 94 | public function addLinks($links) 95 | { 96 | // Add link as the first plugin action link. 97 | $settings_link = sprintf( 98 | '%s', 99 | esc_url(add_query_arg('page', self::SCREEN, admin_url('options-general.php'))), 100 | esc_html__('Settings', 'pm2-modern-plugin') 101 | ); 102 | array_unshift($links, $settings_link); 103 | 104 | 105 | return $links; 106 | } 107 | 108 | /** 109 | * Renders the settings page. 110 | * 111 | * @since 0.0.1 112 | */ 113 | public function renderPage() 114 | { 115 | wp_enqueue_script(self::SCREEN); 116 | $settings = $this 117 | ->plugin 118 | ->getSettings() 119 | ->getAll(); 120 | wp_localize_script( 121 | self::SCREEN, 122 | 'ACTION_PREFIX', 123 | [ 124 | 'apiUrl' => rest_url('pm2-modern-plugin/v1'), 125 | 'settings' => $settings, 126 | 127 | ] 128 | ); 129 | ?> 130 |
131 |

132 | 133 |

134 |
135 |
136 | =7.4" 28 | }, 29 | "require-dev": { 30 | "automattic/vipwpcs": "^2.3", 31 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", 32 | "php-coveralls/php-coveralls": "^2.5", 33 | "phpcompatibility/php-compatibility": "10.x-dev as 9.99.99", 34 | "phpcompatibility/phpcompatibility-wp": "dev-master", 35 | "phpunit/phpunit": "^9.5", 36 | "roots/wordpress-core-installer": "^1.100", 37 | "roots/wordpress-full": "^6.0", 38 | "spatie/phpunit-watcher": "^1.23", 39 | "wp-coding-standards/wpcs": "^2.3", 40 | "wp-phpunit/wp-phpunit": "^6.0", 41 | "yoast/phpunit-polyfills": "^1.0" 42 | }, 43 | "autoload": { 44 | "psr-4": { 45 | "VendorNamespace\\PluginNamespace\\": "classes/", 46 | "VendorNamespace\\PluginNamespace\\Tests\\": "tests/WordPress/" 47 | } 48 | }, 49 | "scripts": { 50 | "lint": "phpcs", 51 | "lint-php8": "phpcs -p --standard=PHPCompatibilityWP --runtime-set testVersion 8.0- --extensions=php --ignore='vendor/,wordpress/,node_modules/' .", 52 | "test": "phpunit", 53 | "test:watch": [ 54 | "Composer\\Config::disableProcessTimeout", 55 | "phpunit-watcher watch" 56 | ], 57 | "format": "phpcbf" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /makeZip.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var archiver = require('archiver'); 3 | const slug = 'pm2-modern-plugin'; 4 | var output = fs.createWriteStream(`${slug}.zip`); 5 | var archive = archiver('zip'); 6 | 7 | console.log( 'Zipping!') 8 | output.on('close', function () { 9 | console.log('Zipped!'); 10 | console.log(archive.pointer() + ' total bytes'); 11 | }); 12 | 13 | archive.on('error', function(err){ 14 | throw err; 15 | }); 16 | 17 | archive.pipe(output); 18 | 19 | archive.append(fs.createReadStream( 20 | __dirname + `/${slug}.php` 21 | ), { name: `/${slug}.php` }); 22 | 23 | //Directories to copy 24 | ['classes', 'build', 'vendor/composer'].forEach( ( dir ) => { 25 | archive.directory(dir, '/' + dir); 26 | }); 27 | 28 | archive.append(fs.createReadStream( 29 | __dirname + '/vendor/autoload.php' 30 | ), { name: 'vendor/autoload.php' }); 31 | 32 | archive.finalize(); 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pm2-modern-plugin", 3 | "version": "1.0.0", 4 | "description": "PLUGIN_DESCRIPTION", 5 | "author": "PLUGIN_AUTHOR_NAME", 6 | "license": "GPL-2.0-or-later", 7 | "main": "build/index.js", 8 | "scripts": { 9 | "build": "wp-scripts build", 10 | "lint": "npm-run-all lint:*", 11 | "lint:php": "composer lint", 12 | "lint:css": "wp-scripts lint-style ./src/*.scss ./admin/*.css", 13 | "lint:js": "wp-scripts lint-js ./src", 14 | "format": "npm-run-all format:*", 15 | "format:php": "composer format", 16 | "format:js": "npm run lint:js -- --fix", 17 | "format:css": "npm run lint:css -- --fix", 18 | "packages-update": "wp-scripts packages-update", 19 | "plugin-zip": "wp-scripts plugin-zip", 20 | "start": "wp-scripts start", 21 | "env": "wp-env", 22 | "test:php": "npm run composer test", 23 | "test:watch": "npm run composer test:watch", 24 | "composer": "wp-env run phpunit composer --working-dir=/var/www/html/wp-content/plugins/pm2-modern-plugin", 25 | "preinstall": "composer install", 26 | "prezip:php": "composer install --no-dev -o", 27 | "prezip": "npm-run-all prezip:*", 28 | "prezip:js": "npm run build", 29 | "zip": "npm run prezip && node makeZip.js" 30 | }, 31 | "devDependencies": { 32 | "@wordpress/env": "^5.8.0", 33 | "@wordpress/scripts": "^25.0.0" 34 | }, 35 | "dependencies": { 36 | "@imaginary-machines/wp-admin-components": "^0.4.1", 37 | "@wordpress/hooks": "^3.23.0", 38 | "archiver": "^5.3.1", 39 | "npm-run-all": "^4.1.5", 40 | "react": "^18.2.0", 41 | "react-dom": "^18.2.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | . 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | tests/*.php 22 | tests/providers/*.php 23 | 24 | 25 | */wordpress/* 26 | */dist/* 27 | */includes/* 28 | */node_modules/* 29 | */vendor/* 30 | 31 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | tests/WordPress 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /pm2-modern-plugin.php: -------------------------------------------------------------------------------- 1 | init(); 49 | } 50 | ); 51 | /** 52 | * Start Plugin 53 | * 54 | * @since 1.0.0 55 | * @param Plugin $plugin 56 | */ 57 | do_action( 58 | 'ACTION_PREFIX_init', 59 | new Plugin( 60 | new Settings(), 61 | ) 62 | ); 63 | -------------------------------------------------------------------------------- /src/api/useSettings.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import apiFetch from '@wordpress/api-fetch'; 3 | 4 | //Function for saving settings 5 | const saveSettings = async ( values ) => { 6 | const r = await apiFetch( { 7 | path: '/pm2-modern-plugin/v1/settings', 8 | method: 'POST', 9 | data: values, 10 | } ).then( ( res ) => { 11 | return res; 12 | } ); 13 | return { update: r }; 14 | }; 15 | 16 | const getSettings = async () => { 17 | const r = await apiFetch( { 18 | path: '/pm2-modern-plugin/v1/settings', 19 | method: 'GET', 20 | } ).then( ( res ) => { 21 | return res; 22 | } ); 23 | return r; 24 | }; 25 | 26 | /** 27 | * Hook for using settings 28 | * 29 | * @return {Object} {saveSettings: function,getSettings:function, isLoaded: boolean, isSaving: boolean, hasSaved: boolean} 30 | */ 31 | export const useSettings = () => { 32 | const [ isSaving, setIsSaving ] = React.useState( false ); 33 | const [ hasSaved, setHasSaved ] = React.useState( false ); 34 | const [ isLoaded, setIsLoaded ] = React.useState( false ); 35 | //Reset the isSaving state after 2 seconds 36 | React.useEffect( () => { 37 | if ( hasSaved ) { 38 | const timer = setTimeout( () => { 39 | setIsSaving( false ); 40 | }, 2000 ); 41 | return () => clearTimeout( timer ); 42 | } 43 | }, [ hasSaved ] ); 44 | return { 45 | saveSettings: ( values ) => { 46 | setIsSaving( true ); 47 | saveSettings( values ).then( () => { 48 | setIsSaving( false ); 49 | setHasSaved( true ); 50 | } ); 51 | }, 52 | getSettings: () => { 53 | setIsLoaded( true ); 54 | getSettings().then( () => { 55 | setIsLoaded( false ); 56 | } ); 57 | }, 58 | isLoaded, 59 | isSaving, 60 | hasSaved, 61 | }; 62 | }; 63 | export default useSettings; 64 | -------------------------------------------------------------------------------- /src/block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schemas.wp.org/trunk/block.json", 3 | "apiVersion": 2, 4 | "name": "pm2-modern-plugin/block-name", 5 | "version": "0.1.0", 6 | "title": "BLOCK_TITLE", 7 | "category": "text", 8 | "icon": "smiley", 9 | "description": "BLOCK_DESCRIPTION", 10 | "supports": { 11 | "html": false 12 | }, 13 | "attributes": { 14 | "content": { 15 | "type": "string", 16 | "source": "html", 17 | "selector": "p" 18 | } 19 | }, 20 | "textdomain": "pm2-modern-plugin", 21 | "editorScript": "file:./index.js", 22 | "editorStyle": "file:./index.css", 23 | "style": "file:./style-index.css" 24 | } 25 | -------------------------------------------------------------------------------- /src/block/edit.js: -------------------------------------------------------------------------------- 1 | import { __ } from '@wordpress/i18n'; 2 | import React from 'react'; 3 | import { useBlockProps, BlockControls } from '@wordpress/block-editor'; 4 | import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; 5 | 6 | import './editor.scss'; 7 | 8 | export default function Edit( { attributes } ) { 9 | const content = attributes.content || ''; 10 | const handler = () => { 11 | // eslint-disable-next-line 12 | alert( 'Hello World!' ); 13 | }; 14 | 15 | return ( 16 |

17 | 18 | 19 | 24 | 25 | 26 | { content } 27 |

28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/block/editor.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * The following styles get applied inside the editor only. 3 | * 4 | * Replace them with your own styles or remove the file completely. 5 | */ 6 | 7 | .wp-block-ufo-ai-wp-ufo-ai-wp { 8 | border: 1px dotted #f00; 9 | } 10 | -------------------------------------------------------------------------------- /src/block/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ 3 | */ 4 | import { registerBlockType } from '@wordpress/blocks'; 5 | 6 | /** 7 | * @see https://www.npmjs.com/package/@wordpress/scripts#using-css 8 | */ 9 | import './style.scss'; 10 | 11 | /** 12 | * Internal dependencies 13 | */ 14 | import Edit from './edit'; 15 | import save from './save'; 16 | import metadata from './block.json'; 17 | 18 | registerBlockType( metadata.name, { 19 | /** 20 | * @see ./edit.js 21 | */ 22 | edit: Edit, 23 | 24 | /** 25 | * @see ./save.js 26 | */ 27 | save, 28 | } ); 29 | -------------------------------------------------------------------------------- /src/block/save.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React hook that is used to mark the block wrapper element. 3 | * It provides all the necessary props like the class name. 4 | * 5 | * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops 6 | */ 7 | import { useBlockProps } from '@wordpress/block-editor'; 8 | 9 | /** 10 | * The save function defines the way in which the different attributes should 11 | * be combined into the final markup, which is then serialized by the block 12 | * editor into `post_content`. 13 | * 14 | * @param {Object} root0 15 | * @param {{content:string}} root0.attributes 16 | * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#save 17 | * 18 | * @return {WPElement} Element to render. 19 | */ 20 | export default function save( { attributes } ) { 21 | const content = attributes.content || ''; 22 | return

{ content }

; 23 | } 24 | -------------------------------------------------------------------------------- /src/block/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * The following styles get applied both on the front of your site 3 | * and in the editor. 4 | * 5 | * Replace them with your own styles or remove the file completely. 6 | */ 7 | -------------------------------------------------------------------------------- /src/components/ApiKeyField.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { FieldTr, Input } from '@imaginary-machines/wp-admin-components'; 4 | import { Spinner } from '@wordpress/components'; 5 | 6 | const name = 'apiKey'; 7 | const label = 'Api Key'; 8 | const id = 'apiKey'; 9 | const ApiKeyField = ( { value, onChange, isSaving } ) => { 10 | return ( 11 | 12 | 19 | { ! isSaving ? : null } 20 | 21 | ); 22 | }; 23 | export default ApiKeyField; 24 | -------------------------------------------------------------------------------- /src/components/RunOnce.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | const RunOnce = ( { fn } ) => { 3 | useEffect( () => { 4 | fn(); 5 | }, [] ); 6 | return null; 7 | }; 8 | export default RunOnce; 9 | -------------------------------------------------------------------------------- /src/editor.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * The following styles get applied inside the editor only. 3 | * 4 | * Replace them with your own styles or remove the file completely. 5 | */ 6 | -------------------------------------------------------------------------------- /src/index.php: -------------------------------------------------------------------------------- 1 | , 11 | id: 'settings', 12 | label: 'Settings', 13 | }, 14 | { 15 | children: , 16 | id: 'otherTab', 17 | label: 'Other', 18 | }, 19 | ]; 20 | 21 | /** 22 | * Primary App Component 23 | */ 24 | const App = () => { 25 | return ; 26 | }; 27 | 28 | /** 29 | * Load the settings page 30 | */ 31 | render( , document.getElementById( 'pm2-modern-plugin-settings' ) ); 32 | -------------------------------------------------------------------------------- /src/settings/otherTab.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { __ } from '@wordpress/i18n'; 3 | 4 | import { Notice, Metabox } from '@imaginary-machines/wp-admin-components'; 5 | export default function OtherTab() { 6 | return ( 7 |
8 | 15 | 21 | 22 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam 23 | sollicitudin tortor lorem, a aliquet elit ultricies eu. In vitae 24 | enim et odio vehicula lacinia ac a tellus. Curabitur sodales, 25 | justo sodales tristique dignissim, nibh diam ultrices leo, ac 26 | vulputate quam felis eget metus. Suspendisse ac mauris sapien. 27 | In a velit finibus, viverra mi eget, lacinia risus. Mauris augue 28 | ex, vulputate vitae iaculis quis, ornare eget nibh. Etiam quis 29 | lacus nec nulla ullamcorper mattis nec nec ligula. Aenean diam 30 | velit, tristique et dolor a, varius convallis nulla. Mauris 31 | imperdiet molestie metus in ornare. 32 | 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/settings/settingsTab.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ExternalLink } from '@wordpress/components'; 3 | import { __ } from '@wordpress/i18n'; 4 | 5 | import { 6 | Form, 7 | FormTable, 8 | TrSubmitButton, 9 | } from '@imaginary-machines/wp-admin-components'; 10 | 11 | import ApiKeyField from '../components/ApiKeyField'; 12 | import useSettings from '../api/useSettings'; 13 | 14 | const SettingsForm = () => { 15 | const { isSaving, saveSettings } = useSettings(); 16 | const [ values, setValues ] = React.useState( () => { 17 | //Try to set defaults from localized data 18 | // eslint-disable-next-line no-undef 19 | if ( ACTION_PREFIX.settings ) { 20 | // eslint-disable-next-line no-undef 21 | return ACTION_PREFIX.settings; 22 | } 23 | return { 24 | apiKey: '', 25 | }; 26 | } ); 27 | const id = 'settings-form'; 28 | 29 | //Save settings handler 30 | const onSubmit = ( e ) => { 31 | e.preventDefault(); 32 | saveSettings( values ).then( ( { update } ) => { 33 | setValues( { ...values, update } ); 34 | } ); 35 | }; 36 | 37 | return ( 38 |
39 | 40 | { __( 'Documentation' ) } 41 | 42 |
43 | 44 | <> 45 | 48 | setValues( { ...values, key: value } ) 49 | } 50 | isSaving={ isSaving } 51 | /> 52 | 57 | <>{ isSaving ? 'Saving...' : '' } 58 | 59 | 60 |
61 |
62 | ); 63 | }; 64 | export default SettingsForm; 65 | -------------------------------------------------------------------------------- /src/settings/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ufo-ai-settings a.components-external-link { 4 | font-size: x-large; 5 | } 6 | 7 | .ufo-ai-wp-wrap h1 { 8 | font-size: 23px; 9 | font-weight: 400; 10 | margin: 0; 11 | padding: 9px 0 4px; 12 | line-height: 1.3; 13 | } 14 | -------------------------------------------------------------------------------- /tests/WordPress/SettingsTest.php: -------------------------------------------------------------------------------- 1 | save( 19 | ['apiKey' => $value] 20 | ); 21 | $this->assertEquals( 22 | ['apiKey' => $value], 23 | get_option('pm2-modern-plugin-settings') 24 | ); 25 | } 26 | 27 | 28 | /** 29 | * Can we get the default settings? 30 | * @group settings 31 | */ 32 | public function test_get_all_settings() { 33 | $settings = new Settings(); 34 | 35 | $current = $settings->getAll(); 36 | $this->assertEquals( 37 | $settings->getDefaults(), 38 | $current, 39 | ); 40 | $value = 'changed'; 41 | $settings->save( 42 | ['apiKey' => $value] 43 | ); 44 | $this->assertEquals( 45 | ['apiKey' => $value], 46 | $settings->getAll() 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/WordPress/SetupTest.php: -------------------------------------------------------------------------------- 1 | assertTrue( defined( 'PM2_MODERN_PLUGIN_DIR' ) ); 21 | $this->assertTrue( defined( 'PM2_MODERN_VERSION' ) ); 22 | $this->assertTrue( defined( 'PM2_MODERN_MAIN_FILE' ) ); 23 | } 24 | 25 | /** 26 | * Classes loaded? 27 | * - autoloader works 28 | * - files exist 29 | * 30 | * @group setup 31 | */ 32 | public function test_classes_exist() { 33 | 34 | $this->assertTrue( class_exists( Plugin::class ) ); 35 | } 36 | 37 | /** 38 | * Test adding hooks. 39 | * 40 | * @group setup 41 | * @group hooks 42 | */ 43 | public function test_add_hooks() { 44 | $plugin = $this->makePlugin(); 45 | $plugin->init(); 46 | $this->assertGreaterThan( 47 | 0, 48 | has_action( 49 | 'plugins_loaded', 50 | [$plugin,'pluginLoaded'] 51 | ) 52 | ); 53 | 54 | $this->assertGreaterThan( 55 | 0, 56 | has_action( 57 | 'rest_api_init', 58 | [$plugin->getRestApi(),'registerRoutes'] 59 | ) 60 | ); 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/WordPress/TestCase.php: -------------------------------------------------------------------------------- 1 |