=== Strands PDF Embed ===
Contributors: strands, LordFren
Tags: pdf, embed, viewer, pdfjs, document
Requires at least: 6.4
Tested up to: 7.0
Requires PHP: 8.1
Stable tag: 1.0.5
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Embed PDFs from your Media Library with a responsive viewer. Works with Gutenberg, Classic Editor, Enfold ALB, Elementor, and WPBakery.

== Description ==

PDF Embed renders PDFs directly in your pages using PDF.js: no plugins for the visitor, no Google Docs iframes, no third-party trackers. PDF.js is bundled inside the plugin and served from your own site, so no third-party requests are ever made.

Built by Fren at Strands Services Ltd. Support: fren@strands.gg.

**Features**

* Continuous vertical scroll across all pages, with lazy rendering via IntersectionObserver
* Page navigation, zoom, fit-to-width, download, and print controls (these toggle which buttons render in the toolbar; they're a UI choice, not access control; see the Security section)
* Global sizing modes: Responsive, Fixed, or Fixed Aspect Ratio (A4, Letter, 16:9, 4:3, 1:1, or custom)
* Light, Dark, or Auto color mode (Auto follows the visitor's OS/browser `prefers-color-scheme`)
* Per-mode color customization (with alpha/transparency support on the Page Shadow field), plus a safely-scoped Custom CSS field for power users
* Live preview on the settings page
* Editors: Gutenberg block, Classic Editor TinyMCE button, Enfold ALB element, Elementor widget, WPBakery element, and `[pdf_embed]` shortcode
* Media Library picker filtered to PDFs only
* Translation-ready (English + Hungarian included)
* Theme-overridable viewer template

**Shortcode**

`[pdf_embed id="123" sizing="responsive" download="yes" navigation="yes" zoom="yes"]`

All attributes are optional except `id`. Per-embed attrs override the global defaults on the Sizing and Appearance tabs.

== Installation ==

1. Upload the `pdf-embed/` folder to `wp-content/plugins/`
2. Activate the plugin via the Plugins screen
3. Visit Settings → PDF Embed to configure default sizing and appearance

== Frequently Asked Questions ==

= Can I override the viewer markup in my theme? =

Yes. Copy `wp-content/plugins/pdf-embed/templates/viewer.php` to `wp-content/themes/your-theme/pdf-embed/viewer.php` and edit.

= Does it work with private / draft attachments? =

It mirrors WordPress's own permission model. The viewer only renders for visitors who have permission to read the underlying post (`current_user_can('read_post', $id)`). In practice that means:

* **Logged-in admins, editors, and the post's author**: drafts and private posts render in their post-preview screens, exactly like the rest of the post content.
* **Anonymous visitors**: drafts and private posts are blocked (an HTML comment is emitted, no viewer). This is a deliberate security boundary; leaked draft URLs cannot expose embedded PDFs.

If you need to share a draft preview with someone who can't log in, that's a WordPress concern, not a PDF Embed one (use a preview/share-link plugin or publish the post privately).

= How do I add translations? =

Drop a `.po` / `.mo` / `.l10n.php` file into the plugin's `languages/` folder using the `pdf-embed` text domain.

= Does it work on multisite? =

Yes. Activation seeds defaults per site, and uninstall cleans options on each site (including the legacy `pdfembed_source` from older versions). No network-wide tables, no `add_site_option` use; per-site settings stay independent.

= Where does the Enfold ALB element show up? =

In Enfold's Advanced Layout Builder, look for **PDF Embed** under the **Media Elements** tab. If your Enfold runs in a non-English language, Enfold groups elements by localized tab name and ours stays in English — so PDF Embed may show up in its own auto-created group instead. The element itself works identically either way.

= How does the Print button work? =

The viewer renders PDFs safely via PDF.js (canvas pixels, no PDF-side JS executes). The Print button, however, hands the raw PDF to the browser's native PDF renderer; that's the only way to get a browser's print dialog. Don't rely on Print for security-sensitive documents.

= Is my PDF.js version patched for CVE-2024-4367? =

Yes. PDF Embed ships PDF.js 5.7.284, well past the 4.2.67 patch.

= If I set `download="no"`, can visitors still save the PDF? =

Yes, technically. The Download button is removed from the toolbar, but the PDF bytes are loaded into the browser by PDF.js to render the document, so a visitor with browser developer tools can still extract them. Use `download="no"` to remove the button as a UX choice (e.g. on pages where you don't want to encourage downloads), not as a security boundary. The same applies to `navigation="no"` and `zoom="no"`: they remove buttons; they don't prevent a determined visitor from re-enabling those interactions in their browser. To actually keep a PDF private, restrict the post that embeds it (private/draft, role-restricted, members-only plugin).

== Screenshots ==

1. The frontend viewer rendering a PDF, with the toolbar (page nav, zoom, download, print) in view.
2. Settings → Appearance: light/dark/auto modes with per-mode color pickers and live preview.
3. Settings → Sizing: responsive, fixed, and fixed-aspect-ratio modes (A4, Letter, 16:9, 4:3, 1:1, custom).
4. Inserting the PDF Embed block in the Gutenberg editor, with the Media Library picker filtered to PDFs.
5. The PDF Embed element in Enfold's Advanced Layout Builder. Elementor widget and WPBakery element work the same way.

== Security ==

* Frontend visitors have no attack surface: no REST endpoints, no AJAX handlers, no form submissions.
* Shortcode attribute sanitizers reject any value outside strict whitelists (units, hex/rgba colors, W:H ratios).
* Attachment access is gated by `current_user_can('read_post', $id)`; contributors cannot embed other users' private PDFs.
* `pdfjsLib.getDocument()` is called with `isEvalSupported: false` to prevent font-based JS execution.
* All output is routed through WordPress's escape functions (`esc_html`, `esc_attr`, `esc_url`, `wp_kses`, `wp_print_inline_script_tag`). The release pipeline includes a check that fails the build if a `phpcs:ignore` for the output-escaping sniff ever appears in shipped code.
* Canvas size is clamped to ~268M pixels per page. Page count is capped at 2000 per embed. Both prevent client-side DoS from hostile PDFs.
* PDF.js ships inside the plugin and loads only from your own domain; no third-party servers are contacted.
* Debug information (attempted mime, extension) is only emitted when `WP_DEBUG` is on.
**Content-Security-Policy**: the plugin emits one inline `<style>` (appearance CSS variables) and one inline `<script type="module">` (PDF.js loader). The script tag is rendered via `wp_print_inline_script_tag()`, so a CSP supplied through the `wp_inline_script_attributes` filter is applied automatically. On sites without  wiring, strict CSP needs `style-src 'unsafe-inline'` and `script-src 'unsafe-inline'` for the viewer to boot.
**Toolbar toggles are UI affordances, not access control.** The shortcode's `download`, `navigation`, and `zoom` attributes (and the matching options in the editors) control which buttons appear in the viewer toolbar; they don't restrict what the visitor's browser can do once the page is rendered. PDF.js needs the entire PDF in the browser to display it, so the bytes are always reachable from the browser's network/devtools panel by anyone who can see the page. The `pdfEmbedGetInstance(container)` JS handle also exposes the underlying viewer methods regardless of which buttons render. If you need to keep a PDF away from a viewer, gate the **post** behind WordPress's permission system (private/draft, role-restricted, members-only plugin). Don't rely on the toolbar toggles.

== Third-party libraries ==

The plugin bundles minified copies of these libraries under `assets/js/vendor/`. Full un-minified source is available from each project's repository at the pinned version listed below:

* **PDF.js** 5.7.284 (`pdf.min.js`, `pdf.worker.min.js`) — Apache 2.0 — https://github.com/mozilla/pdf.js/releases/tag/v5.7.284
* **wp-color-picker-alpha** 3.0.4 (`wp-color-picker-alpha.min.js`) — GPL-2.0-or-later — https://github.com/kallookoo/wp-color-picker-alpha
* **Phosphor Icons** (inline SVG, Regular weight) — MIT — https://github.com/phosphor-icons/core

The plugin's own PHP, JS, and CSS is shipped as-is without minification so the deployed code is also the source.

== Changelog ==

= 1.0.5 =
*Welcome to the WordPress.org plugin directory!
**Tested with WordPress 7.0 — no code changes needed, the viewer and all integrations continue to work cleanly.
**Plugin directory listing assets prepared: banners, icons, and 5 screenshots covering the frontend viewer, Appearance/Sizing settings, the Gutenberg block, and the Enfold ALB element (Elementor and WPBakery work the same way).

= 1.0.4 =
*Got feedback from reviews, and time to fix them!
**Readme link issues resolved.
**Readme adjustments to highlight changes across the board.
**Remote file calling is a nono, so I added them to the plugin itself. Removed the capability of externally calling CDN js files entirely now.
**Integrating seamlessly into AVIA / Enfold is not gonna be doable as it would require hijacking the namespace which is a nono. So I made our own, the downside is: non english enfold will have our own little category - for now.
**Every variable was sanitized, but there were a few phpcs ignore annotations that should not have been left there. All integrations, markup, icons, run through a WP escape function.
**Plugin check came back with an extra issue: unprefixed globals. Elementor widget class (and its file) renamed to Spdfembed_Elementor_Widget so Plugin Check stops yelling at me (And this time I am not just commenting the issue out :D).
**Missed Phosphor icons attribution.
*Revised UX
**The light and dark mode selector and configuratior was a bit messy. Improved for clarity and functionality YAY For UX!
**Settings page now caps at 1440px instead of stretching the full window; previews live next to their own colour pickers (Light next to Light, Dark next to Dark), no more "Preview as" toggle.


= 1.0.3 =
*Welp, I am old. Last time, I checked we always used the CDN versions of included libraries. I was wrong. Swapped the default options.　Thank you WP Plugin guidelines reading for the 5th time.

= 1.0.2 =
*Readme missed links to the PDF js and WP Color picker projects. Duhhh...

= 1.0.1 =
* Assigned slug caused issues on online test I had to fix... Thanks LIFE! :D - assigned slug is now the default. Fingers crossed it wont change again.

= 1.0.0 =
* Initial public release.
