Files
rangerhq-logbook/wp-notes.php
T
ranger 05d8ad52ad fix: hide unused wp_note CPT from admin UI to stop "Add New Note" misroute
The plugin registered a wp_note CPT with show_ui=true and show_in_menu=
'wp-notes', which caused WordPress to auto-inject "All Notes" and
"Add New" submenus under the WP Notes admin menu. The "Add New"
submenu routed to post-new.php?post_type=wp_note — the standard WP
post editor — but the live plugin stores notes in wp_options
(get_option('wp_notes')), not as CPT posts. Anyone clicking that
submenu ended up writing to the wrong storage and their note never
appeared in the WP Notes list.

Fix: show_ui and show_in_menu set to false on the wp_note CPT, and
show_ui / show_admin_column / show_in_rest set to false on the
wp_note_category taxonomy. The CPT/taxonomy remain registered so the
wp_notes_migrate_to_cpt() helper can still call wp_insert_post() —
just no admin UI side-effects until the migration is completed and
the live storage swaps over.

Also adds CHANGELOG.md in Keep-a-Changelog format with the
[Unreleased] entry for this fix and a 3.0.2 baseline note for the
"trimmed from v1.1.5 feature-creep" lineage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 07:26:04 +01:00

1784 lines
68 KiB
PHP

<?php
/**
* A-WP-Notes - WordPress Task Management Plugin
*
* Plugin Name: A-WP-Notes
* Plugin URI: https://icanhelp.ie/wp-notes
* Description: A plugin to add your notes to the WordPress dashboard with import/export functionality
* Version: 3.0.2
* Requires at least: 5.0
* Requires PHP: 7.2
* Author: IR240474
* Author URI: https://rangersmyth.xyz/
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: a-wp-notes
* Domain Path: /languages
*
* @package WP_Notes
*/
/**
* Security check and WordPress core loading
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
die;
}
// One-time initialization of plugin constants
global $wp_notes_init;
if (!isset($wp_notes_init)) {
$wp_notes_init = true;
// Plugin Constants
if (!defined('WP_NOTES_VERSION')) define('WP_NOTES_VERSION', '3.0.2');
if (!defined('WP_NOTES_FILE')) define('WP_NOTES_FILE', __FILE__);
if (!defined('WP_NOTES_PATH')) define('WP_NOTES_PATH', plugin_dir_path(__FILE__));
if (!defined('WP_NOTES_URL')) define('WP_NOTES_URL', plugin_dir_url(__FILE__));
if (!defined('WP_NOTES_BASENAME')) define('WP_NOTES_BASENAME', plugin_basename(__FILE__));
}
// Initialize error logging if not already defined
if (!function_exists('wp_notes_log_error')) {
function wp_notes_log_error($message) {
if (defined('WP_DEBUG') && WP_DEBUG === true) {
error_log('[WP Notes] ' . $message);
}
}
}
// Plugin class autoloader
spl_autoload_register(function ($class) {
$prefix = 'WP_Notes\\';
$base_dir = plugin_dir_path(__FILE__) . 'includes/';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relative_class = substr($class, $len);
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
if (file_exists($file)) {
require $file;
}
});
// Plugin initialization function
function wp_notes_init() {
// Load plugin functionality
if (class_exists('WP_Notes\\Plugin')) {
$plugin = new WP_Notes\Plugin();
$plugin->run();
}
}
// Hook into WordPress init
add_action('plugins_loaded', 'wp_notes_init', 10);
// Register activation and deactivation hooks
register_activation_hook(__FILE__, 'wp_notes_activate');
register_deactivation_hook(__FILE__, 'wp_notes_deactivate');
/**
* Plugin activation handler
*/
function wp_notes_activate() {
// Ensure PHP version is compatible
if (version_compare(PHP_VERSION, '7.2', '<')) {
deactivate_plugins(basename(__FILE__));
wp_die('This plugin requires PHP version 7.2 or higher.');
}
// Initialize plugin settings
add_option('wp_notes_version', WP_NOTES_VERSION);
// Create database tables
wp_notes_create_tables();
// Backup existing notes
$existing_notes = get_option('wp_notes', array());
$existing_done_notes = get_option('wp_done_notes', array());
if (!empty($existing_notes)) {
update_option('wp_notes_backup', $existing_notes);
}
if (!empty($existing_done_notes)) {
update_option('wp_done_notes_backup', $existing_done_notes);
}
}
/**
* Plugin deactivation handler
*/
function wp_notes_deactivate() {
delete_option('wp_notes_version');
}
/**
* Create required database tables
*/
function wp_notes_create_tables() {
global $wpdb;
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
$charset_collate = $wpdb->get_charset_collate();
$sql = array();
// Notes table
$sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wp_notes (
id bigint(20) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
note_content text NOT NULL,
note_status varchar(20) NOT NULL DEFAULT 'active',
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY user_id (user_id),
KEY note_status (note_status)
) $charset_collate;";
foreach ($sql as $query) {
dbDelta($query);
}
}
// Initialize plugin
add_action('plugins_loaded', 'wp_notes_init');
// Set text domain for translations
function wp_notes_load_textdomain() {
load_plugin_textdomain('a-wp-notes', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
add_action('plugins_loaded', 'wp_notes_load_textdomain');
// Include required files
if (defined('WP_NOTES_PATH')) {
require_once WP_NOTES_PATH . 'inc/admin-bar.php';
require_once WP_NOTES_PATH . 'inc/wp-notes-about.php';
require_once WP_NOTES_PATH . 'inc/wp-notes-feedback.php';
require_once WP_NOTES_PATH . 'inc/wp-notes-display.php';
require_once WP_NOTES_PATH . 'inc/wp-notes-styles.php';
}
// Admin Menu
function wp_notes_admin_menu() {
add_menu_page(
'WP Notes',
'WP Notes',
'manage_options',
'wp-notes',
'wp_notes_page_callback',
'dashicons-admin-generic',
3
);
// Add "Create WP Note" to the Admin Bar menu
add_submenu_page(
'wp-notes', // Page title
'Create WP Note', // Menu title
'Create WP Note',
'manage_options', // Capability required to see this item
'wp-notes', // Menu slug
'wp_notes_create_page' // Function to display the page content
);
// Settings submenu
add_submenu_page(
'wp-notes',
'Settings',
'Settings',
'manage_options',
'wp-notes-settings',
'wp_notes_settings_page'
);
// Import/Export submenu
add_submenu_page(
'wp-notes',
'Import/Export',
'Import/Export',
'manage_options',
'wp-notes-import-export',
'wp_notes_import_export_page'
);
// About WP Notes submenu
add_submenu_page(
'wp-notes',
'About WP Notes',
'About WP Notes',
'manage_options',
'wp-notes-about',
'wp_notes_about_page' // Callback for About WP Notes page in wp-notes-about.php
);
// Register settings with validation callback
register_setting(
'wp_notes_settings',
'wp_notes_settings',
array(
'sanitize_callback' => 'wp_notes_validate_settings',
'default' => array(
'default_font' => 'Arial',
'default_size' => '16'
)
)
);
add_settings_section(
'wp_notes_main_section',
'General Settings',
'wp_notes_section_callback',
'wp-notes-settings'
);
add_settings_field(
'wp_notes_default_font',
'Default Font',
'wp_notes_font_callback',
'wp-notes-settings',
'wp_notes_main_section'
);
add_settings_field(
'wp_notes_default_size',
'Default Size',
'wp_notes_size_callback',
'wp-notes-settings',
'wp_notes_main_section'
);
}
// Settings validation callback
function wp_notes_validate_settings($input) {
$output = array();
// Validate font
$allowed_fonts = ['Arial', 'Helvetica', 'Times New Roman', 'Verdana'];
$output['default_font'] = in_array($input['default_font'], $allowed_fonts) ? $input['default_font'] : 'Arial';
// Validate size (8-72px)
$size = absint($input['default_size']);
$output['default_size'] = ($size >= 8 && $size <= 72) ? $size : 16;
add_settings_error(
'wp_notes_settings',
'settings_updated',
'Settings saved successfully.',
'updated'
);
return $output;
}
// Settings page callback
function wp_notes_settings_page() {
// Show any settings messages
settings_errors('wp_notes_settings');
?>
<div class="wrap">
<h1>WP Notes Settings</h1>
<form method="post" action="options.php">
<?php
settings_fields('wp_notes_settings');
do_settings_sections('wp-notes-settings');
submit_button();
?>
</form>
</div>
<?php
}
// Settings section callback
function wp_notes_section_callback() {
echo '<p>Configure default settings for WP Notes.</p>';
}
// Font setting callback
function wp_notes_font_callback() {
$options = get_option('wp_notes_settings');
$current = $options['default_font'] ?? 'Arial';
?>
<select name="wp_notes_settings[default_font]">
<option value="Arial" <?php selected($current, 'Arial'); ?>>Arial</option>
<option value="Helvetica" <?php selected($current, 'Helvetica'); ?>>Helvetica</option>
<option value="Times New Roman" <?php selected($current, 'Times New Roman'); ?>>Times New Roman</option>
<option value="Verdana" <?php selected($current, 'Verdana'); ?>>Verdana</option>
</select>
<?php
}
// Size setting callback
function wp_notes_size_callback() {
$options = get_option('wp_notes_settings');
$current = $options['default_size'] ?? '16';
?>
<input type="number" name="wp_notes_settings[default_size]" value="<?php echo esc_attr($current); ?>" min="8" max="72">
<span class="description">Font size in pixels (8-72)</span>
<?php
}
// Import/Export page callback
function wp_notes_import_export_page() {
// Display settings errors
settings_errors();
if (isset($_POST['export_notes'])) {
wp_notes_export_data();
}
if (isset($_POST['import_notes']) && !empty($_FILES['import_file']['tmp_name'])) {
try {
wp_notes_import_data();
} catch (Exception $e) {
add_settings_error(
'wp_notes_import',
'import_error',
'Error importing notes: ' . esc_html($e->getMessage()),
'error'
);
}
}
// Get current settings for display
$options = get_option('wp_notes_settings');
?>
<div class="wrap">
<h1>Import/Export Notes</h1>
<!-- Export Section -->
<div class="postbox">
<div class="inside">
<h2>Export Notes</h2>
<p>Download your notes as JSON or CSV file.</p>
<form method="post">
<select name="export_format">
<option value="json">JSON</option>
<option value="csv">CSV</option>
</select>
<?php wp_nonce_field('wp_notes_export', 'export_nonce'); ?>
<input type="submit" name="export_notes" class="button button-primary" value="Export Notes">
</form>
</div>
</div>
<!-- Import Section -->
<div class="postbox">
<div class="inside">
<h2>Import Notes</h2>
<p>Import notes from JSON or CSV file.</p>
<form method="post" enctype="multipart/form-data">
<input type="file" name="import_file" accept=".json,.csv">
<?php wp_nonce_field('wp_notes_import', 'import_nonce'); ?>
<input type="submit" name="import_notes" class="button button-primary" value="Import Notes">
</form>
</div>
</div>
</div>
<?php
}
// Export functionality
function wp_notes_export_data() {
if (!isset($_POST['export_nonce']) || !wp_verify_nonce($_POST['export_nonce'], 'wp_notes_export')) {
wp_die('Security check failed');
}
$format = $_POST['export_format'] ?? 'json';
$notes = get_option('wp_notes', array());
$done_notes = get_option('wp_done_notes', array());
$data = array(
'active_notes' => $notes,
'completed_notes' => $done_notes,
'export_date' => current_time('mysql')
);
if ($format === 'json') {
header('Content-Type: application/json');
header('Content-Disposition: attachment; filename=wp-notes-export-' . date('Y-m-d') . '.json');
echo wp_json_encode($data, JSON_PRETTY_PRINT);
} else {
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename=wp-notes-export-' . date('Y-m-d') . '.csv');
$output = fopen('php://output', 'w');
fputcsv($output, array('Type', 'Text', 'Created By', 'Created On', 'Status'));
foreach ($notes as $note) {
fputcsv($output, array(
'active',
$note['text'],
$note['author_name'] ?? 'Unknown',
$note['timestamp'] ?? '',
'Active'
));
}
foreach ($done_notes as $note) {
fputcsv($output, array(
'completed',
$note['text'],
$note['author_name'] ?? 'Unknown',
$note['timestamp'] ?? '',
'Completed'
));
}
fclose($output);
}
exit;
}
// Import functionality
function wp_notes_import_data() {
if (!isset($_POST['import_nonce']) || !wp_verify_nonce($_POST['import_nonce'], 'wp_notes_import')) {
wp_die('Security check failed');
}
$file = $_FILES['import_file'];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($ext === 'json') {
$content = file_get_contents($file['tmp_name']);
$data = json_decode($content, true);
if (json_last_error() === JSON_ERROR_NONE) {
if (isset($data['active_notes'])) {
update_option('wp_notes', $data['active_notes']);
}
if (isset($data['completed_notes'])) {
update_option('wp_done_notes', $data['completed_notes']);
}
add_settings_error('wp_notes_import', 'import_success', 'Notes imported successfully', 'updated');
} else {
add_settings_error('wp_notes_import', 'import_error', 'Invalid JSON file');
}
} elseif ($ext === 'csv') {
$handle = fopen($file['tmp_name'], 'r');
$headers = fgetcsv($handle);
$notes = array();
$done_notes = array();
while (($data = fgetcsv($handle)) !== false) {
$note = array(
'text' => $data[1],
'author_name' => $data[2],
'timestamp' => $data[3],
'color' => '#000000',
'size' => '16',
'font' => 'Arial'
);
if ($data[0] === 'active') {
$notes[] = $note;
} else {
$done_notes[] = $note;
}
}
fclose($handle);
update_option('wp_notes', $notes);
update_option('wp_done_notes', $done_notes);
add_settings_error('wp_notes_import', 'import_success', 'Notes imported successfully', 'updated');
} else {
add_settings_error('wp_notes_import', 'import_error', 'Invalid file format. Please use JSON or CSV files.');
}
}
add_action('admin_menu', 'wp_notes_admin_menu');
// Add "Create WP Note" to the Tools menu 29th Oct 2024.
function wp_notes_add_tools_menu() {
add_management_page(
'Create WP Note', // Page title
'Create WP Note', // Menu title
'manage_options', // Capability required to see this item
'wp-notes-create', // Menu slug
'wp_notes_create_page' // Function to display the page content
);
}
add_action('admin_menu', 'wp_notes_add_tools_menu');
// Enqueue Scripts
function wp_notes_enqueue_scripts() {
// wp_enqueue_script('wp-notes-activity-tracker', WP_NOTES_URL . 'js/wp-notes-activity.js', ['jquery'], null, true); // Activity tracking removed 2025-05-09
wp_enqueue_script('wp-notes-feedback', WP_NOTES_URL . 'js/wp-notes-feedback.js', ['jquery'], null, true);
wp_localize_script('wp-notes-feedback', 'wp_notes_feedback_vars', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wp_notes_feedback_nonce')
));
}
add_action('admin_enqueue_scripts', 'wp_notes_enqueue_scripts');
// Emoji picker initialization
add_action('admin_footer', 'wp_notes_emoji_picker_init');
function wp_notes_emoji_picker_init() {
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
// Initialize emoji picker
function initEmojiPicker() {
// Handle emoji picker toggle
$('.emoji-picker-button, .emoji-input').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var $container = $(this).closest('.emoji-picker-container');
$('.emoji-picker-dropdown').not($container.find('.emoji-picker-dropdown')).hide();
$container.find('.emoji-picker-dropdown').toggle();
});
// Handle emoji selection
$('.emoji-option').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var emoji = $(this).data('emoji');
var $container = $(this).closest('.emoji-picker-container');
var $noteText = $('#wp_notes_text');
// Get cursor position or selection
var start = $noteText[0].selectionStart;
var end = $noteText[0].selectionEnd;
var text = $noteText.val();
// Insert emoji at cursor position
var newText = text.substring(0, start) + emoji + text.substring(end);
$noteText.val(newText);
// Update cursor position
var newCursorPos = start + emoji.length;
$noteText[0].setSelectionRange(newCursorPos, newCursorPos);
// Focus back on textarea
$noteText.focus();
// Hide dropdown
$container.find('.emoji-picker-dropdown').hide();
});
// Close emoji picker when clicking outside
$(document).on('click', function(e) {
if (!$(e.target).closest('.emoji-picker-container').length) {
$('.emoji-picker-dropdown').hide();
}
});
// Handle keyboard navigation
$('.emoji-picker-container').on('keydown', function(e) {
var $dropdown = $(this).find('.emoji-picker-dropdown');
var $button = $(this).find('.emoji-picker-button');
// Toggle dropdown with Enter or Space
if ((e.key === 'Enter' || e.key === ' ') && e.target === $button[0]) {
e.preventDefault();
$dropdown.toggle();
}
// Close with Escape
if (e.key === 'Escape' && $dropdown.is(':visible')) {
$dropdown.hide();
$button.focus();
}
});
}
// Initialize on page load
initEmojiPicker();
});
</script>
<?php
}
// Callback function for "Create WP Note" page
function wp_notes_create_page() {
?>
<div class="wrap">
<h1>Create a New WP Note</h1>
<form method="post">
<label for="wp_notes_text">Note:</label><br>
<textarea name="wp_notes_text" id="wp_notes_text" required></textarea><br>
<label for="wp_notes_color">Color:</label><br>
<input type="color" name="wp_notes_color" id="wp_notes_color" value="#000000"><br>
<label for="wp_notes_size">Size:</label><br>
<input type="number" name="wp_notes_size" id="wp_notes_size" min="8" max="72" value="16"><br>
<label for="wp_notes_font">Font:</label><br>
<select name="wp_notes_font" id="wp_notes_font">
<option value="Arial">Arial</option>
<option value="Helvetica">Helvetica</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Verdana">Verdana</option>
</select><br>
<input type="submit" value="Add Note" class="button button-primary">
</form>
</div>
<?php
}
// JavaScript for saving edited notes
add_action('admin_footer', 'wp_notes_add_inline_scripts');
function wp_notes_add_inline_scripts() {
echo "
<script type='text/javascript'>
jQuery(document).ready(function($) {
// Show edit form
$('.edit-note').on('click', function(e) {
e.preventDefault();
var noteId = $(this).data('note-id');
$('#edit-note-' + noteId).toggle();
});
// AJAX submit the edit form
$('.edit-note-form').on('submit', function(e) {
e.preventDefault();
var noteId = $(this).data('note-id');
var formData = $(this).serialize() + '&action=wp_notes_save_edit&note_id=' + noteId;
$.post(ajaxurl, formData, function(response) {
if (response.success) {
alert('Note updated successfully!');
location.reload(); // Reload to display updated notes
} else {
alert('Error updating note: ' + response.data);
}
});
});
});
</script>";
}
// Handle saving edited notes
add_action('wp_ajax_wp_notes_save_edit', 'wp_notes_save_edit');
function wp_notes_save_edit() {
// Security checks
if (!current_user_can('edit_posts')) {
wp_send_json_error('Insufficient permissions.');
return;
}
// Security check using nonce
check_ajax_referer('wp_notes_nonce', '_wpnonce');
// Validate required fields
$required_fields = ['note_id', 'new_text', 'edit_color', 'edit_size', 'edit_font'];
foreach ($required_fields as $field) {
if (!isset($_POST[$field])) {
wp_send_json_error("Missing required field: $field");
return;
}
}
// Get and sanitize data
$note_id = absint($_POST['note_id']);
$new_text = sanitize_text_field($_POST['new_text']);
$new_color = sanitize_hex_color($_POST['edit_color']);
$new_size = absint($_POST['edit_size']);
// Validate font against allowed list
$allowed_fonts = ['Arial', 'Helvetica', 'Times New Roman', 'Verdana'];
$new_font = in_array($_POST['edit_font'], $allowed_fonts) ? $_POST['edit_font'] : 'Arial';
// Validate size range
if ($new_size < 8 || $new_size > 72) {
wp_send_json_error('Font size must be between 8 and 72 pixels.');
return;
}
$notes = get_option('wp_notes', array());
if (!isset($notes[$note_id])) {
wp_send_json_error('Note not found.');
return;
}
// Update note with validation
$notes[$note_id] = array_merge($notes[$note_id], [
'text' => $new_text,
'color' => $new_color ?: '#000000',
'size' => $new_size,
'font' => $new_font,
'modified_timestamp' => current_time('mysql'),
'modified_by' => get_current_user_id()
]);
if (!update_option('wp_notes', $notes)) {
wp_send_json_error('Failed to update note.');
return;
}
wp_send_json_success([
'message' => 'Note updated successfully',
'note' => $notes[$note_id]
]);
}
/**
* Main WP Notes Page - Central hub for note management
*/
function wp_notes_page_callback() {
if (!current_user_can('edit_posts')) {
wp_die(__('You do not have sufficient permissions to access this page.', 'a-wp-notes'));
}
$notes = get_option('wp_notes', array());
$done_notes = get_option('wp_done_notes', array());
$total_notes = count($notes);
$total_done = count($done_notes);
// Get user settings
$settings = get_option('wp_notes_settings', array(
'default_font' => 'Arial',
'default_size' => '16'
));
?>
<div class="wrap">
<!-- Header Section with WordPress Admin Styling -->
<h1 class="wp-heading-inline">Welcome to WP Notes</h1>
<span class="page-title-action">Version <?php echo esc_html(WP_NOTES_VERSION); ?></span>
<!-- Footer Section with WordPress Admin Styling -->
<div class="wp-notes-footer postbox">
<div class="inside">
<div class="centered">
<a href="https://www.buymeacoffee.com/davidkeanek" target="_blank" class="button button-secondary"
aria-label="Support the developer by buying them a coffee">
<img src="https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=&slug=davidkeanek&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff"
alt="Buy me a coffee button"
style="max-height: 35px;">
</a>
</div>
</div>
</div>
<!-- WordPress Admin Notices Styling -->
<style>
.centered { text-align: center; }
.wp-notes-footer { margin: 20px 0; }
.wp-notes-footer .inside { padding: 20px; }
</style>
<!-- Dismissible Box with WordPress Admin Styling -->
<div id="wp-notes-about-box" class="notice notice-info is-dismissible">
<div class="about-content">
<h2>About WP Notes</h2>
<p>Thank you for using WP Notes! This plugin helps you manage your WordPress tasks and notes efficiently. Keep track of your todos, mark them as complete, and export your data for safekeeping.</p>
<p>I made this plugin for my website to log my daily tasks and notes as a logbook for my clients, so they can see the work done by me or you and get clear value for their investment.</p>
</div>
<button type="button" class="notice-dismiss" onclick="closeAboutBox()" aria-label="Dismiss this notice">
<span class="screen-reader-text">Dismiss this notice</span>
</button>
</div>
<!-- Action Buttons with WordPress Admin Styling -->
<div class="wp-notes-actions">
<button type="button" class="button" onclick="showAboutBoxAgain()" aria-label="Show the about message again">
Show Welcome Message
</button>
<button type="button" class="button" onclick="closeAboutBox()" aria-label="Close the about box">
Close About Box
</button>
</div>
<script>
function showAboutBoxAgain() {
document.getElementById('wp-notes-about-box').style.display = 'block';
// Remove the localStorage value every time they load the plugin page
localStorage.removeItem('wpNotesAboutDismissed'); // Remove dismissal flag
}
</script>
<!-- JavaScript to handle the dismiss action and remember it across page loads -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check if the box has been dismissed previously
if (localStorage.getItem('wpNotesAboutDismissed') === 'true') {
// Check if the box has been dismissed within the last week
const lastDismissed = localStorage.getItem('wpNotesAboutLastDismissed');
const oneWeek = 7 * 24 * 60 * 60 * 1000; // 1 week in milliseconds
const now = Date.now();
if (!lastDismissed || (now - lastDismissed) > oneWeek) {
document.getElementById('wp-notes-about-box').style.display = 'block';
localStorage.removeItem('wpNotesAboutLastDismissed'); // Reset timestamp to show the message
}
}
}
);
function closeAboutBox() {
document.getElementById('wp-notes-about-box').style.display = 'none';
// Store dismissal in localStorage
localStorage.setItem('wpNotesAboutDismissed', 'true');
// Store the current time as the last dismissed time
localStorage.setItem('wpNotesAboutLastDismissed', Date.now());
}
</script>
<!-- Styles for the dismissible box -->
<style>
.wp-notes-about-box {
position: relative;
background: #f9f9f9;
padding: 20px;
border: 1px solid #ccd0d4;
border-radius: 4px;
margin: 20px 0;
}
.wp-notes-close-button {
position: absolute;
top: 10px;
right: 10px;
background: none;
border: none;
font-size: 18px;
color: #aaa;
cursor: pointer;
}
.wp-notes-close-button:hover {
color: #0073aa;
}
</style>
<p>
<button onclick="toggleSection('header')" style="background: #0073aa; color: #fff; border: none; padding: 8px 12px; cursor: pointer;">
Toggle Welcome Section
</button>
<div id="header" style="display: none; margin-top: 10px;">
<div class="wp-notes-welcome" style="background: #fff; padding: 20px; margin: 20px 0; border: 1px solid #ccc;">
<br>
<div class="wp-notes-header" style="margin-bottom: 30px;">
<img src="<?php echo esc_url(WP_NOTES_URL); ?>assets/wp-notes-banner.jpg"
style="max-width: 1200px; width: 100%; height: auto; margin-bottom: 20px;"
alt="WP Notes Banner">
<!-- What's New Section -->
<h1>Welcome to WP Notes v <?php echo esc_html(WP_NOTES_VERSION); ?></h1>
<p>
<div class="wp-notes-whatsnew" style="background: #fff; padding: 20px; margin: 20px 0; border: 1px solid #ccc;">
<button onclick="toggleSection('about-section')" style="background: #0073aa; color: #fff; border: none; padding: 8px 12px; cursor: pointer;">
Toggle Welcome Section
</button>
<!-- Section to Toggle -->
<div id="about-section" style="display: none; margin-top: 10px;">
<h1>About WP Notes</h1>
<p>
Thank you for using WP Notes! This plugin helps you manage your WordPress tasks and notes efficiently.
Keep track of your todo's, mark them as complete, and export your data for safekeeping. I made this
plugin for my website to log my daily tasks and notes as a logbook for my clients, so they can see the work
done by me or you and so PAY, as every task is important and the customers need to know what I've done.
</p>
<p>
<li>Log all your tasks and jobs so prove work is being done.</li>
<li>As you can see, this plugin is very flexible and can be used for a wide range of WordPress users.</li>
<li>I hope you find it useful too.</li>
</p>
<h1>What WP Notes Offers</h1>
<p>WP Notes is a versatile plugin that caters to a wide range of users:</p>
<p>We're excited to offer our plugin to our customers.</p>
<p>WP Notes is a versatile plugin that caters to a wide range of users:</p>
<p>We have several features for you to choose from:</p>
<ul>
<li><strong>Freelancers and site developers</strong> tracking work done on client sites.</li>
<li><strong>Teams</strong> wanting to document changes or keep a changelog in the dashboard.</li>
<li><strong>WordPress power users</strong> wanting a straightforward dashboard notes system.</li>
<li><strong>Small businesses</strong> needing a simple task management solution for WordPress.</li>
<li><strong>Students</strong> wanting to keep track of their daily tasks to prove work is being done.</li>
<li><strong>Teachers</strong> wanting to keep track of their students' progress with Email Notifications.</li>
</ul>
<p>
If you have any questions or feedback, feel free to reach out to us. Enjoy your note-taking experience!
</p>
<p>Join our community and unlock the full potential of WP Notes.</p>
</div>
</div>
</div>
<!-- What's New Section -->
<h1>What's New in WP Notes v <?php echo esc_html(WP_NOTES_VERSION); ?></h1>
<div class="wp-notes-versions" style="background: #fff; padding: 20px; margin: 20px 0; border: 1px solid #ccc;">
<button onclick="toggleSection('version-section')" style="background: #0073aa; color: #fff; border: none; padding: 8px 12px; cursor: pointer;">
Toggle What's New Section
</button>
<br>
<!-- Section to Toggle -->
<div id="version-section" style="display: none;">
<h3>Version 3.0.0 (Current)</h3>
<ul>
<li>Major Update: Removed activity tracking functionality for improved focus and privacy</li>
<li>Added Error Logging: Introduced a comprehensive error logging system to capture plugin-related errors</li>
<li>Added Welcome Section and Version history</li>
<li>Added Image to main menu</li>
<li>Fixed: Edit Function is now working correctly</li>
<li>Added: Popup notification with "Saved" status</li>
<li>Improved: Error handling and stability</li>
<li>Added: Documentation for enhanced error reporting</li>
<li>Feature: Ability to restore older notes</li>
<li>Added: "Created by," "Modified by," and "Edited by" fields</li>
<li>Improved: Task-tracking interface in dashboard</li>
<li>Added: Color, font, and size customization for notes</li>
<li>Enhanced: Edit and mark-as-done functions</li>
<li>Improved: AJAX-based updates for smoother editing</li>
<li>Added: Quick access to Create Note in Tools menu</li>
<li>Added: Create Note option in WP-Notes menu</li>
</ul>
</div>
</div>
</div>
</div>
<!-- JavaScript to toggle visibility of sections -->
<script>
function toggleSection(sectionId) {
var section = document.getElementById(sectionId);
if (section.style.display === "none") {
section.style.display = "block";
} else {
section.style.display = "none";
}
}
</script>
<!-- Note Creation Form with WordPress Admin Styling -->
<div class="postbox">
<div class="postbox-header">
<h2 class="hndle">Add New Note</h2>
</div>
<div class="inside">
<form method="post" class="wp-notes-form">
<div class="form-field">
<label for="wp_notes_text" class="screen-reader-text">Note Content:</label>
<textarea
name="wp_notes_text"
id="wp_notes_text"
class="large-text"
required
rows="4"
aria-label="Enter your note content"
placeholder="Enter your note here..."></textarea>
</div>
<div class="wp-notes-formatting form-field">
<fieldset>
<legend class="screen-reader-text">Note Formatting Options</legend>
<div class="formatting-options">
<div class="format-option">
<label for="wp_notes_color" class="format-label">
<span class="dashicons dashicons-color-picker"></span>
Text Color
</label>
<input
type="color"
name="wp_notes_color"
id="wp_notes_color"
value="#000000"
aria-label="Choose text color">
</div>
<div class="format-option">
<label for="wp_notes_size" class="format-label">
<span class="dashicons dashicons-editor-textcolor"></span>
Size (px)
</label>
<input
type="number"
name="wp_notes_size"
id="wp_notes_size"
min="8"
max="72"
value="16"
class="small-text"
aria-label="Choose text size">
</div>
<div class="format-option">
<label for="wp_notes_font" class="format-label">
<span class="dashicons dashicons-editor-paragraph"></span>
Font
</label>
<select
name="wp_notes_font"
id="wp_notes_font"
class="regular-text"
aria-label="Choose font">
<option value="Arial">Arial</option>
<option value="Helvetica">Helvetica</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Verdana">Verdana</option>
</select>
</div>
<div class="format-option">
<label for="wp_notes_emoji" class="format-label">
<span class="dashicons dashicons-smiley"></span>
Emoji
</label>
<div class="emoji-picker-container">
<input
type="text"
name="wp_notes_emoji"
id="wp_notes_emoji"
placeholder="Click to add emoji"
class="small-text emoji-input"
aria-label="Add emoji"
readonly>
<button type="button"
class="button emoji-picker-button"
aria-label="Open emoji picker">
<span class="dashicons dashicons-smiley"></span>
</button>
<div class="emoji-picker-dropdown" style="display: none;">
<div class="emoji-list">
<!-- Common emojis -->
<button type="button" class="emoji-option" data-emoji="😊">😊</button>
<button type="button" class="emoji-option" data-emoji="👍">👍</button>
<button type="button" class="emoji-option" data-emoji="✅">✅</button>
<button type="button" class="emoji-option" data-emoji="⭐">⭐</button>
<button type="button" class="emoji-option" data-emoji="📝">📝</button>
<button type="button" class="emoji-option" data-emoji="❗">❗</button>
<button type="button" class="emoji-option" data-emoji="❓">❓</button>
<button type="button" class="emoji-option" data-emoji="⚠️">⚠️</button>
</div>
</div>
</div>
</div>
</div>
</fieldset>
</div>
<div class="submit-button">
<input
type="submit"
value="Add Note"
class="button button-primary button-large"
aria-label="Add new note">
</div>
</form>
</div>
</div>
<style>
.wp-notes-form .form-field { margin-bottom: 1em; }
.wp-notes-formatting { background: #f9f9f9; padding: 15px; border-radius: 4px; }
.formatting-options { display: flex; gap: 20px; flex-wrap: wrap; align-items: center; }
.format-option { display: flex; flex-direction: column; gap: 5px; }
.format-label { display: flex; align-items: center; gap: 5px; }
.submit-button { margin-top: 1em; }
.emoji-picker-container { position: relative; }
.emoji-picker-button { margin-left: 5px; padding: 2px 6px; }
.emoji-picker-dropdown {
position: absolute;
top: 100%;
left: 0;
background: white;
border: 1px solid #ccd0d4;
border-radius: 4px;
padding: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
z-index: 100;
}
.emoji-list {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 5px;
}
.emoji-option {
border: none;
background: none;
font-size: 20px;
padding: 5px;
cursor: pointer;
border-radius: 4px;
}
.emoji-option:hover {
background: #f0f0f1;
}
.emoji-input {
cursor: pointer;
}
</style>
<!-- Notes List -->
<h2>Notes Todo List:</h2>
<!-- Active Notes List -->
<div class="wp-notes-active" id="active-notes">
<?php wp_notes_display_notes('active'); ?>
</div>
<!-- Completed Notes List -->
<div class="wp-notes-completed" id="completed-notes">
<?php wp_notes_display_notes('completed'); ?>
</div>
</div>
<?php
}
// List Table Function
function wp_notes_list_table() {
// Check user capabilities
if (!current_user_can('edit_posts')) {
wp_die(__('You do not have sufficient permissions to access this page.', 'a-wp-notes'));
}
$notes = get_option('wp_notes', array());
if (!$notes) {
echo '<p>' . esc_html__('No notes found.', 'a-wp-notes') . '</p>';
return;
}
echo '<table class="wp-list-table widefat striped">
<thead>
<tr>
<th scope="col" class="manage-column column-cb check-column">
<input type="checkbox" id="select-all-notes">
</th>
<th scope="col" class="manage-column">Note</th>
<th scope="col" class="manage-column">Created By</th>
<th scope="col" class="manage-column">Created On</th>
<th scope="col" class="manage-column">Actions</th>
</tr>
</thead>
<tbody>';
foreach ($notes as $key => $note) {
$text = esc_html($note['text']);
$color = esc_attr($note['color'] ?? '#000000');
$size = esc_attr($note['size'] ?? '14');
$font = esc_attr($note['font'] ?? 'Arial');
$timestamp = esc_html($note['timestamp'] ?? current_time('mysql'));
$author = esc_html($note['author_name'] ?? 'Unknown');
echo "<tr>
<td><input type='checkbox' name='note_ids[]' value='$key' class='note-checkbox'></td>
<td style='color: $color; font-size: ${size}px; font-family: $font;'>$text</td>
<td>$author</td>
<td>$timestamp</td>
<td>
<form method='post' style='display:inline;'>
<input type='hidden' name='note_id' value='$key'>
<button type='submit' name='mark_done' value='Mark as Done' class='button button-secondary'>Mark as Done</button>
</form>
<button type='button' class='button edit-note' data-note-id='$key'>Edit</button>
<div id='edit-note-$key' style='display:none;'>
<form class='edit-note-form' data-note-id='$key'>
" . wp_nonce_field('wp_notes_nonce', '_wpnonce', true, false) . "
<input type='hidden' name='note_id' value='$key'>
<textarea name='new_text'>$text</textarea><br>
<input type='color' name='edit_color' value='$color'><br>
<input type='number' name='edit_size' value='$size' min='8' max='72'><br>
<select name='edit_font'>
<option value='Arial' " . selected('Arial', $font, false) . ">Arial</option>
<option value='Helvetica' " . selected('Helvetica', $font, false) . ">Helvetica</option>
<option value='Times New Roman' " . selected('Times New Roman', $font, false) . ">Times New Roman</option>
<option value='Verdana' " . selected('Verdana', $font, false) . ">Verdana</option>
</select><br>
<button type='submit' class='button button-primary'>Save</button>
</form>
</div>
</td>
</tr>";
}
echo '</tbody></table>';
// JavaScript for form toggling and AJAX submissions
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
// Show edit form
$('.edit-note').on('click', function(e) {
e.preventDefault();
var noteId = $(this).data('note-id');
$('.edit-note-form').not('#edit-note-' + noteId).hide();
$('#edit-note-' + noteId).toggle();
});
// AJAX submit the edit form
$('.edit-note-form').on('submit', function(e) {
e.preventDefault();
var $form = $(this);
var $submitButton = $form.find('button[type="submit"]');
var noteId = $form.data('note-id');
// Log form data for debugging
console.log('Form data:', $form.serialize());
console.log('Note ID:', noteId);
// Disable submit button and show loading state
$submitButton.prop('disabled', true).text('Saving...');
// AJAX request
$.ajax({
url: ajaxurl,
type: 'POST',
data: $form.serialize() + '&action=wp_notes_save_edit&note_id=' + noteId,
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error: ' + (response.data || 'Unknown error occurred'));
}
},
error: function(xhr, status, error) {
alert('Server error: ' + error);
},
complete: function() {
$submitButton.prop('disabled', false).text('Save');
}
});
// Emoji Picker Functionality
$('.emoji-picker-button, .emoji-input').on('click', function(e) {
e.preventDefault();
var $container = $(this).closest('.emoji-picker-container');
$container.find('.emoji-picker-dropdown').toggle();
});
// Handle emoji selection
$('.emoji-option').on('click', function() {
var emoji = $(this).data('emoji');
var $container = $(this).closest('.emoji-picker-container');
var $input = $container.find('.emoji-input');
// Append emoji to note text
var $noteText = $('#wp_notes_text');
var currentPosition = $noteText[0].selectionStart;
var text = $noteText.val();
var newText = text.slice(0, currentPosition) + emoji + text.slice(currentPosition);
$noteText.val(newText);
// Update cursor position
$noteText[0].setSelectionRange(currentPosition + emoji.length, currentPosition + emoji.length);
$noteText.focus();
// Hide dropdown
$container.find('.emoji-picker-dropdown').hide();
});
// Close emoji picker when clicking outside
$(document).on('click', function(e) {
if (!$(e.target).closest('.emoji-picker-container').length) {
$('.emoji-picker-dropdown').hide();
}
});
});
</script>
<?php
}
// Done Notes Table Function
function wp_notes_done_table() {
$done_notes = get_option('wp_done_notes', array());
if (!$done_notes) {
echo '<p>' . esc_html__('No completed notes.', 'a-wp-notes') . '</p>';
return;
}
echo '<form method="post"><table class="wp-list-table widefat striped">
<thead>
<tr>
<th scope="col" class="manage-column column-cb check-column">
<input type="checkbox" id="select-all-done">
</th>
<th scope="col">Note</th>
<th scope="col">Created By</th>
<th scope="col">Created On</th>
<th scope="col">Completed By</th>
<th scope="col">Completed On</th>
<th scope="col">Last Modified</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>';
foreach ($done_notes as $key => $note) {
echo '<tr>
<td><input type="checkbox" name="done_ids[]" value="' . esc_attr($key) . '" class="done-checkbox"></td>
<td style="color: ' . esc_attr($note['color']) . '; font-size: 14px; font-family: ' . esc_attr($note['font']) . ';">'
. esc_html($note['text']) . '</td>
<td>' . esc_html($note['author_name'] ?? 'Unknown') . '</td>
<td>' . esc_html($note['timestamp'] ?? 'Unknown') . '</td>
<td>' . esc_html($note['completed_by'] ?? 'Unknown') . '</td>
<td>' . esc_html($note['completed_on'] ?? 'Unknown') . '</td>
<td>' . esc_html($note['last_modified'] ?? 'N/A') . '</td>
<td>
<input type="submit" name="restore_note" value="Restore" class="button button-secondary">
</td>
</tr>';
}
echo '</tbody></table></form>';
// Add JavaScript for select all functionality
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
$('#select-all-done').change(function() {
$('.done-checkbox').prop('checked', $(this).prop('checked'));
});
});
</script>
<?php
}
// Handle note actions
add_action('admin_init', 'wp_notes_handle_actions');
function wp_notes_handle_actions() {
$current_user = wp_get_current_user();
// Check if we're adding a new note
if (isset($_POST['wp_notes_text'])) {
$notes = get_option('wp_notes', array());
// Create the new note
$new_note = array(
'text' => sanitize_text_field($_POST['wp_notes_text']),
'color' => sanitize_hex_color($_POST['wp_notes_color']) ?: '#000000',
'size' => absint($_POST['wp_notes_size']) ?: 16,
'font' => sanitize_text_field($_POST['wp_notes_font']) ?: 'Arial',
'timestamp' => current_time('mysql'),
'author' => $current_user->ID,
'author_name' => $current_user->display_name
);
$notes[] = $new_note;
update_option('wp_notes', $notes);
wp_redirect(admin_url('admin.php?page=wp-notes'));
exit;
}
// Handle "Mark as Done" submissions
if (isset($_POST['mark_done']) && isset($_POST['note_id'])) {
$notes = get_option('wp_notes', array());
$done_notes = get_option('wp_done_notes', array());
$note_id = absint($_POST['note_id']);
if (isset($notes[$note_id])) {
$note = $notes[$note_id];
$note['completed_by'] = $current_user->display_name;
$note['completed_on'] = current_time('mysql');
$done_notes[] = $note;
unset($notes[$note_id]);
update_option('wp_notes', $notes);
update_option('wp_done_notes', $done_notes);
wp_redirect(admin_url('admin.php?page=wp-notes'));
exit;
}
}
// Handle marking notes as done if 'mark_done' action is triggered
if (isset($_POST['mark_done']) && isset($_POST['note_ids'])) {
$notes = get_option('wp_notes', array());
$done_notes = get_option('wp_done_notes', array());
// Move selected notes to done list
foreach ($notes as $key => $note) {
if (in_array($key, $_POST['note_ids'])) {
$note['completed_by'] = $current_user->display_name;
$note['completed_on'] = current_time('mysql');
$done_notes[] = $note;
unset($notes[$key]); // Remove from active notes
}
}
// Update both lists in the database
update_option('wp_notes', $notes);
update_option('wp_done_notes', $done_notes);
// Redirect to avoid form resubmission
wp_redirect(admin_url('admin.php?page=wp-notes'));
exit;
}
// Handle restore note action
if (isset($_POST['restore_note']) && isset($_POST['done_ids'])) {
$notes = get_option('wp_notes', array());
$done_notes = get_option('wp_done_notes', array());
$current_user = wp_get_current_user();
$new_done_notes = array();
foreach ($done_notes as $key => $note) {
if (in_array($key, $_POST['done_ids'])) {
$note['last_modified'] = current_time('mysql');
$note['restored_by'] = $current_user->display_name;
$notes[] = $note;
} else {
$new_done_notes[] = $note;
}
}
update_option('wp_notes', $notes);
update_option('wp_done_notes', $new_done_notes);
}
// Handle edit note action
if (isset($_POST['edit_note']) && isset($_POST['note_ids'])) {
// Add edit functionality here
$notes = get_option('wp_notes', array());
$current_user = wp_get_current_user();
foreach ($_POST['note_ids'] as $key) {
if (isset($notes[$key])) {
$notes[$key]['last_modified'] = current_time('mysql');
$notes[$key]['modified_by'] = $current_user->display_name;
}
}
update_option('wp_notes', $notes);
}
}
// AJAX handler for editing a note
add_action('wp_ajax_wp_notes_edit_note', 'wp_notes_edit_note');
function wp_notes_edit_note() {
if (!isset($_POST['note_id']) || !isset($_POST['edit_text'])) {
wp_send_json_error('Invalid request');
return;
}
$note_id = absint($_POST['note_id']);
$new_text = sanitize_text_field($_POST['edit_text']);
$new_color = sanitize_hex_color($_POST['edit_color']);
$new_size = absint($_POST['edit_size']);
$new_font = sanitize_text_field($_POST['edit_font']);
$notes = get_option('wp_notes', array());
if (isset($notes[$note_id])) {
$notes[$note_id]['text'] = $new_text;
$notes[$note_id]['color'] = $new_color;
$notes[$note_id]['size'] = $new_size;
$notes[$note_id]['font'] = $new_font;
update_option('wp_notes', $notes);
wp_send_json_success();
} else {
wp_send_json_error('Note not found.');
}
}
// Optimize the restore function for performance
function wp_notes_restore($note_id) {
$done_notes = get_option('wp_done_notes', array());
$notes = get_option('wp_notes', array());
if (isset($done_notes[$note_id])) {
$notes[$note_id] = $done_notes[$note_id];
unset($done_notes[$note_id]);
update_option('wp_notes', $notes);
update_option('wp_done_notes', $done_notes);
wp_redirect(admin_url('admin.php?page=wp-notes'));
}
}
// Dashboard widget
function wp_notes_dashboard_widget() {
wp_notes_list_table();
}
function wp_notes_add_dashboard_widgets() {
wp_add_dashboard_widget(
'wp_notes_dashboard_widget',
'WP Notes',
'wp_notes_dashboard_widget'
);
}
add_action('wp_dashboard_setup', 'wp_notes_add_dashboard_widgets');
// Register Custom Post Type for notes
function wp_notes_register_cpt() {
$labels = array(
'name' => __('Notes', 'a-wp-notes'),
'singular_name' => __('Note', 'a-wp-notes'),
'menu_name' => __('WP Notes', 'a-wp-notes'),
'add_new' => __('Add New', 'a-wp-notes'),
'add_new_item' => __('Add New Note', 'a-wp-notes'),
'edit_item' => __('Edit Note', 'a-wp-notes'),
'new_item' => __('New Note', 'a-wp-notes'),
'view_item' => __('View Note', 'a-wp-notes'),
'search_items' => __('Search Notes', 'a-wp-notes'),
'not_found' => __('No notes found', 'a-wp-notes'),
'not_found_in_trash'=> __('No notes found in trash', 'a-wp-notes'),
);
$args = array(
'labels' => $labels,
'public' => false,
// show_ui=false: the live UI stores notes in wp_options, not as
// CPT posts. show_ui=true caused WordPress to auto-inject "All
// Notes" and "Add New" submenus that pointed at post-new.php and
// routed users into the standard post editor — which writes to
// the wrong storage. The CPT remains registered so the
// wp_notes_migrate_to_cpt() helper can still use wp_insert_post.
'show_ui' => false,
'show_in_menu' => false,
'capability_type' => 'post',
'hierarchical' => false,
'supports' => array('title', 'editor', 'author', 'custom-fields'),
'has_archive' => false,
'menu_position' => null,
'show_in_rest' => false,
);
register_post_type('wp_note', $args);
// Register taxonomy for note categories
$tax_labels = array(
'name' => __('Note Categories', 'a-wp-notes'),
'singular_name' => __('Note Category', 'a-wp-notes'),
'search_items' => __('Search Categories', 'a-wp-notes'),
'all_items' => __('All Categories', 'a-wp-notes'),
'parent_item' => __('Parent Category', 'a-wp-notes'),
'parent_item_colon' => __('Parent Category:', 'a-wp-notes'),
'edit_item' => __('Edit Category', 'a-wp-notes'),
'update_item' => __('Update Category', 'a-wp-notes'),
'add_new_item' => __('Add New Category', 'a-wp-notes'),
'new_item_name' => __('New Category Name', 'a-wp-notes'),
'menu_name' => __('Categories', 'a-wp-notes'),
);
register_taxonomy('wp_note_category', 'wp_note', array(
'hierarchical' => true,
'labels' => $tax_labels,
// Hidden alongside the parent CPT — live UI doesn't surface
// categories yet, and showing the taxonomy in admin with the
// parent hidden would just dead-link.
'show_ui' => false,
'show_admin_column' => false,
'query_var' => true,
'show_in_rest' => false,
));
}
add_action('init', 'wp_notes_register_cpt');
// Add custom meta box for note properties
function wp_notes_add_meta_boxes() {
add_meta_box(
'wp_notes_properties',
__('Note Properties', 'a-wp-notes'),
'wp_notes_render_properties_meta_box',
'wp_note',
'side',
'high'
);
}
add_action('add_meta_boxes', 'wp_notes_add_meta_boxes');
// Render meta box content
function wp_notes_render_properties_meta_box($post) {
// Add nonce for security
wp_nonce_field('wp_notes_meta_box', 'wp_notes_meta_box_nonce');
// Get current values
$color = get_post_meta($post->ID, '_wp_note_color', true) ?: '#000000';
$size = get_post_meta($post->ID, '_wp_note_size', true) ?: '16';
$font = get_post_meta($post->ID, '_wp_note_font', true) ?: 'Arial';
$status = get_post_meta($post->ID, '_wp_note_status', true) ?: 'active';
?>
<p>
<label for="wp_note_color"><?php _e('Color:', 'a-wp-notes'); ?></label><br>
<input type="color" id="wp_note_color" name="wp_note_color" value="<?php echo esc_attr($color); ?>">
</p>
<p>
<label for="wp_note_size"><?php _e('Size:', 'a-wp-notes'); ?></label><br>
<input type="number" id="wp_note_size" name="wp_note_size" value="<?php echo esc_attr($size); ?>" min="8" max="72">
</p>
<p>
<label for="wp_note_font"><?php _e('Font:', 'a-wp-notes'); ?></label><br>
<select id="wp_note_font" name="wp_note_font">
<option value="Arial" <?php selected($font, 'Arial'); ?>>Arial</option>
<option value="Helvetica" <?php selected($font, 'Helvetica'); ?>>Helvetica</option>
<option value="Times New Roman" <?php selected($font, 'Times New Roman'); ?>>Times New Roman</option>
<option value="Verdana" <?php selected($font, 'Verdana'); ?>>Verdana</option>
</select>
</p>
<p>
<label for="wp_note_status"><?php _e('Status:', 'a-wp-notes'); ?></label><br>
<select id="wp_note_status" name="wp_note_status">
<option value="active" <?php selected($status, 'active'); ?>><?php _e('Active', 'a-wp-notes'); ?></option>
<option value="completed" <?php selected($status, 'completed'); ?>><?php _e('Completed', 'a-wp-notes'); ?></option>
</select>
</p>
<?php
}
// Save meta box data
function wp_notes_save_meta_box_data($post_id) {
// Check if our nonce is set and verify it
if (!isset($_POST['wp_notes_meta_box_nonce']) ||
!wp_verify_nonce($_POST['wp_notes_meta_box_nonce'], 'wp_notes_meta_box')) {
return;
}
// Don't save during autosave
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
// Check user permissions
if (!current_user_can('edit_post', $post_id)) {
return;
}
// Save the meta fields
$fields = array(
'_wp_note_color' => 'sanitize_hex_color',
'_wp_note_size' => 'absint',
'_wp_note_font' => 'sanitize_text_field',
'_wp_note_status' => 'sanitize_text_field'
);
foreach ($fields as $key => $sanitize_callback) {
if (isset($_POST[str_replace('_', '', $key)])) {
$value = call_user_func($sanitize_callback, $_POST[str_replace('_', '', $key)]);
update_post_meta($post_id, $key, $value);
}
}
}
add_action('save_post_wp_note', 'wp_notes_save_meta_box_data');
// Data Migration Tool
function wp_notes_migrate_to_cpt() {
$existing_notes = get_option('wp_notes', array());
$done_notes = get_option('wp_done_notes', array());
$migrated_count = 0;
$errors = array();
// Start migration
foreach ($existing_notes as $note) {
$post_data = array(
'post_title' => wp_trim_words($note['text'], 10, '...'),
'post_content' => $note['text'],
'post_status' => 'publish',
'post_type' => 'wp_note',
'post_author' => $note['author'] ?? get_current_user_id(),
);
// Insert the post
$post_id = wp_insert_post($post_data, true);
if (!is_wp_error($post_id)) {
// Add note meta data
update_post_meta($post_id, '_wp_note_color', $note['color'] ?? '#000000');
update_post_meta($post_id, '_wp_note_size', $note['size'] ?? '16');
update_post_meta($post_id, '_wp_note_font', $note['font'] ?? 'Arial');
update_post_meta($post_id, '_wp_note_status', 'active');
update_post_meta($post_id, '_wp_note_created', $note['timestamp'] ?? '');
update_post_meta($post_id, '_wp_note_author_name', $note['author_name'] ?? '');
$migrated_count++;
} else {
$errors[] = $post_id->get_error_message();
}
}
// Migrate completed notes
foreach ($done_notes as $note) {
$post_data = array(
'post_title' => wp_trim_words($note['text'], 10, '...'),
'post_content' => $note['text'],
'post_status' => 'publish',
'post_type' => 'wp_note',
'post_author' => $note['author'] ?? get_current_user_id(),
);
$post_id = wp_insert_post($post_data, true);
if (!is_wp_error($post_id)) {
update_post_meta($post_id, '_wp_note_color', $note['color'] ?? '#000000');
update_post_meta($post_id, '_wp_note_size', $note['size'] ?? '16');
update_post_meta($post_id, '_wp_note_font', $note['font'] ?? 'Arial');
update_post_meta($post_id, '_wp_note_status', 'completed');
update_post_meta($post_id, '_wp_note_created', $note['timestamp'] ?? '');
update_post_meta($post_id, '_wp_note_completed_by', $note['completed_by'] ?? '');
update_post_meta($post_id, '_wp_note_completed_on', $note['completed_on'] ?? '');
update_post_meta($post_id, '_wp_note_author_name', $note['author_name'] ?? '');
$migrated_count++;
} else {
$errors[] = $post_id->get_error_message();
}
}
// Create backup of old data
update_option('wp_notes_pre_migration_backup', array(
'notes' => $existing_notes,
'done_notes' => $done_notes,
'migration_date' => current_time('mysql')
));
return array(
'success' => true,
'migrated_count' => $migrated_count,
'errors' => $errors
);
}
// Add migration notice for admin
function wp_notes_migration_notice() {
if (!current_user_can('manage_options')) {
return;
}
$migrated = get_option('wp_notes_migration_completed');
if (!$migrated) {
?>
<div class="notice notice-info is-dismissible">
<p>
<?php _e('WP Notes needs to migrate your existing notes to the new storage system.', 'a-wp-notes'); ?>
<a href="<?php echo esc_url(admin_url('admin.php?page=wp-notes-settings&action=migrate')); ?>" class="button button-primary">
<?php _e('Start Migration', 'a-wp-notes'); ?>
</a>
</p>
</div>
<?php
}
}
add_action('admin_notices', 'wp_notes_migration_notice');
// Handle migration request
function wp_notes_handle_migration() {
if (isset($_GET['page']) && $_GET['page'] === 'wp-notes-settings' &&
isset($_GET['action']) && $_GET['action'] === 'migrate') {
check_admin_referer('wp_notes_migration');
$result = wp_notes_migrate_to_cpt();
if ($result['success']) {
update_option('wp_notes_migration_completed', true);
add_settings_error(
'wp_notes_migration',
'migration_success',
sprintf(
__('Successfully migrated %d notes. %s', 'a-wp-notes'),
$result['migrated_count'],
!empty($result['errors']) ? 'Errors: ' . implode(', ', $result['errors']) : ''
),
'updated'
);
} else {
add_settings_error(
'wp_notes_migration',
'migration_error',
__('Migration failed. Please try again or contact support.', 'a-wp-notes'),
'error'
);
}
wp_redirect(admin_url('admin.php?page=wp-notes-settings'));
exit;
}
}
add_action('admin_init', 'wp_notes_handle_migration');
// End of wp-notes.php
?>