=== Event Tickets with Ticket Scanner ===
Contributors: sasonikolov
Tags: event tickets, ticket scanner, congress, woocommerce tickets, seating plan
Requires at least: 6.0
Requires PHP: 8.1
Stable tag: 3.2.0
Tested up to: 7.1
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html

Sell tickets with WooCommerce: seating plans, PDF tickets with QR codes, door scanner. No per-ticket service fees; free version up to 50 tickets.

== Description ==

**Run your entire ticketing workflow inside WordPress.** No platform service fees, no per-ticket commissions — you only pay your payment processor (Stripe, PayPal, etc.) like any WooCommerce product.

Event Tickets with Ticket Scanner turns any WooCommerce product into a scannable event ticket — complete with QR code, downloadable PDF, and a built-in mobile ticket scanner for your team at the door.

Unlike ticketing platforms that charge service fees on paid tickets, this plugin adds no per-ticket commission — you keep your ticket revenue (minus your payment processor's standard fee). Unlike other WordPress plugins, you get a **visual seating plan designer** and a **ticket PDF designer** included — not as expensive add-ons.

https://youtu.be/uWSdKdOyn70

**Active on 1,000+ WordPress sites** — from small community events to large concert venues.

= Who is this for? =

* **Concert & festival organizers** — sell general admission or assigned seats with interactive seat selection
* **Theaters & venues** — design your seating layout with drag & drop, let customers pick their seats
* **Sports events & arenas** — handle high volumes with offline fallback and team scanner access
* **Conferences & congresses** — give attendees a private companion portal with programme, speakers, downloads and time-controlled access, opened straight from the ticket (see "Congress & Attendee Portal" below)
* **Single events with an info app** — any event, not just big congresses: build a mobile info page (schedule, speaker info, downloads, support pages) your visitors open with their ticket — no separate app to install
* **Clubs, spas, gyms & theme parks** — sell multi-entry passes, family tickets, or memberships with expiration
* **Community events & fundraisers** — get started in minutes with the free version (up to 50 tickets)

= How it works — 3 steps =

1. Install the plugin and create a ticket list under "Event Tickets"
2. Enable "Ticket Sales" on any WooCommerce product
3. Customers receive a unique QR code ticket — scan and redeem at the entrance

That's it. No coding required. [Watch the quickstart video](https://youtu.be/KKLp1Lwqj_U)

= 🎨 Visual Seating Plan Designer =

Create professional venue layouts without any design tools:

* Drag & drop seats, shapes, labels, and text onto your canvas
* Upload venue floor plans as background images
* Rotate, duplicate, and bulk-edit elements
* Color-code seat categories and pricing tiers
* Customers see a **real-time interactive seat map** during checkout
* Seats are automatically blocked during checkout and released on cancel/refund

= 🎟️ Ticket PDF Designer =

Design tickets that match your brand — not generic templates:

* Add your logo, custom colors, header and background images
* Position QR codes exactly where you want them
* Create event badges for staff and VIP passes
* Multi-page PDF support with attached documents
* Full bleed mode for edge-to-edge designs

= 📱 Built-in Ticket Scanner =

No extra app needed. The scanner runs in any mobile browser — or install it as a PWA:

* Scan QR codes with your phone camera — works on iOS and Android
* **Install as PWA** — add to home screen for instant launch without browser chrome
* **Fullscreen mode** — immersive scanning with a single tap
* **Haptic feedback** — vibration confirms valid/invalid tickets instantly
* Grant team members scanner access via Auth Tokens (no WordPress login needed)
* See ticket details, seat position, and venue map instantly after scan
* Voice output confirms valid/invalid tickets for fast processing
* Protection against fake tickets, double redemption, and brute-force attempts
* Supports hardware barcode scanners for high-traffic entrances
* Customizable theme color to match your brand

= 📲 Vollstart Wallet — All Tickets in One App =

Your customers can collect tickets from multiple shops in one free app:

* **Vollstart Wallet** at [wallet.vollstart.com](https://wallet.vollstart.com) — a Progressive Web App that works on iOS, Android, and desktop
* Tickets are added with one click from the ticket page or order email
* QR code always available — even offline
* **Privacy first** — ticket data flows directly from your shop to the customer's browser. Vollstart never receives or stores any data
* [Learn more about security & privacy](https://vollstart.com/vollstart-wallet/)

Enable it in Settings → Digital Wallets. Free for all users.

= 🎓 Congress & Attendee Portal =

Give ticket holders a private event portal — no extra login, no separate app. Assign a **congress** to any ticket product and buyers reach it straight from their ticket ID.

* **Organize content into pages** — a start page plus as many pages as you need (Speakers, Programme, Downloads, Sponsors …), with a sidebar on desktop and a hamburger menu on mobile
* **Rich section types** — formatted text, a day-by-day programme schedule, downloadable files, link lists, image galleries, single images, and embedded videos (paste a YouTube/Vimeo link or upload your own)
* **Password-protect** individual sections for VIP or speaker-only content
* **Fast by design** — the start page loads instantly and other pages are fetched on demand, so even congresses with dozens of speakers stay snappy
* **Installable & offline-friendly** — the portal ships as a mini Progressive Web App
* **Time-controlled access** — open the portal a set number of hours before the event and keep it available for a chosen number of days afterwards
* **Recurring events** — duplicate a congress as a "new edition" in one click

Build it under the Congresses menu, assign it in the product's Event Tickets tab, and optionally add a portal link to the order email. Included free.

= Powerful Ticket Types =

* **Single entry** — classic one-time event ticket
* **Multi-entry passes** — allow multiple scans (configurable limit)
* **Family tickets** — generate multiple tickets per order item
* **Memberships & season passes** — set expiration dates
* **Day chooser tickets** — let customers pick their event date at checkout
* **Purchase allowance codes** — require a valid ticket or access code before a product can be bought. Use it for presale and access codes, or to require a ticket from an earlier event (for example a group-stage ticket in order to buy the final). Per product you decide whether a code unlocks one purchase or several

= Built for WooCommerce =

* Works with product variants (e.g., VIP vs. General Admission)
* Tickets auto-generate on order completion
* Refunded orders automatically recover and recycle ticket numbers
* Compatible with WooCommerce Subscriptions
* Works with WooCommerce PDF Invoices & Packing Slips
* WPML compatible for multilingual stores

= Free vs. Premium =

The free version covers everything you need to start selling tickets — ideal for small events up to **50 tickets**:

* ✅ Ticket generation with QR codes (up to 50 tickets, 5 ticket lists)
* ✅ PDF ticket download (via link in email)
* ✅ Built-in ticket scanner
* ✅ Interactive seating plan designer (1 plan, up to 20 seats)
* ✅ Seat selection at checkout
* ✅ Multi-entry and family tickets
* ✅ Day chooser for date-based events
* ✅ Congress / attendee portal (pages, programme, downloads, media, password-protected sections)
* ✅ Purchase allowance codes (presale/access codes, or require a ticket from an earlier event)
* ✅ Webhooks for third-party integrations

**Premium adds professional features for larger events and removes all ticket limits:**

* 🔓 PDF ticket as email attachment (not just a link)
* 🔓 Team scanner access via Auth Tokens
* 🔓 Calendar invites (ICS files) in emails
* 🔓 Custom flyers and multi-page PDFs
* 🔓 CVV security check on tickets
* 🔓 Brute-force IP blocking
* 🔓 Unlimited tickets (removes the 50-ticket free limit)
* 🔓 Advanced shortcodes for ticket display and validation

[Get Premium](https://vollstart.com/event-tickets-with-ticket-scanner/)

= Links =

* [Documentation](https://vollstart.com/event-tickets-with-ticket-scanner/docs/)
* [Quickstart Video](https://youtu.be/KKLp1Lwqj_U)
* [All Features](https://vollstart.com/event-tickets-with-ticket-scanner/docs/event-tickets-with-ticket-scanner-feature-list/)
* [Premium Plugin](https://vollstart.com/event-tickets-with-ticket-scanner/)
* [Support](mailto:support@vollstart.com)

== Installation ==

= Requirements =
* WordPress 5.0 or greater
* WooCommerce 6.0 or greater
* PHP 8.1 or greater (PHP 8.4 compatible)
* PHP extensions: php-curl, php-imagick

= Installation =

1. Go to **Plugins → Add New** in your WordPress admin
2. Search for "Event Tickets with Ticket Scanner"
3. Click **Install Now**, then **Activate**
4. Go to **Event Tickets** in your admin menu and create your first ticket list
5. Edit any WooCommerce product → open the **Event Tickets** tab → enable ticket sales

= Quick Setup =

The plugin extends WooCommerce so any product can become a ticket. Enable the checkbox on your product, assign a ticket list, and you're ready to sell.

**Tip:** Set the product as "Virtual" so WooCommerce auto-completes the order and triggers ticket generation immediately after payment.

To test: Create a manual order in WooCommerce, set it to "Completed", and check the order email for the ticket link.

**For a step-by-step walkthrough, [watch the quickstart video](https://vollstart.com/event-tickets-with-ticket-scanner/docs/#quickstart).**

== Frequently Asked Questions ==

= Do I need any other plugins? =
You need WooCommerce (free) to handle payments and orders. Everything else is included — no additional ticketing add-ons required.

= Does this work without WooCommerce? =
WooCommerce is required for selling tickets. However, you can use the plugin to manage and validate ticket lists manually without WooCommerce sales.

= Can my team scan tickets without a WordPress account? =
Yes! Premium includes Auth Tokens that give your door staff scanner access via a simple URL — no login required.

= Does the scanner work offline? =
The scanner is browser-based and requires an internet connection. For large events, the plugin includes offline fallback options to prevent interruptions.

= Can customers choose their own seats? =
Yes. Design your venue layout with the drag & drop seating designer, and customers will see an interactive seat map during checkout where they can pick available seats.

= What ticket types can I create? =
Single entry, multi-entry passes, family tickets (multiple tickets per purchase), memberships with expiration dates, and day-chooser tickets where customers pick their event date.

= How are tickets delivered to customers? =
In the free version, the order confirmation email includes a link to download the ticket PDF and view the QR code. Premium allows attaching the PDF directly to the email and adding calendar invites (ICS).

= What happens when an order is refunded? =
The ticket is automatically deactivated, the assigned seat is released, and the ticket number is recovered for reuse.

= Is the plugin compatible with WPML? =
Yes. WPML is supported for multilingual ticket sales. The plugin also ships translations for German, Spanish, French, Italian, Hungarian, Japanese, Dutch, Portuguese, and Chinese.

= What if I exceed the free ticket limit? =
The free version supports up to 50 tickets. If you reach the limit, the plugin shows a notice in the admin area (your customers are never blocked from purchasing). Premium removes the limit entirely.

= Can I use my own QR code or barcode scanner hardware? =
Yes. The built-in scanner page accepts input from hardware barcode scanners in addition to camera-based QR scanning.

= How do I prevent ticket fraud? =
Every ticket number is unique. The scanner detects duplicate redemption attempts. Premium adds CVV verification and brute-force IP blocking for additional security.

= What is the congress / attendee portal? =
It's a private event portal you can attach to a ticket product — organized into pages (start page, speakers, programme, downloads, sponsors, etc.) with text, schedules, files, image galleries and embedded videos. Ticket holders open it directly via their ticket, no separate account needed. You can password-protect individual sections, control when the portal opens and how long it stays available, and the page works offline as a mini PWA. It's included in the free version; build it under the Congresses menu and assign it in the product's Event Tickets tab.

== Screenshots ==

1. **Event Tickets Dashboard** — Manage ticket lists, sold tickets, and everything else from one place in your WordPress admin.
2. **Visual Seating Plan Designer** — Drag & drop seats, rows, shapes and labels to design your venue. No design tools needed.
3. **Interactive Seat Map at Checkout** — Customers pick their own seat on a live seat map. Taken seats are blocked automatically.
4. **Sell Tickets as WooCommerce Products** — Any product becomes a ticket with one checkbox. Works with variations like VIP or General Admission.
5. **PDF Ticket** — Branded PDF ticket with QR code, ready for print or mobile display.
6. **Ticket on Mobile** — Customers see their ticket with QR code, PDF download and calendar file on any device.
7. **Ticket Scanner** — Scan and redeem tickets with any phone. No extra app required; installs as a PWA with fullscreen mode.
8. **Ticket Management** — All generated tickets with status, redemption info, filters and CSV export.
9. **Tickets on the Order Page** — See and quick-scan the tickets of every WooCommerce order directly in the order detail view.
10. **Product Settings** — Enable ticket sales, assign a ticket list and a seating plan on the product's Event Tickets tab.

== Upgrade Notice ==

= 2.8.0 =
Major release: Old premium version will no longer work with this version. Interactive seating plan designer with drag & drop editor. Default ticket template updated to display seat numbers. Please backup your system before upgrading.

= 2.8.10 = 
Old premium version will no longer work with this version. You need to downgrade the basic plugin or get a new license for premium to update your premium plugin too.

== Changelog ==

= 3.2.0 - 2026-09-13 =
* New: Filter the ticket list by scanner account — new search filter "AUTHTOKEN:" shows all tickets redeemed by one ticket scanner (enter the id shown in the scanner details), so you can review each entrance team member's activity without opening every ticket.
* New: The scanner makes a successful read unmistakable — the camera area lights up with a green frame (like card payment terminals) and a clear "code read, waiting for server" message the moment a QR code is recognized, followed by a short deliberate pause before the next scan is processed. Queued scans of group members are never lost and keep their order.
* Fix: The ticket scanner silently dropped scans that arrived while the previous ticket was still loading (slow Wi-Fi, fast consecutive scanning of groups) — those guests were never redeemed in the dashboard. Scans are now queued and processed one after another, with a short on-screen note.
* Fix: Re-scanning a ticket that this scanner redeemed moments ago showed a red "ticket already used" error — confusing operators at the entrance. The scanner now recognizes its own successful redeems and shows a calm green confirmation instead.
* Fix: The same seat could be sold twice — a second order (or a double add-to-cart with the same seat) confirmed a seat that already belonged to another order, creating two paid tickets for one seat while the seat map only showed one sale. A seat already confirmed for another order is now refused, and a selection containing the same seat more than once is rejected at add-to-cart with a clear message.
* Fix: When an order was refunded or cancelled, the freed tickets kept a user registration from the previous buyer — the old owner stayed visible on the ticket and could interfere with its next use. Registrations are now cleared together with the other order information, and when a ticket is reused for a new purchase, any leftover registration or usage information is swept automatically.
* New: Tickets without WooCommerce — the big decoupling step. Ticket lists, ticket numbers, PDF tickets and the ticket scanner now work without a WooCommerce order behind them, for printed cards you hand out yourself. (Details follow with the release.)
* New: The PDF path for tickets without an order — the single ticket PDF, the ticket page and the bulk "one PDF" print sheet now render order-less tickets instead of failing with an order error. The ticket shows its own number, its list, the QR code and the redemption state; everything that normally comes from the order (customer, purchase dates) stays empty. With the "allow tickets without order" switch off, everything behaves exactly as before.
* Fix: Order-related errors (#7001/#7002/#8010/#8013/#8020/#8021) no longer appear for tickets that are deliberately allowed to have no order.
* New: The setup wizard now asks whether you will sell or hand out tickets directly, without a WooCommerce order — at the door, by invoice or as printed cards — and explains what the switch does. The option's description in the settings was extended the same way.
* New: The admin area formerly known as "Congresses" is now the "Event App" — the companion page your visitors open with their ticket (programme, speaker info, downloads, support pages; for a congress just as much as for a single event). Each app row has an "Open example" button that opens the visitor view in a new tab, using one of the app's real tickets. The button appears once a ticket exists.
* Tweak: The support info block now reads "Tested up to", "Requires at least" and "Requires PHP" live from readme.txt instead of carrying hardcoded values that lagged behind (it showed 6.9 while the readme said 7.1).
* New: The setup wizard has a fifth use case, "Direct sales — no WooCommerce" — for tickets you sell at the door, by invoice or as printed cards. It switches the without-order path on and carries scanner defaults instead of order-processing options. When WooCommerce is not installed, this use case is preselected automatically.
* Tweak: The "Allow tickets without a WooCommerce order" setting now lives in its own section, "Direct sales (no WooCommerce order)", instead of deep inside the WooCommerce sales section — easier to find, especially on sites without WooCommerce. Nothing about the option itself has changed.

= 3.1.14 - 2026-09-07 =
* Security: Added nonce verification to the admin-area AJAX endpoint (executeWCBackend) to block cross-site request forgery. The frontend already shipped the nonce; the server now verifies it.
* Security: Restricted WooCommerce order/product data actions (downloadTicketInfosOfProduct, downloadAllTicketsAsOnePDF, removeAllTicketsFromOrder, removeAllNonTicketsFromOrder, downloadPDFTicketBadge) to users with the manage_woocommerce or edit_shop_orders capability. Without this, a low-privilege role granted admin-area access via the "Allow only specific roles" setting could read or delete every customer's ticket data through the plugin-internal role check.
* Security: Removed an `eval(data)` call after `$.getScript()` in the seating-admin loader (`backend.js`, _displaySeatingplanArea, line 1288). `$.getScript()` already executes the loaded script; the extra eval ran every byte of the response through the JS engine again. CWE-95 — a server-side change to `js/seating_admin.js` would have given code-injection on every admin page that opens the seating-plan area. No callers, no behaviour change beyond the redundant execute.
* Security: Ticket codes, scanner auth tokens and ticket idcodes are now generated with a cryptographically secure random source (`random_bytes`) instead of a hash of the current time (`md5(time() ...)`). The old values were predictable: two codes created in the same second shared their time component, so anyone who knew roughly when a ticket was issued could narrow the guessing range dramatically. CWE-338. Five generators were changed — the ticket code and the code-list lock in the WooCommerce assignment path, the per-list and per-order idcode, and the ticket-scanner auth token. The printed format is unchanged (same length and character set), existing tickets and tokens keep working, and no migration is needed.

= 3.1.13 - 2026-09-03 =
* Tweak: Products with variations no longer show the seat selector on the shop overview page. In the overview no variation is chosen, so the selector could not work there anyway - it only pushed the product image out of its card. The "Select options" link leads to the product page, where the full seat selection (and its validation) lives. Simple products keep the quick selector in the overview.

= 3.1.12 - 2026-09-02 =
* Tweak: The premium wizard now says exactly what the "Enable Recommended Settings" button does before you click it: attach tickets as PDF to the purchase email, merge all tickets of an order into one PDF, and raise the attachment limit to 21. Previously the popup only vaguely asked to "enable PDF ticket attachment" and revealed the actual settings only after they had already been changed.
* Tweak: "Skip" in the premium wizard closes the popup instantly instead of waiting for the server. The "don't show again" marker is written exactly once per installation - the first time the wizard is skipped while it is still unset; re-opening the wizard later sends no request at all.
* Fix: In multilingual shops (WPML), the seat check on "add to cart" was silently skipped when the customer bought in a secondary language. The check read the seat settings from the translated product, where they do not exist - a shop could sell numbered-seat tickets without a chosen seat. The check now reads the settings from the original product, like the cart display already did.
* New: A safety net at checkout. If a product with required seat selection reaches the cart or checkout without a seat on its line - possible when the seating plan was still unpublished at add-to-cart, when a seat reservation expired and was removed, or when another plugin rewrote the cart - the order is now blocked with a clear message until a seat is selected. Products with optional seats are unaffected.

= 3.1.11 - 2026-09-01 =
* Fix: Adding a ticket with a chosen seat to the cart failed with "Invalid seat selection" on every attempt since 3.1.8. The seat check compared the chosen seat against a value that the variation-plan rework had left behind, so no seat could ever pass - on products with and without variations. Valid seats are accepted again.

= 3.1.10 - 2026-08-31 =
* Fix: Two errors that blocked a shop after a staging-to-live database migration. (1) The Event Tickets admin page stayed on the loading spinner and showed "#505 auth token not found" as soon as a ticket in the list referenced a scanner token that no longer existed in the new database - the audit column tried to read the token's name and the missing-row error crashed the whole page. (2) Membership products that were never ticket products suddenly demanded a "ticket or access code" in the cart and at checkout, even though their product settings showed no restriction. Both came from values that the database carried over from staging. (1) is caught and falls back to "AUTHTOKEN DELETED" in the audit column; the migration artefact no longer breaks the page. (2) sits behind the same is_ticket gate that already protects the other ticket-specific cart fields (added in 3.1.8): a stale restriction on a non-ticket product is now ignored, both for the input field and for the checkout validation. The restriction still works as before for real ticket products, including variations, where the parent is walked up first.

= 3.1.9 - 2026-08-26 =
* Fix: The "Check License" button in the settings did nothing since 3.1.6 - it showed "Checking..." forever and never contacted the license server, and after saving a license key the page never confirmed the activation. A helper function used by the button was defined in a place the button could not see, so the click stopped with an error before the request was sent. Your license was still checked in the background as before - only the visible check and the confirmation after entering a new key were dead. Moving the helper where every part of the settings page can use it brings both back.

= 3.1.8 - 2026-08-26 =
* New: Fields that another plugin adds to an order line - for example the answers of a product add-on plugin - can now be shown as a column in the ticket list. Switch on "Display a column with the extra fields of the order line" in the settings. Until now those answers were only visible by opening each order, which is why shops with more questions than our two per-ticket fields had to build their own list. A second setting hides individual fields by name, for answers that belong in the order but not in a list. The values belong to the whole order line, not to a single ticket: an order line for three tickets carries one set of answers.
* Fix: A seating plan assigned to a single variation is now actually used in the shop. The product editor has offered a "Seating Plan Override" per variation for a while and saved it correctly, but the shop only ever read the parent product: the seat map on the product page never changed when a customer picked another variation, the cart notice and the seat check when adding to cart used the parent's plan as well. Picking a variation now swaps the map to that variation's plan, and a product whose plan only exists on its variations shows a map at all for the first time. Products without variations are unaffected.
* Tweak: In the variations of a ticket product, the label "This variation is NOT a ticket product" no longer sticks to its checkbox. It now keeps a fixed gap, so the line reads as a label and a box instead of one run-on word.
* New: For plugin developers: fields that your plugin collects at checkout can be listed inside Event Tickets, right where our users write their ticket template. Fill the filter saso_eventtickets_template_order_fields with your fields - key and name are enough - and shop owners no longer have to look up a meta key in the database to print your field on a ticket. The heading above them is your plugin name, taken from the plugin your callback lives in; give your own heading once as the array key if you prefer. Fields of different providers are never merged, not even when two of them pick the same heading. Reading the value itself never needed us: a ticket template prints any order meta with {{ ORDER.get_meta("your_key") }}. The list sits under the replacement tags on the options page and stays empty until a plugin registers something - if yours is missing, ask its provider.
* New: A new setting decides from which order status a seat counts as sold: "Mark seats as sold from this order status". Until now a seat only turned red when the ticket number was created, which is when the order is paid - with a cash voucher or a bank transfer that can be days later, and until then the seat stayed available for everyone else. Set the status to "pending payment" and the seat is gone the moment the order is placed, while the ticket still follows on payment. The default keeps the previous behaviour. The ticket number is written onto the same seat afterwards, so cancelling an order still frees it.
* Fix: A seat now turns red on the seat map when the order is paid, even if the customer took their time or paid later. Selecting a seat reserves it for a while - by default a quarter of an hour - and the plugin marked exactly that reservation as sold when the order was completed. If the reservation had run out in the meantime, and it always has when payment arrives days later through a cash voucher or a bank transfer, there was nothing left to mark, and the plugin treated that as done. The order carried the seat correctly, the seat map kept offering it to everyone else, and the same seat could be sold twice. The sale is now written to the seat map in any case, and a second run of the ticket creation cannot enter the same seat twice. Shops with instant card payment were affected as well whenever a checkout took longer than the reservation.
* Fix: A one-day event no longer prints its date twice on the ticket. When no end date is entered, the plugin fills in the start date internally so the ticket stays valid until the end of the event day - and the printed date treated that filler like an entry, so "12.09.2026 19:00" became "12.09.2026 19:00 - 12.09.2026". Only the date you actually entered is shown now: a start alone stays a start, a real end date still produces a range, and an end time on the same day still shows as "19:00 - 23:00". The same applies to the date picker: a ticket for a chosen day shows that day once instead of "15.06.2026 19:00 - 15.06.2026". For template authors: TICKET.end_date is never empty for the same reason, so testing it tells you nothing. Two new variables do: TICKET.is_end_date_set and TICKET.is_end_time_set.

= 3.1.7 - 2026-08-24 =
* Fix: Removing a single ticket number from an order left that number on the order line. The number was freed in the ticket list, but the order, the email and the invoice PDF kept showing it, and it no longer lined up with the public ticket numbers next to it. The number was compared in its internal spelling against the one printed on the order, which never matched, so nothing was ever removed. Orders with several tickets on one line are the ones affected.
* New: For developers: the decision whether an order line gets a ticket now lives in one place and can be overruled with the filter saso_eventtickets_wc_order_item_is_ticket. It exists for shops where another plugin splits one product into several order lines - split VAT or invoicing plugins - so that only the original line carries the ticket while the other lines stay bookkeeping. Nothing changes unless the filter is used.
* New: For developers: the order manager now answers, per order line, how many ticket numbers a line is supposed to carry and which lines are still short of them (getExpectedTicketAmountForItem, countOrderItemsWithMissingTicketnumbers, orderNeedsTicketNumbers). Ticket creation uses the same calculation, so a tool that looks for missing numbers and the code that draws them can no longer drift apart. The premium bulk assignment is the first user.
* New: The ticket export now has its own column for the value you ask per ticket (the product setting "Request a value for each ticket from dropdown"). Until now only the name per ticket had one, so the answer was in the export but buried inside the raw meta column, where a spreadsheet cannot filter or sort it. The column stays in the file even when a product does not ask for a value, so the header does not change from export to export.
* Tweak: An extra safeguard so a product that is not configured as a ticket can never receive a ticket number, not even when it sits in the same order as tickets. Selling through the shop was never affected - the check was already made for every single order line - but the entry point that other code (the premium plugin, custom code) can call did not make it itself. It does now.

Older entries are archived in changelog.txt shipped with the plugin.
