=== Customer Cloud Gallery – Google Drive & Dropbox Image Gallery ===
Contributors: smartsteve, freemius
Tags: gallery, photography, google-drive, dropbox, photo-gallery
Requires at least: 6.2
Tested up to: 7.0
Stable tag: 1.13.0
Requires PHP: 7.4
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Connect Dropbox or Google Drive and create beautiful image galleries — fast downloads, favorites, password protection. Sell prints with Pro.

== Description ==

**Tired of subscribing to yet another photo service just to share galleries with your clients?** Customer Cloud Gallery lets you deliver beautiful client photo galleries straight from your own Google Drive or Dropbox — on your own WordPress site, with no extra monthly fees.

Connect the Drive or Dropbox folder you already use, paste a shortcode, and you get a fast, mobile-friendly gallery with downloads, favorites and optional password protection. Your originals stay in the cloud; the plugin streams thumbnails and full-resolution files on demand — so your site stays fast, your hosting disk stays empty, and there are no monthly gallery-hosting fees and no per-gallery limits.

= Who it's for =

* **Photographers** delivering client work — wedding, portrait, family, event, newborn, real-estate and product photography
* **Studios & agencies** that want galleries on their *own* domain and brand
* **Schools, clubs & teams** sharing event photos with parents and members
* **Anyone** who already keeps photos in Google Drive or Dropbox

= What you get (Free) =

* **Google Drive *and* Dropbox** — connect one or both; pick the folder per gallery
* **Fast lazy-loading grid** with full-screen lightbox, keyboard navigation and mobile swipe
* **Client favorites** — visitors heart their picks and filter to just those
* **Downloads done right** — single originals, a whole-gallery ZIP, or a visitor's hand-picked selection as one streaming ZIP (multi-GB galleries work)
* **Optional password protection** for client-only delivery — or keep it public
* **GDPR-ready cookie control** — master on/off switch per gallery; visitors can clear their cookie
* **Design options** — accent, hover and border colours
* **Works with any theme** — self-contained shortcode, no template editing

Your files never touch our servers — Drive/Dropbox are accessed with *your own* OAuth credentials (see "External Services" below).

= Earn money with Pro =

**Don't just save on fees — earn real money from your galleries.** The Pro print shop lets your clients order professional prints right inside their gallery: you set the formats, paper types, prices and shipping, and orders arrive by e-mail with payment via PayPal or SEPA bank transfer. No marketplace commission, no card-processing setup.

Pro also adds:

* **Print shop** — clients order prints directly from each gallery, with full order management (New → Confirmed → Shipped)
* **Visitor statistics dashboard** — page views, most-loved photos and top downloads, per gallery and per visitor
* **CSV export** of client favourite selections — ideal for prepping an album or print run
* **Priority support**

The Free plugin above is complete and fully functional on its own — Pro simply adds the print shop, statistics and exports.

= Get the most out of it =

1. Keep one Drive/Dropbox folder per client or event — the gallery mirrors it.
2. Create a gallery, password-protect the page, and send your client the link.
3. Switch on the Pro print shop to take print orders automatically.

= Why cloud storage? =

Uploading full-resolution galleries to your web server is slow, wastes disk quota, and makes migration painful. With Customer Cloud Gallery the originals stay in Drive or Dropbox — the plugin fetches thumbnails on first request, caches them locally, and streams originals on demand. Server load is minimal even for 4K RAW previews.

= Security =

* OAuth tokens are stored AES-256-CBC encrypted using WordPress's AUTH_KEY
* Per-gallery nonces prevent cross-gallery file access (IDOR protection)
* All file-serving endpoints verify that the requested file actually belongs to the requested gallery
* Download nonces expire after 24 hours
* Cache directory blocks PHP execution and directory listing via `.htaccess`
* Visitor cookies are `httponly` + `SameSite=Lax`

== External Services ==

This plugin connects to third-party services to deliver its core functionality. The connections are listed here so you can include them in your site's privacy policy.

= Google Drive API =

Used only when the site administrator connects a Google Drive account in the plugin settings. The plugin uses OAuth 2.0 credentials that you generate yourself in your own Google Cloud Console — your data never passes through any server operated by the plugin author.

What is sent: OAuth tokens, folder IDs and file IDs that you choose to display. No visitor data is transmitted.
When: each time an admin browses folders in the plugin, and on a scheduled cron to refresh thumbnail caches.

* Service URL: https://www.googleapis.com/drive/v3/
* Terms of Service: https://policies.google.com/terms
* Privacy Policy: https://policies.google.com/privacy

= Dropbox API =

Used only when the site administrator connects a Dropbox account in the plugin settings. Uses an OAuth app you register in your own Dropbox developer account.

What is sent: OAuth tokens, folder paths and file IDs that you choose to display. No visitor data is transmitted.
When: each time an admin browses folders, and on a scheduled cron to refresh thumbnail caches.

* Service URL: https://api.dropboxapi.com/
* Terms of Service: https://www.dropbox.com/terms
* Privacy Policy: https://www.dropbox.com/privacy

= Freemius =

The plugin uses the Freemius SDK to deliver license management for the optional Pro upgrade. Freemius is contacted only after the site administrator explicitly opts in during plugin activation. If you skip the opt-in, no data is sent to Freemius and the Free version remains fully functional.

What is sent (only after opt-in): site URL, WordPress version, PHP version, plugin version, an anonymized site identifier and — if a Pro license is activated — the license key for validation.

* Service URL: https://api.freemius.com/
* Terms of Service: https://freemius.com/terms/
* Privacy Policy: https://freemius.com/privacy/

== Installation ==

1. Upload the `customer-cloud-gallery` folder to `/wp-content/plugins/` or install via **Plugins → Add New → Upload Plugin**.
2. Activate the plugin.
3. Go to **Customer Cloud Gallery → Settings** and connect Google Drive or Dropbox (step-by-step OAuth wizard included).
4. Create your first gallery under **Customer Cloud Gallery → Add New**.
5. Copy the shortcode from the gallery editor (e.g. `[ccgal_gallery id="42"]`) and paste it into any page.
6. Set a page password under **Page → Visibility → Password protected** to restrict access.

= Minimum Requirements =

* WordPress 6.2 or higher
* PHP 7.4 or higher (8.1+ recommended)
* PHP extensions: `openssl`, `gd` or `imagick`
* Google Drive API credentials **or** a Dropbox app (free developer accounts work)

== Frequently Asked Questions ==

= Do my photos get uploaded to your servers? =

No. Photos stay in your Google Drive or Dropbox account. The plugin only fetches thumbnails (cached locally for performance) and streams originals on demand. No image data is sent to any third-party or plugin-developer server.

= Do I need a Google account or a Dropbox account? =

Yes — at least one. You create a free OAuth app in Google Cloud Console or Dropbox App Console (both free) and paste the credentials into the plugin settings. A step-by-step guide is included.

= Can I use both Google Drive and Dropbox at the same time? =

Yes. Connect both and choose the storage provider per gallery.

= Does it work with any WordPress theme? =

Yes. The gallery is rendered in a self-contained shortcode block and does not depend on theme templates or scripts.

= What happens if a visitor's session lasts more than 24 hours? =

Download nonces expire after 24 hours. The visitor needs to reload the page to get a fresh nonce — the gallery itself remains accessible as long as the page session (password cookie) is valid.

= Can I import existing Drive or Dropbox folders? =

Yes — just paste the folder URL or folder ID when creating a gallery. The plugin reads the folder contents via the cloud API.

= Does the print-order shop handle payment processing? =

No direct card processing. Orders are sent by e-mail; payment is collected via PayPal.me link or SEPA bank transfer details that you configure. This keeps the plugin PCI-DSS-free.

= Is this plugin GDPR compliant? =

The Free plugin stores only a random visitor cookie ID, used to remember a visitor's favourites between page loads. No e-mail address, no IP address in plain text, no third-party transmission. A cookie-control switch per gallery lets visitors opt out entirely. See the **Privacy Policy** section below for details.

== Screenshots ==

1. Frontend gallery grid — clients browse their photos with hover controls for favorites and download
2. Lightbox view — full-screen photo with keyboard navigation, mobile swipe and one-click favoriting
3. Print-order shop — clients pick formats and paper, place orders directly from the gallery (Pro)
4. Gallery editor — pick a Google Drive or Dropbox folder, set password, get the shortcode
5. Visitor statistics dashboard — page views, top favorites, top downloads per gallery (Pro)
6. Print settings — configure formats, paper types, prices, shipping and payment options (Pro)

== Changelog ==

= 1.13.0 =
* Security hardening: unpublished galleries (draft, private, pending, trashed) can no longer leak original files. The publish-status guard now covers the single-file, ZIP and manifest download endpoints as well as the batch, favorite, order and print-format REST routes. Logged-in editors with access to the gallery are unaffected.
* Performance: per-gallery statistics are now memoized per request, cutting redundant database queries on the gallery list and detail pages; the cloud folder listing is cached in a short-lived transient so concurrent visitors and infinite-scroll requests collapse to a single Drive/Dropbox API call per page instead of one call per view.
* Dropbox galleries now honour the gallery's sort order and "images per page" setting: the Dropbox folder listing is sorted (natural name, name, or date — ascending/descending) and paginated consistently, so the grid order and the ZIP order match. This brings Dropbox in line with the existing Google Drive behaviour.
* The bundled German (de_DE) translation loads again on self-hosted and Pro installations (the textdomain loader was restored on the init hook). Sites served a translation through WordPress.org language packs are unaffected; the language pack still takes precedence when present.
* Fixes: Dropbox statistics detail-page thumbnails and full-size links now resolve correctly; the statistics fetch limit was raised from 30 to 50 with an honest item-count badge.
* Pro statistics detail page UX refactor: the per-gallery statistics page is now collapsible and scales cleanly to galleries with thousands of images. Each of the four top-lists (Top Favorites, Top Viewed, Top Downloaded — single, Top Downloaded — selection) is wrapped in a native HTML5 `<details>` element with the item count visible in the header even when collapsed. Each section has a per-section limit dropdown (5/10/25/50/All, default 10). The visitor activity table now uses true pagination (25 per page default) so that galleries with hundreds of visitors no longer produce a 10,000-pixel-long scroll page.
* Pro inline toggles in the gallery list (WP-Admin → Customer Cloud Gallery → All galleries): two new checkbox columns let you flip a gallery's Statistics-tracking and Print-shop on or off without entering the gallery editor. Each checkbox saves via its own per-gallery-scoped AJAX endpoint with nonce + capability check; optimistic UI feedback, server-authoritative state correction, screen-reader friendly (each checkbox has an aria-label naming the gallery; the disabled-reason for Stats when cookies are off is reachable by keyboard). The Stats toggle correctly mirrors the existing master-cookies dependency — when cookies are off for a gallery, the Stats sub-switch is locked off too.
* Pro stats "unique_visitors" consistency fix carried over from the 1.12.2 hotfix: the top-card counter now counts distinct cookies across all activity tables (views ∪ favorites ∪ downloads), matching the "Besucheraktivität" table below.
* Pro stats deletion fix carried over from the 1.12.2 hotfix: "Reset tracking data" and per-visitor "Daten löschen" now also clear favorites in addition to views and downloads.

= 1.12.2 =
* wp.org Plugin Directory review compliance (round 3):
* cURL → WordPress HTTP API: the four remaining cURL call sites in `includes/class-dropbox-adapter.php` (`download_to_file`, `stream_to_output`, `download_thumbnail`) and `includes/class-google-drive-adapter.php` (`stream_to_output`) have been fully ported to `wp_remote_request()` / `wp_remote_get()` with `stream => true`. File downloads write directly to disk via the WordPress HTTP API (no PHP memory buffering); the streaming-to-browser cases stream the response body to a WordPress temp file via `wp_tempnam()` and then echo it back in 64 KB chunks, with HTTP 206 Range support preserved by forwarding the `Range` header. The Dropbox content-type issue is solved by sending `Content-Type: application/octet-stream`, which Dropbox accepts on `/files/download` and `/files/get_thumbnail_v2`. The plugin no longer contains a single `curl_*()` or `CURLOPT_*` reference in its own code; only the Freemius licensing SDK in `vendor/` still uses cURL, which the Plugin Review Team's guidelines explicitly permit for third-party vendor libraries.
* Plugin Assets handbook compliance: verified that no `banner-*.{png,jpg}`, `icon-*.{png,jpg,svg}`, `screenshot-*.{png,jpg}`, `blueprints/` or `header.png` files exist in the plugin ZIP, and that all plugin-resource references use the dynamic functions `plugins_url()` / `plugin_dir_path()` / `plugin_dir_url()` (via the `CCGAL_URL` / `CCGAL_DIR` constants) rather than hardcoded `/wp-content/plugins/...` paths. Listing assets (banner, icon, screenshots) are prepared in the source repository under `.wordpress-org/` for deployment to the SVN `/assets/` root after approval — they are deliberately excluded from the plugin ZIP per the Plugin Assets handbook.
* Video files in cloud folders are temporarily excluded from gallery listings (image files only). The in-browser video player and the related streaming endpoint are not exercised by visitors in 1.12.2. Video support will be re-introduced in a later release; existing video files in connected Drive/Dropbox folders are not touched, only hidden from the gallery output.
* Pro statistics tracking improvements:
  - The JS-level `state.viewedInLightbox` Set in `assets/js/gallery.js` is now cleared when the lightbox closes, so a visitor who closes the lightbox and reopens the same image is counted as a new engagement (the server-side 10-minute dedup window still applies on top of this — see below).
  - `$wpdb->insert()` calls in `record_view`, `record_image_view` and `record_download` now log to `error_log()` if the insert fails (gated on `WP_DEBUG`), so future silent-skip failures are observable instead of being swallowed by a 200 OK response.
  - Server-side dedup windows are unchanged: 1 hour per (gallery, visitor) for page-loads, 10 minutes per (gallery, file, visitor) for lightbox image-opens. These are appropriate for both client-delivery galleries and public-page galleries (suppresses bot crawl + reload-spam without losing legitimate re-engagement).
* Pro stats deletion fix (`includes/class-dashboard__premium_only.php`): the "Reset tracking data" button on the gallery detail page AND the per-visitor "Daten löschen" button now correctly delete favorites in addition to views and downloads. Previously the favorites table was untouched, so the UI promise ("Deletes all favorites, downloads and views") was not honoured and the per-visitor delete looked like a no-op when the visitor had only favorited images and no other activity.
* Pro stats "Eindeutige Besucher" consistency fix (`includes/class-tracking__premium_only.php::get_gallery_stats`): the top-card counter now counts distinct cookies across ALL activity tables (views ∪ favorites ∪ downloads), not just page-load views. Previously the counter only saw visitors who had a `file_id IS NULL` row, so visitors whose page-load was suppressed by the 1-hour dedup window or who arrived via a full-page cache (no PHP execution) — and only triggered AJAX-based image-views or downloads — were invisible to the top counter even though they correctly appeared in the "Besucheraktivität" table below. Both numbers now agree.

= 1.12.1 =
* wp.org Plugin Directory review compliance (round 2):
* Statistics module fully extracted from the Free build. The tracking class, stats tables, the stats sub-switch UI, the views/downloads admin columns, the view/image-view REST endpoints and all download-recording call sites have been removed from the Free codebase and consolidated into a single Pro-only file (`class-tracking__premium_only.php`). Free has zero stats footprint on disk; Pro hooks into the Free build via generic action/filter names (e.g. `ccgal_download_completed`, `ccgal_gallery_deleted`, `ccgal_uninstall_tables`) so the Free code stays decoupled.
* Frontend JS Stats-Reste removed: the previous `recordView()`/`recordImageView()` functions and the `trackBulkComplete()` beacon are gone. The Free `gallery.js` now only dispatches generic `ccgal:gallery-opened` / `ccgal:image-opened` CustomEvents; the bulk-download helper calls a renamed `notifyBulkCompleted` ping to a `ccgal_notify_bulk_complete` AJAX endpoint. None of those reach a database table in Free.
* Prefix refactor: every declared name (constants, classes, functions, hook names, option keys, post-meta keys, table names, AJAX/admin-post action slugs, CSS classes, custom properties, JS globals, script/style handles, REST namespace, custom HTTP header, cache directory and the gallery post-type slug) has been renamed to the 5-character `ccgal_`/`CCGAL_`/`ccgal-` prefix to comply with the wp.org "prefix must be over 4 characters" rule. The browser visitor cookie keeps its legacy name `wpg_visitor` so existing visitor favourites remain attached after the migration — the only intentional exception.
* Database migration: an idempotent activation-time routine renames all WP options, post-meta keys, the gallery post type, the custom tables (favorites, orders, order_items + Pro: views, downloads), the leading-underscore `_system_page` meta marker, the daily cron event and the thumbnail cache directory. The routine excludes its own version-marker option from the bulk rename (so the guard can still be read on a re-activation if anything below crashes), extends `set_time_limit(300)` for large postmeta tables, and writes a transient admin notice if a `RENAME TABLE` fails (e.g. missing ALTER permission on shared hosting). Shortcodes in existing post_content are rewritten in place (`[ccg_gallery]`, `[hochzeitsgalerie]`, `[customer_cloud_gallery]` → `[ccgal_gallery]`; same for the print variant). The migration guards against re-runs via the legacy/new db-version markers.
* Security: the public AJAX endpoints `ccgal_serve_image` and `ccgal_stream_video` now require a gallery-scoped nonce (`ccgal_gallery_<id>`) issued by the gallery renderer, in addition to the existing file-in-folder check. Draft / private / trashed galleries are rejected on the public branch unless the caller has `edit_post` capability. `$_SERVER['HTTP_RANGE']` is now unslashed and length-capped before the strict `preg_match` validator. `$_COOKIE['wpg_visitor']` is now sanitized through a hex-only whitelist matching the generator. `$_REQUEST['gallery']`, `$_REQUEST['count']` and the array-branch of `$_REQUEST['files']` are now consistently `wp_unslash()`-ed before further sanitization. `json_decode()` of the `_REQUEST['files']` payload runs with `depth=4` so a JSON bomb cannot DoS the file-IDs parser.
* The `Plugin URI` header has been removed from the main plugin file because the SSL certificate for the linked domain is not yet provisioned. The Author URI is unaffected.

= 1.12.0 =
* Faster bulk-download for the "Download all images" button. The button now picks the best path automatically: (1) in browsers with the File System Access API (Chrome/Edge/Opera) the visitor picks a target folder and the photos are written there directly, fetched in parallel (6 connections); (2) on Firefox/Safari/HTTPS sites a service-worker-backed streaming ZIP is built in the browser — no RAM limit, multi-GB galleries work without buffering; (3) small galleries can fall back to a single in-memory ZIP blob; (4) the existing server-streaming ZIP remains as the last-resort fallback. Across realistic gallery sizes this typically reduces wait time by 4–6× on the parallel paths because the server is no longer the single-threaded bottleneck. The hard listing limit per gallery raised from 5,000 to 25,000 files. New endpoint `ccgal_download_manifest` exposes the gallery's file list (JSON: id/name/size) under the same gallery-scoped nonce; a separate completion endpoint (renamed to `ccgal_notify_bulk_complete` in 1.12.1) fires a single `ccgal_download_completed` action when the client-side ZIP is finished, so extension modules can react without per-file inserts. New `assets/js/sw-zip.js` service worker is registered on demand from the gallery scope and only activated when the bulk-download button is used. Manifests are cached as 60-second transients per gallery to protect Drive/Dropbox API quota against repeated clicks. The legacy `ccgal_download_zip` endpoint is unchanged.
* Selection ZIP download moved from Pro back to Free — visitors of any gallery can now pick specific images and download them as a single ZIP without a Pro licence.
* Code structure: Pro-only PHP files renamed with the `__premium_only.php` suffix so the Free build no longer ships any Pro implementation code. Inline `<script>` blocks moved out of PHP into proper enqueued asset files for better caching and CSP compatibility.
* Shortcode refactor for wp.org review compliance: the deprecated `customer_cloud_gallery` and `customer_cloud_gallery_print` shortcodes have been removed. The new prefixed shortcodes `[ccgal_gallery]` and `[ccgal_print]` are now the only supported names. Existing pages are automatically migrated on plugin update — the post content is rewritten in place once, so no user action is required.

= 1.11.8 =
* Phases 1 + 2 of Plugin Check sweep for wp.org compliance: replaced `unlink()` with `wp_delete_file()` in cache, adapters and uninstaller; switched DOS-timestamp generation in the ZIP streamer from `date()` to `gmdate()` to avoid runtime-timezone side effects; added missing `translators:` comments on plural and placeholder strings in dashboard and orders admin; reordered placeholder example text to use positional `%1$s/%2$s` syntax; rewrote `printf( esc_html__( "…#%d" ), $id )` patterns in email templates and admin notices as `echo esc_html( sprintf( __( "…" ), $id ) )` so the placeholder substitution is itself escaped.
* Phase 3 of the Plugin Check sweep: annotated three SQL false-positives where the static analyzer cannot follow `$wpdb->prepare()` results across an assignment (visitor-stats aggregation in `class-tracking.php` and the orders CSV-export query in `class-orders.php`). The CSV-export ternary was also split into a clean `if/else` so each branch can carry its own technical justification; behaviour is unchanged.
* Phase 4 (filesystem operations): annotated `php://output` streams in CSV export handlers (`class-orders.php`, `class-dashboard.php`) and best-effort cache hygiene calls (`@touch`, `@rename` in `class-cache.php`). The Dropbox adapter's paired `fopen`/`fclose` around cURL `CURLOPT_FILE` are documented inline since cURL requires a real PHP file pointer. Uninstaller `rmdir()` calls were already annotated in 1.11.6.
* Phase 5 (cURL → WP HTTP API): migrated `class-google-drive-adapter.php::get_media_download_url` to `wp_remote_head()` (HEAD with `redirection => 0` to read the Location header). All remaining cURL calls — Drive `stream_to_output`, Dropbox `download_to_file` / `stream_to_output` / `download_thumbnail` — are kept and annotated with technical reasons: WP HTTP API has no streaming-write callback (CURLOPT_WRITEFUNCTION) and Dropbox content endpoints require an empty Content-Type header that `wp_remote_post()` cannot send. Migrating these to WP HTTP API would have forced double I/O via temp files for every gallery download.
* Phase 6 (Plugin Check warnings): added file-level `phpcs:disable` blocks with technical justifications across `class-tracking.php`, `class-orders.php`, `class-print-settings.php`, `class-rest-api.php`, `class-admin.php`, `class-downloads.php`, `class-print-page.php`, `class-google-oauth.php`, `class-dropbox-oauth.php`. Direct database queries on plugin-owned custom tables (wp_ccgal_orders, wp_ccgal_order_items, wp_ccgal_views, wp_ccgal_favorites, wp_ccgal_downloads) are by design — they are not cached because they are written and read inside the same request lifecycle. Nonce-verification suppressions on read-only listing/sort/filter parameters are paired with concrete justifications. OAuth callbacks use the OAuth `state` parameter instead of WordPress nonces (a session-bound nonce cannot survive a redirect through Google/Dropbox). Outbound `wp_redirect()` calls (OAuth auth endpoint, signed Drive media URLs) are kept because `wp_safe_redirect()` would block external hosts. Added missing `wp_unslash()` calls on `$_COOKIE['ccgal_visitor']` and `$_SERVER['REMOTE_ADDR']` in `class-tracking.php` and `class-print-rest.php`.
* Code review follow-ups (small but real fixes surfaced while preparing the wp.org submission):
  - Visitor-IP hashing (`class-tracking.php::ip_hash`, `class-print-rest.php::get_visitor_id`) now uses `filter_var( …, FILTER_VALIDATE_IP )` instead of `sanitize_text_field` — `sanitize_text_field` strips `%xx` sequences and would mangle IPv6 zone-id syntax (e.g. `fe80::1%eth0`).
  - `class-google-drive-adapter.php::get_media_download_url` now handles the case where `wp_remote_retrieve_header()` returns an array of duplicate Location headers (rare but possible behind some CDN layers); previously the array would be cast to the literal string "Array" and the call would falsely return `ccgal_bad_location`.
  - REST media-cache redirects in `class-rest-api.php::serve_image` now use `wp_safe_redirect()` instead of `wp_redirect()` — the targets are same-origin cache URLs, so the safer redirect is appropriate.
* PayPal checkout improvements (surfaced during pre-submission smoke testing):
  - PayPal.Me URL now appends the configured currency code (e.g. `paypal.me/USERNAME/25.00EUR`). Previously the link omitted the currency, so PayPal would default to the receiver's account currency (often USD for new accounts), forcing customers to pay in the wrong currency.
  - Order-confirmation screen now shows a PayPal-specific memo hint instead of the generic SEPA "Verwendungszweck" string: "Important: PayPal does not transfer order numbers automatically. On the PayPal page, please enter #1234 in the note/memo field so we can match your payment to your order." PayPal.Me cannot pass an order reference programmatically, so the manual hint is the correct workaround for the free-tier shop.
  - The PayPal payment-method radio button label changed from "PayPal (paypal.me)" to simply "PayPal" since the field now also supports email-based PayPal Standard payments (not just paypal.me). Existing text-overrides for the old "PayPal (paypal.me)" string become orphaned and need to be re-set against the new "PayPal" msgid if a custom label was configured.
  - PayPal payment target field now accepts BOTH a PayPal.Me username AND a PayPal email address. The form auto-detects which mode applies on submit: an "@" in the value switches to PayPal Standard "Buy Now" (_xclick), which works with any PayPal account and additionally passes the order number to PayPal as the item description — the seller sees "Order #1234" directly in the PayPal payment record, no manual memo required. Without "@" the form continues to use PayPal.Me as before. The previous email warning has been replaced by a green "Email mode active" notice in Settings.
  - Removed the redundant "PayPal label in form" and "Bank-transfer label in form" custom-text settings. They duplicated functionality provided by the dedicated Text Overrides system (Settings → Text overrides) and could be misconfigured by accidentally entering an email address as the button label, which would then appear as the visible payment-method label in the checkout (e.g. "tes@er.com" instead of "PayPal"). The two payment buttons now always render with the standard, translatable defaults ("PayPal (paypal.me)" / "Bank transfer (advance payment)"); existing stored values are ignored, and the existing Text Overrides UI can still customize the labels via the i18n string-replacement path.
* Fix: text-override fields could not be cleared once a value had been entered. Previously the JS auto-filled the default text whenever the field became empty, which created an infinite loop — every Backspace immediately re-inserted the default. The placeholder attribute already shows the default visually, so the auto-fill is unnecessary and has been removed; clearing a field now simply clears the override on save.
* UX: Drive and Dropbox step-by-step setup guides (the collapsed accordions on the first-run welcome panel) are now also available directly in the Settings page provider sections, so a returning user who connects a second cloud later can follow the same guide without having to dig through the README. The two guides are extracted into reusable methods (`CCGAL_Welcome::render_drive_setup_guide()`, `render_dropbox_setup_guide()`) and emit byte-identical translatable strings.
* Otherwise, the 1.11.8 release contains only static-analysis / annotation changes required by wp.org's Plugin Check; runtime behaviour is unchanged for the common (IPv4, single-Location-header, same-origin-cache) path.

= 1.11.7 =
* Fix: first-run welcome redirect could fail with "Sorry, you are not allowed to access this page." on some hosts (especially staging environments) because the Settings submenu was not yet registered when the redirect was issued. The redirect now hooks on `admin_menu` priority 9999 and resolves the URL via `menu_page_url()`, with a graceful fallback to the gallery list page.
* Fix: Quick-Start panel no longer auto-expands the Drive/Dropbox setup guides — both accordions are now collapsed by default for a cleaner first impression. Open whichever provider you want to set up.
* Docs: Drive setup guide now explains Google's "This app isn't verified" warning that appears the first time you authorize — clarifies that this is expected for self-hosted OAuth apps and tells you which buttons to click ("Advanced" → "Go to [app] (unsafe)") to proceed.
* Docs: Drive setup guide now also instructs to click "Publish App" on the OAuth consent screen to move the app from Testing to In production — otherwise Google invalidates the refresh token after 7 days of inactivity and the gallery silently disconnects.

= 1.11.6 =
* New: GDPR / WP Privacy API integration — registered personal-data exporter and eraser hooks (Tools → Export/Erase Personal Data) for print-order data; suggested privacy policy paragraph contributed to Tools → Privacy editor.
* Compliance: print-order page is no longer auto-created on Free installs (the page would have rendered an unregistered shortcode). It is created on first Pro activation or on the first Pro page load after a Free→Pro upgrade, and uses the modern print shortcode.
* i18n: registered `wp_set_script_translations()` for the `ccgal-gallery`, `ccgal-admin` and `ccgal-print` scripts so JS strings can be translated through the standard language-pack system.
* Performance: large/admin-only options (`ccgal_print_formats`, `ccgal_paper_price_lists`, OAuth tokens, payment/email settings, etc.) are now stored with `autoload=false` to avoid loading them on every page request site-wide.
* Performance: daily cache-cleanup cron is now scheduled once at activation instead of being re-checked via `wp_next_scheduled()` on every page load.
* Plugin Check: `class-orders.php` order list query refactored to use `%i` placeholder for the column identifier and a strict literal toggle for ASC/DESC, with documented `phpcs:ignore` on the few remaining intentional interpolations.
* Plugin Check: `class-text-overrides.php` translate() call uses a literal text-domain and documented `phpcs:ignore` on the dynamic msgid lookup (the override mechanism is dynamic by design).
* Docs: corrected the Privacy Policy section in `readme.txt` — the visitor cookie value is a random identifier stored as-is, and it is the IP address that is stored as a salted SHA-256 hash (not the cookie ID).

= 1.11.5 =
* New: first-run welcome flow — after activation the plugin redirects once to its Settings page and shows a 2-step Quick-Start panel: (1) connect Google Drive or Dropbox, (2) create your first gallery. Auto-checks each step and disappears once both are done.
* New: inline step-by-step guides for obtaining Google Drive and Dropbox API credentials are embedded in the Quick-Start panel — including the exact OAuth redirect URI to paste into each provider's developer console.
* Hardening: replaced `printf( esc_html__( ... ), '<a ...>...' )` patterns with `wp_kses( sprintf( __( ... ), ... ), $allowed )` so format-string output is correctly escaped while link tags are explicitly whitelisted.
* Hardening: bulk-action redirects in the orders list now use `wp_safe_redirect()` instead of `wp_redirect()`.
* Hardening: order-status nonce now uses a fixed action name and is verified before reading `$_POST['order_id']`.
* Hardening: external OAuth redirect URLs (Google, Dropbox) wrapped in `esc_url_raw()` for defence-in-depth.
* Compliance: removed redundant `Author URI` from the plugin header (matched `Plugin URI`, which the WordPress.org review process disallows).

= 1.11.4 =
* Fix: gallery toolbar buttons (e.g. "Show only favorites") kept the active theme color after a click because some themes style `button:focus` globally — now reset via `:focus:not(:focus-visible)` so the base appearance is restored on mouse-click; keyboard focus styling unchanged
* Fix: same focus-leak resolved for lightbox close/prev/next and lightbox favorite/download buttons
* Compliance: removed `uninstall.php` and moved the cleanup logic into the Freemius `after_uninstall` hook (`CCGAL_Uninstaller::cleanup`) — required by Freemius deployment policy

= 1.11.3 =
* Fix: Statistics submenu was missing in Pro mode — gallery quick-stats detail link now opens the dashboard instead of showing a permission error
* Fix: Print-shop checkbox in the gallery editor showed "enabled" by default for new galleries while the actual setting was off — display now matches stored value
* Fix: Print options menu used different slugs in Free vs Pro — unified to `ccgal-print-options` so bookmarks survive Free→Pro upgrade
* Compat: additional shortcode aliases added for the gallery and print shortcodes so existing user pages keep working unchanged (these aliases were later removed in 1.12.0 with an automatic post-content migration)
* Cleanup: removed stray reference to a third-party AI vendor in the FAQ wording
* Cleanup: switched one stray `wp_redirect()` to `wp_safe_redirect()` for consistency
* Cleanup: registered the `ccgal_allow_print` post-meta with WordPress for proper authorization handling
* i18n: text-domain migrated from `wp-galerie` to `customer-cloud-gallery` (matches new wp.org plugin slug)

= 1.11.2 =
* (Note: the Pro-feature upgrade prompts, locked menus, and visible-but-disabled stats sub-switch introduced in 1.11.2 were all removed again in 1.12.0 and 1.12.1 to comply with the wp.org "fully functional" rule. The Free build no longer ships any Pro-only UI, locked menus or disabled controls.)
* Fix: button labels displayed in wrong case (Title Case) on themes using `text-transform: capitalize` — resolved with `text-transform: none` on `.ccgal-btn`
* Fix: default label for selection download shortened to "Order selection"
* i18n: 19 new strings; DE translation updated (542 translated strings)

= 1.11.1 =
* Statistics sub-menu moved to correct position in admin sidebar
* Selection ZIP download removed from Free tier
* Cookie consent notice updated for GDPR compliance

= 1.11.0 =
* Dropbox integration: full OAuth flow, folder browser, thumbnail streaming
* Storage adapter architecture: Google Drive and Dropbox behind a unified interface
* Design customisation: accent colour, hover colour, border colour
* Text overrides: rename every label and button text in the frontend
* Print shop: paper options with per-format price lists
* Print shop: configurable legal checkboxes (age confirmation, terms acceptance)
* Print shop: shipping cost with free-shipping threshold
* Print shop: order management with status tracking (New → Confirmed → Shipped)
* i18n groundwork: all strings translatable, DE translation included

= 1.0.0 =
* Initial release: Google Drive galleries, lazy-loading, lightbox, favourites, ZIP download, visitor statistics, e-mail print shop

== Upgrade Notice ==

= 1.12.2 =
All four remaining cURL call sites in the Dropbox and Google Drive adapters have been ported to the WordPress HTTP API. Note: video files in connected Drive/Dropbox folders are temporarily hidden from gallery output in this release; the files themselves are untouched and video support will return in a later release. Recommended for all installs. No DB changes.

= 1.12.1 =
A one-shot DB migration renames all plugin tables, options, post-meta keys and the gallery post-type from `wpg_` to `ccgal_`. Requires MySQL ALTER permission. **Back up your database before updating.** Failures surface as an admin notice.

= 1.11.6 =
GDPR / WP Privacy API integration, Plugin Check compliance fixes and several performance improvements (autoload=false on large options, cron scheduled at activation only). Recommended for all installs and required for a clean WordPress.org submission.

= 1.11.5 =
Adds a first-run welcome / Quick-Start panel with inline step-by-step guides for setting up Google Drive and Dropbox API credentials. Recommended for fresh installs.

= 1.11.4 =
Cosmetic fix for toolbar/lightbox buttons keeping a theme's focus color after click. Recommended.

= 1.11.3 =
Important bug fixes for Pro users: Statistics dashboard is now reachable, print-shop checkbox state matches stored value, and the menu slug is unified across Free and Pro. Update recommended.

= 1.11.2 =
Visual upgrade prompts for Pro features in the free version. Bug fixes for button label case and selection download label.

== Privacy Policy ==

Customer Cloud Gallery stores the following data in your WordPress database:

**Visitor cookie (all visitors)**
A random visitor ID is generated server-side on the first gallery visit and stored in a browser cookie. The cookie name is `wpg_visitor` (the legacy name is kept since 1.12.1 so existing visitor favourites remain attached to the correct cookie after the prefix-rename migration; everything else uses the new `ccgal_` prefix). The cookie is valid for 365 days. Its value is a random hexadecimal identifier with no relationship to any real-world identity, and is stored as-is in the database — there is no name, e-mail address, or plain-text IP address recorded against it.

**Favourites**
When a visitor hearts an image, the random visitor ID, the gallery ID, and the cloud file ID are stored in the `wp_ccgal_favorites` table. No personal data is included.

**Download tracking** *(Pro, if the statistics module is enabled per gallery)*
When the Pro statistics module is loaded and enabled per gallery, downloads are recorded in `wp_ccgal_downloads`: random visitor ID, gallery ID, file ID, download type, and a salted SHA-256 hash of the visitor's IP address. The original IP address is never persisted — only the one-way hash, salted with the site's `AUTH_SALT`. The Free plugin never writes to this table.

**Page-view tracking** *(Pro, if the statistics module is enabled per gallery)*
When the Pro statistics module is loaded and the per-gallery statistics sub-switch is on, page and image views are recorded in `wp_ccgal_views`: random visitor ID, gallery ID, file ID, salted IP-hash and timestamp. The Free plugin never writes to this table.

**Print orders** *(Pro, if the print shop is enabled)*
Stores the customer's name, shipping address, e-mail address, optional phone number, optional message and the ordered items in `wp_ccgal_orders` and `wp_ccgal_order_items`. This data is necessary to fulfil the order and is retained until the site administrator deletes the order.

**Personal data export & erasure (WP Privacy Tool)**
The plugin integrates with the standard **Tools → Export Personal Data** and **Tools → Erase Personal Data** workflows. Print orders (Pro) are returned/erased by customer e-mail. Anonymous gallery favorites are, by design, not linkable to an e-mail address; the response message will note this explicitly so the requester knows their cookie-based records cannot be associated with their identity.

**Data deletion**
In addition to the WP Privacy Tool above, site administrators with the Pro statistics module can delete all tracking data for a specific visitor from the Statistics dashboard. Removing the plugin via **Plugins → Delete** erases all options, custom tables, gallery posts, and the thumbnail cache.

**Suggested privacy policy text**
The plugin contributes a ready-made privacy policy paragraph to **Tools → Privacy → Privacy Policy editor** that you can copy into your public privacy page.

**No third-party transmission**
No visitor data is sent to external servers by this plugin. Thumbnails are fetched from Google or Dropbox APIs using the *site administrator's* OAuth credentials — visitor sessions are never used for cloud API calls.

**Photographer's responsibility**
If you use this plugin on your site you are the data controller for your visitors' data. You should inform your visitors about the visitor cookie in your site's privacy policy and, where required, obtain consent before enabling statistics tracking (Pro module only).
