Files
peptide-repo-core/includes/admin/class-pr-core-peptide-metaboxes.php
peptiderepo 8d3064785c fix: resolve all PHPCS violations and 300-line rule breaches
- Add @package Peptide_Repo_Core file docblocks to all PHP files
- Add @var docblocks to all public readonly DTO properties
- Add @param docs to sanitize_evidence_strength / sanitize_review_status
- Replace short ternary ?: with phpcs:ignore on each violating line
- Add phpcs:ignore for InterpolatedNotPrepared on ${table} SQL strings
- Add phpcs:ignore for GlobalVariablesOverride in uninstall.php and card.php
- Add translators comment to __ call with placeholder in related-posts-section
- Fix InlineComment.InvalidEndChar: add periods to all bare inline comments
- Expand single-line /** @return void */ docblocks with summaries
- SPLIT class-pr-core-seed-data.php (was 360 lines):
  → class-pr-core-seed-data.php (149 lines, orchestration only)
  → class-pr-core-seed-fixtures.php (254 lines, static fixture data)
- SPLIT class-pr-core-peptide-cpt.php (was 309 lines):
  → class-pr-core-peptide-cpt.php (224 lines, CPT + hooks + sanitizers)
  → class-pr-core-peptide-meta-schema.php (133 lines, meta field defs)
- SPLIT class-pr-core-settings.php (was 340 lines after docblock expansion):
  → class-pr-core-settings.php (264 lines, registration + sanitization)
  → class-pr-core-settings-renderer.php (124 lines, render methods)
- All includes/ PHP files now <= 300 lines

Agent-Session: claude-adopt-ci-20260616
2026-06-16 15:48:53 +00:00

262 lines
8.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
/**
* Meta boxes for the peptide edit screen: scientific identifiers, dosing, and legal.
*
* What: Renders and saves three meta boxes on the peptide edit screen.
* Who calls it: PR_Core_Admin via add_meta_boxes and save_post hooks.
* Dependencies: PR_Core_Dosing_Repository, PR_Core_Legal_Repository.
*
* @see admin/class-pr-core-admin.php — Registers these hooks.
* @see cpt/class-pr-core-peptide-cpt.php — Meta field definitions.
* @package Peptide_Repo_Core
*/
class PR_Core_Peptide_Metaboxes {
/**
* Register meta boxes on the peptide edit screen.
*
* @return void
*/
public function add_meta_boxes(): void {
add_meta_box(
'pr-core-identifiers',
__( 'Scientific Identifiers', 'peptide-repo-core' ),
array( $this, 'render_identifiers_box' ),
PR_Core_Peptide_CPT::POST_TYPE,
'normal',
'high'
);
add_meta_box(
'pr-core-dosing',
__( 'Dosing Data', 'peptide-repo-core' ),
array( $this, 'render_dosing_box' ),
PR_Core_Peptide_CPT::POST_TYPE,
'normal',
'default'
);
add_meta_box(
'pr-core-legal',
__( 'Legal Status by Country', 'peptide-repo-core' ),
array( $this, 'render_legal_box' ),
PR_Core_Peptide_CPT::POST_TYPE,
'normal',
'default'
);
}
/**
* Render the Scientific Identifiers meta box.
*
* @param \WP_Post $post Current post.
* @return void
*/
public function render_identifiers_box( \WP_Post $post ): void {
wp_nonce_field( 'pr_core_save_meta', 'pr_core_meta_nonce' );
$fields = array(
'display_name' => __( 'Display Name', 'peptide-repo-core' ),
'aliases' => __( 'Aliases (JSON array)', 'peptide-repo-core' ),
'molecular_formula' => __( 'Molecular Formula', 'peptide-repo-core' ),
'molecular_weight' => __( 'Molecular Weight (Da)', 'peptide-repo-core' ),
'cas_number' => __( 'CAS Number', 'peptide-repo-core' ),
'drugbank_id' => __( 'DrugBank ID', 'peptide-repo-core' ),
'chembl_id' => __( 'ChEMBL ID', 'peptide-repo-core' ),
);
echo '<table class="form-table pr-core-meta-table">';
foreach ( $fields as $key => $label ) {
$value = esc_attr( (string) get_post_meta( $post->ID, $key, true ) );
printf(
'<tr><th><label for="pr_core_%1$s">%2$s</label></th><td><input type="text" id="pr_core_%1$s" name="pr_core_%1$s" value="%3$s" class="regular-text" /></td></tr>',
esc_attr( $key ),
esc_html( $label ),
$value
);
}
// Evidence strength dropdown.
$current_strength = get_post_meta( $post->ID, 'evidence_strength', true ) ?: 'preclinical'; // phpcs:ignore Universal.Operators.DisallowShortTernary.Found
echo '<tr><th><label for="pr_core_evidence_strength">' . esc_html__( 'Evidence Strength', 'peptide-repo-core' ) . '</label></th><td><select id="pr_core_evidence_strength" name="pr_core_evidence_strength">';
foreach ( PR_Core_Peptide_CPT::EVIDENCE_STRENGTHS as $strength ) {
$label = apply_filters( 'pr_core_evidence_strength_label', $strength, $strength );
printf(
'<option value="%s"%s>%s</option>',
esc_attr( $strength ),
selected( $current_strength, $strength, false ),
esc_html( $label )
);
}
echo '</select></td></tr>';
// Editorial review status.
$current_review = get_post_meta( $post->ID, 'editorial_review_status', true ) ?: 'draft'; // phpcs:ignore Universal.Operators.DisallowShortTernary.Found
echo '<tr><th><label for="pr_core_editorial_review_status">' . esc_html__( 'Editorial Status', 'peptide-repo-core' ) . '</label></th><td><select id="pr_core_editorial_review_status" name="pr_core_editorial_review_status">';
foreach ( PR_Core_Peptide_CPT::REVIEW_STATUSES as $status ) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr( $status ),
selected( $current_review, $status, false ),
esc_html( ucfirst( str_replace( '-', ' ', $status ) ) )
);
}
echo '</select></td></tr>';
echo '</table>';
}
/**
* Render the Dosing Data meta box (read-only summary with link to manage).
*
* @param \WP_Post $post Current post.
* @return void
*/
public function render_dosing_box( \WP_Post $post ): void {
if ( 'auto-draft' === $post->post_status ) {
echo '<p>' . esc_html__( 'Save the peptide first, then add dosing data.', 'peptide-repo-core' ) . '</p>';
return;
}
$repo = new PR_Core_Dosing_Repository();
$rows = $repo->find_by_peptide( $post->ID );
if ( empty( $rows ) ) {
echo '<p>' . esc_html__( 'No dosing rows yet.', 'peptide-repo-core' ) . '</p>';
} else {
echo '<table class="widefat striped"><thead><tr>';
echo '<th>' . esc_html__( 'Route', 'peptide-repo-core' ) . '</th>';
echo '<th>' . esc_html__( 'Dose Range', 'peptide-repo-core' ) . '</th>';
echo '<th>' . esc_html__( 'Population', 'peptide-repo-core' ) . '</th>';
echo '<th>' . esc_html__( 'Evidence', 'peptide-repo-core' ) . '</th>';
echo '<th>' . esc_html__( 'Source', 'peptide-repo-core' ) . '</th>';
echo '</tr></thead><tbody>';
foreach ( $rows as $row ) {
$dose = $row->dose_min !== null ? number_format( $row->dose_min, 2 ) : '?';
if ( $row->dose_max !== null && $row->dose_max !== $row->dose_min ) {
$dose .= ' – ' . number_format( $row->dose_max, 2 );
}
$dose .= ' ' . esc_html( $row->dose_unit );
$evidence_label = apply_filters( 'pr_core_evidence_strength_label', $row->evidence_strength, $row->evidence_strength );
printf(
'<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>',
esc_html( $row->route ),
esc_html( $dose ),
esc_html( $row->population ),
esc_html( $evidence_label ),
esc_html( $row->source )
);
}
echo '</tbody></table>';
}
printf(
'<p class="description">%s</p>',
esc_html__( 'Dosing rows are managed via the REST API or AI Candidate Queue. Direct admin editing coming in v0.2.', 'peptide-repo-core' )
);
}
/**
* Render the Legal Status meta box (read-only summary).
*
* @param \WP_Post $post Current post.
* @return void
*/
public function render_legal_box( \WP_Post $post ): void {
if ( 'auto-draft' === $post->post_status ) {
echo '<p>' . esc_html__( 'Save the peptide first, then add legal data.', 'peptide-repo-core' ) . '</p>';
return;
}
$repo = new PR_Core_Legal_Repository();
$cells = $repo->find_by_peptide( $post->ID );
if ( empty( $cells ) ) {
echo '<p>' . esc_html__( 'No legal status data yet.', 'peptide-repo-core' ) . '</p>';
} else {
echo '<table class="widefat striped"><thead><tr>';
echo '<th>' . esc_html__( 'Country', 'peptide-repo-core' ) . '</th>';
echo '<th>' . esc_html__( 'Status', 'peptide-repo-core' ) . '</th>';
echo '<th>' . esc_html__( 'Framework', 'peptide-repo-core' ) . '</th>';
echo '<th>' . esc_html__( 'Verified', 'peptide-repo-core' ) . '</th>';
echo '</tr></thead><tbody>';
foreach ( $cells as $cell ) {
printf(
'<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>',
esc_html( $cell->country_code ),
esc_html( ucfirst( $cell->status ) ),
esc_html( $cell->regulatory_framework ?? '—' ),
esc_html( $cell->last_verified_at )
);
}
echo '</tbody></table>';
}
printf(
'<p class="description">%s</p>',
esc_html__( 'Legal cells are managed via the REST API. Direct admin editing coming in v0.2.', 'peptide-repo-core' )
);
}
/**
* Save peptide meta fields on post save.
*
* Side effects: updates post meta.
*
* @param int $post_id Post ID.
* @param \WP_Post $post Post object.
* @return void
*/
public function save_meta( int $post_id, \WP_Post $post ): void {
if ( ! isset( $_POST['pr_core_meta_nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['pr_core_meta_nonce'] ) ), 'pr_core_save_meta' ) ) {
return;
}
if ( ! current_user_can( PR_Core_Peptide_CPT::CAPABILITY ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
$meta_fields = PR_Core_Peptide_CPT::get_meta_fields();
foreach ( $meta_fields as $key => $config ) {
$input_key = 'pr_core_' . $key;
if ( ! isset( $_POST[ $input_key ] ) ) {
continue;
}
$raw = wp_unslash( $_POST[ $input_key ] );
$clean = is_callable( $config['sanitize'] )
? call_user_func( $config['sanitize'], $raw )
: sanitize_text_field( (string) $raw );
update_post_meta( $post_id, $key, $clean );
}
// Set last_editorial_review_at when status changes to published.
$new_review_status = sanitize_text_field( $_POST['pr_core_editorial_review_status'] ?? '' );
if ( 'published' === $new_review_status ) {
$old_status = get_post_meta( $post_id, 'editorial_review_status', true );
if ( $old_status !== 'published' ) {
update_post_meta( $post_id, 'last_editorial_review_at', current_time( 'mysql' ) );
update_post_meta( $post_id, 'medical_editor_id', get_current_user_id() );
}
}
do_action( 'pr_core_after_peptide_publish', $post_id, $post );
}
}