=== left&right Dropbox Portal ===
Contributors: leftandright
Tags: dropbox, client area, documents, file manager, download
Requires at least: 6.2
Tested up to: 6.9
Requires PHP: 7.4
Stable tag: 1.2.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Private document area built on Dropbox: authorised WordPress users browse, search and download the shared files, with no public links.

== Description ==

left&right Dropbox Portal connects one or more Dropbox folders to WordPress and
serves them as a private area. Access is governed by WordPress roles: every rule
links a role to a Dropbox folder, and a user only sees the folders that belong
to them.

Main features:

* OAuth2 connection with refresh token, read-only on the Dropbox account
* WordPress role to Dropbox folder mapping, with several rules per role
* Folder browsing with breadcrumb, list view and grid view
* File name search limited to the assigned folder
* Image and PDF preview
* Streaming download through WordPress: the Dropbox URL never reaches the browser
* Thumbnails generated by Dropbox for images
* Configurable cache to reduce API calls
* Folder picker built into the admin page
* Frontend template overridable from the theme

== Free version, trial and Pro ==

There is a single package. After installation the plugin runs in the free
version; the Pro features stay visible in the admin but switched off, with a
"Pro" label next to them.

Free version:

* OAuth connection to the Dropbox account, read-only
* One role to folder rule
* Folder browsing with breadcrumb
* Protected streaming download through WordPress
* List view, light theme

Pro features:

* Unlimited role to folder rules
* File search
* Image and PDF preview, thumbnails generated by Dropbox
* Grid view
* View only: downloads can be switched off per rule
* Dark and auto theme, custom portal colour
* Configurable cache duration

Trial: from the "Dropbox Documents" page you can start a 7-day Pro trial, with
no credit card and no automatic renewal. When the trial ends the portal keeps
working in the free version: no document is lost and the Pro settings stay
stored, ready to switch back on with a licence. The trial can be used once per
site and does not restart if the plugin is reinstalled.

Licence: paste the key on the plugin page and press Activate. Validation runs
against the licence server set by the LRDBP_LICENSE_ENDPOINT constant or the
`lrdbp_license_endpoint` filter. If the server cannot be reached the licence
stays valid for a 14-day grace period, so a hosting network problem never
switches off a site that has already been paid for.

The limits are commercial only: no security check depends on the licence state.
Authentication, role, nonce and path ownership are verified exactly the same way
in the free version and in the Pro version.

== Security ==

The plugin does not generate Dropbox shared links. Every file request goes
through WordPress, which verifies authentication, role, nonce and that the path
belongs to the allowed folder. Absolute Dropbox paths are never exposed to the
browser: the frontend works with the rule identifier and a relative path.

Security decisions worth knowing:

* A full resolution preview is equivalent to a download: a "save as" is enough.
  For that reason, when a rule has downloads switched off, images are served as
  reduced versions generated by Dropbox and PDF preview is not offered.
* SVG and HTML files are never shown inline, because they can carry executable
  code in the site origin. They stay downloadable as attachments.
* Inline delivery is limited to JPEG, PNG, GIF, WebP, BMP, TIFF and PDF. Every
  other format is forced to attachment, even if another plugin registered its
  MIME type.
* A rule with no folder means the account root. It is rejected on save unless
  the dedicated option is explicitly enabled (only useful with an "App folder"
  Dropbox app).
* The page containing the shortcode declares DONOTCACHEPAGE and sends no-cache
  headers, because the output depends on the user. If you use a full page cache
  at proxy or CDN level, exclude the page there as well.
* File delivery goes through wp-admin/admin-post.php. If a hardening plugin
  blocks /wp-admin/ for non-administrative roles, add an exception for
  admin-post.php, otherwise browsing works but files do not open.

== Installation ==

1. Upload the `lr-dropbox-portal` folder to `/wp-content/plugins/`, or install
   the zip from Plugins > Add New > Upload plugin.
2. Activate the plugin.
3. Go to "Dropbox Documents" in the WordPress menu and follow the four steps on
   the page.

== Dropbox app setup ==

1. Open https://www.dropbox.com/developers/apps and create a "Scoped access" app.
2. Choose "Full Dropbox" (access to the whole account) or "App folder" (access
   limited to a dedicated folder).
3. In the Permissions tab enable: `account_info.read`, `files.metadata.read`,
   `files.content.read`. Save.
4. In the Settings tab paste the Redirect URI shown on the plugin page.
5. Copy App key and App secret into the plugin page, save, and press "Connect
   Dropbox account".

If you change the scopes after connecting the account, disconnect and reconnect:
existing tokens keep the permissions they were generated with.

== Shortcode ==

`[lr_dropbox_portal]`

Attributes:

* `root` - ID of the rule to show. If omitted it shows every folder the user can
  access.
* `view` - `list` (default) or `grid`. Grid is a Pro feature.
* `theme` - `light` (default), `dark` or `auto` (follows the visitor
  preferences). Dark and auto are Pro features.
* `title` - title shown in the coloured bar at the top of the portal.
* `subtitle` - line of text under the title.
* `height` - maximum height of the scrolling area, for example `600px`.

Example: `[lr_dropbox_portal view="grid" theme="auto" title="Project documents" subtitle="Updated from Dropbox" height="620px"]`

== Developer hooks ==

Filters:

* `lrdbp_oauth_scopes` - change the scopes requested from Dropbox.
* `lrdbp_user_roots` - alter the folders a user can access.
* `lrdbp_allow_file_access` - block access to a single file.
* `lrdbp_license_can` - alter the availability of a single Pro feature.
* `lrdbp_license_endpoint` - point to a different licence service.
* `lrdbp_pricing_url` - change the pricing page linked from the admin.

Actions:

* `lrdbp_file_accessed` - fired on every file access, useful for logging and
  audit trails.

Template: copy `templates/browser.php` to
`wp-content/themes/<theme>/lr-dropbox-portal/browser.php`.

== Frequently Asked Questions ==

= Do the files pass through my server? =
Yes. It is a deliberate choice: it is the only way to guarantee that a link does
not work outside the private area. Transfer happens in chunks, so PHP memory is
not saturated, but server bandwidth is used. The size limit is configurable in
the settings.

= Does switching downloads off really prevent getting the file? =
It reduces the surface a great deal but it is not absolute protection: anyone
who can see content on screen can photograph it. With downloads off the plugin
never delivers the original bytes (images are reduced, PDFs are not shown), so
the source file stays out of reach. Stricter requirements need DRM, which this
plugin does not implement.

= Why can I not see files I just uploaded to Dropbox? =
The default cache lasts 5 minutes. Use the Refresh button in the file browser,
or shorten the cache duration in the settings.

= Does it work with team folders? =
Team folders mounted in the connected account are visible, because reading uses
`include_mounted_folders`. Team spaces with a dedicated namespace need extra
configuration that is not part of this version.

== Installing on several client sites ==

Every client uses their own Dropbox account, so each one creates their own
Dropbox app and registers their own Redirect URI. The plugin generates it
automatically from the address of the site it is installed on and shows it on
the configuration page, with a copy button: there are no values to edit in the
code.

If a site answers on more than one address (with and without www, staging,
reverse proxy), the value can be pinned in wp-config.php:

`define( 'LRDBP_REDIRECT_URI', 'https://www.example.com/wp-admin/admin.php?page=lr-dropbox-portal&lrdbp_oauth=callback' );`

The `lrdbp_redirect_uri` filter allows computing it at runtime instead.

== Living with the site theme ==

The portal carries a reset that neutralises the global styles of the theme
inside its own container, so in most cases nothing needs to be done.

If the theme has a fixed header that stays above the content while scrolling, it
helps to give the portal its own scrolling area with the height attribute:

`[lr_dropbox_portal height="620px"]`

That way the search bar stays visible at the top of the portal and only the file
list scrolls, instead of ending up under the site header.

To change the look without modifying the plugin, the CSS variables are enough:

`.lrdbp-portal { --lrdbp-accent: #002b5c; --lrdbp-radius: 6px; }`

== Languages ==

The plugin ships in English, with an Italian translation in
`languages/lr-dropbox-portal-it_IT.po` and `.mo`.

== Changelog ==

= 1.2.0 =
* The plugin becomes freemium: one package for the free version and the paid
  one. Pro features stay visible in the admin but switched off, with a "Pro"
  label next to them, instead of disappearing.
* New 7-day Pro trial, no credit card, started from the plugin page. It can be
  used once per site and does not restart if the plugin is reinstalled.
* New Licence panel: key activation, status check, site deactivation and a link
  to the pricing page.
* The free version limits apply on read, not on write: anyone who configures
  several rules or enables search and previews before the trial ends finds
  everything in place when they activate a licence. No configuration is deleted.
* If the licence server does not answer, the licence stays valid for 14 days: a
  hosting network problem never switches off a site that has already been paid
  for.
* New filters: `lrdbp_license_can`, `lrdbp_license_endpoint`,
  `lrdbp_pricing_url`. New constant `LRDBP_LICENSE_ENDPOINT`.
* No security check depends on the licence state: the limits are commercial
  only.
* Interface, readme and setup guide translated to English, with an Italian
  translation shipped in the languages folder.

= 1.1.3 =
* The portal no longer inherits the styles of the site theme. Many themes apply
  global rules to button, input, a and table with !important: the result was
  oversized black buttons, text forced to uppercase and black borders on the
  table. There is now a high specificity reset inside the portal container that
  brings everything back to a known starting point.
* Fixed: an "x" appeared at the top right when the search was empty. The theme
  forced a display value on the global button rules and the hidden attribute was
  ignored; hidden is now enforced explicitly.
* Fixed: the table header used position sticky even without its own scrolling
  area, so it stuck to the window and ended up under the fixed theme header,
  covering the search bar. It now sticks only when the shortcode sets a height.
* In the grid view cards, the Preview and Download buttons wrap instead of
  overlapping when the card is narrow.

= 1.1.2 =
* Fixed a defect that made every call without arguments unusable (including
  reading the account data): cURL adds the "application/x-www-form-urlencoded"
  Content-Type header by itself to any POST that does not declare one, and
  Dropbox rejects it with HTTP 400. Content-Type is now declared explicitly on
  every call, including download and thumbnail, which were exposed to the same
  problem.
* A missing scope is now recognised even when Dropbox reports it as plain text
  (scope not enabled on the app) and not only as JSON (scope missing from the
  token). The message names the permission and explains that after enabling it
  the account has to be reconnected.
* Diagnostics classify a 400 by reading the message instead of the HTTP status
  alone: it distinguishes a missing scope, a rejected Content-Type and an
  invalid authorisation, which used to be attributed to the same cause.
* Step 1 reminds you that the Submit button in the Permissions tab has to be
  pressed, and that scopes enabled after connecting require reconnecting.

= 1.1.1 =
* Fixed: when Dropbox answered with a plain text message instead of JSON, the
  plugin discarded it and showed the generic "Communication error with Dropbox
  (HTTP 400)". That is exactly the format Dropbox uses to report an invalid
  authorisation header, which is the most likely cause of that message: the real
  reason is now reported.
* The access token is cleaned before use and never sent empty. On an
  authorisation error the request is retried once after regenerating the token
  from the refresh token, so a corrupted token repairs itself.
* Fixed: the page showed the green "Connected" badge just because a refresh
  token was stored, even when every API call failed. The state now reflects the
  result of a real call.
* New "Connection check" panel: it runs two real calls to Dropbox and reports
  HTTP status, raw response, presence and expiry of the tokens in masked form,
  with an indication of what to do.

= 1.1.0 =
* Fixed: connecting to Dropbox always failed with "Invalid redirect_uri". The
  authorisation URL was built with add_query_arg(), which inserts values without
  encoding them: the redirect URI broke at the first "&" and reached Dropbox
  truncated. OAuth URLs are now built with http_build_query.
* Fixed: the same class of problem in download and preview URLs, which would
  have broken with file names containing "&", "#" or "+". Also removed a double
  decoding that corrupted names with a literal "%".
* Added the LRDBP_REDIRECT_URI constant and the lrdbp_redirect_uri filter for
  environments with several hostnames.
* The configuration page reports known redirect URI problems in advance (site
  not on HTTPS, www prefix) and includes a diagnostics section showing the exact
  value sent to Dropbox.
* Redesigned frontend: coloured header, colour coded icons by file type,
  extension badge in the grid view, loading skeleton, illustrated empty states,
  light, dark and auto themes.
* Admin page redesigned as a guided procedure with a completion indicator,
  toggles for download permissions and a colour preview.
* New shortcode attributes: theme and subtitle.

= 1.0.0 =
* First release: OAuth2 connection, role mapping, file browser, search, image
  and PDF preview, protected streaming download.
