MyPWA — плагин для WordPress

PWA-плагин для WordPress — шпаргалка
Что это: плагин, который превращает WordPress-сайт в PWA (Progressive Web App). После установки сайт можно «установить» на телефон как приложение — с иконкой на рабочем столе, офлайн-режимом и без адресной строки браузера.

Куда класть: wp-content/plugins/mypwa.php — один файл, без папки.

Установка: Плагины → активировать → меню MyPWA слева → заполнить иконку и цвета.

Код плагина


<?php
/**
 * Plugin Name: MyPWA
 * Description: PWA для WordPress: манифест, service worker, офлайн-страница, кнопка установки.
 * Version: 2.0
 * Author: Вы
 */

if ( ! defined( 'ABSPATH' ) ) exit;

/* ---------- ХЕЛПЕРЫ ---------- */

function mypwa_defaults() {
    return [
        'name'             => get_bloginfo( 'name' ),
        'short_name'       => get_bloginfo( 'name' ),
        'theme_color'      => '#000000',
        'background_color' => '#ffffff',
        'icon'             => '',
        'screenshot'       => '',
    ];
}

function mypwa_get( $key ) {
    $d = mypwa_defaults();
    return get_option( 'mypwa_' . $key, $d[ $key ] ?? '' );
}

/* ---------- МЕТА-ТЕГИ ---------- */

add_action( 'wp_head', function () {
    $theme = mypwa_get( 'theme_color' );
    $icon  = mypwa_get( 'icon' ) ?: 'https://www.gravatar.com/avatar/?s=512&d=mp';
    ?>
    <link rel="manifest" href="<?php echo esc_url( home_url( '/?mypwa_manifest=1' ) ); ?>">
    <meta name="theme-color" content="<?php echo esc_attr( $theme ); ?>">
    <link rel="apple-touch-icon" href="<?php echo esc_url( $icon ); ?>">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="default">
    <meta name="apple-mobile-web-app-title" content="<?php echo esc_attr( mypwa_get( 'short_name' ) ); ?>">
    <?php
} );

/* ---------- MANIFEST ---------- */

add_action( 'init', function () {
    if ( ! isset( $_GET['mypwa_manifest'] ) ) return;

    header( 'Content-Type: application/manifest+json' );

    $icon = mypwa_get( 'icon' ) ?: 'https://www.gravatar.com/avatar/?s=512&d=mp';
    $shot = mypwa_get( 'screenshot' );

    $manifest = [
        'name'             => mypwa_get( 'name' ),
        'short_name'       => mypwa_get( 'short_name' ),
        'start_url'        => home_url( '/' ),
        'scope'            => '/',
        'display'          => 'standalone',
        'orientation'      => 'portrait',
        'background_color' => mypwa_get( 'background_color' ),
        'theme_color'      => mypwa_get( 'theme_color' ),
        'description'      => get_bloginfo( 'description' ),
        'icons' => [
            [ 'src' => $icon, 'sizes' => '192x192', 'type' => 'image/png', 'purpose' => 'any' ],
            [ 'src' => $icon, 'sizes' => '512x512', 'type' => 'image/png', 'purpose' => 'any' ],
            [ 'src' => $icon, 'sizes' => '512x512', 'type' => 'image/png', 'purpose' => 'maskable' ],
        ],
        'shortcuts' => [
            [ 'name' => 'Главная', 'url' => '/?utm_source=pwa' ],
        ],
    ];

    if ( $shot ) {
        $manifest['screenshots'] = [
            [ 'src' => $shot, 'sizes' => '1080x1920', 'type' => 'image/png', 'form_factor' => 'narrow' ],
        ];
    }

    echo json_encode( $manifest, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
    exit;
} );

/* ---------- SERVICE WORKER ---------- */

add_action( 'init', function () {
    if ( ! isset( $_GET['mypwa_sw'] ) ) return;

    header( 'Content-Type: application/javascript; charset=utf-8' );
    header( 'Service-Worker-Allowed: /' );
    header( 'Cache-Control: no-cache' );

    $offline = '<!DOCTYPE html><html lang="ru"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Нет соединения</title><style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#111;color:#fff;text-align:center;padding:20px}h1{font-size:64px;margin:0 0 10px}p{opacity:.7;max-width:400px}button{margin-top:20px;padding:12px 24px;border:none;border-radius:30px;background:#fff;color:#111;font-size:16px;cursor:pointer}</style></head><body><div><h1>&#x1f4e1;</h1><h2>Нет соединения</h2><p>Проверьте интернет и попробуйте снова.</p><button onclick="location.reload()">Обновить</button></div></body></html>';
    ?>
const CACHE = 'mypwa-v1';
const OFFLINE_HTML = <?php echo json_encode( $offline ); ?>;

self.addEventListener('install', (e) => {
  e.waitUntil(caches.open(CACHE).then((c) => c.addAll(['/'])).catch(() => {}));
  self.skipWaiting();
});

self.addEventListener('activate', (e) => {
  e.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
    )
  );
  self.clients.claim();
});

self.addEventListener('fetch', (e) => {
  if (e.request.method !== 'GET') return;

  if (e.request.mode === 'navigate') {
    e.respondWith(
      fetch(e.request)
        .then((res) => {
          const copy = res.clone();
          caches.open(CACHE).then((c) => c.put(e.request, copy));
          return res;
        })
        .catch(() =>
          caches.match(e.request).then((r) =>
            r || new Response(OFFLINE_HTML, { headers: { 'Content-Type': 'text/html' } })
          )
        )
    );
    return;
  }

  e.respondWith(
    fetch(e.request)
      .then((res) => {
        const copy = res.clone();
        caches.open(CACHE).then((c) => c.put(e.request, copy));
        return res;
      })
      .catch(() => caches.match(e.request))
  );
});
    <?php
    exit;
} );

/* ---------- JS: SW + кнопка установки ---------- */

add_action( 'wp_footer', function () {
    $sw_url = home_url( '/?mypwa_sw=1' );
    ?>
    <script>
    (function () {
      if (!('serviceWorker' in navigator)) return;

      window.addEventListener('load', function () {
        navigator.serviceWorker.register(<?php echo json_encode( $sw_url ); ?>, { scope: '/' })
          .then(function (reg) { console.log('SW ok:', reg.scope); })
          .catch(function (err) { console.warn('SW fail:', err); });
      });

      var deferredPrompt = null;
      var installBtn = null;

      window.addEventListener('beforeinstallprompt', function (e) {
        e.preventDefault();
        deferredPrompt = e;
        if (installBtn) return;

        installBtn = document.createElement('button');
        installBtn.textContent = '&#x1f4f1; Установить приложение';
        installBtn.id = 'mypwa-install-btn';
        installBtn.style.cssText = 'position:fixed;bottom:20px;right:20px;padding:12px 18px;border-radius:30px;background:#000;color:#fff;border:none;cursor:pointer;z-index:9999;box-shadow:0 4px 12px rgba(0,0,0,.2);font-size:14px;font-family:inherit;';

        installBtn.addEventListener('click', function () {
          if (!deferredPrompt) return;
          deferredPrompt.prompt();
          deferredPrompt.userChoice.finally(function () {
            deferredPrompt = null;
            if (installBtn) { installBtn.remove(); installBtn = null; }
          });
        });

        document.body.appendChild(installBtn);
      });

      window.addEventListener('appinstalled', function () {
        if (installBtn) { installBtn.remove(); installBtn = null; }
      });
    })();
    </script>
    <?php
} );

/* ---------- АДМИНКА ---------- */

add_action( 'admin_menu', function () {
    add_menu_page( 'MyPWA', 'MyPWA', 'manage_options', 'mypwa', 'mypwa_admin_page', 'dashicons-smartphone', 80 );
} );

add_action( 'admin_init', function () {
    foreach ( [ 'name', 'short_name', 'theme_color', 'background_color', 'icon', 'screenshot' ] as $f ) {
        register_setting( 'mypwa_group', 'mypwa_' . $f );
    }
} );

function mypwa_admin_page() {
    ?>
    <div class="wrap">
        <h1>MyPWA — настройки</h1>
        <form method="post" action="options.php">
            <?php settings_fields( 'mypwa_group' ); ?>
            <table class="form-table">
                <tr><th>Название приложения</th><td><input type="text" name="mypwa_name" class="regular-text" value="<?php echo esc_attr( get_option( 'mypwa_name', get_bloginfo( 'name' ) ) ); ?>"></td></tr>
                <tr><th>Короткое имя</th><td><input type="text" name="mypwa_short_name" class="regular-text" value="<?php echo esc_attr( get_option( 'mypwa_short_name', get_bloginfo( 'name' ) ) ); ?>"></td></tr>
                <tr><th>URL иконки (512×512)</th><td><input type="text" name="mypwa_icon" class="regular-text" value="<?php echo esc_attr( get_option( 'mypwa_icon', '' ) ); ?>"></td></tr>
                <tr><th>URL скриншота (1080×1920)</th><td><input type="text" name="mypwa_screenshot" class="regular-text" value="<?php echo esc_attr( get_option( 'mypwa_screenshot', '' ) ); ?>"></td></tr>
                <tr><th>Theme color</th><td><input type="color" name="mypwa_theme_color" value="<?php echo esc_attr( get_option( 'mypwa_theme_color', '#000000' ) ); ?>"></td></tr>
                <tr><th>Background color</th><td><input type="color" name="mypwa_background_color" value="<?php echo esc_attr( get_option( 'mypwa_background_color', '#ffffff' ) ); ?>"></td></tr>
            </table>
            <?php submit_button(); ?>
        </form>
        <hr>
        <p><strong>Проверка:</strong>
            <a href="https://web.dev/measure/" target="_blank">web.dev/measure</a> ·
            <a href="<?php echo esc_url( home_url( '/?mypwa_manifest=1' ) ); ?>" target="_blank">manifest.json</a> ·
            <a href="<?php echo esc_url( home_url( '/?mypwa_sw=1' ) ); ?>" target="_blank">sw.js</a>
        </p>
    </div>
    <?php
}

Шпаргалка: что проверять, если не работает

Симптом Причина

Кнопка установки не появляется в Chrome Нужен HTTPS + взаимодействие с сайтом (клик) + ожидание ~30 сек

Яндекс открывает с адресной строкой Ограничение Яндекса, не баг. PWA работает только в Chrome / Safari / Firefox

Manifest не грузится Проверить ?mypwa_manifest=1 напрямую

SW не регистрируется Проверить ?mypwa_sw=1 и заголовок Service-Worker-Allowed: /
iOS не показывает баннер установки Это норма. На iPhone — «Поделиться» → «На экран Домой»

Где что лежит

Файл: wp-content/plugins/mypwa.php

Настройки в БД: mypwa_name, mypwa_icon, mypwa_theme_color и т.д.

Чтобы полностью удалить
Деактивировать и удалить плагин в админке

Опционально: удалить опции из БД

Service Worker в браузерах пользователей отключится сам через ~24 часа


sql
DELETE FROM wp_options WHERE option_name LIKE 'mypwa_%';

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

🧮 Проверка безопасности
15 + 14 = ?