=== Flex Explorer ===
Contributors: flexatech
Tags: file manager, files, download, zip, trash
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 8.0
Stable tag: 1.5.0
License: GPL v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

A sandboxed file manager for WordPress: browse, upload, zip, download and recover your site's files from a fast admin app.

== Description ==

Flex Explorer adds a single-page file manager to wp-admin. Every operation runs
through a sandboxed REST surface that resolves paths against a configurable
root, refuses symlink escapes, and never lets a request reach outside the site.

**What it does**

* **Browse** in icon, list or macOS-style column views, with a folder tree, drag-and-drop move, multi-select, sorting, dotfile toggle and recursive filename search.
* **Upload** with drag-and-drop, enforcing the site's size cap and WordPress filename sanitisation.
* **Create, rename, copy, move and delete** files and folders.
* **Trash** catches deletions: items move to a recoverable store with restore, purge, and automatic cleanup after a retention period you set. Turn it off and deletions are permanent again.
* **Zip and download** a single file or a whole folder. Large selections run as background jobs with live progress, cancel, HTTP Range (resumable) downloads, and a single-use signed token per archive.
* **Extract** a zip in place or into a named folder.
* **Risky-operation gate** asks for confirmation before touching load-bearing files. A file is flagged only when it matches a "Risky file patterns" glob *and* sits inside a "Risky folders" entry, so a stray `*.php` under `uploads/` stays quiet.
* **Preview** images inline; anything else downloads.
* **Per-user interface language** and a light / dark / cream theme, independent of the rest of wp-admin.
* **WP-CLI**: `wp flex-explorer reset` clears the plugin's options and tables.

**There is no file editor in this build**

WordPress.org does not allow a plugin in the directory to edit site files, so
this build ships no editor: the Edit action explains that and points you at the
build that has one. That build is free as well, and it is downloaded from
[flexacommerce.com/products/flex-explorer](https://flexacommerce.com/products/flex-explorer).
Settings, trash contents and background jobs carry over, because both builds use
the same slug, options and tables. Installing one over the other keeps your
configuration.

**Security model**

Access is limited to administrators (`manage_options`, filterable via
`flex_explorer/capabilities/manage`). Mutating calls require a nonce. A site
defining `DISALLOW_FILE_EDIT` switches the plugin off entirely, and on multisite
only network super admins pass, because the sandbox root is the whole network's
filesystem. `wp-config.php`, `.htpasswd`, `.htaccess` and the WordPress core
files at the site root can never be deleted, renamed or moved by anyone, with no
bypass: removing any of them would take the site down.

== External services ==

Your files never leave your server. Flex Explorer reads and writes the
filesystem locally, and the file manager itself makes no outbound calls. The
plugin connects to one external service, only in the admin, for the reason
below.

= Deactivation feedback (Flexa Product Intelligence) =

When you go to deactivate Flex Explorer on the Plugins screen, a short optional
survey asks why. This is served by Flexa's product intelligence service at
https://product-intelligence.flexacommerce.com. It runs only on
`wp-admin/plugins.php`, never on the front end, and never blocks deactivation:
if the service is unreachable, the normal Deactivate link still works.

What is sent, and when:

* On opening the Plugins screen: a request to `/api/v1/config` (product slug and tier) to load the survey configuration. Cached for 6 hours.
* When you deactivate or interact with the survey: the reason you pick and any optional message you type, sent to `/api/v1/deactivations`, `/api/v1/events`, `/api/v1/feedback`, `/api/v1/feature-requests` and `/api/v1/recovery-events`.

Every request includes an anonymous per-site identifier (a random UUID), the
plugin version, and by default your WordPress version, PHP version
and locale. No email, site domain, user identity, file name, file content or
raw IP is collected.

Turn the environment details off with:
`add_filter( 'flex_explorer/deactivation_survey/config', fn( $c ) => array( 'collect_environment' => false ) + $c );`

Disable the survey entirely with:
`add_filter( 'flex_explorer/deactivation_survey/enabled', '__return_false' );`

Service terms and privacy policy: https://flexacommerce.com/pages/terms and https://flexacommerce.com/pages/privacy

== Installation ==

1. Upload the plugin files to `/wp-content/plugins/flex-explorer`, or install through the WordPress **Plugins** screen.
2. Activate the plugin through the **Plugins** screen.
3. Open **Flex Explorer** from the admin menu.

== Frequently Asked Questions ==

= Who can use Flex Explorer? =

Only users with the `manage_options` capability, which in practice means
administrators. The gating capability is filterable via
`flex_explorer/capabilities/manage`.

= Does it work on multisite? =

Yes, with access restricted to network super admins. `manage_options` is a
per-site capability, but the sandbox root is the whole network's filesystem, so a
subsite administrator would otherwise reach every other site's files. A network
that does want per-site access can opt in with the
`flex_explorer/capabilities/require_super_admin` filter.

= Why is there no Flex Explorer menu entry? =

The most likely reason is that your site defines `DISALLOW_FILE_EDIT`, which
switches the plugin off completely: it is a much bigger lever than the core file
editor that constant disables. The Plugins screen shows a note when this is the
case. To keep the constant and still use the file manager, filter
`flex_explorer/capabilities/enabled` to true.

= Where did the file editor go? =

Plugins distributed through the WordPress.org directory are not allowed to edit
site files, so this build has no editor and no write-content route at all: only
"new empty file" remains. Choosing **Edit** opens a dialog with a link to the
full build at
[flexacommerce.com/products/flex-explorer](https://flexacommerce.com/products/flex-explorer),
which is free and keeps the editor. Both builds share the same slug, settings
and tables, so switching between them loses nothing.

= Can files like wp-config.php be deleted? =

No, and that is not configurable. `wp-config.php`, `.htpasswd`, `.htaccess` and
the WordPress core files at the site root (`wp-settings.php`, `wp-load.php`, the
root `index.php`, `wp-login.php`, `xmlrpc.php`, `wp-cron.php` and friends) are
refused for delete, trash, rename and move, for everyone. The core-file guard
matches only the copy at the site root, so a plugin's own `index.php` in a
subfolder stays yours to manage.

= What does "risky operation" mean? =

Deleting, renaming, moving or overwriting an existing load-bearing file shows an
extra confirmation. You control exactly what counts, with two lists under
**Settings → Risky operations**: "Risky file patterns" (e.g. `*.php`,
`.htaccess`) and "Risky folders" (the tokens `@wp-core`, `@active-theme`,
`@active-plugins`, `@mu-plugins`, `@self`, or literal paths). A file is flagged
only when it matches a pattern AND lives inside one of the folders.

= Can I change the root folder? =

Yes, within your WordPress install. The sandbox root defaults to `ABSPATH`, and
the `root_path` setting can narrow it to any folder inside the install, for
example `wp-content` to keep the file manager away from everything else. It
cannot widen past the install: a stored path outside it is refused, because a
setting that reaches the rest of the server is not something a form field should
be able to do. A site that genuinely needs a root elsewhere sets it in code,
with the `flex_explorer/sandbox/root` filter.

= Where are deleted files and background archives kept? =

Both live in a private folder that the plugin creates inside your uploads
directory. The folder name includes 128 random bits generated once per site, and
trashed items are stored without their original file extension, so there is no
URL to guess and nothing in there can be executed. The plugin also writes
`.htaccess` and `web.config` rules that deny direct access, which Apache and IIS
honour; nginx ignores them, which is why the name is randomised rather than
fixed, and why **Settings → Trash** warns you when it detects such a server.

To take HTTP out of the picture altogether, define `FLEX_EXPLORER_STORAGE_DIR`
in `wp-config.php` and point it at a writable folder outside your web root.
Set it before you start using the trash: moving it later leaves the existing
items behind at the old location.

= Is large-file download safe on shared hosting? =

Yes. Big zips run as background jobs that write into the private folder
described above, poll for cancellation, and serve the finished archive over a
sha256 token with HTTP Range support, so an interrupted download can resume.
Finished archives are deleted after 24 hours.

= Does anything get sent off my site? =

Only the optional deactivation survey, documented under "External services"
above, and only when you go to deactivate the plugin. File names and file
contents are never sent anywhere.

== Screenshots ==

1. List view: the folder tree, the breadcrumb, the toolbar and the right-click menu.
2. Column view, macOS style, with details of the selected file in the last pane.
3. Background zip jobs: one archive still queued and cancellable, one finished and ready to download.
4. Trash: each deleted file keeps its original location, so Restore puts it back where it came from.
5. Settings: the confirmation gate for risky operations, plus the patterns and folders that trigger it.

== Changelog ==

= 1.5.0 =
* Security: the trash and the background-zip archives moved out of their old, guessable locations under `uploads/` into a private folder whose name carries 128 random bits generated once per site. Existing trash is moved across automatically on upgrade.
* Security: trashed items are now stored without their original file extension, so a deleted `.php` file cannot be executed by a host that runs PHP inside the uploads directory.
* Security: the access rules written next to that folder now cover Apache 2.2, Apache 2.4 and IIS. The previous `.htaccess` used 2.2 syntax only, which on a 2.4 server without `mod_access_compat` produced a 500 instead of a denial.
* Security: define `FLEX_EXPLORER_STORAGE_DIR` to keep that folder outside your web root entirely. **Settings → Trash** now warns when the server is one that ignores `.htaccess`.
* Security: the `root_path` setting may now only narrow the sandbox root to a folder inside your WordPress install. A stored path outside it is refused, and an existing one is ignored at runtime. Widening the root is still possible in code, with the `flex_explorer/sandbox/root` filter.
* Change: new installs keep trashed items for 30 days instead of forever. Sites already running the plugin keep whatever they have set.
* Fix: the archive folder is now hidden from the file manager itself, like the trash folder already was, so one administrator can no longer browse to another's finished archive.

= 1.4.0 =
* First WordPress.org release. Same plugin as the direct download, minus the file editor, which the directory guidelines do not allow. The Edit action links to the free full build instead.
* New: an optional deactivation survey. If you deactivate Flex Explorer, a short form asks why, so the next release fixes what actually went wrong. Answering is optional, the form never blocks deactivation, and what it sends is documented under "External services", including the filters to switch it off.
* New: "File manager" and "Get the full version" links on the plugin's row on the Plugins screen.

= 1.3.0 =
* Security: on a network, access is limited to network super admins (filterable via `flex_explorer/capabilities/require_super_admin`), because `manage_options` is per-site while the sandbox root is the whole network's filesystem.
* Security: a site defining `DISALLOW_FILE_EDIT` disables the plugin entirely (no menu, no REST, no admin page), with a note on the Plugins screen explaining why and a `flex_explorer/capabilities/enabled` filter to override it.
* Fix: bundled translations now load. WordPress only looks for plugin translations in its own languages directory unless the plugin registers its path; the plugin now registers `i18n/languages` on `init`.
* Fix: column view panes scroll vertically again when a folder has more rows than fit.
* Change: the minimum PHP version is now 8.0, down from 8.2.
* Harden: `$_SERVER` reads are sanitized and the dev-only scripts refuse to run outside WP-CLI.

= 1.2.0 =
* New: trash. Deleting moves items to a recoverable store with restore, purge and automatic cleanup; turning the setting off restores immediate deletion.

= 1.1.0 =
* New: configurable risky-operation confirmation gate. A file is flagged only when it both matches a "Risky file patterns" glob and sits inside a "Risky folders" entry. Folders accept the dynamic tokens `@wp-core`, `@active-theme`, `@active-plugins`, `@mu-plugins`, `@self`, or literal paths. New filter: `flex_explorer/risk/folders`.
* Improve: the Settings dialog body scrolls on short screens, so content is never clipped.
* Improve: a consistent thin scrollbar rail across the column view and other scrolling panes.
* Fix: removed the WordPress admin focus halo from the plugin's checkboxes and radios.

= 1.0.0 =
* React 18 admin single-page app (Vite 6, Tailwind v4) with TanStack Query and Zustand.
* Sandboxed REST surface (`flex-explorer/v1`) for browse, preview, upload, create, delete, rename, copy, move, zip, unzip and search.
* Icon, list and macOS-style column views with drag-and-drop move, multi-select and per-folder search.
* 3-tier zip pipeline (buffered, streamed, background job) with HTTP Range download support and a single-use sha256 token gate.
* Confirmation prompt before destructive operations on load-bearing files.
* Per-user locale override and an OS-aware light/dark theme toggle.
* WP-CLI: `wp flex-explorer reset`.

= 0.2.3 =
* Resolve the content directory through `wp_upload_dir()` instead of the `WP_CONTENT_DIR` constant, so the browser root follows custom content and uploads locations.
* Restore the original `zlib.output_compression` setting after a download streams, keeping the change confined to that one request.

= 0.2.2 =
* New: search filenames in the current folder and all subfolders (results are bounded); matching folds multibyte characters where available.

= 0.2.1 =
* Lowered the ZIP size limit to 50 MB so archives stay within typical shared-hosting execution limits.

= 0.2.0 =
* New: download an individual file (sent as an attachment, never rendered inline).
* New: download the current folder as a ZIP, with file-count and total-size limits, skipping symlinks and blocked files.
* Downloads flush pending output buffers and disable on-the-fly compression before streaming, so binary files and archives are never corrupted or truncated.

= 0.1.1 =
* Security: on multisite, restrict access to network super admins instead of every per-site administrator.
* Security: disable the plugin (including its admin menu) when `DISALLOW_FILE_EDIT` is defined as true.

= 0.1.0 =
* Initial release: read-only browsing of wp-content with inline text and image preview.

== Upgrade Notice ==

= 1.5.0 =
Security release. Deleted files and background archives move to a private folder with a randomised name, and trashed files lose their original extension so nothing in there can run. Your existing trash is moved for you. Two things change behaviour: `root_path` can no longer point outside your WordPress install, and new installs purge trash after 30 days.

= 1.4.0 =
First release in the WordPress.org directory. Adds an optional "why are you leaving?" survey on deactivation; nothing is sent unless you deactivate, and a filter turns it off entirely.
