What is in this release
- Google Photos: the API is gone, two ways remain
- The new Event Access page
- Password links changed
- The Ajax List rework
- The promotion engine
- Send Image by eMail
- Everything else that changed
- Your update checklist
1. Google Photos: the API is gone, two ways remain
Let us start with the removal, because it is the one thing in this release that can silently change what your visitors see.
What was removed
Support for Google Photos via the Google Photos Library API is gone. Google deprecated that API in 2025 in a way that made it impractical for a gallery extension. I wrote about it back then in Oops, they did it again – changes to the Google Photos API integration.
The classes, the controller, the account selector and the album picker for that integration have been removed from the codebase, and the database update drops the two columns that stored the connection:
ALTER TABLE `#__eventgallery_folder` DROP COLUMN `googlephotosaccountid`;
ALTER TABLE `#__eventgallery_folder` DROP COLUMN `googlephotostitle`;
UPDATE `#__eventgallery_folder` SET published=0 WHERE FOLDERTYPEID=4;
That last line is the one to read twice: every Event still using the API folder type gets unpublished by the update. Not deleted – unpublished. Those Events did not work anymore anyhow, because the API behind them no longer answers the way Event Gallery needed it to. Unpublishing them means your visitors get a clean site instead of a page full of broken images, and you keep the Event around to decide what to do with it.
If you want to know which Events those are before you update, look for Events with the folder type Google Photos in the backend.
What still works
Two ways to get pictures out of Google Photos remain, and neither of them needs an API project, a consent screen or OAuth credentials:
Google Photos – Shared Page. Open Google Photos, share an album, copy the link, and paste that link into a new Event. Event Gallery pulls the shared page and reads the image URLs from it. The content of the link is refreshed automatically every 24 hours. If you changed the album and want the change immediately, use the cache-clear button on the Event Gallery overview page and clear the cache for Google Photos – Shared Pages.
The caveats are unchanged and worth repeating: only the image URL and its width and height are available as metadata. No EXIF, no filenames. And Google can change how it renders shared pages at any moment, so this is inherently less stable than a real API would be – though as the last two years showed, an API is no guarantee either. The free version shows 30 photos per album; Extended shows up to 150 (sometimes 300), which is a hard limit of the Google shared pages themselves. They simply do not reveal more.
Google Photos Picker. While uploading images to an Event, you can pick files from your Google Photos collection instead of your hard drive. The images are then copied to your webspace and are ordinary local images from that point on – with all the metadata, all the image types, all the sizes. If you want Google Photos as a source rather than as a live backend, this is the better route anyway.
2. The new Event Access page
The problem
You shoot a wedding. You want the couple, and only the couple, to see the gallery. So you password-protect the Event and send them a link plus a password. Fine – for one couple.
Now do that for a school with forty classes, or a race with three hundred participants. Every one of them needs a different link, because every Event lives at a different address. That is forty emails with forty different URLs, or three hundred cards each printed with its own address. Every one an opportunity for a typo.
The solution
Event Access is a new Joomla menu item type. It renders a landing page with essentially one thing on it: a password field. A visitor types the password of a password-protected Event and lands directly in that gallery.
That means one link for your whole site. Put it in your main menu, print it on the card you hand out, and the only thing that differs per customer is the password. The customer does not have to know the address of their gallery, and you do not have to keep track of which address you sent to whom.
What it will and will not open
The page never opens an Event the visitor could not have seen anyway. That matters, because the page effectively lets anybody try any password against every Event of the site. Concretely:
- An unpublished Event is never opened, whatever password is entered.
- An Event restricted to a user group the visitor is not a member of is never opened either – the group check runs regardless of the password.
- If one password happens to belong to several Events, all of them are unlocked and the visitor gets a short list to pick from. This is a feature, not a compromise: give a whole class the same password and they get their group photo Event plus the sports day Event in one go.
The Instructions option
The menu item has exactly one option worth configuring: Instructions. It is the text above the password field, and it replaces the generic sentence that would otherwise be there.
It is a multilingual field and it accepts HTML. So you can write a full paragraph, add a link to your contact page, or drop in a mailto address:
Your password is printed on the card you received after the shoot. Lost it? Just drop me a line.
Leave it empty and the shipped default sentence appears. In the backend the field lives under the menu item's options and is stored in params, not in the request variables – worth knowing if you script your menu items, because a request variable would be URL-encoded into the menu link and never reach the frontend at all.
How it defends itself
A public form that checks a secret against every Event on the site is exactly the kind of thing a bot enjoys. So it protects itself twice.
Captcha. It uses the captcha you configured in Joomla under Global Configuration. If you have no captcha configured, there is none – the field simply renders nothing. No separate Event Gallery captcha setting, no second place to configure.
Attempt limit. A visitor gets ten wrong passwords per hour. After that even the right password is refused until the hour has passed. The counting happens in two places at once:
- Per session, which is cheap and catches the merely curious.
- Per IP address in the database, which still holds when a bot throws its cookies away between requests.
The IP address itself is never written to the database. What is stored is a SHA-256 hash of the address combined with the secret of your Joomla installation, along with the time of the attempt, in a new table:
CREATE TABLE IF NOT EXISTS `#__eventgallery_passwordattempt` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`iphash` varchar(64) NOT NULL,
`created` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `passwordattempt_iphash_created_idx` (`iphash`,`created`),
KEY `passwordattempt_created_idx` (`created`)
) ENGINE=InnoDB ...
Rows older than an hour are deleted whenever the next attempt is recorded, so the table only ever holds the last hour of activity. It tells you that somebody tried too often. It does not tell you who.
The Event Access page and the ordinary password page of an Event share the same budget, so switching between the two forms does not make guessing any cheaper.
If you are behind a proxy
This one bites people, so please read it. If your site sits behind a proxy, a load balancer or a CDN, switch on System → Global Configuration → Server → Behind Load Balancer.
Without it, Joomla sees the address of the proxy for every single visitor. Every visitor then shares one IP budget, and the wrong passwords of one guest count against everybody else. Ten mistyped passwords from one confused customer and your whole site is locked out for an hour.
If ten is the wrong number
Ten attempts per hour is a compromise. If it is too strict or too generous for your site, define the constant before Event Gallery does:
define('COM_EVENTGALLERY_MAX_PASSWORD_ATTEMPTS_PER_HOUR', 25);
Template overrides
The password page now renders a captcha. If you have an override of html/com_eventgallery/password/default.php and a captcha configured in Joomla, you must add the captcha block to your override – otherwise nobody can get through the form anymore. The shipped template does it like this:
<?php IF ($this->form != false): ?>
<?php foreach ($this->form->getFieldset('password') as $field): ?>
<div class="control-group form-group">
<?php if (!$field->hidden): ?>
<?php echo $field->label; ?>
<?php endif; ?>
<div class="controls">
<?php echo $field->input; ?>
</div>
</div>
<?php endforeach; ?>
<?php ENDIF; ?>
Your override should also render the prefilled password by adding value="<?php echo $this->escape($this->password); ?>" to the password input – see the next section for why that matters.
Without a captcha configured in Joomla the block renders nothing at all, so an override that lacks it keeps working exactly as before.
3. Password links changed
This is closely tied to the Event Access page and it will be visible to your customers, so it gets its own section.
Links that carry an event password, like:
index.php?option=com_eventgallery&view=event&folder=wedding&password=secret
no longer open the gallery directly. They now land on the password page with the password already filled in, and the visitor submits the form once. The links you handed out keep working. They just cost one more click.
Why
Because a GET request is followed by everything that sees it. A search engine crawler. The link preview of a chat app. The virus scanner in a mail client. Every one of those unlocked the gallery when a link like that passed through – and none of them ever should.
And on the attack side: as long as a GET request could unlock a gallery, an attacker could try passwords without ever meeting the captcha and without any convenient place to count the attempts. Now exactly one request unlocks anything, and it is the one that passes both the captcha and the attempt limit.
What the old protection looked like
Worth spelling out, because the improvement is bigger than it sounds. Previously, a wrong password added a five second delay after ten tries, kept in the session. A bot could reset that by dropping its cookies. And the delay tied up a PHP process for five seconds per attempt – so the "protection" was also a rather effective way for an attacker to exhaust your process pool.
Now the attempts are counted per session and per IP, and once the budget is used up the request is refused immediately. No sleeping PHP processes.
4. The Ajax List rework
The Ajax List is the layout with a large main image and a strip of thumbnails underneath. It has been rebuilt, and this is the change most likely to make you go "oh, finally".
The old behaviour, and why it was wrong
The layout used to assign a fixed number of thumbnails to each page, configured through two options: Number of thumbs per page and Number of thumbs on first page.
A fixed number is a fine idea until you meet a phone. Twenty thumbnails might be two comfortable rows on a desktop. On a narrow screen the same twenty become six rows – and six rows of thumbnails push the main image right off the screen. The visitor arrives at your gallery and sees thumbnails. The whole point of the layout, the big picture, is somewhere below the fold.
The new behaviour
The layout now measures. It works out how many thumbnails fit into a row at the current window width, then fills up as many rows as the available height allows. Every page is filled completely; only the last one holds the remainder.
The height is set by a single new option: Event Gallery → Options → Event - Ajax List → Maximum height of the thumbnail area. It defaults to 280px, which is roughly three rows at the default thumbnail size. The field accepts 80 to 2000 in steps of 10.
So a narrow window now gets more pages instead of more rows. The thumbnail area stays the height you asked for, on every device, and the main image stays where the visitor can see it.
Two options are gone as a consequence – Number of thumbs per page and Number of thumbs on first page – both globally and on every menu item that used the layout. Your stored values are simply ignored. There is nothing to migrate; set the maximum height once if the default does not suit you and you are done.
Resizing works properly now
Resize the browser window and the thumbnails are redistributed and the paging bar is rebuilt immediately. Better: the image the visitor is currently looking at stays selected and is followed to its new page. No more losing your place because you rotated your phone.
The grid actually lines up
The thumbnails are laid out as a proper grid. The columns line up across all rows, the remaining space is distributed evenly between them, and the last row starts on the left instead of having its few items spread across the full width like a justified paragraph with three words in it.
The intro content is handled automatically
If you show the event name and description inline, that content stays on the first page and counts towards the maximum height. The first page therefore holds fewer thumbnails automatically – which is precisely what the old Number of thumbs on first page option made you calculate by hand. One less thing to get wrong.
This is controlled by Show Information Inline, which decides whether the event name and description appear inline on the first thumbnail page or above the gallery.
Thumbnails are cropped, not squashed
Thumbnails in the Ajax List are now cropped to the square that the Height of the thumbnails option asks for, instead of being squeezed into it.
This was a real and increasingly visible bug. The delivered thumbnail keeps the proportions of the original image, so a wide landscape shot got compressed horizontally into the square slot – and the larger you set the thumbnail size, the more obviously distorted it looked. Now it is cropped to a square and looks like a photograph.
Height of the thumbnails itself defaults to 75 and accepts 25 to 500.
The main image got a stage
The main image now sits in a stage of its own. Its height follows the width of the gallery and the viewport, rather than the aspect ratio of whatever image happens to be inside it.
This means the page stops jumping. Previously, clicking from a landscape image to a portrait image changed the height of the main image area, which shoved everything below it up or down. Now the stage keeps its height and the image is fitted into it – without cropping and without distortion.
The space a portrait image leaves next to it is filled with a blurred copy of the same image instead of an empty box. It looks deliberate rather than like a rendering accident, and it keeps the visitor's eye on the picture.
If you override the template
The markup changed. The template now renders all thumbnails into a single .page element and the JavaScript splits them up afterwards.
If you have an override of html/com_eventgallery/event/ajaxpaging.php or of the snippet behind it, and that override builds the pages itself, it will keep rendering – but its pages get replaced on the first distribution. Which means your override is doing work that is thrown away. Have a look at the shipped template and simplify accordingly.
5. The promotion engine
This is the largest new feature in 6.0.0. Event Gallery can now discount a cart – automatically, or when a customer enters a promotion code.
A promotion is built from two independent halves:
- Discount rules describe how much the price is reduced.
- Condition rules describe when the promotion is active.
Leave the conditions empty and the promotion always applies. Leave the code list empty and the promotion applies without the customer entering anything at all. The two are genuinely independent, which is what makes the whole thing composable.
You find it in the backend under Event Gallery → Promotions, and it has its own entry in the left Joomla menu and its own card on the Event Gallery dashboard.
5.1 Discount rules
A promotion needs at least one discount rule. Several rules are added up, so you can hand out a percentage and free shipping in the same promotion.
| Rule | What it does |
|---|---|
| Percentage off the cart | Reduces the whole cart by a percentage, e.g. 20%. Use Maximum discount to cap what the promotion can ever be worth. |
| Fixed amount off the cart | Reduces the cart by a fixed amount, e.g. a 50 EUR voucher. Never larger than the cart itself. |
| Percentage off selected image types | Like the percentage rule, but only the line items of the selected image types or image type groups are reduced. |
| Fixed amount off selected image types | Like the fixed rule, limited to the selected image types or image type groups. |
| Free shipping | Compensates the shipping costs. |
Internally these are the rule types cart.percent, cart.fixed, imagetype.percent, imagetype.fixed and shipping.free. Percentages accept 0–100 in steps of 0.01; amounts accept any positive value in steps of 0.01, in the currency configured for your shop.
A note on free shipping. The shipping method keeps showing its normal price and the promotion cancels it out as a separate line. That is deliberate: the customer can see what they saved. A shipping row that silently reads 0.00 communicates nothing.
A discount can never make the total negative. If the rules would reduce the cart by more than the customer actually owes, the discount is capped at the amount that is due. An order whose total ends up at zero is marked as paid right away – no pointless trip to a payment provider for a 0.00 charge.
5.2 Condition rules
Condition rules decide whether a promotion is active for the current cart. You pick whether all of the rules or any of the rules have to be fulfilled.
| Rule | What it compares |
|---|---|
| Cart value | The subtotal of the cart, e.g. at least 50. |
| Number of items in the cart | The sum of all quantities. An image ordered three times counts as three. |
| Number of cart positions | The number of distinct line items. An image ordered three times counts as one. |
| Cart contains an image type | Matches when the cart holds at least one item of the selected image types. |
| Number of items of an image type | How many items of the selected image types are in the cart. |
| Value of the items of an image type | The summed-up price of the items of the selected image types. |
| Number of items of an image type group | Like the image type quantity rule, but for a whole image type group. |
| Cart type | Restricts to digital, physical or mixed carts. |
| Event | Restricts to carts containing images of certain events. Enter folder names separated by commas, and choose whether the cart only has to contain one of them or may contain nothing but those. |
| Customer user group | Restricts to members of certain Joomla user groups. Guests are never a member of any group other than the public one. |
Note the deliberate distinction between items and positions – three prints of the same photo are three items but one position. Both are legitimate things to want to count, so both exist.
5.3 Combining conditions
To express "the cart contains 5 images of image type A and 1 image of image type B", add two Number of items of an image type rules and combine them with all of the rules:
| Type | Image type | Comparison | Value |
|---|---|---|---|
| Number of items of an image type | A | at least | 5 |
| Number of items of an image type | B | at least | 1 |
All condition rules of a single promotion are combined the same way – either all, or any. There is no nesting, no bracket editor, no boolean expression language.
That is on purpose. If you need something that neither all nor any can express, split it into two promotions. Two promotions with the same discount and different conditions have exactly the same effect as one rule that mixes both operators, and – this is the actual argument – it will still be readable when you come back to it in eight months and wonder why a customer got 20% off.
5.4 Promotion codes
A promotion can carry any number of codes. Add them on the Promotion codes tab and the promotion only applies once the customer has entered one of them. Leave the list empty and the promotion applies automatically whenever its conditions are met.
Codes are not case sensitive, and each code may only exist once across all promotions – enforced by a unique index on the database column. Otherwise it would be ambiguous which promotion a customer just unlocked.
Every code carries its own limits:
| Setting | What it does |
|---|---|
| Active | Switches a single code off without touching the promotion or the other codes of the same campaign. |
| Max. redemptions | How often this code may be used in total. 0 = unlimited. |
| Max. per customer | How often a single customer may use this code. 0 = unlimited. Only enforceable for registered customers – a guest has no identity that outlives their session. |
| Used | How often the code has been used. Edit it to give a code back. |
5.5 Personalized codes – the reason for all of this
This is what "any number of codes per promotion" is actually for.
You want to give three hundred wedding guests their own 10%-off code. Not one shared code that ends up on a voucher site, and not three hundred separate promotions.
So: build the campaign once – one discount, one set of conditions – then add one code per recipient with Max. redemptions set to 1. Every recipient gets their own allowance, and one person burning their code affects nobody else.
Typing three hundred rows by hand is not a plan, so use the Add codes in bulk field: paste one code per line and set Max. redemptions for bulk codes to 1, which is the default. On save, every line becomes a code of this promotion.
Crucially, codes already in the list keep their own settings and their counter, so you can paste the same list again – with a few new names added – without resetting anybody's usage.
A promotion applies only once per order. If a customer enters a second code belonging to the same promotion, they are told so, rather than the second code silently doing nothing and leaving them wondering. Codes of different promotions do stack, subject to the exclusive flag below.
5.6 Entering codes in the frontend
Customers enter codes in the cart and during the checkout. If you only work with automatic promotions, hide the form entirely with the component option Show promotion code form, which is on by default.
One detail that took some thought: an entered code stays with the cart even while it does not qualify.
Say a customer applies a code that requires an order above 50 EUR, then removes an image and drops below the threshold. The discount disappears – but the code stays attached. Add something back to the cart and the discount comes back on its own. The checkout lists such codes together with the reason they are currently inactive, so the customer can see "you need 5 EUR more" rather than concluding that your shop ate their voucher.
The codes live on the cart and the order themselves, via two new columns:
ALTER TABLE `#__eventgallery_cart` ADD COLUMN `promotioncodes` text DEFAULT NULL;
ALTER TABLE `#__eventgallery_order` ADD COLUMN `promotioncodes` text DEFAULT NULL;
5.7 Validity and limits – the campaign budget
The settings on the Validity and limits tab apply to the promotion as a whole, across every one of its codes. Think of them as the budget of the campaign, while the per-code limits above are the allowance of a single recipient. Both have to allow a redemption for the promotion to apply.
| Setting | What it does |
|---|---|
| Valid from / Valid until | Restricts the promotion to a period. Leave a field empty to leave that end open. |
| Maximum redemptions | How often the promotion may be used in total across all of its codes. 0 = unlimited. For an automatic promotion this is the only redemption limit there is. |
| Maximum redemptions per customer | How often a single customer may use the promotion across all of its codes. 0 = unlimited. Registered customers only. |
| Redemptions so far | How often it has been used. Edit it to reset the campaign. |
| Priority | Promotions with a higher priority are applied first, and the higher priority also wins among exclusive promotions. |
| Exclusive | An exclusive promotion does not stack. When it applies, no other promotion is used. |
The two levels combine in a useful way: a campaign of 500 personalized single-use codes with Maximum redemptions set to 200 hands out at most 200 discounts, even though 500 codes are in circulation. First come, first served, with a hard ceiling on what the campaign can cost you.
5.8 Redemptions, races and refunds
Both counters are raised when the order is created, and every redemption is logged in a dedicated table with the order, the customer, the code that was used, and the discounted amount:
CREATE TABLE IF NOT EXISTS `#__eventgallery_promotion_redemption` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`promotionid` int(11) NOT NULL,
`promotioncodeid` int(11) DEFAULT NULL,
`orderid` varchar(50) DEFAULT NULL,
`userid` varchar(45) DEFAULT NULL,
`code` varchar(64) DEFAULT NULL,
`amount` decimal(8,2) DEFAULT 0,
`currency` varchar(3) DEFAULT NULL,
`created` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`), ...
);
Redemptions are also linked to the related orders in the admin interface, so you can go from a promotion to the orders it paid for.
A code that runs out while it is still sitting in someone's cart simply stops applying. The cart is recalculated before the order is created, so the customer always pays the total that was last shown to them. And to keep two customers checking out simultaneously from both grabbing the last redemption of a code, the engine reserves and releases redemptions around the checkout rather than counting after the fact.
Unpaid orders do consume a redemption. If you cancel an order and want to give the voucher back, raise the Used counter of the code, or Redemptions so far of the promotion, by hand.
5.9 Stacking
By default, every promotion whose conditions are met applies, and the discounts add up.
Promotions are processed in priority order, and each one only sees the discount budget its predecessors left over – which is what makes it impossible to discount a cart below zero no matter how many promotions pile up.
Mark a promotion as Exclusive to stop it being combined with others. Among several exclusive promotions, the higher priority wins.
5.10 Tax – the part nobody wants to think about
Prices in Event Gallery include tax, so a discount has to reduce the tax as well. Otherwise your VAT return quietly stops matching your orders.
The discount is spread across the line items it applies to, and the tax share of every portion is tracked individually. That keeps the VAT correct even when the discounted items sit at different tax rates – a print at one rate and a digital download at another, discounted by one 20%-off promotion.
There is nothing to configure. The tax rate of a discount is always derived from the items it reduces, so it can never contradict them.
One thing to be aware of: percentage-based payment fees, shipping costs and surcharges are calculated on the undiscounted subtotal. If you want a percentage fee to shrink along with the discount, model it as a promotion instead.
5.11 You must edit your order email
This is the one manual step in the whole release, so please do not skip it.
Email templates live in the database and are never overwritten by an update – which is right, because otherwise every update would throw away your customisations. The consequence is that your existing New Order template does not know about discounts.
Without the change the mail still shows the correct total, but the discount row is missing. So the listed items and the total do not add up, and your customer sends you an email asking whether they have been overcharged.
Go to Event Gallery → Email Templates, edit the body of the new_order template, and add this block to the summary table right before the surcharge row:
{if isset($data->order->promotions)}
{foreach $data->order->promotions as $promotion}
<tr class="promotion">
<td>
{$promotion->name}
</td>
<td>
{$promotion->price}
</td>
</tr>
{/foreach}
{/if}
It is a loop rather than a single row because promotions stack, so there can be more than one discount on an order. Besides name and price, every entry also offers description and code.
If you never customised your order mail, use the Load Default button in the toolbar of the template instead. It replaces subject and body with the shipped default, which already contains the promotion block. Be aware that this discards any change you made to that template – so if you are not sure, add the block by hand.
6. Send Image by eMail
What it is
Event Gallery already had an email sharing option. It put a link into a mail.
The new option sends the image itself, embedded in the mail. The recipient opens the message and sees the photograph – no link to follow, no page to load, no decision about whether the link is safe to click. For "look at this picture of us", that is the entire interaction.
Which image gets sent
It uses the same image size that the download uses. Specifically: if Event Gallery → Options → Social → Use Original Files allows this visitor to get the original image, the original goes out. Otherwise the largest thumbnail does.
That is a deliberate design choice rather than a shortcut. It means this feature can never hand out an image that the visitor could not already have downloaded – including the user-group restriction on original downloads. There is no second permission model to keep in sync with the first one.
Turning it on
Switch it on globally under Event Gallery → Options → Social → Send Image by eMail, which is off by default, and turn it off per Event like every other sharing option.
The size limit
Images can be large, and mail servers famously dislike large. So there is Event Gallery → Options → Social → Maximum Image Size for eMails (MB), which appears once the feature is enabled. It defaults to 10 MB and accepts 1 to 100.
The fallback chain is: an original above the limit is sent as the largest thumbnail instead. If even that is too big, the visitor is asked to use the download rather than the mail silently vanishing into a spam folder or bouncing three hours later.
What the visitor sees
A small form asking for:
- Recipient address – required, validated as an email address.
- Sender name – optional, max 100 characters.
- Reply address – optional; if it is not a valid address, it is dropped rather than rejected.
- Message – optional, max 1000 characters.
- Data privacy confirmation – a required checkbox.
- Captcha – the one you configured in Joomla. No captcha configured, no captcha shown.
The mail always goes to exactly one recipient. The address is cleaned and validated before use, and anything that is not a single valid address is refused – a form on a public page that sends mail is a header-injection target, and it is treated like one.
The rate limit
Sending mail from your server on behalf of anonymous visitors is an open endpoint, so it is capped: 8 mails per hour per visitor, counted in the session over a rolling one-hour window. Adjust it if you need to:
define('COM_EVENTGALLERY_MAX_SHARE_MAILS_PER_HOUR', 15);
The mail itself
The mail is a new email template named "Shared Image", so you can change its subject and text in Event Gallery → Email Templates exactly like every other mail Event Gallery sends. It is a new template, so it arrives with the update and does not conflict with anything you have customised.
Logging
Every sent image shows up in the download log for that image, with the type eMail (large image) or eMail (original image) depending on what actually went out. So your download statistics stay honest: an image that left your server counts as an image that left your server, regardless of which button caused it.
GDPR
Since this is the one sharing option that is not passive, the GDPR documentation was extended for it. All other sharing options in Event Gallery only offer a link for the visitor to click – no JavaScript SDKs, nothing transmitted before an interaction.
For this one: the visitor enters the recipient address and optionally a name, a reply address and a message. Event Gallery uses that data for this one mail and does not store it. The visitor has to confirm your data privacy policy before the mail is sent. What is stored is an entry in the download log for the image – exactly as a download would produce.
7. Everything else that changed
The features above are the headline. Here is the rest, and some of it is important.
Security
Track my Order got the same brute force protection. Order numbers are handed out one after another, so the email address was the only thing somebody had to guess in order to see the address, the phone number and the download links of an order. Failed lookups are now counted per session and per IP address – ten per hour, configurable via COM_EVENTGALLERY_MAX_TRACKORDER_ATTEMPTS_PER_HOUR.
The two forms use separate budgets, so somebody guessing order numbers cannot lock a visitor out of a password-protected Event. The contexts even go into the hash, so the rows of one form cannot be told apart from the rows of the other in the database.
The tracking links in your order mails are unaffected – they carry the right order number and email address, and only failed lookups count.
Download tokens use a real random source. The token in the download links of an order now comes from the cryptographic random source instead of uniqid(). uniqid() is mostly the current time, which made the tokens of orders placed around the same moment similar enough to be worth guessing. Links already sitting in your customers' mailboxes keep working; only orders placed from this version on get the new token.
Email templates are rendered under a security policy. A template may use placeholders, {if}, {foreach} and the usual modifiers like upper, truncate or date_format exactly as before. It can no longer call a static method of a PHP class, read a file from the server with {include file="..."}, or look at $_SERVER, $_ENV and PHP constants.
The reason: editing an email template only requires the Edit permission of Event Gallery, which is a great deal less than Super User – and without the policy, such a template could run arbitrary code on the server. Every shipped template is unaffected. If a template of yours uses one of the blocked constructs it now reports an error instead of rendering, so check your customised templates after updating.
Payments are verified properly before an order counts as paid.
- Stripe now checks that the checkout session really was paid, and that it was paid in the amount and the currency of the order.
- PayPal checks that the money went to the configured receiver account and matches the amount and currency of the order. The PayPal payment also now carries the order number inside the message PayPal signs, instead of only in the notification address. Payments started before the update still work; they take the old route and say so in the log.
- A payment that does not match leaves the order waiting for payment and writes the reason into the plugin log. So if an order stays unpaid unexpectedly, that log is the first place to look.
- If you use the deprecated PayPal Adaptive Payments plugin, please read its log after your first payment. PayPal shut that API down, so its notifications could not be tested against a real payment. The plugin now refuses anything it cannot verify rather than assuming it was paid. Consider switching to the ordinary PayPal plugin.
Usability
- Sorting Events in the backend now takes the asc/desc ordering into account when moving Events, so up actually means up. Moving items also respects your current filter, so you can sort sensibly even when filtered-out Events sit in between.
- The password page of an Event got a more modern layout.
- Tabs remember where you were. The backend is full of tabs for configuration options, and the last open tab now re-opens after you hit Save. No more clicking back to where you were after every save.
- Switching between on-demand and pre-rendered images no longer requires deleting
.htaccess/web.configfiles by hand. The files for the thumbnail cache protection are handled automatically. - The FAQ was extended with the steps to switch from on-demand rendering to pre-rendered images.
Under the hood
- Joomla 7 support, achieved by removing deprecated code and moving to current coding standards: the Joomla event dispatcher instead of
triggerEvent,MailerFactoryInterfacefor mail, dependency injection for the HTTP factory, propergetDatabase()usage, consistentDateinstantiation, and correct integer casting for user IDs throughout. - Minimum PHP 8.2.
- Plugin and component installer scripts refactored to anonymous classes implementing
ServiceProviderInterface.
Bug fixes
- Videos were not displayed in the lightbox when inserted with the content plugin in thumbnail mode.
- Fixed an exception when adding new tags to Events using the batch tool.
8. Your update checklist
Print this bit out.
Before you update
- Back up. Three new tables, two new columns, two dropped columns and an
UPDATEthat unpublishes Events. Nothing here is designed to hurt, but back up anyway. - Check your PHP and Joomla versions. PHP 8.2+, Joomla 5, 6 or 7.
- Look for Google Photos API Events. Anything on the API folder type will be unpublished by the update. Decide now whether those albums move to a shared link, get uploaded via the picker, or simply retire.
After you update
- Edit the new_order email template and add the promotion block – or hit Load Default if you never customised it. Do this even if you are not planning to use promotions yet, so it is already right when you do.
- Check your customised email templates for static method calls,
{include file="..."}or$_SERVER/$_ENV/constant access. Those now report an error instead of rendering. - If you are behind a proxy or CDN, switch on Global Configuration → Server → Behind Load Balancer. Otherwise every visitor shares one attempt budget.
- Set the Ajax List thumbnail area height if 280px does not suit your layout. Your old thumbs-per-page values are gone and ignored.
- Check your template overrides:
html/com_eventgallery/password/default.phpneeds the captcha block and the prefilled password value; forhtml/com_eventgallery/event/ajaxpaging.phpthe markup changed and page-building overrides are now redundant. - Test a payment if you use Stripe, PayPal or especially PayPal Adaptive Payments, and read the plugin log afterwards.
- Tell your customers that password links now need one extra click. They still work – but if you have a support inbox, a heads-up saves you a few emails.
Then go and try the new things
- Create an Event Access menu item, write a friendly Instructions text, and hand out one link instead of forty.
- Build a promotion. Start with something simple – 10% off carts above 50 EUR, no codes, applies automatically – and watch the checkout do the maths.
As always, if something behaves oddly after the update, the plugin logs and the Event Gallery logs are the first place to look, and I am reachable through the usual channels.
Happy shooting.