=== ALTOS ===
Contributors: ffscz
Tags: performance, admin, optimization, speed, defer
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.4.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Skip unnecessary plugins per admin screen and frontend page — 30–50% faster WordPress, with automatic data-driven suggestions.

== Description ==

Most WordPress sites carry dozens of active plugins — but on any given page, only a handful are actually needed. The rest load anyway, burning memory, database queries, and response time on every single request.

**ALTOS** fixes that. It gives you per-screen control over which plugins load on each admin page and each frontend page type. The result: 30–50% faster admin, fewer queries, less memory — with no permanent deactivation required.

ALTOS operates at the WordPress core level via the MU-plugins mechanism, intercepting the plugin-loading pipeline before WordPress fully initialises. It cleanly skips heavy code paths on screens where they are not needed — while preserving admin menus, dependency relationships, and safety guards.

**Key features**

* **Per-screen admin control** — Set rules for each admin screen (Dashboard, Posts, Pages, Settings, WooCommerce, Custom Post Types, ...).
* **Frontend Optimizer** — Opt-in per-page-type control for the frontend (homepage, single posts, archives, WooCommerce pages, ...).
* **Three states per rule** — Enabled (always load), Disabled (never load), Defer (load after the main page).
* **Conditional rules** – Skip a plugin only on mobile or desktop devices, or only for specific users.
* **Smart menu preservation** — Admin menu entries stay accessible for blocked/deferred items via an automatic snapshot system.
* **Hierarchical rules** — Global > group > screen, with intelligent inheritance.
* **Automatic mode with suggestions** — Confidence-scored recommendations from real performance samples; you review and apply manually.
* **Dependency cascade detection** — Catches dependent items when a parent is blocked.
* **Request-type optimisation** — Separate rules for AJAX, REST API, WP-Cron, and WP-CLI.
* **Update Optimizer** — Three strategies to reduce update-check overhead without breaking badge counts.
* **Per-plugin performance metrics** — Real-time load time, DB queries, memory.
* **Developer tools** — Admin drawer, admin-bar integration, REST endpoints, extensive filters/actions.
* **Built-in translations** — Czech, Slovak, Polish, German, French, Spanish.

**Typical impact**

* 30–50% faster admin pages.
* 20–40 fewer database queries per admin page.
* 1–5 MB less memory per blocked item.

**Safety by default**

* Manual mode requires explicit rules — nothing is changed without your action.
* Automatic mode only *suggests* — never applies on its own.
* The loader protects itself from being blocked.
* Page-builder bypass for Elementor, Bricks, Divi, Beaver Builder during active editing sessions.
* Emergency recovery URL for one-click bypass if a misconfiguration breaks the admin.

== External Services ==

This plugin does not contact any external services. It does not send analytics, telemetry, license checks, or any other network requests. All rules, samples, snapshots, and configuration are stored exclusively in your own WordPress database.

The "Update Optimizer" feature exclusively *throttles* the standard WordPress update-check HTTP requests to `api.wordpress.org` — it never adds new endpoints and never sends additional data.

== Installation ==

1. Install via **Plugins → Add new → Upload Plugin** in WordPress, or upload the unzipped folder to `/wp-content/plugins/altos/`.
2. Activate the plugin through the **Plugins** screen in WordPress.
3. After activation, an admin notice will ask for your permission to install a small MU-loader file in `wp-content/mu-plugins/`. Click "Install loader file" to enable full per-screen filtering. Without it, the plugin runs in limited mode. The file is removed automatically on deactivation.
4. Go to **Settings → ALTOS** in your WordPress admin.
5. Configure rules manually, or enable **Automatic mode** to get data-driven suggestions after a day or two of sampling.

**Requirements**

* PHP 7.4 or newer (PHP 8.0+ recommended).
* WordPress 6.0 or newer.
* Write permission for `wp-content/mu-plugins/` (almost all hosts allow this; some managed-WordPress hosts restrict it).

== Frequently Asked Questions ==

= How does this differ from "deactivating" a plugin? =

ALTOS runs **before** WordPress loads regular plugins, using the MU-plugins mechanism. That means the code is genuinely *not loaded* on screens where you don't need it — not loaded-and-then-suppressed. Deactivation is global and permanent; this lets you keep an item active site-wide while skipping it on specific admin screens or request types.

= How does Automatic mode work? =

For 24–48 hours after you enable it, the loader samples real performance data on your own admin: load time, database queries, memory per active item, per admin screen. It then analyses those samples to suggest rules with a confidence score (0–100 %) and estimated savings. **Suggestions are never applied automatically** — you review them and apply with one click.

= Is it safe to block items? =

ALTOS has multiple safeguards:

* Dependency cascade detection — if you block a parent (e.g. WooCommerce), dependents are flagged automatically.
* Smart menu preservation keeps blocked items' admin menu entries reachable.
* Critical scenarios (page-builder editing, WooCommerce checkout/cart) are auto-excluded.
* The loader itself is auto-protected from being blocked.

Recommendation: enable Automatic mode on a staging site first, review the suggestions, then export to production.

= Does this work with page builders? =

Yes. Elementor, Bricks, Divi, and Beaver Builder edit sessions are detected and the loader steps out of the way to avoid breaking editor previews.

= What about WooCommerce? =

Full WooCommerce support: cart, checkout and account pages are excluded from any blocking on the frontend. Admin screens are recognised per area (products, orders, settings, …) so you can keep WooCommerce active on its own screens while skipping it elsewhere.

= Does it affect the frontend? =

The Frontend Optimizer is an opt-in feature. If you leave it disabled (default), the frontend is completely unaffected. With it enabled, you can selectively skip items per page-type context (homepage, single, archive, …).

= Can I disable a plugin only on mobile, or only for a specific user? =

Yes. Open **Settings → ALTOS → Administration** and scroll to **Conditional Rules (device / user)**. Each rule names a plugin, where it applies (admin, frontend or both), the device (any, mobile only, desktop only) and who it applies to: everyone including visitors, visitors only, all logged-in users, or a WordPress role; for the administrator role you can pick one specific administrator account. A rule with "Mobile only" keeps the plugin loaded on desktop and skips it on phones; a rule with "Administrator → admin2" skips the plugin for that account and nobody else. Conditions in one rule are combined (device AND user); several rules for the same plugin are alternatives.

Rules are evaluated when plugins are loaded, so the plugin code really does not run. The Plugins screen and the ALTOS settings page always load everything, WooCommerce stays protected on shop pages, and safe mode switches the rules off. The device comes from `wp_is_mobile()`, the user from the login cookie without signature verification: treat these rules as a performance optimisation, not as access control. Rules with users do not apply to visitors who are not logged in.

Developers can implement other conditions (time of day, IP ranges, ...) with the `sapm_admin_filtered_plugins` and `sapm_frontend_filtered_plugins` filters from an mu-plugin; `SAPM_Core::get_logged_in_user_hint()` returns the user login available at that early stage.

= How does the Update Optimizer work? =

Three strategies are available:

1. **TTL extension** — Throttle the outbound `api.wordpress.org` update HTTP calls so they only re-run after a configurable interval (12 h → 24–72 h). WordPress keeps its own cache; we only reduce how often it gets refreshed.
2. **Page-specific** — Only run update checks on the dedicated **Plugins**, **Themes** and **Updates** admin pages.
3. **Cron-only** — Move all update checks to background WP-Cron; the admin never blocks on them.

All strategies preserve update badge counts and "Check now" buttons still work as expected.

= Can I use it on multisite? =

Yes. Network activation is supported but each subsite needs its own per-site rules. The MU-loader is created once for the network.

= Does the plugin contact any external servers? =

No. See the **External Services** section above.

= What languages is the plugin translated into? =

English (source) plus Czech (cs_CZ), Slovak (sk_SK), Polish (pl_PL), German (de_DE), French (fr_FR), and Spanish (es_ES). Translations live in `/languages/` as `.po`/`.mo`. Community translations from `translate.wordpress.org` are also picked up automatically.

== Screenshots ==

1. Main dashboard showing per-screen rules for WordPress core areas.
2. Automatic mode with confidence-scored suggestions and estimated savings.
3. Hierarchical rules: group-level primary rules with screen-level overrides.
4. Admin drawer overlay for quick rule changes on any admin screen.
5. Per-plugin performance metrics with load-time and query counts.
6. Update Optimizer with three strategies and a live "stale data" indicator.
7. Request-type rules for AJAX / REST API / WP-Cron / WP-CLI.

== Changelog ==

= 1.4.1 – 2026-09-16 =
* **FIXED** The `active_plugins` guard compared against the already filtered option value, so a plugin activated or deactivated during a filtered request (for example from a frontend request or an AJAX call) could silently deactivate the plugins ALTOS had hidden. The guard now compares against the stored list. The same protection covers network-activated plugins on multisite.
* **FIXED** Multisite: frontend rules now also apply to network-activated plugins.
* **FIXED** After "Install loader file" the admin is returned to the page where the notice was clicked instead of the Plugins screen.
* **IMPROVED** The plugin dependency map used for cascade blocking is cached for 12 hours and refreshed when plugins are activated, deactivated, updated or deleted, so filtered requests no longer read every plugin header.
* **ADDED** Conditional Rules: do not load a plugin only on mobile or only on desktop devices, only for visitors, only for logged-in users, only for a WordPress role, or only for one specific administrator (Settings → ALTOS → Administration → Conditional Rules). Roles and administrators are picked from WordPress, nothing is typed by hand. The Save button stays disabled until at least one rule exists. Rules apply in the admin and on the frontend at plugin-load time; the Plugins screen, the ALTOS settings page, WooCommerce on shop pages and safe mode are exempt. Hidden plugins are listed in the toolbar and side panels.
* **ADDED** `sapm_admin_filtered_plugins` filter for the final plugin list on admin screens and `SAPM_Core::get_logged_in_user_hint()`; the `sapm_frontend_filtered_plugins` filter now also runs in passthrough mode, so developers can add their own conditions from an mu-plugin.
* **IMPROVED** When a language pack from translate.wordpress.org is installed, the bundled translation is loaded as a fallback for strings the pack does not have yet, so new strings are not shown in English until the pack catches up.

* **IMPROVED** Settings page: one size for all buttons with the icon and label centred as a group, one style for selects, text inputs and textareas, pill checkboxes everywhere (including the new rules table and the AJAX/REST detection option), and one neutral border for every info box and section instead of coloured side or top bars. Button icons always follow the button text colour (third-party admin CSS, for example Elementor on WordPress 7.x, recoloured them and made them invisible on blue buttons, which pushed the label off-centre). A developer comment that leaked into the Update Optimizer tab and debug output shown as visible text under WP_DEBUG are fixed.

= 1.4.0 – 2026-09-15 =
* **SECURITY** Update Optimizer no longer answers skipped update checks with a fake "no updates" response (WordPress stored it, so available security updates could stay hidden). Skipped requests now get an empty 503 response and WordPress as well as third-party updaters keep the update data they already have. Cron and WP-CLI requests are never throttled, core's "Check again" button always reaches the API, and the throttle uses its own last-check timestamp.
* **SECURITY** Only the third-party updater endpoints selected in the settings are blocked (the list of known endpoints was blocked unconditionally before); the whitelist is honoured for third-party updaters too.
* **SECURITY** The frontend safe-mode cookie is now signed (HMAC) and expires; a visitor can no longer disable frontend filtering by sending an arbitrary cookie.
* **SECURITY** Request-type sampling stores data only for AJAX actions and REST namespaces that actually exist on the site, and frontend performance snapshots are kept only for verified administrators.
* **FIXED** Frontend plugin rules did not apply to the real plugin-load pass (the frontend class was not available at MU-loader time), so blocked plugins still loaded for visitors. The core loader now includes it. Logged-in administrators keep the full site when admin bypass is on.
* **FIXED** Early (URL-based) context detection now understands WordPress query variables, so with plain permalinks a homepage rule no longer applies to search, single posts, pages, archives and feeds. It also recognises the WooCommerce cart, checkout, account and shop pages and product/category/tag URLs before WooCommerce is loaded, so the WooCommerce protection covers the real plugin-load pass. The "WooCommerce protection" checkbox is now honoured.
* **FIXED** Plugins hidden by frontend rules are protected against accidental deactivation: the `active_plugins` guard now covers frontend requests and only re-adds plugins that ALTOS itself hid in the current request instead of discarding the whole change; `activate-plugin` / `deactivate-plugin` AJAX actions are recognised.
* **FIXED** The frontend side panel received no data (`window.SAPM_FRONTEND_BAR` was added after the footer scripts were printed) and stayed empty. The data is now printed with `wp_print_inline_script_tag()`.
* **FIXED** Update Optimizer used a `match()` expression (PHP 8.0 syntax) although the plugin supports PHP 7.4.
* **FIXED** Implicitly nullable parameters (deprecated in PHP 8.4) and `preg_match()` on a possibly null path (PHP 8.1 deprecation).
* **FIXED** Frontend context labels are now translated on `init` instead of at MU-loader time. This removes the WordPress 6.7+ notice "Translation loading for the altos domain was triggered too early" on frontend requests.
* **FIXED** Sampling table schema is dbDelta-compatible (no `IF NOT EXISTS`), so future schema changes can be applied to existing tables.
* **FIXED** MU-loader install is atomic (temporary file + rename), the result is checked, consent is recorded only on success and a failure shows an admin notice.
* **FIXED** Uninstall now also removes frontend settings/rules, asset rules, safe-mode key, admin theme, consent state, per-page overrides (post/term meta), legacy options and the immediate update-check cron event; multisite loops restore the blog context properly.
* **CHANGED** Toolbar: the top-level "⚡ALTOS" item now opens the plugin settings page; the side panel opens from the new "Quick Plugin Management" submenu item. Same behaviour in the admin and on the frontend.
* **CHANGED** The brand name "ALTOS" (toolbar, menu, page title) is no longer a translatable string, so language packs cannot localise it (the Czech pack rendered the toolbar item as "VÝŠKY"). The frontend toolbar item and panel no longer use the legacy "SAPM" name.
* **IMPROVED** Bundled translations are registered with `load_plugin_textdomain()` on `init` for WordPress versions before 6.7; the MU-loader consent notice is translated; network activation creates the sampling table on every site; `update_option()` autoload uses `false` instead of the deprecated `'no'`.
* **UPDATED** Tested with WordPress 7.1 and PHP 8.3.

= 1.3.9 — 2026-06-10 =
* **UPDATED** Tested with WordPress 7.0.

= 1.3.8 — 2026-05-18 =
* **NEW** Explicit user consent flow for the MU-loader install. After activation, an admin notice asks whether to install the small loader file in `wp-content/mu-plugins/`. Without consent the plugin runs in limited mode (filtering still works for later option queries; only the very first plugin-load pass is unaffected).
* **CHANGED** No more silent self-healing of the MU-loader file. The file is created only on explicit click of "Install loader file" in the admin notice, and removed automatically on deactivation.
* **FIXED** Plugin URI header now points to the WordPress.org plugin directory.
* **FIXED** Two inline `<script>` blocks in the settings page extracted to a dedicated `assets/admin-handlers.js` enqueued via `wp_enqueue_script`. The frontend bar's JSON payload now goes through `wp_add_inline_script()` instead of an inline `<script>` tag.
* **FIXED** All `$_POST` / `$_GET` / `$_SERVER` super-global reads now use `wp_unslash()` plus an explicit `sanitize_*()` helper. `$_SERVER[REQUEST_TIME_FLOAT]` is cast through `(float)` before arithmetic. `$_SERVER[REQUEST_URI]` goes through `sanitize_text_field( wp_unslash( … ) )`.
* **FIXED** All `json_decode()` results from POST payloads are now deep-sanitised by a new recursive `sanitize_rules_array()` helper before being persisted. `stripslashes()` replaced with `wp_unslash()` where applicable.
* **IMPROVED** All uses of `WP_PLUGIN_DIR` / `WP_CONTENT_DIR` annotated with explanatory comments documenting why the WordPress-provided constants are the correct API here (no `__FILE__`-relative helper exists for resolving other plugins' base paths or the must-use plugins folder at the MU-loader load stage).

= 1.3.7 — 2026-05-15 =
* **NEW** Internationalisation: ships with full Czech, Slovak, Polish, German, French, and Spanish translations.
* **NEW** Per-language `.pot`, `.po`, and `.mo` files in `/languages/`. Domain Path header added.
* **CHANGED** Plugin renamed to "ALTOS" with slug `altos` for the WordPress.org directory.
* **CHANGED** Self-hosted GitHub updater removed — WordPress.org handles updates from now on.
* **CHANGED** Update Optimizer refactored to throttle HTTP requests only; it no longer hooks the update transients (cleaner separation from WordPress's own updater).
* **FIXED** Two stray non-English strings replaced with translatable English source strings.
* **FIXED** Hardened output escaping across the admin UI (`_e()` → `esc_html_e()` etc.).
* **FIXED** Replaced `parse_url()`, `unlink()`, `date()` and `mt_rand()` with their WordPress-recommended counterparts.
* **IMPROVED** All input super-globals now go through `wp_unslash()` + `sanitize_*`.
* **IMPROVED** Stricter `phpcs:ignore` documentation on the plugin's own database queries (custom `sampling_data` table).

= 1.3.5 =
* **FIXED** MU-loader self-healing — recreates a missing MU file on `admin_init` to prevent permanent loss after a failed activation.
* **FIXED** Early translation loading warning on WP 6.7+ (`_load_textdomain_just_in_time` notice).
* **IMPROVED** MU-plugin directory resolution consistently respects the `WPMU_PLUGIN_DIR` constant.

= 1.3.2 =
* **FIXED** Self-protection in filtering: the loader can no longer block itself.
* **FIXED** Invalid frontend rule modes safely fall back to passthrough.
* **IMPROVED** Lower overhead in frontend/cache and auto-rules paths.

= 1.3.0 =
* **NEW** SHA256 package integrity validation before install.
* **IMPROVED** Frontend drawer uses a shared drawer CSS with unified visual style.
* **IMPROVED** Frontend bar JavaScript refactor (Shadow DOM mount, per-page override UX, reset flow).
* **IMPROVED** Update metadata caching strategy (lock + fallback + HTTP validators).

= 1.2.0 =
* **NEW** Automatic mode with confidence-scored suggestions.
* **NEW** Custom table for sampling data, 30-day retention.
* **NEW** Update Optimizer (TTL extension / Page-specific / Cron-only).
* **NEW** Dependency cascade detection.
* **NEW** Request-type rules (AJAX, REST API, WP-Cron, WP-CLI).
* **NEW** Per-plugin performance metrics.
* **NEW** Admin drawer overlay.
* **IMPROVED** Hierarchical rule system with global / group / screen levels.
* **IMPROVED** Smart menu preservation system.
* **IMPROVED** WooCommerce integration.
* **IMPROVED** REST API endpoints for programmatic control.

= 1.1.0 =
* **NEW** Per-screen control system.
* **NEW** Defer-loading support.
* **NEW** Admin drawer interface.
* **NEW** Performance monitoring dashboard.

= 1.0.0 =
* Initial release.

== Upgrade Notice ==

= 1.4.1 =
New: Conditional Rules (do not load a plugin on mobile or desktop devices, or for specific users). Fixes the active_plugins guard (hidden plugins could be deactivated during a filtered request), applies frontend rules to network-activated plugins, caches the dependency map.

= 1.4.0 =
Security and compatibility release: skipped update checks no longer hide updates, frontend plugin rules now really apply to visitors, signed safe-mode cookie, PHP 7.4 compatibility restored, WordPress 7.1 tested. The side panel now opens from the toolbar submenu.

= 1.3.9 =
Tested with WordPress 7.0. No code changes.

= 1.3.8 =
Adds an explicit consent step before installing the loader file into `wp-content/mu-plugins/`. After upgrading, look for the admin notice and click "Install loader file" to keep per-screen filtering active.

= 1.3.7 =
First release published on WordPress.org. Plugin renamed to "ALTOS" (slug `altos`). Adds full translations for cs/sk/pl/de/fr/es. Recommended for all installations.

= 1.3.5 =
Bugfix release: fixes MU-plugin creation failure on Windows, self-healing MU-loader, and WP 6.7+ early translation warning.

= 1.3.2 =
Maintenance and hardening release.

= 1.2.0 =
Major update — back up your database before upgrading and test on staging first.

== Privacy ==

ALTOS stores all of its data in your own WordPress installation:

* Rules and configuration are stored in the WordPress options table.
* Performance sampling data is stored in a plugin-owned custom table (`{prefix}sapm_sampling_data`) with 30-day retention.
* No personal data about visitors or administrators is collected.

The plugin does not transmit any data to external services and does not contact any external endpoints other than throttling WordPress's existing `api.wordpress.org` update checks when the (opt-in) Update Optimizer is enabled.
