| application | by dg | Presenter::argsToParams() now distinguish between NULL and '' | | | |
| application | by dg | removed {templatePrint} | | | |
| application | by dg | uses nette/routing 4.0 | | | |
| application | by dg | RoutingPanel: added support for AI agents | | | |
| application | by dg | removed support for @annotations (BC break) | | | |
| application | by dg | Presenter: removed constructor (BC break!) | | | |
| application | by dg | Component: only UI components can be added to presenter/component (BC break) WIP | | | |
| application | by dg | Component: method checkRequirements() is called for createComponent<Name>() methods (BC break) | | | |
| application | by dg | Revert "UI\PresenterComponent: removed references created by loadState() for persistent parameters. [Closes nette/nette#703][Closes nette/nette#703][Closes #69]" (possible BC break) This reverts commit cda17f460d020b0f042364d4e140742022a7e94d. See https://forum.nette.org/cs/35528-stejne-pojmenovany-parametr-akce-presenteru-a-persistentni-odlisne-chovani-v-nette-2-0-oproti-aktualnimu#p221742 BC break: Property must be nullable, ie: #[Persistent] public ?int $foo | | | |
| application | by dg | Presenter::handleInvalidLink() -> processInvalidLink() (BC break) | | | |
| application | by dg | added type hints (BC break) | | | |
| application | by dg | composer: increased dependencies versions | | | |
| application | by dg | opened 4.0-dev | | | |
| application | by dg | added support for backed enums in parameters [Closes nette/nette#1558] Persistent, action, render and signal parameters can be typed as a backed enum. The URL carries the case value: link() unwraps BackedEnum instances before the URL is built (http_build_query would otherwise decompose the object into an array), and incoming scalars are losslessly converted back via tryFrom(); an invalid value yields 4xx, not 500. | | | |
| application | by dg | typo | | | |
| application | by dg | UI\Form: adapted to the SubmissionSource model of nette/forms 4.0 The form no longer overrides receiveHttpData(), isAnchored() and beforeRender(); instead it overrides createDefaultSource() to return null and sets the @internal FormSubmissionSource when anchored to a presenter. The source detects submission via the 'submit' signal, reads the application request and installs the hidden signal field in its prepare() render hook; it resolves the presenter from the form lazily, so a form re-anchored to another presenter keeps working. The catch-up pass of setSubmissionSource() replaces the manual loadHttpData() loop in validateParent(). The form owns its cross-origin state (private ?FetchSite $allowedOrigin + allowCrossOrigin()) independently of nette/forms; the same-origin check intentionally stays in signalReceived(), where a failure is reported via Presenter::detectedCsrf(). Requires nette/forms 4.0. | | | |
| application | by dg | UI\Form: compatible with both nette/forms 3.x and the upcoming 4.0 Two forward-compatible additions to the classic presenter form: - The form owns its cross-origin state (private ?FetchSite $allowedOrigin + allowCrossOrigin()) instead of relying on the protected $crossOrigin inherited from Nette\Forms\Form. Forms 3.x declares that property untyped while 4.0 removes it, so no redeclaration can satisfy both; a differently named private property works with either version. - createDefaultSource() returns null, which under forms 4.0 prevents the lazy default HttpSource from materializing (the form is anchored via the presenter). Under 3.x the method is unused; its return type references an interface that only exists in 4.0, which is safe because without a parent method there is no variance check. The classic receiveHttpData()/isAnchored()/beforeRender() overrides double as the documented BC seam of forms 4.0, so a single class works with both major versions. | | | |
| application | by dg | ControlNode: deprecate missing comma before arguments Brings the {form}/{input}/{label} deprecation to {control} via the shared TagParser::consumeCommaBeforeArguments() helper. Triggers E_USER_DEPRECATED when a token follows the name (and optional :method) without a separating comma, pointing to the offending column; the tag still compiles. A trailing |modifier is not mistaken for arguments. | | | |
| nette | by dg | added AGENTS.md | | | |
| nette | by dg | improved readme.md | | | |
| nette | by dg | composer: relaxed dependencies to minimum required versions Caret constraints replaced with >=, so the metapackage never holds back a newer major of any component. Minimums raised to the versions Nette 4.0 actually needs, added nette/command-line, dropped nette/tokenizer. | | | |
| nette | by dg | composer: removed obsolete nette/tokenizer | | | |
| nette | by dg | singleline declare statements | | | |
| nette | by dg | updated .gitattributes | | | |
| latte | by dg | HtmlHelpers: contenteditable, draggable, spellcheck & hidden are not boolean attributes (BC break) None of them is a boolean attribute in HTML; they are enumerated ones, so rendering them as a bare name was wrong in three separate ways: - a string 'false' is truthy in PHP, so contenteditable={$s} rendered the attribute present, i.e. the exact opposite of what was asked for - the empty value is not a keyword of draggable, it maps to the invalid value default 'auto', so draggable=true could not be expressed at all - 'attribute missing' and 'attribute false' are different states; the false value is what disables editing inside a contenteditable region, and dropping the attribute silently inherited from the parent instead Two new categories replace them. 'tristate' (contenteditable, draggable, spellcheck, writingsuggestions) maps bool to the true/false keywords, exactly like aria-* already does, so formatAriaAttribute now delegates to it. The 'valuedBool' one (hidden, popover) stays a flag but keeps a non-empty value, so hidden="until-found" survives. Falsy values are tested first there: hidden with a '0' coming from a database must not render hidden="0", which is an invalid value and would map back to the hidden state. | | | |
| mail | by dg | CssInliner: resolves declarations by the CSS cascade Rules were applied in the order they appeared, so the last one to mention a property won. A browser does not work that way: p.intro { color: red } followed by p { color: blue } paints the intro red, while the inliner painted it blue. The inlined mail therefore looked different from the page the CSS was written for, and the more carefully the stylesheet was written, the more it diverged. Declarations now compete the way they do in an author stylesheet: !important first, then an existing inline style, then specificity, ties going to the later rule. Each selector in a comma-separated list carries its own specificity, and one part the DOM engine rejects (::marker) no longer discards the whole rule. The argument of an ordinary functional pseudo-class is a keyword or an An+B expression, not a selector, so the idents in :nth-child(odd) or :nth-child(-n+3) do not count as type selectors; counting them would inflate specificity enough to flip a winner. Only the winner of each property is written out, so a property appears in the style attribute once rather than several times with the earlier values trailing. HTML attributes generated for Outlook (bgcolor, width) drop the !important marker, which has no meaning in an attribute. A '}' inside a style attribute would close the block the attribute is wrapped in for parsing and turn the remainder into rules of its own, read back as the element's inline declarations. Such an attribute is not a plain declaration list and is kept verbatim. | | | |
| mail | by dg | CssInliner: do not emit an attribute a value cannot express, and fold property case - width: auto (or inherit, or calc(...)) was cast to an integer and emitted as width="0", collapsing the cell in Outlook. The inliner thus broke a layout the source CSS had left perfectly fine. A numeric attribute is now written only for a plain length or percentage, and calc() no longer slips through on its '%'. - Property names are case-insensitive in CSS but were compared verbatim, so COLOR and color ended up as two separate declarations, and a WIDTH: 600px never produced the width attribute for Outlook. Custom properties keep their case, being genuinely case-sensitive. | | | |
| mail | by dg | FileMailer: writes emails to .eml files instead of sending them In development and in tests, mail must not leave the machine, yet it still needs looking at. Redirecting everything to a developer's inbox (Interceptor) needs a working transport, and the Tracy panel only shows the envelope, not the message. FileMailer drops each message into a directory as a complete .eml file, which any mail client opens -- the rendered HTML, the attachments, the headers, all of it. It is a Mailer like any other, so it plugs in wherever a real transport goes, and it signs with DKIM if given a signer, which makes the signature inspectable too. | | | |
| mail | by dg | FallbackMailer: stops retrying a mailer that refused for good Every failure was treated as worth another go. A message the server rejected outright -- 550 no such user, bad credentials -- was resent to the same server retryCount times, sleeping between rounds, for an answer that could not change. Sending was slow to fail exactly when failing fast was the only useful outcome. SendException now knows whether it is permanent, and SmtpException derives that from the reply: 5xx is a permanent negative completion, 4xx a transient one (RFC 5321, §4.2.1). A mailer that fails permanently drops out of the remaining rounds, so the fallback is tried immediately and the others keep their retries. A failure with no reply behind it (timeout, dropped connection) stays retryable, as does a plain SendException, so existing mailers behave exactly as before. | | | |
| mail | by dg | Message: added setUnsubscribe() for one-click unsubscribe Gmail and Yahoo require bulk senders to offer one-click unsubscribe, and getting it right means two headers that have to agree: List-Unsubscribe with the target, and List-Unsubscribe-Post to announce that a bare POST is enough. A lone List-Unsubscribe does not satisfy the requirement, which is easy to get wrong by hand -- exactly the kind of knowledge the library should hold. setUnsubscribe($url, $email) writes both, announcing one-click only alongside a URL (a mailto target has nothing to POST to). The unsubscribe headers also join the DKIM defaults, so the address a click goes to cannot be swapped in transit; they are only signed when present, so no existing signature changes. | | | |
| mail | by dg | Message: converts internationalized domains to punycode An address like jan@příklad.cz passed validation and then travelled with a UTF-8 domain in the headers and, worse, in the SMTP envelope. That is only legal when both peers negotiate SMTPUTF8 (RFC 6531); an ordinary server rejects the RCPT TO or mangles the address, and the failure is puzzling because the address looked fine going in. The domain of every address -- From, To, Cc, Bcc, Reply-To, Return-Path -- is now encoded to its ASCII form, so jan@xn--pklad-zsa96e.cz goes on the wire. Display names keep their diacritics (they are MIME-encoded anyway), and so does the local part: a non-ASCII local part really does need SMTPUTF8 and cannot be encoded away. Needs ext-intl; without it the address is passed through unchanged, as before. | | | |
| mail | by dg | MimePart: header names are case-insensitive RFC 5322 header names are case-insensitive, but they were stored and looked up by exact spelling: getHeader('from') did not find 'From', and setHeader('SUBJECT') added a second Subject header next to the existing one rather than replacing it. Everything that reads headers back -- DkimSigner, SmtpMailer, Interceptor -- had to spell them exactly as Message writes them or silently see nothing. Lookups now ignore case and the spelling first used is the one kept, so generated messages are unchanged. DkimSigner matches the headers it signs the same way; it compares names taken from the raw message against the configured list, which would otherwise miss a header the configuration spells differently and produce a signature the receiver cannot verify. The remaining exact-spelling lookups follow suit: getEncodedMessage() finds the Content-Type header to append the multipart boundary to -- an oddly spelled one would have kept its multipart type but lost the boundary, a body no mail client can parse -- and SendmailMailer strips the To and Subject lines that mail() adds itself whatever their case. | | | |