id !== $this->hook_suffix ) {
return;
}
if ( '' !== self::get_api_key() ) {
return;
}
?>
' . esc_html__( 'Sign up at rss.dynamiapps.com', 'dynamirss' ) . ''
);
?>
hook_suffix = add_menu_page(
__( 'DynamiRSS', 'dynamirss' ),
__( 'DynamiRSS', 'dynamirss' ),
'manage_options',
'dynamirss',
array( $this, 'render_page' ),
'dashicons-rss'
);
}
public function register_settings(): void {
register_setting( 'dynamirss', self::OPTION_KEY, array( $this, 'sanitize' ) );
}
public function sanitize( $input ): array {
$existing = self::get_options();
return array(
'api_key' => isset( $input['api_key'] ) ? sanitize_text_field( (string) $input['api_key'] ) : $existing['api_key'],
'cache_ttl' => isset( $input['cache_ttl'] ) ? max( 0, (int) $input['cache_ttl'] ) : $existing['cache_ttl'],
'default_limit' => isset( $input['default_limit'] ) ? max( 1, min( 200, (int) $input['default_limit'] ) ) : $existing['default_limit'],
'fetch_extras' => ! empty( $input['fetch_extras'] ),
);
}
/**
* @return array{api_key:string,cache_ttl:int,default_limit:int,fetch_extras:bool}
*/
public static function get_options(): array {
$defaults = array(
'api_key' => defined( 'DYNAMIRSS_API_KEY' ) ? DYNAMIRSS_API_KEY : '',
'cache_ttl' => 120,
'default_limit' => 12,
'fetch_extras' => true,
);
$saved = get_option( self::OPTION_KEY, array() );
if ( ! is_array( $saved ) || empty( $saved ) ) {
// Fall back to the pre-rename option so upgrading sites keep their
// saved API key instead of silently reverting to defaults.
$legacy = get_option( self::LEGACY_OPTION_KEY, array() );
$saved = is_array( $legacy ) ? $legacy : array();
}
// The constant wins only when the saved option is empty, so the UI can
// still override a constant if an admin fills the field in later.
if ( empty( $saved['api_key'] ) && defined( 'DYNAMIRSS_API_KEY' ) ) {
$saved['api_key'] = DYNAMIRSS_API_KEY;
}
return array_merge( $defaults, $saved );
}
public static function get_api_url(): string {
return self::API_SCAN_URL;
}
public static function get_categories_url(): string {
return self::API_CATEGORIES_URL;
}
public static function get_channels_url(): string {
return self::API_CHANNELS_URL;
}
public static function get_api_key(): string {
return self::get_options()['api_key'];
}
public static function get_cache_ttl(): int {
return self::get_options()['cache_ttl'];
}
public static function get_default_limit(): int {
return self::get_options()['default_limit'];
}
public static function get_default_fetch_extras(): bool {
return self::get_options()['fetch_extras'];
}
/**
* Category vocabulary used to populate the Category dropdown in the block
* editor and the frontend filter bar. Pulled live from the dynamiRSS API's
* /categories endpoint (cached for an hour) rather than hand-maintained,
* so it can never drift from what the /scan endpoint's category filter
* actually accepts.
*
* @return string[]
*/
public static function get_categories(): array {
$cached = get_transient( self::CATEGORIES_TRANSIENT );
if ( is_array( $cached ) ) {
return $cached;
}
$result = DynamiRSS_Api_Client::instance()->fetch_categories();
if ( $result['error'] ) {
// Live fetch failed — serve the last known-good list rather than
// blanking out the category filter on a transient API outage.
$fallback = get_option( self::CATEGORIES_CACHE_OPTION, array() );
return is_array( $fallback ) ? $fallback : array();
}
set_transient( self::CATEGORIES_TRANSIENT, $result['categories'], HOUR_IN_SECONDS );
update_option( self::CATEGORIES_CACHE_OPTION, $result['categories'], false );
return $result['categories'];
}
/**
* Channel list used to populate the block editor's channel pickers, pulled
* from the dynamiRSS API's /channels endpoint and cached exactly like
* get_categories() above: an hour-long transient, with a last-known-good
* copy to fall back on if the live fetch fails.
*
* @return array
*/
public static function get_channels(): array {
$cached = get_transient( self::CHANNELS_TRANSIENT );
if ( is_array( $cached ) ) {
return $cached;
}
$result = DynamiRSS_Api_Client::instance()->fetch_channels();
if ( $result['error'] ) {
$fallback = get_option( self::CHANNELS_CACHE_OPTION, array() );
return is_array( $fallback ) ? $fallback : array();
}
set_transient( self::CHANNELS_TRANSIENT, $result['channels'], HOUR_IN_SECONDS );
update_option( self::CHANNELS_CACHE_OPTION, $result['channels'], false );
return $result['channels'];
}
/**
* The languages the API has channels in, taken from the channel list (the
* API has no endpoint of its own for them).
*
* @return array Language code => display name, sorted by name.
*/
public static function get_languages(): array {
$languages = array();
foreach ( self::get_channels() as $channel ) {
$code = strtolower( trim( (string) ( $channel['language'] ?? '' ) ) );
if ( '' !== $code && ! isset( $languages[ $code ] ) ) {
$languages[ $code ] = self::get_language_label( $code );
}
}
asort( $languages );
return $languages;
}
/** A readable name for a language code in the site's language, or the code itself if PHP's intl extension is missing. */
public static function get_language_label( string $code ): string {
$label = class_exists( 'Locale' ) ? (string) Locale::getDisplayLanguage( $code, get_locale() ) : '';
return '' !== $label && $label !== $code ? $label : strtoupper( $code );
}
public function render_page(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$o = self::get_options();
?>