Files
peptide-news-plugin/admin/class-pn-admin-settings-page.php
peptiderepo 5ece95f236 refactor(admin): split 1089-line admin class into 7 focused files
Addresses excessive line count in Peptide_News_Admin (1089 lines)
by extracting responsibilities into single-purpose classes:

- class-pn-admin-assets.php (88 lines): CSS/JS enqueuing
- class-pn-admin-menu.php (98 lines): Menu & submenu registration
- class-pn-admin-field-renderers.php (325 lines): 25+ field renderers (static)
- class-pn-admin-settings.php (289 lines): Settings registration & sanitization
- class-pn-admin-log-viewer.php (155 lines): Plugin log viewer UI & AJAX
- class-pn-admin-settings-page.php (214 lines): Settings page + manual buttons
- class-pn-admin-dashboard-pages.php (109 lines): Dashboard/articles/cost pages
- class-peptide-news-admin.php (143 lines): Thin orchestrator (delegator)

All files under 300 lines. Original public interface preserved exactly
so no consumer code or tests break. Updated ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 16:46:27 +07:00

215 lines
8.1 KiB
PHP

<?php
declare( strict_types=1 );
/**
* Settings page rendering.
*
* Renders the main settings form with:
* - Manual fetch/summary generation buttons with AJAX handlers
* - Settings form (sections/fields via WordPress)
* - Plugin usage documentation
* - Real-time plugin log viewer with filtering and pagination
*
* @since 2.5.0
* @see Peptide_News_Admin — Main admin orchestrator
* @see Peptide_News_Admin_Settings — Field renderers
*/
class Peptide_News_Admin_Settings_Page {
/** @var Peptide_News_Admin_Settings Settings instance for field rendering */
private $settings;
/**
* Constructor.
*
* @param Peptide_News_Admin_Settings $settings Settings instance.
*/
public function __construct( Peptide_News_Admin_Settings $settings ) {
$this->settings = $settings;
}
/**
* Render the settings page with form, buttons, and log viewer.
*
* @return void
*/
public function render_settings_page(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Handle manual fetch trigger.
if ( isset( $_POST['peptide_news_fetch_now'] ) ) {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'peptide-news' ), 403 );
}
check_admin_referer( 'peptide_news_fetch_now_action' );
$fetcher = new Peptide_News_Fetcher();
$fetcher->fetch_all_sources();
echo '<div class="notice notice-success"><p>' . esc_html__( 'Fetch completed!', 'peptide-news' ) . '</p></div>';
}
echo '<div class="wrap">';
echo '<h1>' . esc_html( get_admin_page_title() ) . '</h1>';
// Fetch Now button.
echo '<form method="post" style="display:inline-block;margin-right:12px;">';
wp_nonce_field( 'peptide_news_fetch_now_action' );
submit_button( __( 'Fetch Articles Now', 'peptide-news' ), 'secondary', 'peptide_news_fetch_now', false );
echo '</form>';
// Backfill Sources button (fixes "news.google.com" source labels).
echo '<button type="button" id="peptide-backfill-sources" class="button button-secondary">';
echo esc_html__( 'Fix Article Sources', 'peptide-news' );
echo '</button>';
echo '<span id="peptide-backfill-result" style="margin-left:10px;"></span>';
// Generate AI Summaries button.
echo '<br style="margin-bottom:8px;">';
echo '<button type="button" id="peptide-generate-summaries" class="button button-primary" style="margin-top:8px;">';
echo esc_html__( 'Generate AI Summaries', 'peptide-news' );
echo '</button>';
echo '<span id="peptide-summary-result" style="margin-left:10px;"></span>';
?>
<script>
(function($) {
$('#peptide-backfill-sources').on('click', function() {
var $btn = $(this), $result = $('#peptide-backfill-result');
$btn.prop('disabled', true).text('<?php echo esc_js( __( 'Fixing...', 'peptide-news' ) ); ?>');
$result.text('');
$.post(peptideNewsAdmin.ajax_url, {
action: 'peptide_news_backfill_sources',
nonce: peptideNewsAdmin.admin_nonce
}, function(response) {
$btn.prop('disabled', false).text('<?php echo esc_js( __( 'Fix Article Sources', 'peptide-news' ) ); ?>');
if (response.success) {
$result.text(response.data.updated + ' article(s) updated.');
} else {
$result.text('Error: ' + (response.data || 'Unknown error'));
}
}).fail(function() {
$btn.prop('disabled', false).text('<?php echo esc_js( __( 'Fix Article Sources', 'peptide-news' ) ); ?>');
$result.text('Request failed.');
});
});
// Generate AI Summaries — loops in batches until all articles are done.
// Uses a 3-second delay between batches to respect free-tier rate limits,
// and retries up to 3 times when a batch fails before giving up.
$('#peptide-generate-summaries').on('click', function() {
var $btn = $(this), $result = $('#peptide-summary-result');
var totalProcessed = 0;
var retries = 0;
var maxRetries = 3;
$btn.prop('disabled', true);
$result.text('<?php echo esc_js( __( 'Starting...', 'peptide-news' ) ); ?>');
function runBatch() {
$btn.text('<?php echo esc_js( __( 'Generating...', 'peptide-news' ) ); ?>');
$.post(peptideNewsAdmin.ajax_url, {
action: 'peptide_news_generate_summaries',
nonce: peptideNewsAdmin.admin_nonce
}, function(response) {
if (response.success) {
totalProcessed += response.data.processed;
var remaining = response.data.remaining;
$result.text(totalProcessed + ' summarized, ' + remaining + ' remaining...');
if (remaining > 0 && response.data.processed > 0) {
// Success — reset retry counter and continue after a delay
// to respect the free-tier rate limit (20 req/min).
retries = 0;
setTimeout(runBatch, 3000);
} else if (remaining > 0 && retries < maxRetries) {
// No progress — likely rate limited. Wait longer and retry.
retries++;
$result.text(totalProcessed + ' summarized, ' + remaining + ' remaining... (rate limited, retrying in 10s, attempt ' + retries + '/' + maxRetries + ')');
setTimeout(runBatch, 10000);
} else {
// Done — either all processed or exhausted retries.
$btn.prop('disabled', false).text('<?php echo esc_js( __( 'Generate AI Summaries', 'peptide-news' ) ); ?>');
if (remaining === 0) {
$result.text('Done! ' + totalProcessed + ' article(s) summarized.');
} else {
var errMsg = totalProcessed + ' summarized. ' + remaining + ' could not be processed.';
if (response.data.errors && response.data.errors.length > 0) {
errMsg += ' Error: ' + response.data.errors[0];
}
$result.text(errMsg);
}
}
} else {
$btn.prop('disabled', false).text('<?php echo esc_js( __( 'Generate AI Summaries', 'peptide-news' ) ); ?>');
$result.text('Error: ' + (response.data || 'Unknown error'));
}
}).fail(function() {
if (retries < maxRetries) {
retries++;
$result.text(totalProcessed + ' completed. Request failed, retrying in 10s... (attempt ' + retries + '/' + maxRetries + ')');
setTimeout(runBatch, 10000);
} else {
$btn.prop('disabled', false).text('<?php echo esc_js( __( 'Generate AI Summaries', 'peptide-news' ) ); ?>');
$result.text('Request failed. ' + totalProcessed + ' completed before failure.');
}
});
}
runBatch();
});
})(jQuery);
</script>
<?php
// Last fetch info.
$last_fetch = get_option( 'peptide_news_last_fetch' );
if ( $last_fetch ) {
$extra_info = '';
if ( isset( $last_fetch['filtered_out'] ) && $last_fetch['filtered_out'] > 0 ) {
$extra_info .= sprintf(
' | %s: %d',
esc_html__( 'Filtered out', 'peptide-news' ),
$last_fetch['filtered_out']
);
}
if ( isset( $last_fetch['ai_processed'] ) ) {
$extra_info .= sprintf(
' | %s: %d',
esc_html__( 'AI analyzed', 'peptide-news' ),
$last_fetch['ai_processed']
);
}
printf(
'<p class="description">%s: %s | %s: %d | %s: %d%s</p>',
esc_html__( 'Last fetch', 'peptide-news' ),
esc_html( $last_fetch['time'] ),
esc_html__( 'Found', 'peptide-news' ),
intval( $last_fetch['found'] ),
esc_html__( 'New stored', 'peptide-news' ),
intval( $last_fetch['new_stored'] ),
esc_html( $extra_info )
);
}
// Settings form.
echo '<form method="post" action="options.php">';
settings_fields( 'peptide_news_settings_group' );
do_settings_sections( 'peptide-news-settings' );
submit_button();
echo '</form>';
// Shortcode usage info.
echo '<h2>' . esc_html__( 'Usage', 'peptide-news' ) . '</h2>';
echo '<p><strong>' . esc_html__( 'Shortcode:', 'peptide-news' ) . '</strong> <code>[peptide_news]</code> ' .
esc_html__( 'or', 'peptide-news' ) . ' <code>[peptide_news count="5"]</code></p>';
echo '<p><strong>' . esc_html__( 'Widget:', 'peptide-news' ) . '</strong> ' .
esc_html__( 'Add "Peptide News" from Appearance > Widgets.', 'peptide-news' ) . '</p>';
// ── Plugin Log Viewer ──────────────────────────────────────────
Peptide_News_Admin_Log_Viewer::render_log_viewer();
echo '</div>';
}
}