Initial commit: KGV-PWA plugin with manifest, service worker and install prompt
This commit is contained in:
519
kgv-pwa.php
Normal file
519
kgv-pwa.php
Normal file
@@ -0,0 +1,519 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: KGV PWA
|
||||
* Plugin URI: https://apex-project.de/
|
||||
* Description: Progressive Web App Unterstützung fuer KGV-Webseiten – Web App Manifest, Service Worker und Install-Banner.
|
||||
* Version: 1.0.0
|
||||
* Author: Ronny Grobel
|
||||
* Author URI: https://apex-project.de/
|
||||
* Text Domain: kgv-pwa
|
||||
* Update URI: https://git.apex-project.de/Wordpress_Plugins/KGV-PWA
|
||||
* Gitea Plugin URI: https://git.apex-project.de/Wordpress_Plugins/KGV-PWA
|
||||
* Requires Plugins: KGV-Updater
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
final class KGV_PWA_Plugin {
|
||||
|
||||
const VERSION = '1.0.0';
|
||||
const OPTION_KEY = 'kgv_pwa_settings';
|
||||
const REWRITE_VERSION_OPTION = 'kgv_pwa_rewrite_version';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function instance() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function __construct() {
|
||||
add_action( 'init', array( $this, 'add_rewrite_rules' ) );
|
||||
add_action( 'init', array( $this, 'maybe_flush_rewrite_rules' ), 20 );
|
||||
add_filter( 'query_vars', array( $this, 'add_query_vars' ) );
|
||||
add_action( 'template_redirect', array( $this, 'handle_manifest' ) );
|
||||
add_action( 'template_redirect', array( $this, 'handle_service_worker' ) );
|
||||
add_action( 'wp_head', array( $this, 'output_head_tags' ) );
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
|
||||
add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
|
||||
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
||||
add_shortcode( 'kgv_pwa_install_button', array( $this, 'render_install_button' ) );
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Activation / Deactivation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public static function activate() {
|
||||
self::instance()->add_rewrite_rules();
|
||||
flush_rewrite_rules();
|
||||
update_option( self::REWRITE_VERSION_OPTION, self::VERSION );
|
||||
|
||||
if ( false === get_option( self::OPTION_KEY ) ) {
|
||||
add_option( self::OPTION_KEY, self::default_settings() );
|
||||
}
|
||||
}
|
||||
|
||||
public static function deactivate() {
|
||||
flush_rewrite_rules();
|
||||
delete_option( self::REWRITE_VERSION_OPTION );
|
||||
}
|
||||
|
||||
public function maybe_flush_rewrite_rules() {
|
||||
$installed_version = get_option( self::REWRITE_VERSION_OPTION, '' );
|
||||
|
||||
if ( self::VERSION === $installed_version ) {
|
||||
return;
|
||||
}
|
||||
|
||||
flush_rewrite_rules( false );
|
||||
update_option( self::REWRITE_VERSION_OPTION, self::VERSION );
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Rewrite Rules & Query Vars
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function add_rewrite_rules() {
|
||||
add_rewrite_rule( '^manifest\.json$', 'index.php?kgv_pwa=manifest', 'top' );
|
||||
add_rewrite_rule( '^sw\.js$', 'index.php?kgv_pwa=sw', 'top' );
|
||||
}
|
||||
|
||||
public function add_query_vars( $vars ) {
|
||||
$vars[] = 'kgv_pwa';
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Manifest Endpoint
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function handle_manifest() {
|
||||
if ( 'manifest' !== get_query_var( 'kgv_pwa' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = $this->get_settings();
|
||||
|
||||
$manifest = array(
|
||||
'name' => $settings['app_name'],
|
||||
'short_name' => $settings['short_name'],
|
||||
'description' => $settings['description'],
|
||||
'start_url' => home_url( '/' ),
|
||||
'scope' => home_url( '/' ),
|
||||
'display' => $settings['display'],
|
||||
'background_color' => $settings['background_color'],
|
||||
'theme_color' => $settings['theme_color'],
|
||||
'lang' => str_replace( '_', '-', get_locale() ),
|
||||
'icons' => $this->get_manifest_icons( $settings ),
|
||||
);
|
||||
|
||||
header( 'Content-Type: application/manifest+json; charset=utf-8' );
|
||||
header( 'Cache-Control: max-age=3600' );
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo wp_json_encode( $manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
|
||||
exit;
|
||||
}
|
||||
|
||||
private function get_manifest_icons( $settings ) {
|
||||
$icons = array();
|
||||
|
||||
foreach ( array( '192', '512' ) as $size ) {
|
||||
$key = 'icon_' . $size;
|
||||
if ( ! empty( $settings[ $key ] ) ) {
|
||||
$icons[] = array(
|
||||
'src' => esc_url_raw( $settings[ $key ] ),
|
||||
'sizes' => $size . 'x' . $size,
|
||||
'type' => 'image/png',
|
||||
'purpose' => 'any maskable',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: WordPress-Site-Icon
|
||||
if ( empty( $icons ) ) {
|
||||
$site_icon_512 = get_site_icon_url( 512 );
|
||||
$site_icon_192 = get_site_icon_url( 192 );
|
||||
|
||||
if ( $site_icon_512 ) {
|
||||
$icons[] = array(
|
||||
'src' => $site_icon_512,
|
||||
'sizes' => '512x512',
|
||||
'type' => 'image/png',
|
||||
'purpose' => 'any',
|
||||
);
|
||||
}
|
||||
|
||||
if ( $site_icon_192 ) {
|
||||
$icons[] = array(
|
||||
'src' => $site_icon_192,
|
||||
'sizes' => '192x192',
|
||||
'type' => 'image/png',
|
||||
'purpose' => 'any',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $icons;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Service Worker Endpoint
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function handle_service_worker() {
|
||||
if ( 'sw' !== get_query_var( 'kgv_pwa' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sw_file = plugin_dir_path( __FILE__ ) . 'assets/js/service-worker.js';
|
||||
|
||||
if ( ! file_exists( $sw_file ) ) {
|
||||
status_header( 404 );
|
||||
exit;
|
||||
}
|
||||
|
||||
header( 'Content-Type: application/javascript; charset=utf-8' );
|
||||
header( 'Service-Worker-Allowed: /' );
|
||||
header( 'Cache-Control: no-cache, no-store, must-revalidate' );
|
||||
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
readfile( $sw_file );
|
||||
exit;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Frontend: Head Tags & Script
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function output_head_tags() {
|
||||
$settings = $this->get_settings();
|
||||
$app_name = esc_attr( $settings['app_name'] );
|
||||
$theme_hex = esc_attr( $settings['theme_color'] );
|
||||
$icon_url = ! empty( $settings['icon_192'] ) ? esc_url( $settings['icon_192'] ) : '';
|
||||
|
||||
echo '<link rel="manifest" href="' . esc_url( $this->get_manifest_url() ) . '">' . "\n";
|
||||
echo '<meta name="theme-color" content="' . $theme_hex . '">' . "\n";
|
||||
echo '<meta name="application-name" content="' . $app_name . '">' . "\n";
|
||||
echo '<meta name="apple-mobile-web-app-capable" content="yes">' . "\n";
|
||||
echo '<meta name="apple-mobile-web-app-status-bar-style" content="default">' . "\n";
|
||||
echo '<meta name="apple-mobile-web-app-title" content="' . $app_name . '">' . "\n";
|
||||
|
||||
if ( $icon_url ) {
|
||||
echo '<link rel="apple-touch-icon" href="' . $icon_url . '">' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
private function get_manifest_url() {
|
||||
// Query-URL funktioniert auch dann, wenn Pretty-Rewrite noch nicht aktiv ist.
|
||||
return home_url( '/?kgv_pwa=manifest' );
|
||||
}
|
||||
|
||||
private function get_service_worker_url() {
|
||||
// Query-URL funktioniert auch dann, wenn Pretty-Rewrite noch nicht aktiv ist.
|
||||
return home_url( '/?kgv_pwa=sw' );
|
||||
}
|
||||
|
||||
public function enqueue_assets() {
|
||||
$sw_url = $this->get_service_worker_url();
|
||||
$scope = trailingslashit( home_url( '/' ) );
|
||||
|
||||
wp_enqueue_style(
|
||||
'kgv-pwa',
|
||||
plugin_dir_url( __FILE__ ) . 'assets/css/kgv-pwa.css',
|
||||
array(),
|
||||
self::VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'kgv-pwa-register',
|
||||
plugin_dir_url( __FILE__ ) . 'assets/js/pwa-register.js',
|
||||
array(),
|
||||
self::VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'kgv-pwa-register',
|
||||
'kgvPwaConfig',
|
||||
array(
|
||||
'swUrl' => esc_url_raw( $sw_url ),
|
||||
'scope' => esc_url_raw( $scope ),
|
||||
'iosInstallNotice' => __( 'Auf iPhone/iPad: Im Browser auf Teilen tippen und dann "Zum Home-Bildschirm" waehlen.', 'kgv-pwa' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcode [kgv_pwa_install_button] – rendert einen Installations-Button.
|
||||
* Kann in Seiten, Posts und Widgets verwendet werden.
|
||||
*
|
||||
* Attribute:
|
||||
* label – Beschriftung des Buttons (Standard: "App installieren")
|
||||
* class – Zusätzliche CSS-Klassen
|
||||
*/
|
||||
public function render_install_button( $atts ) {
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'label' => __( 'App installieren', 'kgv-pwa' ),
|
||||
'class' => '',
|
||||
),
|
||||
$atts,
|
||||
'kgv_pwa_install_button'
|
||||
);
|
||||
|
||||
$classes = 'kgv-pwa-install-btn';
|
||||
if ( ! empty( $atts['class'] ) ) {
|
||||
$classes .= ' ' . sanitize_html_class( $atts['class'] );
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<button type="button" class="%s" onclick="document.dispatchEvent(new CustomEvent(\'kgv-pwa-install\'))">%s</button>',
|
||||
esc_attr( $classes ),
|
||||
esc_html( $atts['label'] )
|
||||
);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Admin Settings
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function add_settings_page() {
|
||||
add_options_page(
|
||||
__( 'KGV PWA', 'kgv-pwa' ),
|
||||
__( 'KGV PWA', 'kgv-pwa' ),
|
||||
'manage_options',
|
||||
'kgv-pwa',
|
||||
array( $this, 'render_settings_page' )
|
||||
);
|
||||
}
|
||||
|
||||
public function register_settings() {
|
||||
register_setting(
|
||||
'kgv_pwa_settings_group',
|
||||
self::OPTION_KEY,
|
||||
array( $this, 'sanitize_settings' )
|
||||
);
|
||||
}
|
||||
|
||||
public function sanitize_settings( $input ) {
|
||||
$defaults = self::default_settings();
|
||||
$output = array();
|
||||
|
||||
$output['app_name'] = isset( $input['app_name'] )
|
||||
? sanitize_text_field( $input['app_name'] )
|
||||
: $defaults['app_name'];
|
||||
|
||||
$output['short_name'] = isset( $input['short_name'] )
|
||||
? sanitize_text_field( $input['short_name'] )
|
||||
: $defaults['short_name'];
|
||||
|
||||
$output['description'] = isset( $input['description'] )
|
||||
? sanitize_textarea_field( $input['description'] )
|
||||
: '';
|
||||
|
||||
$output['theme_color'] = isset( $input['theme_color'] )
|
||||
&& preg_match( '/^#[0-9a-fA-F]{6}$/', $input['theme_color'] )
|
||||
? $input['theme_color']
|
||||
: $defaults['theme_color'];
|
||||
|
||||
$output['background_color'] = isset( $input['background_color'] )
|
||||
&& preg_match( '/^#[0-9a-fA-F]{6}$/', $input['background_color'] )
|
||||
? $input['background_color']
|
||||
: $defaults['background_color'];
|
||||
|
||||
$valid_display = array( 'standalone', 'fullscreen', 'minimal-ui', 'browser' );
|
||||
$output['display'] = isset( $input['display'] )
|
||||
&& in_array( $input['display'], $valid_display, true )
|
||||
? $input['display']
|
||||
: $defaults['display'];
|
||||
|
||||
$output['icon_192'] = isset( $input['icon_192'] ) ? esc_url_raw( $input['icon_192'] ) : '';
|
||||
$output['icon_512'] = isset( $input['icon_512'] ) ? esc_url_raw( $input['icon_512'] ) : '';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
private static function default_settings() {
|
||||
return array(
|
||||
'app_name' => get_bloginfo( 'name' ),
|
||||
'short_name' => get_bloginfo( 'name' ),
|
||||
'description' => get_bloginfo( 'description' ),
|
||||
'theme_color' => '#2e7d32',
|
||||
'background_color' => '#ffffff',
|
||||
'display' => 'standalone',
|
||||
'icon_192' => '',
|
||||
'icon_512' => '',
|
||||
);
|
||||
}
|
||||
|
||||
public function get_settings() {
|
||||
$settings = get_option( self::OPTION_KEY, array() );
|
||||
|
||||
if ( ! is_array( $settings ) ) {
|
||||
$settings = array();
|
||||
}
|
||||
|
||||
return wp_parse_args( $settings, self::default_settings() );
|
||||
}
|
||||
|
||||
public function render_settings_page() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = $this->get_settings();
|
||||
$option_prefix = esc_attr( self::OPTION_KEY );
|
||||
$display_modes = array(
|
||||
'standalone' => 'Standalone',
|
||||
'fullscreen' => 'Fullscreen',
|
||||
'minimal-ui' => 'Minimal UI',
|
||||
'browser' => 'Browser',
|
||||
);
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'KGV PWA Einstellungen', 'kgv-pwa' ); ?></h1>
|
||||
|
||||
<p>
|
||||
<?php esc_html_e( 'Manifest:', 'kgv-pwa' ); ?>
|
||||
<a href="<?php echo esc_url( $this->get_manifest_url() ); ?>" target="_blank">
|
||||
<?php echo esc_url( $this->get_manifest_url() ); ?>
|
||||
</a>
|
||||
|
|
||||
<?php esc_html_e( 'Service Worker:', 'kgv-pwa' ); ?>
|
||||
<a href="<?php echo esc_url( $this->get_service_worker_url() ); ?>" target="_blank">
|
||||
<?php echo esc_url( $this->get_service_worker_url() ); ?>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<form method="post" action="options.php">
|
||||
<?php settings_fields( 'kgv_pwa_settings_group' ); ?>
|
||||
<table class="form-table" role="presentation">
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="kgv_pwa_app_name"><?php esc_html_e( 'App-Name', 'kgv-pwa' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text"
|
||||
id="kgv_pwa_app_name"
|
||||
name="<?php echo $option_prefix; ?>[app_name]"
|
||||
value="<?php echo esc_attr( $settings['app_name'] ); ?>"
|
||||
class="regular-text">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="kgv_pwa_short_name"><?php esc_html_e( 'Kurzname', 'kgv-pwa' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text"
|
||||
id="kgv_pwa_short_name"
|
||||
name="<?php echo $option_prefix; ?>[short_name]"
|
||||
value="<?php echo esc_attr( $settings['short_name'] ); ?>"
|
||||
class="regular-text">
|
||||
<p class="description"><?php esc_html_e( 'Maximal 12 Zeichen empfohlen.', 'kgv-pwa' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="kgv_pwa_description"><?php esc_html_e( 'Beschreibung', 'kgv-pwa' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<textarea id="kgv_pwa_description"
|
||||
name="<?php echo $option_prefix; ?>[description]"
|
||||
rows="3"
|
||||
class="large-text"><?php echo esc_textarea( $settings['description'] ); ?></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="kgv_pwa_theme_color"><?php esc_html_e( 'Theme-Farbe', 'kgv-pwa' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="color"
|
||||
id="kgv_pwa_theme_color"
|
||||
name="<?php echo $option_prefix; ?>[theme_color]"
|
||||
value="<?php echo esc_attr( $settings['theme_color'] ); ?>">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="kgv_pwa_background_color"><?php esc_html_e( 'Hintergrundfarbe', 'kgv-pwa' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="color"
|
||||
id="kgv_pwa_background_color"
|
||||
name="<?php echo $option_prefix; ?>[background_color]"
|
||||
value="<?php echo esc_attr( $settings['background_color'] ); ?>">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="kgv_pwa_display"><?php esc_html_e( 'Anzeigemodus', 'kgv-pwa' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<select id="kgv_pwa_display" name="<?php echo $option_prefix; ?>[display]">
|
||||
<?php foreach ( $display_modes as $val => $label ) : ?>
|
||||
<option value="<?php echo esc_attr( $val ); ?>"<?php selected( $settings['display'], $val ); ?>>
|
||||
<?php echo esc_html( $label ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<p class="description"><?php esc_html_e( '"Standalone" empfohlen – App öffnet ohne Browser-UI.', 'kgv-pwa' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="kgv_pwa_icon_192"><?php esc_html_e( 'Icon 192 × 192 (URL)', 'kgv-pwa' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="url"
|
||||
id="kgv_pwa_icon_192"
|
||||
name="<?php echo $option_prefix; ?>[icon_192]"
|
||||
value="<?php echo esc_url( $settings['icon_192'] ); ?>"
|
||||
class="large-text"
|
||||
placeholder="https://…/icon-192.png">
|
||||
<p class="description"><?php esc_html_e( 'PNG, 192 × 192 px. Leer lassen, um das WordPress-Site-Icon zu verwenden.', 'kgv-pwa' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="kgv_pwa_icon_512"><?php esc_html_e( 'Icon 512 × 512 (URL)', 'kgv-pwa' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="url"
|
||||
id="kgv_pwa_icon_512"
|
||||
name="<?php echo $option_prefix; ?>[icon_512]"
|
||||
value="<?php echo esc_url( $settings['icon_512'] ); ?>"
|
||||
class="large-text"
|
||||
placeholder="https://…/icon-512.png">
|
||||
<p class="description"><?php esc_html_e( 'PNG, 512 × 512 px.', 'kgv-pwa' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<?php submit_button( __( 'Einstellungen speichern', 'kgv-pwa' ) ); ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
register_activation_hook( __FILE__, array( 'KGV_PWA_Plugin', 'activate' ) );
|
||||
register_deactivation_hook( __FILE__, array( 'KGV_PWA_Plugin', 'deactivate' ) );
|
||||
|
||||
add_action( 'plugins_loaded', array( 'KGV_PWA_Plugin', 'instance' ) );
|
||||
Reference in New Issue
Block a user