feature: Initial commit

This commit is contained in:
Keith Solomon
2026-08-09 20:27:58 -05:00
commit 360fe48089
134 changed files with 14125 additions and 0 deletions
+169
View File
@@ -0,0 +1,169 @@
<?php
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Add settings page
*
* @since 1.0.0
* @return void
*/
function projects_portfolio_add_settings_page() {
add_submenu_page(
'edit.php?post_type=projects',
esc_html__( 'Settings', 'projects-wp' ),
esc_html__( 'Settings', 'projects-wp' ),
'manage_options',
'projects-wp-settings',
'projects_portfolio_render_settings_page'
);
}
add_action( 'admin_menu', 'projects_portfolio_add_settings_page' );
/**
* Render the settings page.
*
* @since 1.0.0
* @return void
*/
function projects_portfolio_render_settings_page() {
// Handle settings save.
if ( isset( $_POST['projects_portfolio_save_settings'] ) ) {
projects_portfolio_save_settings();
}
if ( get_transient( 'projects_portfolio_settings_saved' ) ) {
settings_errors( 'projects_portfolio_messages' );
delete_transient( 'projects_portfolio_settings_saved' );
}
// Retrieve saved settings.
$api_token = get_option( 'projects_portfolio_github_api_token', '' );
$share_telemetry = get_option( 'projects_portfolio_share_telemetry', '0' );
// Templates Settings.
$templates_settings = [
'version' => esc_html__( 'Version', 'projects-wp' ),
'last_updated' => esc_html__( 'Last Updated', 'projects-wp' ),
'license' => esc_html__( 'License', 'projects-wp' ),
'language' => esc_html__( 'Language', 'projects-wp' ),
'downloads' => esc_html__( 'Downloads', 'projects-wp' ),
'forks' => esc_html__( 'Forks', 'projects-wp' ),
'stargazers_count' => esc_html__( 'Stars', 'projects-wp' ),
'open_issues_count' => esc_html__( 'Issues', 'projects-wp' ),
'github_owner' => esc_html__( 'GitHub Owner', 'projects-wp' ),
];
// Archives Settings
$archives_settings = [
'archive_title' => esc_html__( 'Archive Title', 'projects-wp' ),
'project_title' => esc_html__( 'Project Title', 'projects-wp' ),
'project_excerpt' => esc_html__( 'Project Excerpt', 'projects-wp' ),
'project_buttons' => esc_html__( 'Project Buttons', 'projects-wp' ),
];
?>
<div class="wrap">
<h1><?php esc_html_e( 'Projects for WordPress Settings', 'projects-wp' ); ?></h1>
<form method="post">
<?php wp_nonce_field( 'projects_portfolio_save_settings', 'projects_portfolio_settings_nonce' ); ?>
<h2><?php esc_html_e( 'General Settings', 'projects-wp' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row">
<label for="projects_portfolio_github_api_token"><?php esc_html_e( 'GitHub API Token', 'projects-wp' ); ?></label>
</th>
<td>
<input type="password" id="projects_portfolio_github_api_token" name="projects_portfolio_github_api_token" value="<?php echo esc_attr( $api_token ); ?>" class="regular-text" />
</td>
</tr>
</table>
<h2><?php esc_html_e( 'Templates Settings', 'projects-wp' ); ?></h2>
<table class="form-table">
<?php foreach ( $templates_settings as $key => $label ) :
$value = get_option( "projects_portfolio_templates_$key", '0' ); ?>
<tr>
<th scope="row"><?php esc_html_e( $label ); ?></th>
<td>
<label class="toggle-switch">
<input type="checkbox" name="projects_portfolio_templates_<?php esc_attr_e( $key ); ?>" value="1" <?php checked($value, '1'); ?> />
<span class="slider"></span>
</label>
</td>
</tr>
<?php endforeach; ?>
</table>
<h2><?php esc_html_e( 'Archives Settings', 'projects-wp' ); ?></h2>
<table class="form-table">
<?php foreach ( $archives_settings as $key => $label ) :
$value = get_option( "projects_portfolio_archives_$key", '0' ); ?>
<tr>
<th scope="row"><?php esc_html_e( $label ); ?></th>
<td>
<label class="toggle-switch">
<input type="checkbox" name="projects_portfolio_archives_<?php esc_attr_e($key); ?>" value="1" <?php checked($value, '1'); ?> />
<span class="slider"></span>
</label>
</td>
</tr>
<?php endforeach; ?>
</table>
<input type="hidden" name="projects_portfolio_save_settings" value="1" />
<?php submit_button( esc_html__( 'Save Settings', 'projects-wp' ) ); ?>
</form>
</div>
<?php
}
/**
* Save the settings from the settings page.
*
* @since 1.0.0
* @return void
*/
function projects_portfolio_save_settings() {
if ( ! isset( $_POST['projects_portfolio_settings_nonce'] ) || ! wp_verify_nonce( $_POST['projects_portfolio_settings_nonce'], 'projects_portfolio_save_settings' ) ) {
return;
}
update_option( 'projects_portfolio_github_api_token', sanitize_text_field( $_POST['projects_portfolio_github_api_token'] ?? '' ) );
update_option( 'projects_portfolio_share_telemetry', isset( $_POST['projects_portfolio_share_telemetry'] ) ? '1' : '0' );
// Templates Settings Keys.
$templates_keys = [
'version',
'last_updated',
'license',
'language',
'downloads',
'forks',
'stargazers_count',
'open_issues_count',
'github_owner',
];
foreach ( $templates_keys as $key ) {
update_option( "projects_portfolio_templates_$key", isset( $_POST["projects_portfolio_templates_$key"] ) ? '1' : '0' );
}
// Archives Settings Keys.
$archives_keys = [
'archive_title',
'project_title',
'project_excerpt',
'project_buttons',
];
foreach ( $archives_keys as $key ) {
update_option( "projects_portfolio_archives_$key", isset( $_POST["projects_portfolio_archives_$key"] ) ? '1' : '0' );
}
add_settings_error( 'projects_portfolio_messages', 'projects_portfolio_settings_saved', esc_html__( 'Settings saved successfully.', 'projects-wp' ), 'updated' );
set_transient( 'projects_portfolio_settings_saved', true, 30 );
}
+53
View File
@@ -0,0 +1,53 @@
<?php
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Register Custom Post Type and Taxonomy
*
* @since 1.0.0
* @return void
*/
function projects_portfolio_register_cpt_and_taxonomy() {
register_post_type( 'projects', [
'labels' => [
'name' => esc_html__( 'Projects', 'projects-wp' ),
'singular_name' => esc_html__( 'Project', 'projects-wp' ),
'add_new_item' => esc_html__( 'Add New Project', 'projects-wp' ),
],
'public' => true,
'has_archive' => true,
'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt' ],
'rewrite' => [ 'slug' => 'projects' ],
'show_in_rest' => true,
'menu_icon' => 'dashicons-format-gallery'
] );
register_taxonomy( 'project-type', 'projects', [
'labels' => [
'name' => esc_html__( 'Project Types', 'projects-wp' ),
'singular_name' => esc_html__( 'Project Type', 'projects-wp' ),
],
'hierarchical' => true,
'show_in_rest' => true,
'show_admin_column' => true,
'rewrite' => [
'slug' => 'project-type',
'with_front' => false,
],
] );
if ( ! term_exists( 'plugin', 'project_type' ) ) {
wp_insert_term( esc_html__( 'Plugin', 'projects-wp' ), 'project_type' );
}
if ( ! term_exists( 'theme', 'project_type' ) ) {
wp_insert_term( esc_html__( 'Theme', 'projects-wp' ), 'project_type' );
}
if ( ! term_exists( 'pattern', 'project_type' ) ) {
wp_insert_term( esc_html__( 'Pattern', 'projects-wp' ), 'project_type' );
}
}
add_action( 'init', 'projects_portfolio_register_cpt_and_taxonomy' );
+62
View File
@@ -0,0 +1,62 @@
<?php
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Add GitHub Repository URL meta box.
*
* @since 1.0.0
* @return void
*/
function projects_portfolio_add_meta_boxes() {
add_meta_box(
'projects_portfolio_github_url',
esc_html__( 'GitHub URL', 'projects-wp' ),
'projects_portfolio_render_meta_box',
'projects',
'side'
);
}
add_action( 'add_meta_boxes', 'projects_portfolio_add_meta_boxes' );
/**
* Renders the GitHub Repository URL meta box for the Projects WP plugin.
*
* Outputs a label and input field for setting the GitHub repository URL,
* as well as a nonce for security.
*
* @param WP_Post $post The post object currently being edited.
*
* @since 1.0.0
* @return void
*/
function projects_portfolio_render_meta_box( $post ) {
wp_nonce_field( 'projects_portfolio_save_meta_box', 'projects_portfolio_meta_box_nonce' );
$github_url = get_post_meta( $post->ID, '_projects_portfolio_github_url', true );
echo '<label for="projects_portfolio_github_url">' . __( 'GitHub Repository URL:', 'projects-wp' ) . '</label>';
echo '<input type="url" id="projects_portfolio_github_url" name="projects_portfolio_github_url" value="' . esc_attr( $github_url ) . '" style="width: 100%;" />';
}
/**
* Saves the GitHub repository URL meta box data for the Projects WP plugin.
*
* @param int $post_id The ID of the post currently being saved.
*
* @since 1.0.0
* @return void
*/
function projects_portfolio_save_meta_box( $post_id ) {
if ( ! isset( $_POST['projects_portfolio_meta_box_nonce'] ) || ! wp_verify_nonce( $_POST['projects_portfolio_meta_box_nonce'], 'projects_portfolio_save_meta_box' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( isset( $_POST['projects_portfolio_github_url'] ) ) {
update_post_meta( $post_id, '_projects_portfolio_github_url', esc_url_raw( $_POST['projects_portfolio_github_url'] ) );
}
}
add_action( 'save_post', 'projects_portfolio_save_meta_box' );
+133
View File
@@ -0,0 +1,133 @@
<?php
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Callback function for the custom projects endpoint.
*
* @param WP_REST_Request $request The REST API request object.
*
* @since 1.0.0
* @return WP_REST_Response
*/
function projects_portfolio_get_projects_data( $request ) {
$args = [
'post_type' => 'projects',
'posts_per_page' => $request->get_param( 'per_page' ) ?? 10,
'paged' => $request->get_param( 'page' ) ?? 1,
];
$query = new WP_Query( $args );
$projects = [];
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
$project_id = get_the_ID();
$github_url = get_post_meta( $project_id, '_projects_portfolio_github_url', true );
$github_data = projects_portfolio_get_github_data( $github_url );
// Fetch project-type taxonomy terms (names).
$project_types = wp_get_post_terms( $project_id, 'project-type', [
'fields' => 'names',
] );
$projects[] = [
'id' => $project_id,
'title' => get_the_title(),
'excerpt' => get_the_excerpt(),
'permalink' => get_permalink(),
'thumbnail' => get_the_post_thumbnail_url( $project_id, 'large' ),
'download_count' => (int) get_post_meta( $project_id, '_projects_portfolio_download_count', true ),
'download_url' => esc_url( site_url( '/download/' . $project_id ) ),
'github_url' => $github_url,
'github_data' => [
'owner' => [
'avatar_url' => $github_data['owner']['avatar_url'] ?? '',
'name' => $github_data['owner']['login'] ?? '',
'profile' => $github_data['owner']['html_url'] ?? '',
],
'last_updated' => ! empty( $github_data['updated_at'] )
? date_i18n( get_option( 'date_format' ), strtotime( $github_data['updated_at'] ) )
: '',
'language' => $github_data['language'] ?? '',
'license' => $github_data['license']['name'] ?? 'None',
'stars' => $github_data['stargazers_count'] ?? 0,
'forks' => $github_data['forks_count'] ?? 0,
'issues' => $github_data['open_issues_count'] ?? 0,
],
'version' => projects_portfolio_get_version_from_github( $github_url ),
'project_types' => $project_types,
'content' => get_the_content(),
];
}
wp_reset_postdata();
}
$response = [
'projects' => $projects,
'total' => $query->found_posts,
'pages' => $query->max_num_pages,
];
return rest_ensure_response( $response );
}
/**
* Register REST API endpoint for popular projects.
*
* @since 1.0.0
* @return VolumeVolumeInfoDimensions
*/
function projects_portfolio_register_popular_endpoint() {
register_rest_route(
'projects/v1',
'/popular',
[
'methods' => 'GET',
'callback' => 'projects_portfolio_get_popular_projects',
'permission_callback' => '__return_true',
]
);
}
/**
* Callback function for popular projects endpoint.
*
* @since 1.0.0
* @return void
*/
function projects_portfolio_get_popular_projects( $data ) {
$args = [
'post_type' => 'projects',
'posts_per_page' => $data->get_param( 'per_page' ) ?? 10,
'meta_key' => '_projects_portfolio_download_count',
'orderby' => 'meta_value_num',
'order' => 'DESC',
];
$query = new WP_Query( $args );
$projects = [];
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
$projects[] = [
'id' => get_the_ID(),
'title' => get_the_title(),
'download_count' => (int) get_post_meta( get_the_ID(), '_projects_portfolio_download_count', true ),
'download_url' => esc_url( site_url( '/download/' . get_the_ID() ) ),
'github_url' => get_post_meta( get_the_ID(), '_projects_portfolio_github_url', true ),
'permalink' => get_permalink(),
];
}
}
wp_reset_postdata();
return rest_ensure_response( $projects );
}
add_action( 'rest_api_init', 'projects_portfolio_register_popular_endpoint' );