Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:25:50 +08:00
commit 73ee9697a5
34 changed files with 8885 additions and 0 deletions

View File

@@ -0,0 +1,181 @@
# Simple WordPress Plugin Template
This is a functional programming pattern for WordPress plugins. Best for small to medium plugins that don't require complex object-oriented architecture.
## Features
✅ Complete plugin header with all fields
✅ ABSPATH security check
✅ Unique function prefix (mysp_)
✅ Custom post type registration (Books)
✅ Admin settings page with nonce verification
✅ AJAX handler with security checks
✅ Proper activation/deactivation hooks
✅ Uninstall script for cleanup
✅ Internationalization ready
✅ Conditional asset loading
✅ Security best practices (sanitization, escaping, capability checks)
## Installation
1. Copy this folder to `wp-content/plugins/`
2. Rename folder and files to match your plugin name
3. Find and replace the following:
- `My Simple Plugin` → Your plugin name
- `my-simple-plugin` → your-plugin-slug
- `mysp_` → yourprefix_
- `MYSP_` → YOURPREFIX_
- `https://example.com` → Your website
- `Your Name` → Your name
4. Activate in WordPress admin
## Structure
```
my-simple-plugin/
├── my-simple-plugin.php # Main plugin file
├── uninstall.php # Cleanup on uninstall
├── README.md # This file
├── assets/ # CSS/JS files (create as needed)
│ ├── css/
│ │ ├── admin-style.css
│ │ └── style.css
│ └── js/
│ ├── admin-script.js
│ └── script.js
└── languages/ # Translation files (create as needed)
```
## Included Examples
### Custom Post Type
- Registers "Books" post type
- Gutenberg-enabled
- Archive page support
- Custom rewrite slug
### Settings Page
- Located in Settings → Simple Plugin
- Nonce verification
- Sanitization and validation
- Settings error handling
### AJAX Handler
- Action: `wp_ajax_mysp_action`
- Nonce verification
- Capability checking
- JSON response
### Activation/Deactivation
- Flushes rewrite rules
- Sets default options
- Cleans up scheduled events
### Uninstall
- Deletes all plugin options
- Clears transients
- Multisite support
- Optional: Delete custom post type data
## Security Checklist
- [x] ABSPATH check at top of file
- [x] Unique function prefix (mysp_)
- [x] Nonces for all forms
- [x] Capability checks (current_user_can)
- [x] Input sanitization (sanitize_text_field)
- [x] Output escaping (esc_html, esc_attr)
- [x] AJAX nonce verification (check_ajax_referer)
- [x] Conditional asset loading (don't load everywhere)
- [x] Proper uninstall cleanup
## Next Steps
1. Create the `assets/` directory structure
2. Add your CSS and JavaScript files
3. Extend with additional features:
- Meta boxes
- Shortcodes
- Widgets
- REST API endpoints
- Custom taxonomies
- WP-CLI commands
## Distribution & Auto-Updates
### Enabling GitHub Auto-Updates
You can provide automatic updates from GitHub without submitting to WordPress.org:
**1. Install Plugin Update Checker library:**
```bash
cd your-plugin/
git submodule add https://github.com/YahnisElsts/plugin-update-checker.git
```
**2. Add to your main plugin file:**
```php
// Include Plugin Update Checker
require plugin_dir_path( __FILE__ ) . 'plugin-update-checker/plugin-update-checker.php';
use YahnisElsts\PluginUpdateChecker\v5\PucFactory;
// Initialize update checker
$updateChecker = PucFactory::buildUpdateChecker(
'https://github.com/yourusername/your-plugin/',
__FILE__,
'your-plugin-slug'
);
// Set branch (default: main)
$updateChecker->setBranch( 'main' );
// Use GitHub Releases (recommended)
$updateChecker->getVcsApi()->enableReleaseAssets();
```
**3. For private repos, add token to wp-config.php:**
```php
define( 'YOUR_PLUGIN_GITHUB_TOKEN', 'ghp_xxxxxxxxxxxxx' );
```
Then in your plugin:
```php
if ( defined( 'YOUR_PLUGIN_GITHUB_TOKEN' ) ) {
$updateChecker->setAuthentication( YOUR_PLUGIN_GITHUB_TOKEN );
}
```
**4. Create releases:**
```bash
# Update version in plugin header
git add my-simple-plugin.php
git commit -m "Bump version to 1.0.1"
git tag 1.0.1
git push origin main
git push origin 1.0.1
# Create GitHub Release (optional but recommended)
# - Upload pre-built ZIP file
# - Add release notes
```
### Resources
- **Complete Guide**: See `references/github-auto-updates.md`
- **Implementation Examples**: See `examples/github-updater.php`
- **Plugin Update Checker**: https://github.com/YahnisElsts/plugin-update-checker
## Resources
- [WordPress Plugin Handbook](https://developer.wordpress.org/plugins/)
- [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/)
- [Plugin Security](https://developer.wordpress.org/apis/security/)
## License
GPL v2 or later

View File

@@ -0,0 +1,272 @@
<?php
/**
* Plugin Name: My Simple Plugin
* Plugin URI: https://example.com/my-simple-plugin/
* Description: A simple WordPress plugin demonstrating functional programming pattern with security best practices.
* Version: 1.0.0
* Requires at least: 5.9
* Requires PHP: 7.4
* Author: Your Name
* Author URI: https://example.com/
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: my-simple-plugin
* Domain Path: /languages
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Define plugin constants
define( 'MYSP_VERSION', '1.0.0' );
define( 'MYSP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'MYSP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'MYSP_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
/**
* Initialize the plugin
*/
function mysp_init() {
// Load text domain for translations
load_plugin_textdomain( 'my-simple-plugin', false, dirname( MYSP_PLUGIN_BASENAME ) . '/languages' );
// Register custom post type (example)
mysp_register_book_post_type();
}
add_action( 'init', 'mysp_init' );
/**
* Register a custom post type (example)
*/
function mysp_register_book_post_type() {
$labels = array(
'name' => _x( 'Books', 'post type general name', 'my-simple-plugin' ),
'singular_name' => _x( 'Book', 'post type singular name', 'my-simple-plugin' ),
'menu_name' => _x( 'Books', 'admin menu', 'my-simple-plugin' ),
'add_new' => _x( 'Add New', 'book', 'my-simple-plugin' ),
'add_new_item' => __( 'Add New Book', 'my-simple-plugin' ),
'edit_item' => __( 'Edit Book', 'my-simple-plugin' ),
'new_item' => __( 'New Book', 'my-simple-plugin' ),
'view_item' => __( 'View Book', 'my-simple-plugin' ),
'search_items' => __( 'Search Books', 'my-simple-plugin' ),
'not_found' => __( 'No books found', 'my-simple-plugin' ),
'not_found_in_trash' => __( 'No books found in Trash', 'my-simple-plugin' ),
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'books' ),
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => 5,
'menu_icon' => 'dashicons-book',
'show_in_rest' => true, // Enable Gutenberg editor
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
);
register_post_type( 'book', $args );
}
/**
* Enqueue admin scripts and styles
*/
function mysp_admin_enqueue_scripts( $hook ) {
// Only load on specific admin pages
if ( 'edit.php' !== $hook && 'post.php' !== $hook && 'post-new.php' !== $hook ) {
return;
}
$screen = get_current_screen();
if ( $screen && 'book' === $screen->post_type ) {
wp_enqueue_style(
'mysp-admin-style',
MYSP_PLUGIN_URL . 'assets/css/admin-style.css',
array(),
MYSP_VERSION
);
wp_enqueue_script(
'mysp-admin-script',
MYSP_PLUGIN_URL . 'assets/js/admin-script.js',
array( 'jquery' ),
MYSP_VERSION,
true
);
// Localize script with nonce and AJAX URL
wp_localize_script(
'mysp-admin-script',
'myspData',
array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'mysp_ajax_nonce' ),
)
);
}
}
add_action( 'admin_enqueue_scripts', 'mysp_admin_enqueue_scripts' );
/**
* Enqueue frontend scripts and styles
*/
function mysp_enqueue_scripts() {
// Only load on single book pages
if ( is_singular( 'book' ) ) {
wp_enqueue_style(
'mysp-style',
MYSP_PLUGIN_URL . 'assets/css/style.css',
array(),
MYSP_VERSION
);
wp_enqueue_script(
'mysp-script',
MYSP_PLUGIN_URL . 'assets/js/script.js',
array( 'jquery' ),
MYSP_VERSION,
true
);
}
}
add_action( 'wp_enqueue_scripts', 'mysp_enqueue_scripts' );
/**
* Add settings page to admin menu
*/
function mysp_add_settings_page() {
add_options_page(
__( 'My Simple Plugin Settings', 'my-simple-plugin' ),
__( 'Simple Plugin', 'my-simple-plugin' ),
'manage_options',
'my-simple-plugin',
'mysp_render_settings_page'
);
}
add_action( 'admin_menu', 'mysp_add_settings_page' );
/**
* Render settings page
*/
function mysp_render_settings_page() {
// Check user capabilities
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Handle form submission
if ( isset( $_POST['mysp_settings_submit'] ) ) {
// Verify nonce
if ( ! isset( $_POST['mysp_settings_nonce'] ) || ! wp_verify_nonce( $_POST['mysp_settings_nonce'], 'mysp_settings_action' ) ) {
wp_die( __( 'Security check failed', 'my-simple-plugin' ) );
}
// Sanitize and save option
$option_value = isset( $_POST['mysp_option'] ) ? sanitize_text_field( $_POST['mysp_option'] ) : '';
update_option( 'mysp_option', $option_value );
// Show success message
add_settings_error(
'mysp_messages',
'mysp_message',
__( 'Settings Saved', 'my-simple-plugin' ),
'updated'
);
}
// Get current option value
$option_value = get_option( 'mysp_option', '' );
// Display settings page
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<?php settings_errors( 'mysp_messages' ); ?>
<form method="post" action="">
<?php wp_nonce_field( 'mysp_settings_action', 'mysp_settings_nonce' ); ?>
<table class="form-table">
<tr>
<th scope="row">
<label for="mysp_option"><?php esc_html_e( 'Example Option', 'my-simple-plugin' ); ?></label>
</th>
<td>
<input
type="text"
id="mysp_option"
name="mysp_option"
value="<?php echo esc_attr( $option_value ); ?>"
class="regular-text"
/>
<p class="description"><?php esc_html_e( 'Enter some text here', 'my-simple-plugin' ); ?></p>
</td>
</tr>
</table>
<?php submit_button( __( 'Save Settings', 'my-simple-plugin' ), 'primary', 'mysp_settings_submit' ); ?>
</form>
</div>
<?php
}
/**
* Example AJAX handler
*/
function mysp_ajax_handler() {
// Verify nonce
check_ajax_referer( 'mysp_ajax_nonce', 'nonce' );
// Check user capability
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied', 'my-simple-plugin' ) ) );
}
// Get and sanitize input
$input = isset( $_POST['data'] ) ? sanitize_text_field( $_POST['data'] ) : '';
// Process and return response
wp_send_json_success( array(
'message' => __( 'Success!', 'my-simple-plugin' ),
'data' => $input,
) );
}
add_action( 'wp_ajax_mysp_action', 'mysp_ajax_handler' );
/**
* Activation hook
*/
function mysp_activate() {
// Register post type before flushing rewrite rules
mysp_register_book_post_type();
// Flush rewrite rules
flush_rewrite_rules();
// Set default options
if ( false === get_option( 'mysp_option' ) ) {
add_option( 'mysp_option', '' );
}
// Set activation timestamp
if ( false === get_option( 'mysp_activated_time' ) ) {
add_option( 'mysp_activated_time', current_time( 'timestamp' ) );
}
}
register_activation_hook( __FILE__, 'mysp_activate' );
/**
* Deactivation hook
*/
function mysp_deactivate() {
// Flush rewrite rules
flush_rewrite_rules();
// Clear any scheduled events
wp_clear_scheduled_hook( 'mysp_cron_event' );
}
register_deactivation_hook( __FILE__, 'mysp_deactivate' );

View File

@@ -0,0 +1,56 @@
<?php
/**
* Uninstall script
*
* This file is called when the plugin is uninstalled via WordPress admin.
* It should clean up all plugin data.
*/
// Exit if not called by WordPress
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
exit;
}
// Delete plugin options
delete_option( 'mysp_option' );
delete_option( 'mysp_activated_time' );
// Delete transients
delete_transient( 'mysp_cache' );
// For multisite, delete from all sites
if ( is_multisite() ) {
global $wpdb;
$blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
foreach ( $blog_ids as $blog_id ) {
switch_to_blog( $blog_id );
delete_option( 'mysp_option' );
delete_option( 'mysp_activated_time' );
delete_transient( 'mysp_cache' );
restore_current_blog();
}
}
// Delete custom post type data (optional - consider if users want to keep content)
// Uncomment the following if you want to delete all custom post type posts on uninstall
/*
$books = get_posts( array(
'post_type' => 'book',
'posts_per_page' => -1,
'post_status' => 'any',
) );
foreach ( $books as $book ) {
wp_delete_post( $book->ID, true ); // true = force delete (skip trash)
}
*/
// If you created custom database tables, drop them here
/*
global $wpdb;
$table_name = $wpdb->prefix . 'mysp_custom_table';
$wpdb->query( "DROP TABLE IF EXISTS $table_name" );
*/