# Разширяване на Symelo Image Optimizations

За разработчици на добавки. Symelo Image Optimizations оптимизира изображения с библиотеките за изображения на PHP (Imagick и GD). Добавките могат да регистрират още **механизми** (енкодери за JPEG, PNG, WebP, AVIF, PDF или преоразмеряване), да добавят **карти и раздели** към екрана на плъгина, както и **предупреждения, бележки, системна информация и полета за състоянието**. Този документ е договорът: всичко описано тук продължава да работи в рамките на версия 1.x на API.

Съдържание:

1. [Версии](#версии)
2. [Зареждане на добавка](#зареждане-на-добавка)
3. [Хукове](#хукове)
4. [Механизми](#механизми)
5. [Скорост на фоновата обработка](#скорост-на-фоновата-обработка)
6. [Административен екран](#административен-екран)
7. [Състояние, Здраве на сайта и системна информация](#състояние-здраве-на-сайта-и-системна-информация)
8. [Файлове, опции и почистване](#файлове-опции-и-почистване)
9. [Правила за сигурност](#правила-за-сигурност)
10. [Публични класове](#публични-класове)

## Версии

`SYMELO_IMAGES_API_VERSION` е низ във формат `major.minor`, в момента `1.0`. Дефинира се само когато плъгинът реално работи (PHP 7.4 или по-нова версия), преди да се задейства `symelo_images_loaded`.

- **Второстепенните версии (1.1, 1.2, …)** само добавят: нови хукове, нови параметри в края на хук, нови публични методи, нови методи с реализация по подразбиране в `AbstractEngine`, нови свойства на `Job`, нови задачи в `Settings::TASKS`, нови ключове в масиви (състояние, профили, секции на системната информация), нови CSS класове.
- **Основните версии (2.0)** могат да нарушат съвместимостта. Несъвместими промени са: премахване или преименуване на хук, публичен клас, метод или константа, описани тук; промяна на параметрите или на типа на връщаната стойност на хук или метод; промяна на значението на свойство на `Job`, на име на задача или на ключ на профил; добавяне на метод към `EngineInterface`; премахване на CSS клас или атрибут `data-`, изброени в [Административен екран](#административен-екран).
- `EngineInterface` не се променя в рамките на 1.x. Незадължителните възможности на механизмите, добавени по-късно, идват като методи на `AbstractEngine` с реализация по подразбиране, така че механизмите, които го наследяват, продължават да работят. Директното имплементиране на `EngineInterface` е позволено, но тогава не получавате тези реализации по подразбиране.
- Всичко, което не е описано в този документ (други класове, частни и вътрешни методи, структурата на опцията с настройки, таблиците в базата данни, admin-post действията и nonce кодовете `symelo_images_*`), е вътрешно и може да се промени във всяка версия.

Проверете версията, преди да използвате каквото и да е:

```php
function my_addon_api_ok(): bool {
	if ( ! defined( 'SYMELO_IMAGES_API_VERSION' ) ) {
		return false; // Not installed, not active, or PHP too old.
	}
	$version = (string) SYMELO_IMAGES_API_VERSION;
	return 1 === (int) $version && version_compare( $version, '1.0', '>=' ); // Major 1, at least 1.0.
}
```

## Зареждане на добавка

- Добавете `Requires Plugins: symelo-image-optimizations` в заглавката на добавката. Тогава WordPress 6.5 и по-новите версии няма да активират добавката без плъгина и няма да деактивират плъгина, докато добавката е активна. Въпреки това защитете всичко с проверки: при WordPress 6.4 или когато файловете са премахнати ръчно, добавката се зарежда и без плъгина.
- Плъгините се зареждат по азбучния ред на папките си, а плъгините, активирани за мрежата, се зареждат преди тези на сайта, така че добавката може да се зареди преди или след Symelo Image Optimizations. Закачете се по следния начин:

```php
if ( did_action( 'symelo_images_loaded' ) ) {
	my_addon_start( \Symelo\Images\Plugin::instance() );
} else {
	add_action( 'symelo_images_loaded', 'my_addon_start' );
}
// Still not started once every plugin is loaded: show an admin notice.
add_action( 'plugins_loaded', 'my_addon_check', 20 );
```

- В `my_addon_start()` първо проверете версията на API. Преди това не зареждайте нищо, което наследява или имплементира клас на плъгина (например механизми): без плъгина PHP би спрял с фатална грешка. Основният файл на добавката, нейният autoloader и кодът ѝ за начално зареждане трябва да работят без плъгина.
- Деактивирането на Symelo Image Optimizations никога не трябва да поврежда сайта: тогава добавката само показва известие („Install and activate Symelo Image Optimizations“).

## Хукове

### Действия

| Действие | Аргументи | Кога се задейства |
|---|---|---|
| `symelo_images_loaded` | `Plugin $plugin` | Веднъж, в края на зареждането на плъгина, докато се зареждат плъгините. Тук закачете добавката си. |
| `symelo_images_register_engines` | `EngineManager $engines` | Всеки път, когато се изгражда списъкът с механизми: при първото използване в рамките на заявка (отложено, напр. когато се оптимизира изображение, на екрана на плъгина, при REST `GET /status`, в Здраве на сайта) и отново след `Plugin::reset_engines()` (след запазване на настройките). Извикайте `$engines->add( $engine )`. Не пазете референция към мениджъра. |
| `symelo_images_file_optimized` | `FileRecord $after, FileRecord $before` | След като файл е оптимизиран, оптимизацията му е неуспешна, бил е пропуснат или е възстановен. Само за четене: не променяйте файлове тук. |
| `symelo_images_engines_tab` | `Plugin $plugin` | В края на раздела **Engines**, след собствените му карти. Изведете картите си тук. |
| `symelo_images_tab_{$slug}` | `Plugin $plugin` | Извежда панела на раздел, добавен чрез `symelo_images_tabs`. Всеки раздел се извежда при всяко зареждане на екрана и се показва при щракване. |

### Филтри

| Филтър | Аргументи | Връща | За какво служи |
|---|---|---|---|
| `symelo_images_capability` | `string $capability` (`'manage_options'`) | Право | Кой може да използва екрана на плъгина, масовата оптимизация и REST API. Прочетете го с `Plugin::capability()`. |
| `symelo_images_engine_order` | `string[] $ids, string $task` | ID на механизми | Автоматичният ред за дадена задача. `$ids` вече е сортиран по ранг; пренаредете или премахнете ID. Непознатите ID и механизмите, които не обработват задачата, се пренебрегват. |
| `symelo_images_tabs` | `array $tabs` (`[]`) | `slug => label` | Допълнителни раздели, показани преди **Guide** и **Support**. Slug стойностите минават през `sanitize_key()`; вградените slug стойности (`overview`, `compression`, `delivery`, `folders`, `tools`, `advanced`, `guide`, `support`) се пренебрегват. |
| `symelo_images_warnings` | `string[] $warnings, Plugin $plugin` | Низове с обикновен текст | Предупреждения, показвани в раздела **Overview** (по едно известие за всяко), в системната информация, в `wp symelo-images status` и в `GET /status`. |
| `symelo_images_notes` | `string[] $notes, Plugin $plugin` | Низове с обикновен текст | Бележки, показвани под таблицата с механизми в раздела **Engines**, в системната информация и в `GET /status`. |
| `symelo_images_system_info` | `array $sections, Plugin $plugin` | `title => [ label => value ]` | Секциите на системната информация в раздела **Support** (обикновен текст). Добавете своя собствена секция. Никога не включвайте пароли, ключове или токени. |
| `symelo_images_rest_status` | `array $status, Plugin $plugin` | Масив | Отговорът на `GET /wp-json/symelo-images/v1/status`. Добавете един собствен ключ (напр. `my_addon`); не променяйте съществуващите ключове. |
| `symelo_images_pace` | `array $profile, string $speed` | Профил | Профилите на Background speed (вижте [Скорост на фоновата обработка](#скорост-на-фоновата-обработка)). |
| `symelo_images_max_pixels` | `int $pixels` (60 000 000) | Пиксели | По-големите изображения се пропускат. |
| `symelo_images_max_bytes` | `int $bytes` (100 MB) | Байтове | По-големите файлове се пропускат. |
| `symelo_images_imagick_limits` | `array $limits` | Ограничения | Ограниченията на ресурсите на ImageMagick (име на константа `RESOURCETYPE_*` => стойност). |
| `symelo_images_worker_budget` | `int $seconds` | Секунди | Колко дълго работи едно фоново изпълнение (минимум 5). |
| `symelo_images_dispatch_args` | `array $args` | Аргументи за `wp_remote_post()` | Заявката, която сайтът изпраща към самия себе си, за да стартира фонови изпълнения. |
| `symelo_images_skip_page` | `bool $skip` (`false`) | Bool | `true` пропуска обработката за `<picture>` и отложено зареждане за текущата заявка. |
| `symelo_images_more_from_symelo` | `array $products` (`[]`) | Продукти | „More from Symelo LTD“ в раздела Support. |

Тестове за Здраве на сайта: използвайте филтъра `site_status_tests` на ядрото с ID на тестове със собствен префикс; не е нужно нищо от този плъгин.

## Механизми

Механизмът кодира файлове за една или повече **задачи**: `jpeg` (компресиране на JPEG), `png` (компресиране на PNG), `webp` (копия в WebP), `avif` (копия в AVIF), `pdf` (компресиране на PDF) и `resize` (мащабиране и завъртане, използва се и за подготовка на копия за механизми, които не могат да правят това). `Settings::TASKS` ги изброява. Плъгинът включва `imagick` и `gd`; нито един вграден механизъм не обработва `pdf`.

### Регистриране

```php
use Symelo\Images\Engines\AbstractEngine;
use Symelo\Images\Engines\EngineException;
use Symelo\Images\Engines\EngineManager;
use Symelo\Images\Engines\Job;

final class My_Webp_Engine extends AbstractEngine {

	public function id(): string {
		return 'my-webp';
	}

	public function label(): string {
		return 'My WebP encoder';
	}

	public function tasks(): array {
		return array( 'webp' => $this->works() ? true : __( 'The encoder is missing.', 'my-addon' ) );
	}

	public function rank( string $task ): int {
		return 10; // Before Imagick (50) and GD (60).
	}

	public function accepts( Job $job ): bool {
		return $this->can( $job->task ) && in_array( $job->info->format, array( 'jpeg', 'png' ), true ) && ! $job->needs_preparing();
	}

	public function run( Job $job, string $dest ): void {
		if ( ! my_encode( $job->source(), $dest, $job->quality ) ) {
			throw new EngineException( esc_html__( 'My encoder failed.', 'my-addon' ) );
		}
	}

	private function works(): bool {
		return true;
	}
}

add_action(
	'symelo_images_register_engines',
	static function ( EngineManager $engines ) {
		$engines->add( new My_Webp_Engine() );
	}
);
```

`EngineManager::add()` заменя механизъм със същото ID; `remove( $id )` премахва механизъм. ID трябва да е това, което връща `sanitize_key()` (малки латински букви, цифри, `_`, `-`), и не може да бъде `auto`. Правете ги отличими: добавяйте префикс, освен ако ID не е името на добре известна програма (`cwebp`).

### EngineInterface

| Метод | Договор |
|---|---|
| `id(): string` | Уникално и постоянно ID. Записва се като избора на администратора за дадена задача, затова никога не го променяйте. |
| `label(): string` | Името, което виждат администраторите. |
| `origin(): string` | Откъде идва механизмът, показва се под името му (напр. „PHP extension“, „Installed on the server“), или `''`. |
| `version(): string` | Откритата версия или `''`. При `''` механизъм, който не може да прави нищо, показва **Not found**; при наличие на версия — **Outdated**. |
| `note(): string` | Едно съобщение с обикновен текст за механизма (защо не е наличен или предупреждение), или `''`. |
| `tasks(): array` | Всяка задача, която механизмът обработва, и дали работи на този сървър: `task => true` или `task => 'reason'` (обикновен текст). Пропуснете задачите, които той никога не обработва. Извиква се често: кеширайте скъпите проверки (за текущата заявка или в опция с отпечатък). |
| `rank( string $task ): int` | Позиция в автоматичния ред: по-ниските стойности са първи. Imagick е 50, а GD — 60 за всяка задача; `AbstractEngine::RANK` е 100. Специализираните енкодери, които превъзхождат библиотеките на PHP, връщат по-малко от 50. |
| `explicit_only(): bool` | `true`: никога не се избира от **Automatic**, а само когато администраторът го посочи за задача (напр. механизъм със загуби, който прави компромиси със сигурността). |
| `can_transform(): bool` | Дали самият механизъм прилага EXIF ориентацията и мащабирането (`Job::$resize`). Вижте [Подготвени копия](#подготвени-копия). |
| `accepts( Job $job ): bool` | Дали приема конкретния файл (формат, анимация, цветово пространство, размер, памет, възможности на PDF…). Отказът е нормален и евтин: заданието получава следващият механизъм. Не трябва да записва нищо. |
| `run( Job $job, string $dest ): void` | Кодира в `$dest`. При неуспех хвърля `EngineException` (вижте [Грешки и резултати](#грешки-и-резултати)). |

`AbstractEngine` имплементира всичко освен `id()`, `label()`, `tasks()` и `run()`: без произход, версия или бележка, ранг `static::RANK` (100), не е само за изричен избор, не може да трансформира, `accepts()` = `can( $job->task )`. Добавя и `can( string $task ): bool` (задачата е `true` в `tasks()`). `EngineManager::can( $engine, $task )` прави същото за всеки механизъм.

### Рангове и автоматичен ред

За дадена задача редът се изгражда от всички регистрирани механизми, в чийто `tasks()` присъства тази задача (независимо дали са използваеми на този сървър):

1. сортирани по `rank( $task )`, като по-ниските са първи; при равни рангове се запазва редът на регистриране (Imagick, GD, след това добавките в реда, в който са извикали `add()`);
2. прекарани през `symelo_images_engine_order` (`$ids`, `$task`), който може да пренарежда или премахва ID.

Този ред е това, което показва разделът **Engines**. За да оптимизира файл, плъгинът опитва последователно:

1. механизма, който администраторът е избрал за задачата (**Engine for each job**), ако все още е регистриран и работи на този сървър;
2. всички останали механизми в реда, с изключение на тези само за изричен избор;

и използва първия, в чийто `tasks()` задачата е `true` и чийто `accepts()` връща `true`. Ако `run()` не успее, се опитва следващият. Механизмът, показан като **Automatic (…)**, е първият, който по принцип работи за задачата (`EngineManager::preferred()`).

Изборът за всяка задача се проверява спрямо регистрираните механизми: ако запазите механизъм, който е регистриран, но не може да изпълни задачата на този сървър, се показва съобщението „*engine* can't do that job on this server, so Automatic is used instead.“ Запазен избор за механизъм, който вече не е регистриран (добавката е деактивирана), безшумно се третира като **Automatic** (`EngineManager::choice()` връща `auto`) и се премахва при следващото запазване на раздела Engines.

### Job

`Job` описва едно задание. Механизмите четат свойствата му и никога не ги променят (плъгинът подава същото задание на следващия механизъм).

| Свойство / метод | Значение |
|---|---|
| `string $task` | Задачата (`Settings::TASKS`). |
| `ImageInfo $info` | Източникът: `path`, `format` (`jpeg`, `png`, `gif`, `webp`, `avif`, `pdf`, разпознат по съдържанието), `bytes`, `width`, `height`, `animated`, `alpha`, `cmyk`, `bit_depth`, `orientation` (EXIF, 1–8), `has_icc`, `srgb_icc`, `jpeg_quality` (приблизителна оценка, 0 когато е неизвестна). |
| `source(): string` | Абсолютният път до файла източник (`$info->path`). Четете от него; никога не записвайте в него. |
| `int $quality` | От 1 до 100 или 0 за без загуби. За `jpeg` при 0 е допустима само оптимизация без загуби. |
| `?array $resize` | `[width, height]`, до които да се мащабира, или `null`. |
| `bool $strip` | Премахване на EXIF/XMP/IPTC. Плъгинът и сам премахва метаданните от резултатите в JPEG, PNG и WebP, като запазва цветовия профил и ориентацията, така че запазването на маркерите е безопасно. |
| `bool $lossy_png` | Задача `png`: разрешено е намаляване на цветовете. |
| `string $pdf_preset` | Задача `pdf`: `''` без загуби (ниво Lossless), `printer` умерено (Balanced, Custom), `ebook` по-малък файл (Aggressive). Механизмите без загуби го пренебрегват. |
| `int $threads` | Нишки на енкодера според Background speed (0 = стойността по подразбиране на енкодера). |
| `needs_preparing(): bool` | Пикселите трябва да бъдат мащабирани (`$resize`) или завъртени (JPEG с `orientation` > 1). |
| `with_source( ImageInfo $info ): Job` | Копие, което чете друг файл, с изчистено `resize`. |
| `libavif_quality(): int` | `$quality`, преобразувано към скалата на libavif (за AVIF енкодери, базирани на libavif). |

### Подготвени копия

Механизмите, които не могат да завъртат или мащабират, връщат `false` от `can_transform()` и отказват (`accepts()` → `false`) заданията, за които `needs_preparing()` е вярно. Тогава плъгинът създава PNG без загуби, който вече е завъртян и мащабиран (с механизма за `resize`, Imagick или GD), и извиква `accepts()` отново с `$job->with_source( $prepared )`. Механизъм, който не декодира пиксели (например оптимизатор на JPEG без загуби, който запазва EXIF ориентацията), може да приема завъртени JPEG файлове, стига `$resize` да е `null`.

### Грешки и резултати

- `run()` записва резултата в `$dest`, който е нов път (презапишете го, ако съществува). По време на оптимизация това е нов файл в частната временна папка на плъгина, който плъгинът изтрива след това; самотестът на доставянето записва тестови копия в `uploads/symelo-images/public/`. Не записвайте никъде другаде освен собствени временни файлове в `Paths::temp_dir()` и ги изтривайте преди връщане, включително при неуспех.
- При неуспех хвърлете `EngineException` със съобщение за администраторите, екранирано като всяко съобщение на изключение в WordPress: `new EngineException( esc_html__( '…', 'my-addon' ) )` или `esc_html( sprintf( … ) )`. Плъгинът съхранява `$e->plain_message()`, което отменя това екраниране, и екранира отново навсякъде, където го извежда. Съобщението се показва заедно с файла и се опитва следващият механизъм.
- Когато механизмът е сработил, но резултатът не може да бъде по-малък, хвърлете `EngineException::no_gain()`: плъгинът спира да опитва други механизми за това задание и запазва това, което има.
- След `run()` плъгинът проверява резултата: съдържанието му трябва да е в очаквания формат (разпознат по байтовете), резултатите в същия формат се запазват само ако са осезаемо по-малки (с 1 KB или 1%, освен при преоразмеряване), копията в WebP и AVIF — само ако са по-малки от файла (а AVIF копието — и по-малко от WebP копието), а PDF файловете — само ако резултатът е PDF и е по-малък. Записът на място е атомарен, а за оригинала първо се прави резервно копие.
- Механизмите за PDF проверяват `Image\PdfInfo::read( $job->source() )` (`encrypted`, `signed`, `forms`): подписаните PDF файлове никога не трябва да се презаписват.

## Скорост на фоновата обработка

`Queue\Pace` съдържа профилите на скоростта от **Advanced → Background speed** (`fast`, `normal`, `gentle`).

| Ключ | Значение | fast | normal | gentle |
|---|---|---|---|---|
| `budget` | Секунди, през които работи една фонова порция | 25 | 15 | 5 |
| `pause` | Секунди между порциите при масова обработка | 0 | 0 | 30 |
| `nice` | Приоритет на процесора за програмите енкодери (0 нормален … 19 най-нисък) | 0 | 10 | 19 |
| `idle_io` | Приоритет „idle“ на диска за програмите енкодери (Linux `ionice -c 3`) | не | не | да |
| `threads` | Нишки на енкодера (0 = стойността по подразбиране на енкодера); също в `Job::$threads` | 0 | 2 | 1 |
| `max_load` | Масовата обработка изчаква, докато натоварването за 1 минута на процесорно ядро е поне толкова (0 = никога) | 0 | 2.0 | 0.7 |

- `Pace::current(): array` е действащият профил (с приложен филтър `symelo_images_pace`). Механизъм, който стартира програми енкодери, чете `nice` и `idle_io` от него при всяко изпълнение, а броя нишки — от `Job::$threads`.
- `Pace::profile( string $speed ): array` връща един профил; `Pace::SPEEDS` изброява скоростите.
- `Pace::busy( array $profile ): bool`, `Pace::load_per_core(): ?float` и `Pace::cpu_count(): int` показват дали сървърът е натоварен, за ваша собствена фонова работа.
- Качванията и изричните изпълнения („Optimize now“, WP-CLI без `--paced`, REST API) не изчакват паузи и не следят натоварването, но използват приоритета и нишките от профила.

## Административен екран

Екранът е **Медия → Image Optimizations** (slug на страницата `Admin\Admin::PAGE`, `symelo-images`; ID на екрана `media_page_symelo-images`). Всеки раздел се извежда при всяко зареждане на страницата и се превключва в браузъра, затова картите трябва да се изобразяват бързо.

- **Карти в раздела Engines:** извеждайте ги на `symelo_images_engines_tab`.
- **Раздели:** добавете `slug => label` чрез `symelo_images_tabs`, след което изведете съдържанието на панела на `symelo_images_tab_{$slug}`. Плъгинът го обвива в `<div class="symelo-panel" data-panel="{slug}">`. `Admin\Ui::url( array( 'tab' => $slug ) )` дава връзка към него; връзките с `data-tab-link="{slug}"` превключват разделите на място.
- **Ресурси:** `assets/admin.css` и `assets/admin.js` на плъгина се зареждат на екрана. Зареждайте своите само на този екран (`admin_enqueue_scripts`, `$hook_suffix` съдържа `symelo-images`), със собствен идентификатор (handle).

Маркиране и помощни функции за повторна употреба (стабилни в рамките на 1.x):

| Клас, атрибут или помощна функция | Употреба |
|---|---|
| `<div class="symelo-card">` с `<h2>` | Карта. |
| `<table class="widefat striped symelo-table">` | Таблица в карта. |
| `Ui::badge( $status, $label )` → `.symelo-badge .symelo-badge--{ok,warn,bad,info,muted}` | Значка за състояние (екранира етикета си; извеждайте я с `phpcs:ignore` за `OutputNotEscaped` и тази причина). |
| `Ui::checkbox( $name, $checked, $label, $help, $disabled, $why )` → `.symelo-check` | Поле за отметка, което изпраща `0`, когато не е отметнато; неактивните показват `$why` в `.symelo-unsupported`. |
| `.description`, `.symelo-note`, `.symelo-unsupported`, `.symelo-inline-error` | Помощен текст, бележка под състояние, защо нещо не е възможно, грешка на място. |
| `.symelo-button-row` | Бутони в един ред (във `<form>` или `<p>`). |
| `.button-link.symelo-link-danger`, `.button.symelo-button-danger` | Необратими действия. |
| `data-confirm="Question?"` на бутон за изпращане | Иска потвърждение преди изпращане (при празна стойност пита „Are you sure?“). |
| `.symelo-options` / `label.symelo-option` | Радио опции със заглавие и описание. |
| `.symelo-inline-fields`, `table.symelo-meta`, `details.symelo-details`, `textarea.symelo-code` с бутон `data-copy="#id"` | Полета в един ред, таблица етикет/стойност, сгъваема секция, код за копиране. |
| `Ui::url( array $args )`, `Ui::bytes( int )`, `Ui::time( int )` | URL адрес на екрана, размери на файлове, дати в часовата зона на сайта. |
| `Admin\Notices::flash( $type, $message )` | Съобщение с обикновен текст (`success`, `error`, `warning`, `info`), показвано под заглавката, след като вашият обработчик пренасочи обратно. |

Формуляри и обработчици:

- Изпращайте към `admin-post.php` с действие със собствен префикс (`admin_post_{your_prefix}_{action}`). Никога не изпращайте към действията `symelo_images_*` на плъгина и не използвайте неговите nonce кодове.
- В обработчика: първо проверете `current_user_can( \Symelo\Images\Plugin::capability() )` (спрете с `wp_die( …, 403 )`), след това `check_admin_referer( '{your_prefix}_{action}' )`, след това почистете всяко поле. Пренасочете обратно с `wp_safe_redirect( Ui::url( array( 'tab' => 'tools' ) ) ); exit;`.
- След като промените нещо, което засяга механизмите, извикайте `Plugin::instance()->reset_engines()`, за да се изгради отново списъкът с механизми при следващото използване.
- REST маршрути: собствено пространство от имена, `permission_callback`, който проверява `Plugin::capability()`, и схеми на аргументите за всеки вход.

## Състояние, Здраве на сайта и системна информация

- `symelo_images_warnings` / `symelo_images_notes`: низове с обикновен текст (вижте [Филтри](#филтри)). Предупрежденията са за неща, които администраторът трябва да поправи (енкодер с известни пробиви в сигурността); бележките са неутрална информация. Собственият тест на плъгина в Здраве на сайта „Image libraries are up to date“ обхваща само неговите библиотеки; добавяйте свои тестове за Здраве на сайта чрез `site_status_tests` на ядрото.
- `symelo_images_system_info`: `$sections['My Add-on'] = array( 'Version' => '1.2.3', … )`. Стойностите са обикновен текст.
- `symelo_images_rest_status`: `$status['my_addon'] = array( … )`.

## Файлове, опции и почистване

- Плъгинът притежава `uploads/symelo-images/` (`backups/`, `tmp/`, `public/`) и изтрива `uploads/symelo-images/tools/` (папка от предварителни версии), когато бъде инсталиран или обновен. Съхранявайте файловете на добавката в собствена папка, например `uploads/{your-slug}/`, защитена като тази на плъгина (`index.php`, `.htaccess` с `Require all denied`, `web.config`).
- `Support\Paths::temp_dir()` е частната временна папка на плъгина, за краткотрайни файлове на механизмите. Файловете, по-стари от един ден, се изтриват ежедневно; изтривайте своите веднага.
- Съхранявайте настройките в собствена опция. Никога не записвайте в `symelo_images_settings`, в таблицата на плъгина или в другите му опции.
- Когато плъгинът бъде изтрит, той премахва transient записите, чиито имена започват със `symelo_images_`, затова transient записите на добавката с такива имена трябва да са само кеш.
- Почиствайте при собственото си деинсталиране (`uninstall.php`), без да зареждате плъгина.

## Правила за сигурност

Добавките трябва да спазват правилата на плъгина:

- Синтаксис на PHP 7.4 и API на WordPress 6.4, освен ако добавката не изисква по-нови версии.
- Първо право, после nonce, във всеки обработчик; почиствайте входа, екранирайте изхода, използвайте `$wpdb->prepare()` за всяка заявка.
- Никога не приемайте път до файл или програма от заявка или настройка; работете само с `Job::source()`, `$dest` и папки, които притежавате.
- Стартирайте програми само с масиви от аргументи (без shell), минимална среда и ограничения на времето и отказвайте версии с известни пробиви в сигурността.
- Съобщенията в `EngineException` се екранират там, където се хвърлят.

## Публични класове

Стабилни в рамките на API 1.x (пространство от имена `Symelo\Images`):

| Клас | Публичен API |
|---|---|
| `Plugin` | `instance()`, `capability()`, `settings()`, `engines()`, `reset_engines()` |
| `Engines\EngineInterface`, `Engines\AbstractEngine` | Както е описано по-горе |
| `Engines\EngineManager` | `add()`, `remove()`, `all()`, `get()`, `order()`, `candidates()`, `choice()`, `preferred()`, `supports()`, `can()` (статичен), `usable_tasks()` (статичен) |
| `Engines\Job` | Свойствата и методите по-горе |
| `Engines\EngineException` | `NO_GAIN`, `no_gain()`, `plain_message()` |
| `Image\ImageInfo` | Публичните свойства, `read()`, `sniff()`, `read_bytes()`, `pixels()`, `is_image()` |
| `Image\PdfInfo` | `read()` |
| `Queue\Pace` | `SPEEDS`, `profile()`, `current()`, `busy()`, `load_per_core()`, `cpu_count()` |
| `Settings\Settings` | `TASKS` (самите ключове на настройките не са част от API) |
| `Support\Paths` | `temp_dir()` |
| `Support\SystemPackages` | `find()`, `is_patched()` (версии на пакети в Debian/Ubuntu с пренесени поправки за сигурност) |
| `Admin\Admin` | `PAGE` |
| `Admin\Ui` | `url()`, `badge()`, `checkbox()`, `bytes()`, `time()` |
| `Admin\Notices` | `flash()` |
