Restore to commit 74e578279624c6045ca440a3459ebfa1f8d54191

This commit is contained in:
southseact-3d
2026-02-07 20:32:41 +00:00
commit ed67b7741b
252 changed files with 99814 additions and 0 deletions

View File

@@ -0,0 +1,590 @@
<?php
/**
* Admin Handler
*
* @package PCChangelogManager
*/
// Prevent direct access to the file.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles all admin functionality for the changelog manager.
*/
class PC_CLM_Admin {
/**
* Instance of the post type class.
*
* @var PC_CLM_Post_Type
*/
private $post_type;
/**
* Constructor.
*
* @param PC_CLM_Post_Type $post_type Post type instance.
*/
public function __construct( $post_type ) {
$this->post_type = $post_type;
$this->init_hooks();
}
/**
* Initialize admin hooks.
*/
private function init_hooks() {
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) );
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
add_action( 'save_post_' . $this->post_type->get_post_type_slug(), array( $this, 'save_meta' ), 10, 2 );
add_filter( 'manage_' . $this->post_type->get_post_type_slug() . '_posts_columns', array( $this, 'set_custom_columns' ) );
add_action( 'manage_' . $this->post_type->get_post_type_slug() . '_posts_custom_column', array( $this, 'render_custom_columns' ), 10, 2 );
add_filter( 'post_row_actions', array( $this, 'modify_row_actions' ), 10, 2 );
add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
add_action( 'parent_file', array( $this, 'highlight_menu' ) );
add_action( 'admin_init', array( $this, 'handle_add_new_form' ) );
}
/**
* Enqueue admin styles.
*/
public function enqueue_styles() {
$screen = get_current_screen();
if ( ! $screen ) {
return;
}
// Only enqueue on changelog post type screens.
if ( $this->post_type->get_post_type_slug() !== $screen->post_type ) {
return;
}
// Enqueue admin stylesheet.
if ( file_exists( PC_CLM_PLUGIN_DIR . 'admin/css/admin-style.css' ) ) {
wp_enqueue_style(
'pc-clm-admin-style',
PC_CLM_PLUGIN_URL . 'admin/css/admin-style.css',
array(),
PC_CLM_VERSION
);
}
}
/**
* Add admin menu items.
*/
public function add_admin_menu() {
add_menu_page(
__( 'Changelog', 'pc-changelog-manager-abc123' ),
__( 'Changelog', 'pc-changelog-manager-abc123' ),
'manage_options',
'pc-clm-view-changelog',
array( $this, 'render_view_changelog_page' ),
'dashicons-backup',
30
);
add_submenu_page(
'pc-clm-view-changelog',
__( 'Add New Changelog Entry', 'pc-changelog-manager-abc123' ),
__( 'Add New', 'pc-changelog-manager-abc123' ),
'manage_options',
'pc-clm-add-new',
array( $this, 'render_add_new_page' )
);
}
/**
* Render the main menu page (no-op).
* The top-level menu now links directly to the post list to avoid redirects
* that may run after headers/styles have already been sent.
*/
public function render_main_menu_page() {
// Intentionally left blank to avoid header() calls after output.
}
/**
* Render the view changelog page - displays changelog entries in admin.
*/
public function render_view_changelog_page() {
$post_type = new PC_CLM_Post_Type();
$query = $post_type->get_entries();
?>
<div class="wrap">
<h1 class="wp-heading-inline"><?php esc_html_e( 'View Changelog', 'pc-changelog-manager-abc123' ); ?></h1>
<a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=' . $post_type->get_post_type_slug() ) ); ?>" class="page-title-action">
<?php esc_html_e( 'Add New Entry', 'pc-changelog-manager-abc123' ); ?>
</a>
<hr class="wp-header-end">
<?php if ( $query->have_posts() ) : ?>
<div class="pc-clm-admin-view-list">
<?php
while ( $query->have_posts() ) :
$query->the_post();
$post_id = get_the_ID();
$version = $post_type->get_meta( $post_id, 'version_number' );
$release_date = $post_type->get_meta( $post_id, 'release_date' );
$category = $post_type->get_meta( $post_id, 'category' );
$categories = $post_type->get_categories();
$category_name = isset( $categories[ $category ] ) ? $categories[ $category ] : $category;
?>
<div class="pc-clm-admin-entry">
<div class="pc-clm-admin-entry-header">
<?php if ( $version ) : ?>
<span class="pc-clm-admin-version"><?php echo esc_html( $version ); ?></span>
<?php endif; ?>
<?php if ( $release_date ) : ?>
<span class="pc-clm-admin-date"><?php echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $release_date ) ) ); ?></span>
<?php endif; ?>
<?php if ( $category ) : ?>
<span class="pc-clm-admin-category pc-clm-category-<?php echo esc_attr( $category ); ?>"><?php echo esc_html( $category_name ); ?></span>
<?php endif; ?>
</div>
<h3 class="pc-clm-admin-entry-title">
<a href="<?php echo esc_url( get_edit_post_link( $post_id ) ); ?>">
<?php the_title(); ?>
</a>
</h3>
<div class="pc-clm-admin-entry-content">
<?php echo wp_kses_post( wp_trim_words( get_the_content(), 30, '...' ) ); ?>
</div>
<div class="pc-clm-admin-entry-actions">
<a href="<?php echo esc_url( get_edit_post_link( $post_id ) ); ?>" class="button button-small">
<?php esc_html_e( 'Edit', 'pc-changelog-manager-abc123' ); ?>
</a>
<a href="<?php echo esc_url( get_permalink( $post_id ) ); ?>" target="_blank" class="button button-small">
<?php esc_html_e( 'View', 'pc-changelog-manager-abc123' ); ?>
</a>
</div>
</div>
<?php
endwhile;
wp_reset_postdata();
?>
</div>
<?php else : ?>
<div class="pc-clm-admin-empty">
<p>
<span class="dashicons dashicons-backup" style="font-size: 48px; width: 48px; height: 48px; display: block; margin: 0 auto 16px; opacity: 0.5;"></span>
<?php esc_html_e( 'No changelog entries found.', 'pc-changelog-manager-abc123' ); ?>
</p>
<a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=' . $post_type->get_post_type_slug() ) ); ?>" class="button button-primary">
<?php esc_html_e( 'Add Your First Changelog Entry', 'pc-changelog-manager-abc123' ); ?>
</a>
</div>
<?php endif; ?>
</div>
<?php
}
/**
* Handle the add new changelog form submission.
*/
public function handle_add_new_form() {
if ( ! isset( $_POST['pc_clm_add_new_nonce'] ) || ! isset( $_POST['pc_clm_submit_new'] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['pc_clm_add_new_nonce'] ) ), 'pc_clm_add_new' ) ) {
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$title = isset( $_POST['pc_clm_title'] ) ? sanitize_text_field( wp_unslash( $_POST['pc_clm_title'] ) ) : '';
$content = isset( $_POST['pc_clm_content'] ) ? wp_kses_post( wp_unslash( $_POST['pc_clm_content'] ) ) : '';
$version = isset( $_POST['pc_clm_version'] ) ? sanitize_text_field( wp_unslash( $_POST['pc_clm_version'] ) ) : '1.0.0';
$release_date = isset( $_POST['pc_clm_release_date'] ) ? sanitize_text_field( wp_unslash( $_POST['pc_clm_release_date'] ) ) : current_time( 'Y-m-d' );
$category = isset( $_POST['pc_clm_category'] ) ? sanitize_text_field( wp_unslash( $_POST['pc_clm_category'] ) ) : 'new-features';
if ( empty( $title ) ) {
return;
}
$post_id = wp_insert_post( array(
'post_title' => $title,
'post_content' => $content,
'post_status' => 'publish',
'post_type' => $this->post_type->get_post_type_slug(),
) );
if ( ! is_wp_error( $post_id ) ) {
$this->post_type->update_meta( $post_id, 'version_number', $version );
$this->post_type->update_meta( $post_id, 'release_date', $release_date );
$this->post_type->update_meta( $post_id, 'category', $category );
add_action( 'admin_notices', function() {
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Changelog entry added successfully!', 'pc-changelog-manager-abc123' ) . '</p></div>';
} );
}
}
/**
* Render the add new changelog page.
*/
public function render_add_new_page() {
$categories = $this->post_type->get_categories();
$default_version = '1.0.0';
$default_date = current_time( 'Y-m-d' );
?>
<div class="wrap">
<h1 class="wp-heading-inline"><?php esc_html_e( 'Add New Changelog Entry', 'pc-changelog-manager-abc123' ); ?></h1>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=pc-clm-view-changelog' ) ); ?>" class="page-title-action">
<?php esc_html_e( 'View All Entries', 'pc-changelog-manager-abc123' ); ?>
</a>
<hr class="wp-header-end">
<div class="pc-clm-admin-add-form">
<form method="post" action="">
<?php wp_nonce_field( 'pc_clm_add_new', 'pc_clm_add_new_nonce' ); ?>
<table class="form-table">
<tr>
<th scope="row">
<label for="pc_clm_title"><?php esc_html_e( 'Title', 'pc-changelog-manager-abc123' ); ?> <span class="required">*</span></label>
</th>
<td>
<input type="text" id="pc_clm_title" name="pc_clm_title" class="regular-text" required>
<p class="description"><?php esc_html_e( 'Enter a title for this changelog entry.', 'pc-changelog-manager-abc123' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="pc_clm_version"><?php esc_html_e( 'Version Number', 'pc-changelog-manager-abc123' ); ?></label>
</th>
<td>
<input type="text" id="pc_clm_version" name="pc_clm_version" value="<?php echo esc_attr( $default_version ); ?>" class="regular-text">
<p class="description"><?php esc_html_e( 'The version number (e.g., 1.0.0)', 'pc-changelog-manager-abc123' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="pc_clm_release_date"><?php esc_html_e( 'Release Date', 'pc-changelog-manager-abc123' ); ?></label>
</th>
<td>
<input type="date" id="pc_clm_release_date" name="pc_clm_release_date" value="<?php echo esc_attr( $default_date ); ?>">
<p class="description"><?php esc_html_e( 'The release date for this version.', 'pc-changelog-manager-abc123' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="pc_clm_category"><?php esc_html_e( 'Category', 'pc-changelog-manager-abc123' ); ?></label>
</th>
<td>
<select id="pc_clm_category" name="pc_clm_category">
<?php foreach ( $categories as $slug => $name ) : ?>
<option value="<?php echo esc_attr( $slug ); ?>">
<?php echo esc_html( $name ); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( 'The category for this changelog entry.', 'pc-changelog-manager-abc123' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="pc_clm_content"><?php esc_html_e( 'Content', 'pc-changelog-manager-abc123' ); ?></label>
</th>
<td>
<?php
wp_editor( '', 'pc_clm_content', array(
'media_buttons' => true,
'textarea_rows' => 10,
'teeny' => false,
) );
?>
<p class="description"><?php esc_html_e( 'Describe the changes in this version.', 'pc-changelog-manager-abc123' ); ?></p>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="pc_clm_submit_new" class="button button-primary" value="<?php esc_attr_e( 'Add Changelog Entry', 'pc-changelog-manager-abc123' ); ?>">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=pc-clm-view-changelog' ) ); ?>" class="button">
<?php esc_html_e( 'Cancel', 'pc-changelog-manager-abc123' ); ?>
</a>
</p>
</form>
</div>
</div>
<?php
}
/**
* Highlight the correct menu item.
*
* @param string $parent_file Parent menu slug.
* @return string
*/
public function highlight_menu( $parent_file ) {
global $submenu_file, $current_screen;
if ( ! isset( $current_screen ) ) {
return $parent_file;
}
// Handle our custom view changelog page.
if ( isset( $current_screen->base ) && 'pc-clm-view-changelog' === $current_screen->base ) {
$parent_file = 'pc-clm-view-changelog';
$submenu_file = 'pc-clm-view-changelog';
}
// Handle our custom add new page.
if ( isset( $current_screen->base ) && 'pc-clm-add-new' === $current_screen->base ) {
$parent_file = 'pc-clm-view-changelog';
$submenu_file = 'pc-clm-add-new';
}
return $parent_file;
}
/**
* Add meta boxes to the changelog entry editor.
*/
public function add_meta_boxes() {
add_meta_box(
'pc_clm_entry_details',
__( 'Entry Details', 'pc-changelog-manager-abc123' ),
array( $this, 'render_entry_details_meta_box' ),
$this->post_type->get_post_type_slug(),
'normal',
'high'
);
add_meta_box(
'pc_clm_preview',
__( 'Preview', 'pc-changelog-manager-abc123' ),
array( $this, 'render_preview_meta_box' ),
$this->post_type->get_post_type_slug(),
'side',
'low'
);
}
/**
* Render the entry details meta box.
*
* @param WP_Post $post Current post object.
*/
public function render_entry_details_meta_box( $post ) {
// Add nonce field.
wp_nonce_field( 'pc_clm_save_meta', 'pc_clm_meta_nonce' );
// Get current values.
$version_number = $this->post_type->get_meta( $post->ID, 'version_number' );
$release_date = $this->post_type->get_meta( $post->ID, 'release_date' );
$category = $this->post_type->get_meta( $post->ID, 'category' );
$categories = $this->post_type->get_categories();
// Set default values for new posts.
if ( empty( $version_number ) ) {
$version_number = '1.0.0';
}
if ( empty( $release_date ) ) {
$release_date = current_time( 'Y-m-d' );
}
?>
<div class="pc-clm-meta-fields">
<div class="pc-clm-field-group">
<label for="pc_clm_version_number"><?php esc_html_e( 'Version Number', 'pc-changelog-manager-abc123' ); ?></label>
<input type="text" id="pc_clm_version_number" name="pc_clm_version_number" value="<?php echo esc_attr( $version_number ); ?>" placeholder="e.g., 1.0.0" />
<p class="description"><?php esc_html_e( 'The version number for this changelog entry (e.g., 1.0.0, 2.1.3).', 'pc-changelog-manager-abc123' ); ?></p>
</div>
<div class="pc-clm-field-group">
<label for="pc_clm_release_date"><?php esc_html_e( 'Release Date', 'pc-changelog-manager-abc123' ); ?></label>
<input type="date" id="pc_clm_release_date" name="pc_clm_release_date" value="<?php echo esc_attr( $release_date ); ?>" />
<p class="description"><?php esc_html_e( 'The release date for this version.', 'pc-changelog-manager-abc123' ); ?></p>
</div>
<div class="pc-clm-field-group">
<label for="pc_clm_category"><?php esc_html_e( 'Category', 'pc-changelog-manager-abc123' ); ?></label>
<select id="pc_clm_category" name="pc_clm_category">
<?php foreach ( $categories as $slug => $name ) : ?>
<option value="<?php echo esc_attr( $slug ); ?>" <?php selected( $category, $slug ); ?>>
<?php echo esc_html( $name ); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( 'The category for this changelog entry.', 'pc-changelog-manager-abc123' ); ?></p>
</div>
</div>
<?php
}
/**
* Render the preview meta box.
*
* @param WP_Post $post Current post object.
*/
public function render_preview_meta_box( $post ) {
$view_page_url = pc_clm_get_changelog_page_url();
?>
<div class="pc-clm-preview-box">
<p><?php esc_html_e( 'View your changelog page to see how entries will appear to visitors.', 'pc-changelog-manager-abc123' ); ?></p>
<a href="<?php echo esc_url( $view_page_url ); ?>" target="_blank" class="button button-secondary">
<span class="dashicons dashicons-external" style="margin-top: 3px;"></span>
<?php esc_html_e( 'View Changelog Page', 'pc-changelog-manager-abc123' ); ?>
</a>
</div>
<?php
}
/**
* Save meta box data.
*
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
*/
public function save_meta( $post_id, $post ) {
// Verify nonce.
if ( ! isset( $_POST['pc_clm_meta_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['pc_clm_meta_nonce'] ) ), 'pc_clm_save_meta' ) ) {
return $post_id;
}
// Check if user has permission.
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
// Check if autosave.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}
// Save version number.
if ( isset( $_POST['pc_clm_version_number'] ) ) {
$this->post_type->update_meta( $post_id, 'version_number', sanitize_text_field( wp_unslash( $_POST['pc_clm_version_number'] ) ) );
}
// Save release date.
if ( isset( $_POST['pc_clm_release_date'] ) ) {
$this->post_type->update_meta( $post_id, 'release_date', sanitize_text_field( wp_unslash( $_POST['pc_clm_release_date'] ) ) );
}
// Save category.
if ( isset( $_POST['pc_clm_category'] ) ) {
$allowed_categories = array_keys( $this->post_type->get_categories() );
$category = sanitize_text_field( wp_unslash( $_POST['pc_clm_category'] ) );
if ( in_array( $category, $allowed_categories, true ) ) {
$this->post_type->update_meta( $post_id, 'category', $category );
}
}
}
/**
* Set custom columns for the admin list view.
*
* @param array $columns Existing columns.
* @return array
*/
public function set_custom_columns( $columns ) {
unset( $columns['date'] );
$new_columns = array(
'version' => __( 'Version', 'pc-changelog-manager-abc123' ),
'release_date' => __( 'Release Date', 'pc-changelog-manager-abc123' ),
'category' => __( 'Category', 'pc-changelog-manager-abc123' ),
'date' => __( 'Date', 'pc-changelog-manager-abc123' ),
);
return array_merge( $columns, $new_columns );
}
/**
* Render custom column content.
*
* @param string $column Column name.
* @param int $post_id Post ID.
*/
public function render_custom_columns( $column, $post_id ) {
switch ( $column ) {
case 'version':
$version = $this->post_type->get_meta( $post_id, 'version_number' );
echo esc_html( $version );
break;
case 'release_date':
$release_date = $this->post_type->get_meta( $post_id, 'release_date' );
if ( $release_date ) {
echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $release_date ) ) );
} else {
echo '&mdash;';
}
break;
case 'category':
$category = $this->post_type->get_meta( $post_id, 'category' );
$categories = $this->post_type->get_categories();
$category_name = isset( $categories[ $category ] ) ? $categories[ $category ] : $category;
$category_class = 'pc-clm-category-' . esc_attr( $category );
echo '<span class="pc-clm-category-badge ' . esc_attr( $category_class ) . '">';
echo esc_html( $category_name );
echo '</span>';
break;
}
}
/**
* Modify row actions in the admin list.
*
* @param array $actions Existing actions.
* @param WP_Post $post Post object.
* @return array
*/
public function modify_row_actions( $actions, $post ) {
if ( $this->post_type->get_post_type_slug() !== $post->post_type ) {
return $actions;
}
// Remove quick edit as it doesn't work well with custom meta.
if ( isset( $actions['inline hide-if-no-js'] ) ) {
unset( $actions['inline hide-if-no-js'] );
}
return $actions;
}
}
/**
* Get the changelog page URL.
*
* @return string
*/
function pc_clm_get_changelog_page_url() {
// First try to get the custom changelog page.
$changelog_page = get_page_by_path( 'changelog' );
if ( $changelog_page && 'publish' === $changelog_page->post_status ) {
return get_permalink( $changelog_page );
}
// Fall back to the post type archive URL.
$post_type = get_post_type_object( 'pc_changelog' );
if ( $post_type && $post_type->has_archive ) {
return get_post_type_archive_link( 'pc_changelog' );
}
// Final fallback to home URL with changelog slug.
return home_url( '/changelog/' );
}

View File

@@ -0,0 +1,168 @@
<?php
/**
* Custom Post Type Handler
*
* @package PCChangelogManager
*/
// Prevent direct access to the file.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles registration and management of the changelog custom post type.
*/
class PC_CLM_Post_Type {
/**
* Post type slug.
*
* @var string
*/
const POST_TYPE_SLUG = 'pc_changelog';
/**
* Constructor.
*/
public function __construct() {
// Intentionally empty.
}
/**
* Register the custom post type.
*/
public function register() {
$labels = array(
'name' => _x( 'Changelogs', 'Post type general name', 'pc-changelog-manager-abc123' ),
'singular_name' => _x( 'Changelog Entry', 'Post type singular name', 'pc-changelog-manager-abc123' ),
'menu_name' => _x( 'Changelogs', 'Admin Menu text', 'pc-changelog-manager-abc123' ),
'name_admin_bar' => _x( 'Changelog Entry', 'Add New on Toolbar', 'pc-changelog-manager-abc123' ),
'add_new' => __( 'Add New', 'pc-changelog-manager-abc123' ),
'add_new_item' => __( 'Add New Changelog Entry', 'pc-changelog-manager-abc123' ),
'new_item' => __( 'New Changelog Entry', 'pc-changelog-manager-abc123' ),
'edit_item' => __( 'Edit Changelog Entry', 'pc-changelog-manager-abc123' ),
'view_item' => __( 'View Changelog Entry', 'pc-changelog-manager-abc123' ),
'all_items' => __( 'All Changelog Entries', 'pc-changelog-manager-abc123' ),
'search_items' => __( 'Search Changelog Entries', 'pc-changelog-manager-abc123' ),
'parent_item_colon' => __( 'Parent Changelog Entries:', 'pc-changelog-manager-abc123' ),
'not_found' => __( 'No changelog entries found.', 'pc-changelog-manager-abc123' ),
'not_found_in_trash' => __( 'No changelog entries found in Trash.', 'pc-changelog-manager-abc123' ),
'featured_image' => _x( 'Changelog Cover Image', 'Overrides the "Featured Image" phrase for this post type.', 'pc-changelog-manager-abc123' ),
'set_featured_image' => _x( 'Set cover image', 'Overrides the "Set featured image" phrase.', 'pc-changelog-manager-abc123' ),
'remove_featured_image' => _x( 'Remove cover image', 'Overrides the "Remove featured image" phrase.', 'pc-changelog-manager-abc123' ),
'use_featured_image' => _x( 'Use as cover image', 'Overrides the "Use as featured image" phrase.', 'pc-changelog-manager-abc123' ),
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => false,
'query_var' => true,
'rewrite' => array( 'slug' => 'changelog-entry' ),
'capability_type' => 'post',
'map_meta_cap' => true,
'has_archive' => false,
'hierarchical' => false,
'menu_position' => 30,
'menu_icon' => 'dashicons-backup',
'supports' => array(
'title',
'editor',
'revisions',
'author',
),
'show_in_rest' => true,
'rest_base' => 'changelog',
);
register_post_type( self::POST_TYPE_SLUG, $args );
}
/**
* Get the post type slug.
*
* @return string
*/
public function get_post_type_slug() {
return self::POST_TYPE_SLUG;
}
/**
* Get changelog entries.
*
* @param array $args Query arguments.
* @return WP_Query
*/
public function get_entries( $args = array() ) {
$defaults = array(
'post_type' => self::POST_TYPE_SLUG,
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'date',
'order' => 'DESC',
);
$args = wp_parse_args( $args, $defaults );
return new WP_Query( $args );
}
/**
* Get a single changelog entry.
*
* @param int $post_id Post ID.
* @return WP_Post|null
*/
public function get_entry( $post_id ) {
$post = get_post( $post_id );
if ( ! $post || self::POST_TYPE_SLUG !== $post->post_type ) {
return null;
}
return $post;
}
/**
* Get categories for changelog entries.
*
* @return array
*/
public function get_categories() {
return array(
'new-features' => __( 'New Features', 'pc-changelog-manager-abc123' ),
'bug-fixes' => __( 'Bug Fixes', 'pc-changelog-manager-abc123' ),
'improvements' => __( 'Improvements', 'pc-changelog-manager-abc123' ),
'security' => __( 'Security', 'pc-changelog-manager-abc123' ),
'deprecated' => __( 'Deprecated', 'pc-changelog-manager-abc123' ),
'removed' => __( 'Removed', 'pc-changelog-manager-abc123' ),
);
}
/**
* Get changelog entry meta.
*
* @param int $post_id Post ID.
* @param string $key Meta key.
* @param bool $single Whether to return a single value.
* @return mixed
*/
public function get_meta( $post_id, $key, $single = true ) {
return get_post_meta( $post_id, '_pc_clm_' . $key, $single );
}
/**
* Set changelog entry meta.
*
* @param int $post_id Post ID.
* @param string $key Meta key.
* @param mixed $value Meta value.
* @return bool
*/
public function update_meta( $post_id, $key, $value ) {
return update_post_meta( $post_id, '_pc_clm_' . $key, $value );
}
}

View File

@@ -0,0 +1,438 @@
<?php
/**
* Public Handler
*
* @package PCChangelogManager
*/
// Prevent direct access to the file.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles all public-facing functionality for the changelog manager.
*/
class PC_CLM_Public {
/**
* Instance of the post type class.
*
* @var PC_CLM_Post_Type
*/
private $post_type;
/**
* Constructor.
*
* @param PC_CLM_Post_Type $post_type Post type instance.
*/
public function __construct( $post_type ) {
$this->post_type = $post_type;
$this->init_hooks();
}
/**
* Initialize public hooks.
*/
private function init_hooks() {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
add_filter( 'template_include', array( $this, 'template_loader' ) );
add_shortcode( 'changelog', array( $this, 'shortcode_changelog' ) );
add_action( 'widgets_init', array( $this, 'register_widgets' ) );
add_action( 'wp_ajax_pc_clm_upvote', array( $this, 'handle_upvote' ) );
add_action( 'wp_ajax_nopriv_pc_clm_upvote', array( $this, 'handle_upvote' ) );
}
/**
* Enqueue public styles.
*/
public function enqueue_styles() {
// Check if we're on the changelog page or if shortcode is used.
if ( ! $this->is_changelog_page() && ! $this->is_changelog_single() && ! $this->has_shortcode() ) {
return;
}
$min_suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
if ( file_exists( PC_CLM_PLUGIN_DIR . 'public/css/public-style.css' ) ) {
// Enqueue main stylesheet.
if ( ! wp_style_is( 'pc-clm-public-style', 'enqueued' ) ) {
wp_enqueue_style(
'pc-clm-public-style',
PC_CLM_PLUGIN_URL . 'public/css/public-style.css',
array(),
PC_CLM_VERSION
);
}
}
}
/**
* Check if we're on the changelog page.
*
* @return bool
*/
private function is_changelog_page() {
return is_post_type_archive( $this->post_type->get_post_type_slug() );
}
private function is_changelog_single() {
return is_singular( $this->post_type->get_post_type_slug() );
}
/**
* Check if the page has the changelog shortcode.
*
* @return bool
*/
private function has_shortcode() {
if ( ! is_singular() ) {
return false;
}
$post = get_post();
if ( ! $post ) {
return false;
}
return has_shortcode( $post->post_content, 'changelog' );
}
public function template_loader( $template ) {
if ( is_singular( $this->post_type->get_post_type_slug() ) ) {
$custom_template = PC_CLM_PLUGIN_DIR . 'public/templates/single-pc_changelog.php';
if ( file_exists( $custom_template ) ) {
return $custom_template;
}
}
return $template;
}
/**
* Changelog shortcode.
*
* @param array $atts Shortcode attributes.
* @return string
*/
public function shortcode_changelog( $atts ) {
$atts = shortcode_atts(
array(
'limit' => 10,
'category' => '',
'show_date' => 'yes',
'show_title' => 'yes',
'order' => 'DESC',
),
$atts,
'changelog'
);
// Enqueue styles for the shortcode output.
if ( file_exists( PC_CLM_PLUGIN_DIR . 'public/css/public-style.css' ) ) {
wp_enqueue_style( 'pc-clm-public-style', PC_CLM_PLUGIN_URL . 'public/css/public-style.css', array(), PC_CLM_VERSION );
}
// Build query args.
$query_args = array(
'post_type' => $this->post_type->get_post_type_slug(),
'post_status' => 'publish',
'posts_per_page' => intval( $atts['limit'] ),
'orderby' => 'date',
'order' => 'DESC' === $atts['order'] ? 'DESC' : 'ASC',
);
// Filter by category if specified.
if ( ! empty( $atts['category'] ) ) {
$query_args['meta_query'] = array(
array(
'key' => '_pc_clm_category',
'value' => sanitize_text_field( $atts['category'] ),
),
);
}
$query = new WP_Query( $query_args );
ob_start();
if ( $query->have_posts() ) {
?>
<div class="pc-clm-shortcode-wrapper">
<?php if ( 'yes' === $atts['show_title'] ) : ?>
<h2 class="pc-clm-shortcode-title"><?php esc_html_e( 'Changelog', 'pc-changelog-manager-abc123' ); ?></h2>
<?php endif; ?>
<div class="pc-clm-entries-list">
<?php
while ( $query->have_posts() ) :
$query->the_post();
$this->render_entry( get_the_ID(), $atts );
endwhile;
?>
</div>
</div>
<?php
wp_reset_postdata();
} else {
?>
<div class="pc-clm-no-entries">
<p><?php esc_html_e( 'No changelog entries found.', 'pc-changelog-manager-abc123' ); ?></p>
</div>
<?php
}
return ob_get_clean();
}
/**
* Render a single changelog entry.
*
* @param int $post_id Post ID.
* @param array $atts Shortcode attributes.
*/
private function render_entry( $post_id, $atts ) {
$version = $this->post_type->get_meta( $post_id, 'version_number' );
$release_date = $this->post_type->get_meta( $post_id, 'release_date' );
$category = $this->post_type->get_meta( $post_id, 'category' );
$categories = $this->post_type->get_categories();
$category_name = isset( $categories[ $category ] ) ? $categories[ $category ] : $category;
$upvote_count = $this->get_upvote_count( $post_id );
$has_voted = $this->has_user_voted( $post_id );
$entry_classes = array( 'pc-clm-entry' );
if ( $category ) {
$entry_classes[] = 'pc-clm-category-' . $category;
}
?>
<article class="<?php echo esc_attr( implode( ' ', $entry_classes ) ); ?>" id="pc-clm-entry-<?php echo esc_attr( $post_id ); ?>">
<div class="pc-clm-entry-inner">
<div class="pc-clm-entry-header">
<div class="pc-clm-entry-meta">
<?php if ( $version ) : ?>
<span class="pc-clm-version-badge"><?php echo esc_html( $version ); ?></span>
<?php endif; ?>
<?php if ( 'yes' === $atts['show_date'] && $release_date ) : ?>
<span class="pc-clm-release-date"><?php echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $release_date ) ) ); ?></span>
<?php endif; ?>
<?php if ( $category ) : ?>
<span class="pc-clm-category-badge pc-clm-category-<?php echo esc_attr( $category ); ?>"><?php echo esc_html( $category_name ); ?></span>
<?php endif; ?>
</div>
<div class="pc-clm-upvote-section">
<button class="pc-clm-upvote-btn <?php echo $has_voted ? 'voted' : ''; ?>"
data-post-id="<?php echo esc_attr( $post_id ); ?>"
<?php echo $has_voted ? 'disabled' : ''; ?>>
<span class="pc-clm-upvote-icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2l2.4 7.4h7.6l-6 4.6 2.3 7.4-6.3-4.6-6.3 4.6 2.3-7.4-6-4.6h7.6z"/>
</svg>
</span>
<span class="pc-clm-upvote-count"><?php echo esc_html( $upvote_count ); ?></span>
</button>
<span class="pc-clm-upvote-label"><?php echo esc_html( _n( 'upvote', 'upvotes', $upvote_count, 'pc-changelog-manager-abc123' ) ); ?></span>
</div>
</div>
<h3 class="pc-clm-entry-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h3>
<div class="pc-clm-entry-content">
<?php the_excerpt(); ?>
</div>
</div>
</article>
<?php
}
/**
* Enqueue public scripts.
*/
public function enqueue_scripts() {
// Check if we're on the changelog page or if shortcode is used.
if ( ! $this->is_changelog_page() && ! $this->is_changelog_single() && ! $this->has_shortcode() ) {
return;
}
$min_suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
if ( file_exists( PC_CLM_PLUGIN_DIR . 'public/js/public-script.js' ) ) {
wp_enqueue_script(
'pc-clm-public-script',
PC_CLM_PLUGIN_URL . 'public/js/public-script.js',
array( 'jquery' ),
PC_CLM_VERSION,
true
);
wp_localize_script(
'pc-clm-public-script',
'pc_clm_ajax',
array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'pc_clm_upvote_nonce' ),
)
);
}
}
/**
* Handle upvote AJAX request.
*/
public function handle_upvote() {
check_ajax_referer( 'pc_clm_upvote_nonce', 'nonce' );
if ( ! isset( $_POST['post_id'] ) ) {
wp_die( 'Invalid request' );
}
$post_id = intval( $_POST['post_id'] );
$ip_address = $_SERVER['REMOTE_ADDR'];
$user_id = get_current_user_id();
// Check if user has already voted (by IP or user ID if logged in)
$voted_key = $user_id ? "pc_clm_voted_user_{$user_id}" : "pc_clm_voted_ip_{$ip_address}";
$voted_posts = get_transient( $voted_key );
if ( ! $voted_posts ) {
$voted_posts = array();
}
if ( in_array( $post_id, $voted_posts ) ) {
wp_send_json_error( array( 'message' => __( 'You have already voted for this entry.', 'pc-changelog-manager-abc123' ) ) );
return;
}
// Get current vote count
$vote_count = get_post_meta( $post_id, '_pc_clm_upvotes', true ) ?: 0;
$new_vote_count = intval( $vote_count ) + 1;
// Update vote count
update_post_meta( $post_id, '_pc_clm_upvotes', $new_vote_count );
// Mark as voted (expires after 30 days)
$voted_posts[] = $post_id;
set_transient( $voted_key, $voted_posts, 30 * DAY_IN_SECONDS );
wp_send_json_success( array(
'vote_count' => $new_vote_count,
'message' => __( 'Thank you for voting!', 'pc-changelog-manager-abc123' ),
) );
}
/**
* Get upvote count for a post.
*
* @param int $post_id Post ID.
* @return int
*/
public function get_upvote_count( $post_id ) {
$vote_count = get_post_meta( $post_id, '_pc_clm_upvotes', true );
return intval( $vote_count );
}
/**
* Check if current user has voted for a post.
*
* @param int $post_id Post ID.
* @return bool
*/
public function has_user_voted( $post_id ) {
$ip_address = $_SERVER['REMOTE_ADDR'];
$user_id = get_current_user_id();
$voted_key = $user_id ? "pc_clm_voted_user_{$user_id}" : "pc_clm_voted_ip_{$ip_address}";
$voted_posts = get_transient( $voted_key );
if ( ! $voted_posts ) {
return false;
}
return in_array( $post_id, $voted_posts );
}
/**
* Register widgets.
*/
public function register_widgets() {
require_once PC_CLM_PLUGIN_DIR . 'public/widgets/class-changelog-widget.php';
register_widget( 'PC_CLM_Widget' );
}
}
/**
* Template function to display changelog entries.
*
* @param array $args Query arguments.
*/
function pc_clm_display_changelog( $args = array() ) {
$post_type = new PC_CLM_Post_Type();
$query = $post_type->get_entries( $args );
if ( $query->have_posts() ) {
?>
<div class="pc-clm-entries-list">
<?php
while ( $query->have_posts() ) :
$query->the_post();
$post_id = get_the_ID();
$version = $post_type->get_meta( $post_id, 'version_number' );
$release_date = $post_type->get_meta( $post_id, 'release_date' );
$category = $post_type->get_meta( $post_id, 'category' );
$categories = $post_type->get_categories();
$category_name = isset( $categories[ $category ] ) ? $categories[ $category ] : $category;
$entry_classes = array( 'pc-clm-entry' );
if ( $category ) {
$entry_classes[] = 'pc-clm-category-' . $category;
}
?>
<article id="post-<?php the_ID(); ?>" <?php post_class( $entry_classes ); ?>>
<div class="pc-clm-entry-inner">
<div class="pc-clm-entry-meta">
<?php if ( $version ) : ?>
<span class="pc-clm-version-badge"><?php echo esc_html( $version ); ?></span>
<?php endif; ?>
<?php if ( $release_date ) : ?>
<span class="pc-clm-release-date"><?php echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $release_date ) ) ); ?></span>
<?php endif; ?>
<?php if ( $category ) : ?>
<span class="pc-clm-category-badge pc-clm-category-<?php echo esc_attr( $category ); ?>"><?php echo esc_html( $category_name ); ?></span>
<?php endif; ?>
</div>
<h3 class="pc-clm-entry-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h3>
<div class="pc-clm-entry-content">
<?php the_excerpt(); ?>
</div>
</div>
</article>
<?php
endwhile;
?>
</div>
<?php
} else {
?>
<div class="pc-clm-no-entries">
<p><?php esc_html_e( 'No changelog entries found.', 'pc-changelog-manager-abc123' ); ?></p>
</div>
<?php
}
wp_reset_postdata();
}