=== WP Rocket PHP 8 Guard ===
Contributors: webvozdux
Requires at least: 5.0
Tested up to: 7.1
Requires PHP: 7.0
Stable tag: 1.2
License: GPLv2 or later

Чинит фатал WP Rocket на PHP 8, который проявляется после обновления WordPress до 7.1.

== Что за проблема ==

После обновления WordPress до 7.1 сайты с WP Rocket начинают падать:

  PHP Fatal error: Uncaught TypeError: substr(): Argument #1 ($string)
  must be of type string, int given
  .../wp-rocket/inc/ThirdParty/Plugins/CDN/Cloudflare.php

Падение происходит на хуке init, то есть ложится всё сразу: фронт,
админка, admin-ajax и wp-cron.

== Почему ==

WordPress хранит колбэки под ключом, который строит _wp_filter_build_unique_id()
в wp-includes/plugin.php. Для замыкания это:

  return (string) spl_object_id( $callback );

то есть чистое число в виде строки, например "6556". PHP при записи в массив
автоматически кастует такую строку в int.

WP Rocket в методе unregister_callback() передаёт этот ключ в substr(). Файл
объявлен с declare(strict_types=1), поэтому на PHP 8 получается TypeError.

Метод unregister_cloudflare_clean_on_post() подписан на init и является
единственным в классе БЕЗ проверки is_plugin_active('cloudflare/cloudflare.php').
Поэтому падает даже там, где плагин Cloudflare не установлен вовсе.

Затронуты все проверенные версии WP Rocket: 3.18.2, 3.20.1.2, 3.23.2.1.

== Что делает плагин ==

Находит проблемную строку регуляркой (номер строки и версия WP Rocket роли
не играют) и добавляет каст (string). Семантика не меняется: настоящие колбэки
WP Rocket имеют ключ вида "<id><имя_метода>", хвост совпадает, разрегистрация
работает как задумано вендором; числовой ключ чужого замыкания просто
пропускается.

Патч в чужом плагине слетает при каждом его обновлении, поэтому сторож
проверяет файл заново и накладывает патч автоматически.

== Установка ==

Плагины - Добавить новый - Загрузить плагин - выбрать ZIP - Установить - Активировать.

Патч накладывается сразу при активации. На странице «Плагины» показывается
строка состояния.

== Важно ==

Ставить НУЖНО ДО обновления WordPress до 7.1.

Если сайт уже упал, залить плагин через админку не получится - она тоже
фаталит. В этом случае патч накладывается через FTP или SSH, а плагин
ставится уже после подъёма сайта.

== Когда можно удалить ==

Когда WP Rocket выпустит версию с исправлением. Плагин это заметит сам и
покажет: «код WP Rocket изменился, знакомой строки нет - сторож можно отключить».

При деактивации патч НЕ откатывается - иначе сайт немедленно ляжет.

== Changelog ==

= 1.2 =
* Версия для установки через админку (без SSH)
* Запуск на plugins_loaded с приоритетом -9999 - успевает до падения на init
* Повторная проверка после обновления любого плагина (upgrader_process_complete)
* Строка состояния на странице «Плагины»

= 1.1 =
* Версия для mu-plugins
* Поиск якоря регуляркой вместо привязки к номеру строки
* Атомарная запись через rename, сохранение исходных прав файла
