Recent Activities
This page shows what are we working on.
-
head
-
1.0.10
-
Version 1.0.10 b8b88d
-
Fix child labels ignoring schedule, store and product status
- Skip expired labels and pass active period to frontend
- Filter index by store view, skip disabled and out-of-website children
- Validate $mode before using it in SQL column names
- Require swissup/module-pro-labels ^1.10.0 (active period columns) 3074b5
-
-
head
-
1.10.3
-
Version 1.10.3 fee5d6
-
Fix stored XSS in category labels data-mage-init JSON (#50)
Labels JSON is output in a single-quoted data-mage-init attribute. The
browser decodes HTML entities in attribute values before JS reads them,
so escaped variable values (<img onerror=..>) and entity-encoded
label text turned into live HTML and were inserted via insertAdjacentHTML.
" could also break out of the JSON and inject other mage-init
components.
Encode with JSON_HEX_AMP | JSON_HEX_TAG so no "&", "<", ">" reach HTML.
Values decode identically in JSON.parse; script-context consumers
(x-magento-init) are unaffected.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> bc43fd -
Fix stored XSS in category labels data-mage-init JSON
Labels JSON is output in a single-quoted data-mage-init attribute. The
browser decodes HTML entities in attribute values before JS reads them,
so escaped variable values (<img onerror=..>) and entity-encoded
label text turned into live HTML and were inserted via insertAdjacentHTML.
" could also break out of the JSON and inject other mage-init
components.
Encode with JSON_HEX_AMP | JSON_HEX_TAG so no "&", "<", ">" reach HTML.
Values decode identically in JSON.parse; script-context consumers
(x-magento-init) are unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> a1aeee -
Force string type for typed functions.
https://app.basecamp.com/6059321/buckets/44137853/todos/10378503157#__recording_10383529593 d5c6c0 -
Implement label content validation and enhance security for label rendering b73f5a
-
Merge tag '1.10.2'
1.10.2 d539b4
-
-
head
-
fix(cron): do not append inbound e-mail to a ticket the sender does not own (#70)
Pick-up resolved the ticket from In-Reply-To or from the number in the
subject and appended the message without looking at the sender, so a
forged From: could post into another customer's ticket — as staff, if
the address matched a department user.
The sender now has to belong to the ticket. When it does not, the
reference is dropped and the message opens its own ticket, with a
warning in the log: nothing is lost and nothing lands in a conversation
it does not belong to.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 10123f -
feat(security): add sender check for inbound e-mail (#70)
Answers whether an unauthenticated From: may be appended to an existing
ticket: staff of the receiving department, the address the ticket was
opened with, or the account it belongs to. Covered by unit tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 22fee1 -
fix(message): reject replies to tickets the visitor does not own (#69)
The reply controller loaded the ticket by its public number and appended
the message without ever tying the ticket to the session, so any visitor
holding a valid form key could post into another customer's ticket.
The ticket now has to pass the same owner check the view controller
applies, and an unknown ticket and a foreign one give the same answer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 9428a3 -
refactor(ticket): take the view controller's owner check from the shared service (#69)
No behaviour change: the same two rules, now expressed once so the view
and the reply controller cannot drift apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 06c155 -
feat(security): add ticket access checker shared by view and reply (#69)
A single place that answers "may this visitor act on this ticket":
a customer ticket belongs to its customer, a guest ticket to whoever
presents the e-mail it was created with. Covered by unit tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 057646
-
-
1.4.9
-
Version 1.4.9 b91af7
-
Merge pull request #66 from swissup/fix/attachment-path-traversal-65
fix(file): close arbitrary file read via client-controlled attachment path (#65) ef4e71 -
fix(file): match whole attachment entries, not substrings, when authorising download (#65)
The download check used LIKE %path%, so a visitor could attach
'/a/b/ab.png.mine.png' and fetch another visitor's '/a/b/ab.png' with
their own message hash. Now the path must equal one entry of the
';' separated list, case-sensitively.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 5b6ebd -
Merge remote-tracking branch 'origin/master' into fix/attachment-path-traversal-65 8bc6e2
-
Merge pull request #68 from swissup/fix/laminas-mail-dependency-67
fix(deps): suggest laminas/laminas-mail for inbound email (#67) 00ecdf -
fix(email-storage): clear error when laminas-mail has no Xoauth2 support (#67)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> d31dde -
fix(upload): spend registered attachments only after a successful save (#65)
filter() no longer mutates the registry; markUsed() runs after the save, so
a validation error or exception does not force the visitor to re-upload.
register() now ignores non-string values.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 4c69a8 -
fix(file): use explicit LIKE ESCAPE that ignores NO_BACKSLASH_ESCAPES (#65)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 2289d7 -
fix(cron): skip e-mail pickup with a log message when laminas-mail is missing (#67)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 2af508 -
fix(deps): suggest laminas-mail instead of requiring it (#67)
laminas-mail 2.25.1 caps PHP at 8.3, so a hard require makes the module
uninstallable on PHP 8.4. Suggest it (and the oroinc PHP 8.4 fork) instead.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> ca9ad8 -
fix(email-storage): initialise $authResponse before by-ref readLine (#67)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> a0a990 -
fix(deps): declare laminas/laminas-mail ^2.20 (#67)
Magento 2.4.8 mails via symfony/mailer and no longer requires it, but
inbound email uses Laminas\Mail, including Xoauth2 classes added in 2.20.0.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 7b43d6 -
fix(upload): accept only attachments uploaded in the visitor's own session (#65)
Ticket and message forms posted any string as the attachment path.
Now the upload endpoint registers each stored file in the session and the
save controllers keep only registered, not yet used, files.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> e0cb82 -
feat(security): add session registry of uploaded attachments (#65)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 86ced9 -
fix(message): do not build download links for unsafe stored paths (#65)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 761eed -
fix(file): reject unsafe paths in download and escape LIKE wildcards (#65)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> a34414 -
fix(file): add attachment path validator to FileInfo (#65)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 7e53d7
-
-
head
-
Don't process invisible fields (causing js errors on breeze checkout) 64742b
-
-
head
-
2.32.1
-
Version 2.32.1 c5afe6
-
Prevent infinite loop when head contains invalid stylesheet link 582a77
-
Fixed incorrect magnifier image when using expanded gallery fc2f6b
-
Prevent auth popup form submit when recaptcha is not ready yet
Closes #101 740ada -
Fixed incorrectly marked lazyload when main image is not the first one d5cd6b
-
Do not add dom duplicates to $.registry 84beb6
-
-
head
-
fix(skills): check the query pool and the result where they are built (#189)
Review of the contract itself turned up four places where it promised
more than it enforced.
QuerySkillPool refused a misregistered skill only through the return
type of get(): a \TypeError, which the LocalizedException handlers
around every pool call do not catch, and only on the path that calls
get() — getAll() has no return type to protect it and handed the wrong
object straight to a consumer. The members are now checked once at
construction, with the di.xml item's code in the message. SkillPool is
left exactly as it was; widening its behaviour is not part of #189.
QueryResult validated nothing, so the "structure beats a string"
argument stopped one level short of the resolver that reads it. An id
decoded from a model's JSON arrives as "42" as readily as 42, and a
resolver comparing a string against a loaded product finds nothing and
reports nothing. entity_type and entity_id are now normalised, a row
carrying neither is refused, and the optional fields are left untouched.
QueryResultInterface::getItems() documented its shape in PHPStan
generics, which Magento's TypeProcessor cannot read — isArrayType()
matches `[]` and `ArrayOf` only, so the shape would be taken for a class
name the day a webapi route names this interface. Now `array[]` for
Magento with the shape kept on a @phpstan-return beside it.
docs/DEVELOPMENT.md still said every capability implements SkillInterface
and registers into SkillPool, which is the sentence #190 would read
before putting a search skill in the wrong pool. Both kinds are
described now.
Unit suite 2208 tests. 2.4.7 and 2.4.8 validator runs repeated on the
amended tree, positive control still fires.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 1d185e -
Merge pull request #202 from breezefront/fix/openai-error-classification-201
fix(providers): OpenAI 401/429 were never classified, and a bulk 429 was never retriable (#201) 89ddd9 -
fix(providers): mark a provider outage retriable too, 529 included (#201)
Follow-up to the rate-limit work in this branch, which left 5xx alone.
A provider that was reachable and could not answer says nothing about the
request either, so it wants the same handling a 429 now gets: keep the bulk
operation for the admin's Retry button instead of recording it as dead work.
Model\Exception\ProviderUnavailableException carries the same
TemporaryStateExceptionInterface, and all three text providers and both image
generators raise it.
The statuses are a list - 500, 502, 503, 504, 529 - and not a `>= 500`. 501 is
the 5xx that will still be unimplemented tomorrow, and retrying it is the loop
the list exists to avoid. 529 is in it because Anthropic answers an overloaded
model with a status no range check written for the other four would catch.
The provider's own words travel with the new sentence rather than being
replaced by it. A 5xx from a proxy standing in for the endpoint carries the
only explanation there is, and two existing tests rely on exactly that: the
Gemini and OpenAI-image redaction tests use a 5xx to prove the API key is
masked on the way out. Both still pass untouched, which is the check that the
detail - and the redaction - survived the reclassification.
Verified by running it: the six new cases all fail against the previous commit,
the full suite is green (2205 tests), and di:compile's validators report no
findings over the module's 225 classes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> affc3b -
fix(providers): OpenAI 401/429 were never classified, and a bulk 429 was never retriable (#201)
handleException() decided what had gone wrong by looking for the status
number in the exception message. openai-php never writes it there, so both
branches were dead and every failure reached the merchant as the SDK's own
sentence - the reported one being "OpenAI error: Request rate limit has been
exceeded." The status is read off the exception now, the way Claude and
Gemini already read theirs.
It cannot be read the same way, though: openai-php exceptions have no
getResponse(), they carry a public $response, and only ErrorException adds
getStatusCode(). statusOf() names each shape instead of probing for one.
An exhausted balance is separated from a rate limit while we are here. OpenAI
answers both with 429 and only the error code tells them apart, and the two
want opposite handling now that one of them is retried.
Which is the second half. README promised that a 429 in Bulk Actions was
"automatically retried by Magento"; it was not retried at all, because every
provider failure reached AbstractConsumer as a plain LocalizedException and
was recorded as STATUS_TYPE_NOT_RETRIABLY_FAILED - so a store that tripped
its per-minute limit mid-run lost every remaining product permanently. A rate
limit now raises Model\Exception\RateLimitException, which carries Magento's
TemporaryStateExceptionInterface, and the consumer honours that interface on
its LocalizedException branch as it already did on the NoSuchEntityException
one. All three text providers and both image generators raise it.
Magento still does not re-run a retriable operation on its own, so the README
says what actually happens instead: the operation is listed in the admin's
bulk detail page and the Retry button re-queues it.
The unit tests fed handleException() `new \Exception('Error 429 Too Many
Requests')`, a message openai-php would never produce, which is how the dead
branches stayed green. They now build the SDK's real exception types and go
through chat() rather than reflecting into the private method.
Verified by running it: the five new OpenAI cases all fail against master -
including one that master mislabels a statusless timeout as a rate limit
because its text contains "429" - and the full suite is green (2198 tests).
setup:di:compile's own validators report no findings on the module, the #197
check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 51119b -
feat(skills): a query-scoped skill contract, so a search skill can exist (#189)
SkillInterface::execute() takes an `object $entity`. A product-discovery
skill has none: it is handed a phrase and the storefront scope, and the
entities are its result. #190 and #192 both stop here.
Adds, without touching anything that exists:
* Api\QuerySkillInterface — getCode/getLabel/execute(string $query,
array $history, array $options). Scope travels in $options, as it
already does through GenerateService, AiRequest and every entity
skill; a typed context object would have to be assembled by each of
the planned callers for no consumer that exists yet.
* Api\Data\QueryResultInterface + Model\Data\QueryResult — answer text
plus the entities it refers to. A query skill returns structure, not
a string: BuildPage shows the cost of the alternative, its caller
json_decodes the result and finds a malformed answer three frames
from where it was produced (BulkConsumer.php:348). Cost and latency
stay out — the usage ledger (#171) already records both per request.
* Model\QuerySkillPool — a second pool, not a widened SkillPool.
Every consumer of SkillPool assumes entity semantics: SkillSupport
and BulkAction\ViewData call getSupportedEntityTypes(), McpServer
gives each pooled skill a tool whose schema requires entityType and
entityId. One pool of a common parent type does not stay typed; it
becomes a parent type plus an instanceof in four places, and a query
skill nobody filtered out is published as a tool no client can call.
QuerySkillInterface deliberately does not share a parent interface with
SkillInterface. Declaring getCode() and getLabel() twice keeps this
change strictly additive, so no implementor — ours or a merchant's —
can break on upgrade; extracting the parent later is itself backwards
compatible, so the option costs nothing to defer.
The five existing skills, SkillPool, BulkConsumer and the MCP tool list
are untouched. Unit tests cover resolution of both kinds and both
cross-kind mistakes.
Checked on 2.4.7 as well as 2.4.8 (#197): ConstructorIntegrity,
TypeDuplication and ArgumentSequence against a standalone
magento/framework 103.0.7.0-patch10 and 103.0.8.0-patch1 — clean on
both, with the #197 widening re-introduced as a positive control to
prove the gate was live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 013f50 -
fix(content-builder): keep the failed payload and name the output limit (#188)
Review of #199 found five things the structured-output move left behind.
The providers carry no logger, so a payload that is not put on the
exception is gone: StructuredResponseException now holds the raw reply
and BuildPage logs it, which is what master's logUnreadableReply() was
written to keep. Null on a truncation or a refusal, where the stop
reason settles it before a body exists.
A document cut off inside the "components" string is the one shape of
#143 the stop reason cannot catch — the envelope closes normally and
the provider reports an ordinary stop — so the sentence for an
unreadable document names the output limit itself.
Also: a negative "bands" is read as no count rather than as the number
it says, because `minimum` is outside the subset Schema\Validator
accepts and BandAudit would otherwise report "read as -2 band(s)"; and
a reply that missed the schema no longer borrows the document's
sentence, in the same words and punctuation on both skills.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 8568ec -
chore(content-builder): move BuildPage and PlanPage to structured output (#188)
Both skills asked for JSON in the prompt and dug the answer out of the reply
text afterwards. That cost us the one fact that separates the failures: a
document the provider cut off at the output limit and a document the model
mangled both reached the merchant as json_decode's "Syntax error" (#143), and
they want opposite answers — raise a number, or try again.
Both now call AiRequest::sendStructured() with a response schema (#187), and a
call that produced no usable answer arrives as StructuredResponseException with
a reason. Each skill turns the three reasons into three named sentences,
carrying the provider's own message — the one that names "Max Tokens" — whole.
PlanPage is fully structured: PlanSchema describes {"sections": [...]} and the
skill parses nothing at all. extractJson() and its json_decode are gone.
BuildPage gets as far as the schema subset reaches. A Content Builder node has
free-form "props" and recursive "children", and neither is expressible under
OpenAI strict mode — nor, therefore, under the intersection Schema\Validator
holds. So DocumentSchema schematises the envelope ("bands" an integer or null,
"unsupported" a list of strings, both checked on the way out) and the document
travels inside it as a JSON string, read by the new ContentBuilder\DocumentReader.
DocumentSchema's class comment sets out why, and what finishing the job would
take: a flat node list with parent references, which is a measurement to run
rather than a refactor to slip in here.
What that removes from Model/Skill: the fenced-block regex, the widest-bracket-run
candidates, the lone-backslash repair pass, the hand-written bracket scanner that
sliced a broken list element by element, and the envelope scraper. Around 630
lines of BuildPage. The partial-page salvage goes with them — a truncation is now
reported as a truncation before any document exists, instead of handing back a
prefix nobody could verify.
StopReason stays and is unchanged; it is read off the response field by
Provider\StructuredResponse, which is where it already was.
Checked: no json_decode or preg_match over a model reply is left in Model/Skill;
the two schemas pass Schema\Validator and lower onto all three dialects
(SkillSchemaTest); truncation, refusal and a reply that missed the shape each
produce a different named error in both skills' tests. 2185 unit tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> e506b3
-
-
1.8.22
-
Version 1.8.22 9893b7
-
fix(providers): StructuredResponseException cause type breaks di:compile on 2.4.7 (#197) (#198)
* fix(providers): StructuredResponseException cause type breaks di:compile on 2.4.7 (#197)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* style(providers): wrap the cause-type comment (#197)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> 1d4842 -
style(providers): wrap the cause-type comment (#197)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 602747 -
fix(providers): StructuredResponseException cause type breaks di:compile on 2.4.7 (#197)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> a961ae
-
-
head
-
Fix missing aria-level on tab headings in expanded layout 2523f0
-
-
1.13.6
-
Version 1.13.6 fe61d0
-
Fix product visibility bypass and GraphQL tab conditions bypass (#38)
* Fix product visibility bypass and GraphQL tab conditions bypass
- Helper\Product::canShow(): parent_id fallback now applies only to
enabled products and only when parent_id is their real parent
(catalog_product_relation). Previously any visible product id in
parent_id exposed disabled products via the easytabs AJAX endpoint.
- GraphQL getEasyTab now validates tab conditions (customer group, etc.)
using the customer group from GraphQL context, like the storefront does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address review: stable parent check, duplicate aliases, real customer group
- Helper\Product: check parent's children via type instance
getChildrenIds() instead of Relation::getRelationsByChildren(),
whose return shape is not a stable API. Drops ObjectManager usage.
- GraphQL data provider: with duplicate aliases, return first tab that
passes conditions instead of failing on the highest-priority one.
- GraphQL resolver: load caller's customer group via customer repository
instead of defaulting to guest when context has no customer_group_id
(guest fallback exposed "not in group X" tabs on older Magento).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pick same tab as storefront for duplicate aliases in GraphQL
Mirror Block\Tabs::_buildTabs(): among tabs passing conditions, prefer
the requested store view over default store view (0), and within a store
take the same tab the storefront does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 90330e -
Pick same tab as storefront for duplicate aliases in GraphQL
Mirror Block\Tabs::_buildTabs(): among tabs passing conditions, prefer
the requested store view over default store view (0), and within a store
take the same tab the storefront does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 06c0a8 -
Address review: stable parent check, duplicate aliases, real customer group
- Helper\Product: check parent's children via type instance
getChildrenIds() instead of Relation::getRelationsByChildren(),
whose return shape is not a stable API. Drops ObjectManager usage.
- GraphQL data provider: with duplicate aliases, return first tab that
passes conditions instead of failing on the highest-priority one.
- GraphQL resolver: load caller's customer group via customer repository
instead of defaulting to guest when context has no customer_group_id
(guest fallback exposed "not in group X" tabs on older Magento).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 3871b9 -
Fix product visibility bypass and GraphQL tab conditions bypass
- Helper\Product::canShow(): parent_id fallback now applies only to
enabled products and only when parent_id is their real parent
(catalog_product_relation). Previously any visible product id in
parent_id exposed disabled products via the easytabs AJAX endpoint.
- GraphQL getEasyTab now validates tab conditions (customer group, etc.)
using the customer group from GraphQL context, like the storefront does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 3a8cc0
-
-
1.12.1
-
Version 1.12.1 b0203e
-
Skip composer cache dirs outside open_basedir
Probing ~/.cache/composer, ~/.composer/cache and /etc/xdg triggered an
open_basedir warning that Magento converts into an exception. Composer
falls back to var/composer_home/cache in this case.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> f11bc2 -
Don't run composer from the backend to read channels
The package grid ran composer in-process to read repositories and
credentials. ComposerApplication's constructor probed cache dirs outside
the Magento root, which fails under open_basedir.
- ChannelManager reads repositories from composer.json and credentials
from root auth.json (or var/composer_home/auth.json) directly.
- ComposerApplication prepares composer on the first run() instead of
in the constructor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> e9f763
-
-
1.12.0
-
Version 1.12.0 346881
-
Typo fix 50a7b6
-
Cleanup CSS ea255a
-
Fix grid styles 4d48e9
-
Stop running composer from the backend (#17)
Running composer through the web server is not reliable, so the backend no
longer dispatches composer operations. Install, Update, Remove, Enable and
Disable now open a modal with the command to copy and run in a terminal, both
for a single row and for a massaction. Run Installer stays a real backend
operation - the one-click installer does not use composer.
Enable and Disable had no console equivalent, so marketplace:package:enable and
marketplace:package:disable were added (aliases marketplace:enable and
marketplace:disable).
Channel configuration and Tasks History are removed from the backend as well.
Channels are managed with the marketplace:channel:* and marketplace:auth:*
commands. With the settings form gone, ChannelsSave was the last job producer,
so the whole queue is removed: JobDispatcher, QueueDispatcher, Validator, the
Job entity, the activity widget, the queue crons and the job table.
The SettingsDataProvider modifiers are kept as empty stubs so that the channel
modules registering them in the pool keep compiling.
BREAKING: the swissup_marketplace_job table is dropped on setup:upgrade, and
HandlerInterface::validateBeforeDispatch() is removed - it had no callers left.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> cccd99 -
Ignore the packages with an invalid name
The channel metadata is not validated, and the package name ends up in
the command we suggest to copy and run. A name containing ;, $() or a
whitespace turns that command into something else.
Filtered where the channel data is read, so that such a package never
reaches the grid, the suggested command, or the installer request. The
pattern is the one composer validates the names with, so nothing that
composer is able to install is dropped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 956e42 -
Improve package disable/enable operations cc8491
-
Address review comments
- Massaction: require the row to expose the action before offering a
command. Local.php marks every downloaded package as enabled and only
overrides it for the modules, so a downloaded theme passed the shared
disable check even though Links.php never adds Enable/Disable to a
non-module row - marketplace:disable would then write a derived, bogus
module name to config.php.
- Copy button: writeText() is asynchronous and rejects when the clipboard
is blocked. Copy now returns a deferred, falls back to execCommand on
rejection, and the Copied label is shown only once a copy succeeded.
- README grammar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> f2fdaf -
Stop running composer from the backend
Running composer through the web server was never reliable, so the backend no
longer dispatches composer operations. Install, Update, Remove, Enable and
Disable now open a modal with the command to copy and run in a terminal, both
for a single row and for a massaction. Run Installer stays a real backend
operation - the one-click installer does not use composer.
Enable and Disable had no console equivalent, so marketplace:package:enable and
marketplace:package:disable were added (aliases marketplace:enable and
marketplace:disable).
Channel configuration and Tasks History are removed from the backend as well.
Channels are managed with the marketplace:channel:* and marketplace:auth:*
commands. With the settings form gone, ChannelsSave was the last job producer,
so the whole queue is removed: JobDispatcher, QueueDispatcher, Validator, the
Job entity, the activity widget, the queue crons and the job table.
The SettingsDataProvider modifiers are kept as empty stubs so that the channel
modules registering them in the pool keep compiling.
BREAKING: the swissup_marketplace_job table is dropped on setup:upgrade, and
HandlerInterface::validateBeforeDispatch() is removed - it had no callers left.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> d70a96 -
Forward command output strings to terminal c17318
-
Allow using dry-run option 2d329d
-
Reuse composer_cache from user's directory if possible 9cc10a
-
Command shortcuts 2cfa4c
-
-
0.7.15
-
Version 0.7.15 cd67bf
-
fix(smtp): reset STARTTLS state when the handshake fails (#44)
A 220 reply to STARTTLS marked the connection as upgraded before the
TLS handshake ran; if the handshake failed, the flag stayed set on a
plain socket until the next start(). Clear it when start() fails and
drop the redundant stop() override.
Tests now also drive start() against a scripted SMTP server in a child
process: a real STARTTLS handshake followed by AUTH, a server that
does not offer STARTTLS, and a failed handshake.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 8b8e10 -
fix(smtp): allow mail after a STARTTLS upgrade (#44)
SocketStream::isTLS() reports implicit TLS (port 465) only and stays
false after STARTTLS, so RequireTlsSmtp refused AUTH on ports 587/2525
even though the connection was encrypted. Track the accepted STARTTLS
in the transport and reset it on reconnect.
Fixes #44
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 622063 -
fix(smtp): reset STARTTLS state when the handshake fails (#44)
A 220 reply to STARTTLS marked the connection as upgraded before the
TLS handshake ran; if the handshake failed, the flag stayed set on a
plain socket until the next start(). Clear it when start() fails and
drop the redundant stop() override.
Tests now also drive start() against a scripted SMTP server in a child
process: a real STARTTLS handshake followed by AUTH, a server that
does not offer STARTTLS, and a failed handshake.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 4c4769 -
fix(smtp): allow mail after a STARTTLS upgrade (#44)
SocketStream::isTLS() reports implicit TLS (port 465) only and stays
false after STARTTLS, so RequireTlsSmtp refused AUTH on ports 587/2525
even though the connection was encrypted. Track the accepted STARTTLS
in the transport and reset it on reconnect.
Fixes #44
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> f5e969
-
-
0.7.14
-
Version 0.7.14 d58265
-
feat(log): delete logged emails after a configurable retention period (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> e84db5 -
fix(acl): use matching ACL resources and ignore redirect and token fields in save (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> e3a5f5 -
fix(service): stop exposing the stored password in the form and URL (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> e1d05d -
fix(smtp): enforce the SSL and TLS options of mail services (#42)
SSL now uses implicit TLS (smtps). TLS now requires STARTTLS instead of silently continuing in clear text.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 61be08 -
fix(grid): check ACL and stop returning bodies and passwords in grid data (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> a0da7f -
fix(acl): require ACL resources for inline edit and mass actions (#42)
These actions fell back to Magento_Backend::admin, so any admin role could change or delete mail services.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> f2baec -
test: add PHPUnit setup (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 4ee61c -
feat(log): delete logged emails after a configurable retention period (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 355c80 -
fix(acl): use matching ACL resources and ignore redirect and token fields in save (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 72377f -
fix(service): stop exposing the stored password in the form and URL (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> f0a25a -
fix(smtp): enforce the SSL and TLS options of mail services (#42)
SSL now uses implicit TLS (smtps). TLS now requires STARTTLS instead of silently continuing in clear text.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> b914b0 -
fix(acl): use matching ACL resources and ignore redirect and token fields in save (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 5d4124 -
fix(service): stop exposing the stored password in the form and URL (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 354f53 -
fix(smtp): enforce the SSL and TLS options of mail services (#42)
SSL now uses implicit TLS (smtps). TLS now requires STARTTLS instead of silently continuing in clear text.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 342801 -
fix(grid): check ACL and stop returning bodies and passwords in grid data (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 8a3b83 -
fix(acl): require ACL resources for inline edit and mass actions (#42)
These actions fell back to Magento_Backend::admin, so any admin role could change or delete mail services.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 6d5242 -
test: add PHPUnit setup (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> dec9a8
-