= 0.2.0 owns `peptide` CPT + `peptide_category` taxonomy as
// of v4.5.0. Gate CPT-dependent surfaces so PSA degrades gracefully
// instead of binding hooks against an unregistered post type.
if ( ! PSA_Dependency_Check::is_satisfied() ) {
return;
}
PSA_Search::init();
PSA_AI_Generator::init();
PSA_Batch_Enrichment::init();
PSA_Template::init();
PSA_Directory::init();
}
add_action( 'init', 'psa_init' );
function psa_admin_init() {
PSA_Upgrade::maybe_run();
// Meta boxes + admin columns bind to the `peptide` CPT; only register
// when PR Core has provided it.
if ( ! PSA_Dependency_Check::is_satisfied() ) {
return;
}
PSA_Post_Type::init_admin();
}
add_action( 'admin_init', 'psa_admin_init' );
/**
* Check if current page is the Echo KB main page.
* Uses is_page() which is reliable even in block templates.
*/
function psa_is_kb_page() {
$settings = get_option( 'psa_settings', array() );
$kb_page = $settings['kb_page_id'] ?? 73;
return is_page( $kb_page ) || is_page( 'peptide-database' );
}
/**
* Enqueue assets on pages that need the search:
* - Homepage (front page) where the hero search lives
* - KB page (page ID 73)
* - Any page/post containing the [peptide_search] shortcode
* - Single peptide pages
* - Any page/post containing the [peptide_directory] shortcode
*/
function psa_maybe_enqueue_assets() {
// Homepage: always enqueue (hero section + search overlay use the shortcode).
if ( is_front_page() ) {
psa_enqueue_frontend_assets();
return;
}
// KB page: enqueue and return early (no $post dependency).
if ( psa_is_kb_page() ) {
psa_enqueue_frontend_assets();
return;
}
global $post;
if ( ! is_a( $post, 'WP_Post' ) ) {
return;
}
$has_search = has_shortcode( $post->post_content, 'peptide_search' );
$has_directory = has_shortcode( $post->post_content, 'peptide_directory' );
$is_peptide = is_singular( 'peptide' );
if ( ! $has_search && ! $has_directory && ! $is_peptide ) {
return;
}
if ( $has_search || $is_peptide ) {
psa_enqueue_frontend_assets();
}
if ( $has_directory ) {
psa_enqueue_directory_assets();
}
}
add_action( 'wp_enqueue_scripts', 'psa_maybe_enqueue_assets' );
/**
* Shared helper to enqueue search CSS, JS, and localize script.
*/
function psa_enqueue_frontend_assets() {
wp_enqueue_style(
'psa-styles',
PSA_PLUGIN_URL . 'assets/css/peptide-search.css',
array(),
PSA_VERSION
);
wp_enqueue_script(
'psa-search',
PSA_PLUGIN_URL . 'assets/js/peptide-search.js',
array( 'jquery' ),
PSA_VERSION,
true
);
wp_localize_script(
'psa-search',
'psaAjax',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'psa_search_nonce' ),
'i18n' => array(
'error' => __( 'Error:', 'peptide-search-ai' ),
'searchFailed' => __( 'Search failed.', 'peptide-search-ai' ),
'unexpected' => __( 'Unexpected response.', 'peptide-search-ai' ),
'rateLimited' => __( 'Too many requests. Please try again later.', 'peptide-search-ai' ),
'networkError' => __( 'Could not connect to the server. Please try again.', 'peptide-search-ai' ),
'timeout' => __( 'Request timed out. The server took too long to respond. Please try again.', 'peptide-search-ai' ),
'connectionError' => __( 'Network error. Please check your connection and try again.', 'peptide-search-ai' ),
'serverError' => __( 'Server error. Please try again later.', 'peptide-search-ai' ),
'unavailable' => __( 'Server is temporarily unavailable. Please try again later.', 'peptide-search-ai' ),
'peptidesFound' => __( 'peptide(s) found', 'peptide-search-ai' ),
'pendingMsg' => __( 'is currently being added to our database. Please check back again later.', 'peptide-search-ai' ),
'invalidMsg' => __( 'does not appear to be a recognized peptide. Please check the spelling or try a different search term.', 'peptide-search-ai' ),
'verified' => __( 'Verified', 'peptide-search-ai' ),
'curated' => __( 'Curated', 'peptide-search-ai' ),
),
)
);
}
/**
* Enqueue directory-specific CSS and JS for the [peptide_directory] shortcode.
* Includes the search CSS as a base dependency (spinner styles, badge styles).
*/
function psa_enqueue_directory_assets() {
// Directory depends on search CSS for shared styles (spinner, badges).
wp_enqueue_style(
'psa-styles',
PSA_PLUGIN_URL . 'assets/css/peptide-search.css',
array(),
PSA_VERSION
);
wp_enqueue_style(
'psa-directory-styles',
PSA_PLUGIN_URL . 'assets/css/psa-directory.css',
array( 'psa-styles' ),
PSA_VERSION
);
wp_enqueue_script(
'psa-directory',
PSA_PLUGIN_URL . 'assets/js/psa-directory.js',
array(),
PSA_VERSION,
true
);
// psaDirectory localization is handled in PSA_Directory::render_shortcode()
// because it needs to run after get_terms() to populate category data.
}
/**
* On the KB page, hide Echo KB search and inject ours via wp_footer.
*/
function psa_replace_kb_search() {
if ( ! psa_is_kb_page() ) {
return;
}
$search_html = PSA_Search::render_search_form(
array(
'placeholder' => 'Search for a peptide (e.g., BPC-157, Thymosin Beta-4)...',
)
);
$encoded = wp_json_encode( $search_html );
echo '';
echo '';
}
add_action( 'wp_footer', 'psa_replace_kb_search' );
function psa_activate() {
// As of PSA v4.5.0, PR Core (>= 0.2.0) owns the `peptide` CPT and
// `peptide_category` taxonomy — PSA no longer registers them here. The
// 8 default category terms were seeded on earlier PSA versions and
// remain in wp_term_taxonomy; PR Core inherits them by registering the
// same taxonomy name, so no re-seed is required. PSA only owns its
// own cost-tracker table and still needs to flush rewrite rules on
// activation so the `peptide` CPT permalinks resolve on this install.
PSA_Cost_Tracker::create_table();
flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'psa_activate' );
function psa_deactivate() {
flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'psa_deactivate' );
function psa_get_client_ip() {
$headers = array(
'HTTP_CF_CONNECTING_IP',
'REMOTE_ADDR',
);
foreach ( $headers as $header ) {
if ( ! empty( $_SERVER[ $header ] ) ) {
$ip = explode( ',', sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) ) );
$ip = trim( $ip[0] );
if ( filter_var( $ip, FILTER_VALIDATE_IP ) ) {
return $ip;
}
}
}
return '0.0.0.0';
}
function psa_get_admin_user_id() {
$admins = get_users(
array(
'role' => 'administrator',
'number' => 1,
'fields' => 'ID',
)
);
return ! empty( $admins ) ? (int) $admins[0] : 0;
}