Recent Activities

This page shows what are we working on.

swissup / module-pro-labels-configurable-product

25 minutes ago success
  • head
    • Implement labels loading and updating for configurable products with AJAX support at listing as well (close #2) c90c71

    • Add AJAX controller for fetching product labels and update label handling logic 33072b

  • 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

swissup / module-pro-labels

42 minutes ago success
  • head
    • Refactor preloadLabels and getProductLabels methods to accept mode parameter 22d0a7

    • Add class to div for prolabels initialization in toHtmlProductLabels method 325ba2

  • 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 (&lt;img onerror=..&gt;) and entity-encoded
      label text turned into live HTML and were inserted via insertAdjacentHTML.
      &quot; 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

swissup / module-helpdesk

6 hours ago success
  • 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

swissup / module-address-field-manager

6 hours ago success

breezefront / module-breeze

7 hours ago success
  • head
    • Added `validation.check` method 531321

    • `$.grep` implementation c5ec90

    • Fixed possible js error `Cannot read properties of undefined` fb7bef

    • Do not load js component if it uses `componentDisabled: true` cd0baf

  • 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

breezefront / module-breeze-ai

7 hours ago success
  • 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

swissup / module-easytabs

7 hours ago success
  • 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

swissup / module-marketplace

13 hours ago success
  • 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

swissup / module-email

1 day ago success
  • 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