v1.0.0
This commit is contained in:
commit
1ef08bd731
27 changed files with 4854 additions and 0 deletions
153
admin/class-fontdelbosc-transit-admin.php
Normal file
153
admin/class-fontdelbosc-transit-admin.php
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin settings page.
|
||||
*
|
||||
* @package Fontdelbosc_Transit
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Admin UI.
|
||||
*/
|
||||
class Fontdelbosc_Transit_Admin {
|
||||
|
||||
/**
|
||||
* Hook the admin pieces.
|
||||
*/
|
||||
public static function init(): void {
|
||||
add_action( 'admin_menu', array( __CLASS__, 'add_menu' ) );
|
||||
add_action( 'admin_init', array( __CLASS__, 'register_settings' ) );
|
||||
add_action( 'admin_post_fontdelbosc_transit_flush', array( __CLASS__, 'handle_flush' ) );
|
||||
add_action( 'admin_notices', array( __CLASS__, 'notices' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the settings page under Settings.
|
||||
*/
|
||||
public static function add_menu(): void {
|
||||
add_options_page(
|
||||
__( 'Font del Bosc Trànsit', 'fontdelbosc-transit' ),
|
||||
__( 'Font del Bosc Trànsit', 'fontdelbosc-transit' ),
|
||||
'manage_options',
|
||||
'fontdelbosc-transit',
|
||||
array( __CLASS__, 'render_page' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the options entry.
|
||||
*/
|
||||
public static function register_settings(): void {
|
||||
register_setting(
|
||||
'fontdelbosc_transit',
|
||||
Fontdelbosc_Transit_Plugin::option_name(),
|
||||
array(
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => array( __CLASS__, 'sanitize' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the submitted options.
|
||||
*
|
||||
* @param mixed $input Raw input.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function sanitize( $input ): array {
|
||||
$current = Fontdelbosc_Transit_Plugin::get_options();
|
||||
$input = is_array( $input ) ? $input : array();
|
||||
|
||||
$clean = array(
|
||||
'incident_cache_ttl' => self::clamp( Fontdelbosc_Transit_Payload::int( $input['incident_cache_ttl'] ?? null ) ?? 300, 60, 86400 ),
|
||||
'zbe_cache_ttl' => self::clamp( Fontdelbosc_Transit_Payload::int( $input['zbe_cache_ttl'] ?? null ) ?? 86400, 3600, 604800 ),
|
||||
'default_view' => 'all' === Fontdelbosc_Transit_Payload::str( $input['default_view'] ?? null ) ? 'all' : 'c15',
|
||||
'max_incidents' => self::clamp( Fontdelbosc_Transit_Payload::int( $input['max_incidents'] ?? null ) ?? 20, 1, 50 ),
|
||||
'show_zbe_2026_notice' => empty( $input['show_zbe_2026_notice'] ) ? false : true,
|
||||
'delete_data_on_uninstall' => empty( $input['delete_data_on_uninstall'] ) ? false : true,
|
||||
);
|
||||
|
||||
$zones = array();
|
||||
if ( isset( $input['zbe_municipalities'] ) && is_array( $input['zbe_municipalities'] ) ) {
|
||||
foreach ( $input['zbe_municipalities'] as $slug ) {
|
||||
$zones[] = Fontdelbosc_Transit_Api_Zbe::sanitize_slug( Fontdelbosc_Transit_Payload::str( $slug ) ?? '' );
|
||||
}
|
||||
}
|
||||
|
||||
$clean['zbe_municipalities'] = array_values( array_unique( array_filter( $zones ) ) );
|
||||
|
||||
if ( array() === $clean['zbe_municipalities'] && isset( $input['zbe_municipalities'] ) ) {
|
||||
$clean['zbe_municipalities'] = (array) $current['zbe_municipalities'];
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamp an integer between bounds.
|
||||
*
|
||||
* @param int $value Raw value.
|
||||
* @param int $min Minimum.
|
||||
* @param int $max Maximum.
|
||||
* @return int
|
||||
*/
|
||||
private static function clamp( int $value, int $min, int $max ): int {
|
||||
return max( $min, min( $max, $value ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the cache flush action.
|
||||
*/
|
||||
public static function handle_flush(): void {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'Insufficient permissions.', 'fontdelbosc-transit' ) );
|
||||
}
|
||||
|
||||
check_admin_referer( 'fontdelbosc_transit_flush' );
|
||||
|
||||
Fontdelbosc_Transit_Data_Manager::flush();
|
||||
|
||||
wp_safe_redirect(
|
||||
add_query_arg(
|
||||
'fontdelbosc_transit_flushed',
|
||||
'1',
|
||||
admin_url( 'options-general.php?page=fontdelbosc-transit' )
|
||||
)
|
||||
);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin notices: activation 2026 info and flush confirmation.
|
||||
*/
|
||||
public static function notices(): void {
|
||||
if ( get_transient( 'fontdelbosc_transit_2026_notice' ) ) {
|
||||
delete_transient( 'fontdelbosc_transit_2026_notice' );
|
||||
echo '<div class="notice notice-info is-dismissible"><p>';
|
||||
echo '<strong>' . esc_html__( 'Font del Bosc — Trànsit', 'fontdelbosc-transit' ) . ':</strong> ';
|
||||
echo esc_html__( 'From 1 January 2026, label B vehicles are banned in Catalan ZBEs when high-pollution protocols are activated.', 'fontdelbosc-transit' );
|
||||
echo '</p></div>';
|
||||
}
|
||||
|
||||
if ( isset( $_GET['fontdelbosc_transit_flushed'] ) && '1' === $_GET['fontdelbosc_transit_flushed'] ) {
|
||||
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Cache flushed.', 'fontdelbosc-transit' ) . '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public static function render_page(): void {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'Insufficient permissions.', 'fontdelbosc-transit' ) );
|
||||
}
|
||||
|
||||
$view = FONTDELBOSC_TRANSIT_DIR . 'admin/views/settings-page.php';
|
||||
|
||||
if ( file_exists( $view ) ) {
|
||||
include $view;
|
||||
}
|
||||
}
|
||||
}
|
||||
182
admin/views/settings-page.php
Normal file
182
admin/views/settings-page.php
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
/**
|
||||
* Settings page view.
|
||||
*
|
||||
* @package Fontdelbosc_Transit
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Settings-page variables.
|
||||
*
|
||||
* @var array<string, mixed> $fontdelbosc_options
|
||||
*/
|
||||
$fontdelbosc_options = Fontdelbosc_Transit_Plugin::get_options();
|
||||
$fontdelbosc_status = get_option( 'fontdelbosc_transit_status', array() );
|
||||
$fontdelbosc_index = Fontdelbosc_Transit_Data_Manager::get_zbe_index();
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'Font del Bosc Trànsit', 'fontdelbosc-transit' ); ?></h1>
|
||||
|
||||
<form method="post" action="options.php">
|
||||
<?php settings_fields( 'fontdelbosc_transit' ); ?>
|
||||
|
||||
<h2><?php esc_html_e( 'Incidents', 'fontdelbosc-transit' ); ?></h2>
|
||||
<table class="form-table" role="presentation">
|
||||
<tr>
|
||||
<th scope="row"><label for="fontdelbosc-transit-incident-ttl"><?php esc_html_e( 'Cache lifetime (seconds)', 'fontdelbosc-transit' ); ?></label></th>
|
||||
<td>
|
||||
<input type="number" id="fontdelbosc-transit-incident-ttl" name="<?php echo esc_attr( Fontdelbosc_Transit_Plugin::option_name() ); ?>[incident_cache_ttl]" value="<?php echo esc_attr( (string) $fontdelbosc_options['incident_cache_ttl'] ); ?>" min="60" max="86400" step="60" class="small-text" />
|
||||
<p class="description"><?php esc_html_e( 'How long incident data is cached. Default 300 (5 minutes); the source updates in near real time.', 'fontdelbosc-transit' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="fontdelbosc-transit-max"><?php esc_html_e( 'Maximum incidents shown', 'fontdelbosc-transit' ); ?></label></th>
|
||||
<td>
|
||||
<input type="number" id="fontdelbosc-transit-max" name="<?php echo esc_attr( Fontdelbosc_Transit_Plugin::option_name() ); ?>[max_incidents]" value="<?php echo esc_attr( (string) $fontdelbosc_options['max_incidents'] ); ?>" min="1" max="50" class="small-text" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Default view', 'fontdelbosc-transit' ); ?></th>
|
||||
<td>
|
||||
<label><input type="radio" name="<?php echo esc_attr( Fontdelbosc_Transit_Plugin::option_name() ); ?>[default_view]" value="c15" <?php checked( $fontdelbosc_options['default_view'], 'c15' ); ?> /> <?php esc_html_e( 'Traffic on C-15', 'fontdelbosc-transit' ); ?></label><br />
|
||||
<label><input type="radio" name="<?php echo esc_attr( Fontdelbosc_Transit_Plugin::option_name() ); ?>[default_view]" value="all" <?php checked( $fontdelbosc_options['default_view'], 'all' ); ?> /> <?php esc_html_e( 'All Catalunya traffic', 'fontdelbosc-transit' ); ?></label>
|
||||
<p class="description"><?php esc_html_e( 'What [fontdelbosc_transit] renders when called without a view attribute.', 'fontdelbosc-transit' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2><?php esc_html_e( 'Low Emission Zones (ZBE)', 'fontdelbosc-transit' ); ?></h2>
|
||||
<table class="form-table" role="presentation">
|
||||
<tr>
|
||||
<th scope="row"><label for="fontdelbosc-transit-zbe-ttl"><?php esc_html_e( 'Cache lifetime (seconds)', 'fontdelbosc-transit' ); ?></label></th>
|
||||
<td>
|
||||
<input type="number" id="fontdelbosc-transit-zbe-ttl" name="<?php echo esc_attr( Fontdelbosc_Transit_Plugin::option_name() ); ?>[zbe_cache_ttl]" value="<?php echo esc_attr( (string) $fontdelbosc_options['zbe_cache_ttl'] ); ?>" min="3600" max="604800" step="3600" class="small-text" />
|
||||
<p class="description"><?php esc_html_e( 'ZBE rules change rarely; 86400 (24 hours) is a sensible default.', 'fontdelbosc-transit' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Zones shown', 'fontdelbosc-transit' ); ?></th>
|
||||
<td>
|
||||
<fieldset>
|
||||
<?php
|
||||
$fontdelbosc_zones = (array) $fontdelbosc_options['zbe_municipalities'];
|
||||
$fontdelbosc_index_list = is_array( $fontdelbosc_index ) ? $fontdelbosc_index : Fontdelbosc_Transit_Api_Zbe::DEFAULT_CATALAN_ZONES;
|
||||
|
||||
foreach ( $fontdelbosc_index_list as $fontdelbosc_slug ) :
|
||||
$fontdelbosc_id = 'fontdelbosc-transit-zone-' . esc_attr( $fontdelbosc_slug );
|
||||
?>
|
||||
<label for="<?php echo esc_attr( $fontdelbosc_id ); ?>">
|
||||
<input type="checkbox" id="<?php echo esc_attr( $fontdelbosc_id ); ?>" name="<?php echo esc_attr( Fontdelbosc_Transit_Plugin::option_name() ); ?>[zbe_municipalities][]" value="<?php echo esc_attr( $fontdelbosc_slug ); ?>" <?php checked( in_array( $fontdelbosc_slug, $fontdelbosc_zones, true ) ); ?> />
|
||||
<?php echo esc_html( preg_replace( '/(?<!^)[A-Z]/', ' $0', $fontdelbosc_slug ) ?? $fontdelbosc_slug ); ?>
|
||||
</label><br />
|
||||
<?php endforeach; ?>
|
||||
<p class="description"><?php esc_html_e( 'Catalan zones are pre-selected. When the DGT publishes new zones they appear here automatically.', 'fontdelbosc-transit' ); ?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( '2026 notice', 'fontdelbosc-transit' ); ?></th>
|
||||
<td>
|
||||
<label for="fontdelbosc-transit-2026"><input type="checkbox" id="fontdelbosc-transit-2026" name="<?php echo esc_attr( Fontdelbosc_Transit_Plugin::option_name() ); ?>[show_zbe_2026_notice]" value="1" <?php checked( $fontdelbosc_options['show_zbe_2026_notice'] ); ?> /> <?php esc_html_e( 'Show the etiqueta B 2026 restriction notice in the ZBE view.', 'fontdelbosc-transit' ); ?></label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Data on uninstall', 'fontdelbosc-transit' ); ?></th>
|
||||
<td>
|
||||
<label for="fontdelbosc-transit-delete"><input type="checkbox" id="fontdelbosc-transit-delete" name="<?php echo esc_attr( Fontdelbosc_Transit_Plugin::option_name() ); ?>[delete_data_on_uninstall]" value="1" <?php checked( $fontdelbosc_options['delete_data_on_uninstall'] ); ?> /> <?php esc_html_e( 'Delete options and cached data when the plugin is uninstalled.', 'fontdelbosc-transit' ); ?></label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<?php submit_button(); ?>
|
||||
</form>
|
||||
|
||||
<h2><?php esc_html_e( 'Status', 'fontdelbosc-transit' ); ?></h2>
|
||||
<table class="form-table" role="presentation">
|
||||
<?php if ( isset( $fontdelbosc_status['incidents'] ) ) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Incidents', 'fontdelbosc-transit' ); ?></th>
|
||||
<td>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: date of the last fetch, 2: source, 3: incident count. */
|
||||
esc_html__( 'Last fetch: %1$s (%2$s source, %3$s incidents).', 'fontdelbosc-transit' ),
|
||||
esc_html( Fontdelbosc_Transit_Maps::format_time( gmdate( 'Y-m-d H:i:s', (int) $fontdelbosc_status['incidents']['last_fetch'] ) . ' UTC' ) ),
|
||||
esc_html( (string) $fontdelbosc_status['incidents']['source'] ),
|
||||
esc_html( (string) $fontdelbosc_status['incidents']['count'] )
|
||||
);
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php if ( isset( $fontdelbosc_status['zbe'] ) ) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Low Emission Zones (ZBE)', 'fontdelbosc-transit' ); ?></th>
|
||||
<td>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: date of the last fetch, 2: zone count. */
|
||||
esc_html__( 'Last fetch: %1$s (%2$s zones loaded).', 'fontdelbosc-transit' ),
|
||||
esc_html( Fontdelbosc_Transit_Maps::format_time( gmdate( 'Y-m-d H:i:s', (int) $fontdelbosc_status['zbe']['last_fetch'] ) . ' UTC' ) ),
|
||||
esc_html( (string) $fontdelbosc_status['zbe']['zones'] )
|
||||
);
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</table>
|
||||
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
|
||||
<input type="hidden" name="action" value="fontdelbosc_transit_flush" />
|
||||
<?php wp_nonce_field( 'fontdelbosc_transit_flush' ); ?>
|
||||
<?php submit_button( __( 'Flush cache', 'fontdelbosc-transit' ), 'secondary', 'submit', false ); ?>
|
||||
</form>
|
||||
|
||||
<h2><?php esc_html_e( 'Documentation', 'fontdelbosc-transit' ); ?></h2>
|
||||
|
||||
<h3><?php esc_html_e( 'Shortcode', 'fontdelbosc-transit' ); ?></h3>
|
||||
<table class="widefat striped" role="presentation">
|
||||
<thead>
|
||||
<tr><th><?php esc_html_e( 'Attribute', 'fontdelbosc-transit' ); ?></th><th><?php esc_html_e( 'Default', 'fontdelbosc-transit' ); ?></th><th><?php esc_html_e( 'Values', 'fontdelbosc-transit' ); ?></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>view</code></td><td><code>c15</code></td><td><code>c15</code>, <code>all</code>, <code>zbe</code></td></tr>
|
||||
<tr><td><code>max</code></td><td><?php esc_html_e( 'Setting above', 'fontdelbosc-transit' ); ?></td><td>1–50</td></tr>
|
||||
<tr><td><code>comarca</code></td><td>—</td><td><code>altpenedes</code>, <code>anoia</code>, <code>baixllobregat</code>, <code>garraf</code> — <?php esc_html_e( 'comma-separated list allowed', 'fontdelbosc-transit' ); ?></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
<code>[fontdelbosc_transit]</code> ·
|
||||
<code>[fontdelbosc_transit view="all" max="10"]</code> ·
|
||||
<code>[fontdelbosc_transit view="all" comarca="anoia"]</code> ·
|
||||
<code>[fontdelbosc_transit view="zbe"]</code>
|
||||
</p>
|
||||
|
||||
<h3><?php esc_html_e( 'Widget', 'fontdelbosc-transit' ); ?></h3>
|
||||
<p><?php esc_html_e( 'A sidebar widget with an editable title shows the five latest C-15 incidents or a summary of the selected Low Emission Zones.', 'fontdelbosc-transit' ); ?></p>
|
||||
|
||||
<h3><?php esc_html_e( 'Data sources', 'fontdelbosc-transit' ); ?></h3>
|
||||
<ul>
|
||||
<li><?php esc_html_e( 'Incidents: Servei Català de Trànsit via the DGT national access point (DATEX2), enriched with the SCT public RSS feed, which provides the incident text and links.', 'fontdelbosc-transit' ); ?></li>
|
||||
<li><?php esc_html_e( 'Low Emission Zones: DGT national access point (DATEX2 v3), with the zone list taken from the live index.', 'fontdelbosc-transit' ); ?></li>
|
||||
<li><?php esc_html_e( 'Attribution is mandatory and cannot be disabled: Incidències — Servei Català de Trànsit (SCT) / CIVICAT; ZBE — DGT / Punt d\'Accés Nacional.', 'fontdelbosc-transit' ); ?></li>
|
||||
</ul>
|
||||
|
||||
<h3><?php esc_html_e( 'Caching', 'fontdelbosc-transit' ); ?></h3>
|
||||
<p><?php esc_html_e( 'Incident data is cached for the lifetime set above (5 minutes by default) and zone rules for 24 hours. When a source fails, the last cached data is served instead (stale-while-error) and the footer shows the age of the data using the source\'s own publication timestamp.', 'fontdelbosc-transit' ); ?></p>
|
||||
<p><?php esc_html_e( 'Use the Flush cache button above to force a fresh fetch of everything.', 'fontdelbosc-transit' ); ?></p>
|
||||
|
||||
<h3><?php esc_html_e( 'Data, privacy and uninstall', 'fontdelbosc-transit' ); ?></h3>
|
||||
<p><?php esc_html_e( 'The plugin stores no personal data and no secrets: only the settings entry, a small fetch-status entry, and transients holding the cached public data. No API keys are needed.', 'fontdelbosc-transit' ); ?></p>
|
||||
<p><?php esc_html_e( 'By default, uninstalling keeps the stored data. With "Delete options and cached data when the plugin is uninstalled" enabled, uninstalling removes the settings, the status entry and every cached transient — nothing is left behind.', 'fontdelbosc-transit' ); ?></p>
|
||||
|
||||
<h3><?php esc_html_e( 'Security', 'fontdelbosc-transit' ); ?></h3>
|
||||
<p><?php esc_html_e( 'No user input reaches the external endpoints: all URLs are hardcoded constants. The only state-changing admin action requires the manage_options capability and a nonce. All output is escaped, and the XML responses are parsed with external entity loading disabled.', 'fontdelbosc-transit' ); ?></p>
|
||||
|
||||
<h3><?php esc_html_e( 'Compatibility', 'fontdelbosc-transit' ); ?></h3>
|
||||
<p><?php esc_html_e( 'WordPress 6.4 – 7.1 and PHP 8.2 – 8.5, single site (Multisite is not supported).', 'fontdelbosc-transit' ); ?></p>
|
||||
|
||||
<h3><?php esc_html_e( 'The 2026 etiqueta B rule', 'fontdelbosc-transit' ); ?></h3>
|
||||
<p><?php esc_html_e( 'From 1 January 2026, label B vehicles are banned in Catalan ZBEs when high-pollution protocols are activated. The persistent notice in the ZBE view can be disabled with the toggle above.', 'fontdelbosc-transit' ); ?></p>
|
||||
</div>
|
||||
Loading…
Reference in a new issue