Commit Graph

2452 Commits

Author SHA1 Message Date
OpenProject Actions CI 49b802ca02 Merge branch 'release/17.5' into dev 2026-05-28 08:51:09 +00:00
Kabiru Mwenja ab32cab495 Merge pull request #23337 from opf/bug/74762-numeric-id-in-the-email-notification-after-adding-watchers
bug/74762 Numeric ID in the email notification after adding watchers
2026-05-28 11:38:02 +03:00
OpenProject Actions CI eb6717f81e Merge branch 'release/17.5' into dev 2026-05-27 14:11:35 +00:00
Kabiru Mwenja 3036e852a9 Tighten render_mode and mail formatting helper docstrings
Strip a forward-looking aside about future external surfaces in RenderMode;
the invariant is that external surfaces need both absolute URLs and static
rendering. Replace "in practice" with "a coupled set" to drop the soft
hedge.

Drop the lead "wrappers around format_text" sentence on MailFormattingHelper
since the module body already shows the wrapping; the WHY (channel pinning,
extension/helper name parity) is the part worth documenting.
2026-05-27 13:22:56 +03:00
Kabiru Mwenja 499d7820a2 Add render_mode flag and MailFormattingHelper
`format_text` accepts `render_mode:` (`:in_app_html`, `:external_html`,
`:external_text`), which resolves the `only_path`, `static_html` and
`plain_text` context flags as a set. External surfaces (mailer HTML
body, future RSS/PDF/webhook) need absolute URLs and static rendering
together; pinning the trio at the public API keeps callers from
forgetting one. Explicit primitive kwargs still override.

`MailFormattingHelper` exposes `format_mail_html` and `format_mail_text`
thin wrappers around `format_text(render_mode:)`. The `_html` / `_text`
suffix matches the `.html.erb` / `.text.erb` template extension so
caller intent stays visible in the view, with no introspection of
`formats`.

The five WorkPackageMailer view sites use the helpers; `_work_package_details`,
`mentioned.html`, `mentioned.text`, `watcher_changed.html`, `watcher_changed.text`
drop the `static_html:`/`only_path:`/`plain_text:` boilerplate.
2026-05-27 13:04:26 +03:00
Oliver Günther 4cc9faef4f Consistently use OpenProject::SqlSanitization instead of forwarding to sanitize_sql_array 2026-05-27 10:08:16 +02:00
Oliver Günther 4724150e3d Avoid quote_string in favor of bindings where possible 2026-05-27 10:08:16 +02:00
Kabiru Mwenja f5957d800f Collapse plain-text formatter into a context option
Same pattern as the static-HTML collapse: the `markdown_as_text` format
symbol was a thin subclass setting a context flag and swapping the filter
list. Replace it with `plain_text: true` on the existing rich formatter,
which now picks between `RICH_FILTERS` and `TEXT_FILTERS` constants based
on the flag. `static_html:` and `plain_text:` now sit as peer options on
one format.

Rename the `as_text` context key to `plain_text` for symmetry with
`static_html`. Update both mailer `.text.erb` views and the two handler
predicates that branch on the flag.
2026-05-26 22:10:29 +03:00
Kabiru Mwenja c607b36b8e Rename with_hash_prefix to format_display_id 2026-05-26 22:10:07 +03:00
Kabiru Mwenja 94c13c11fa Collapse static-HTML formatter into a context option
The static-HTML pipeline differs from the rich pipeline only by a
context flag - both share the same filter chain. The dedicated
`Markdown::StaticHtmlFormatter` and `:markdown_as_static_html`
format symbol were pure boilerplate around that one-line override.

Callers now pass `format: :rich, static_html: true` and the matchers
read `context[:static_html]` directly.
2026-05-26 14:50:59 +03:00
Kabiru Mwenja 2ca379fe37 Trim verbose comments
Strip explanatory paragraphs and cross-reference jargon
("Mirrors X", "Shared with Y", etc.) from comments introduced
on this branch. Keep only the WHY — the parts of the design
intent that aren't already evident from method names and
short bodies. Pre-existing comments on methods this branch
didn't author stay as-is.
2026-05-26 14:32:37 +03:00
Kabiru Mwenja 989dbf9da8 Name the preload-required predicate
Compress the "do we need to load WP records?" condition into
`preload_required?(context)` so the call site reads as intent rather
than a tangle of two unrelated signals. The reasoning (semantic mode
needs row lookup; static-HTML output needs type/subject for the
anchor) moves to a comment on the predicate, where it belongs.
2026-05-26 14:24:03 +03:00
Kabiru Mwenja e70b9ab21c Tighten mention pipeline: helper extraction, principal preload, coverage
Lift the static-anchor label composition out of LinkHandlers and into
a small Helpers::StaticMacroLabel module so the envelope path
(MentionFilter) and the text-reference path (LinkHandlers) share one
shape — same module called from both, no cross-class reach-through.

Batch the User and Group mention preloads alongside the existing WP
preload so a note with N principals costs one SELECT per type rather
than N. Class lookup now reads from indexed hashes; visibility-gating
stays where it was (at the find for principals, separate from the
label for WPs).

Rename SemanticIdentifier.format → with_hash_prefix; the prior name
was broad enough to invite misuse for arbitrary work-package values.
Override StaticHtmlFormatter#filters explicitly so a future filter
appearing in Formatter#filters is a deliberate decision to apply to
mailer-side rendering, not an automatic one.

Spec coverage: classic-mode quickinfo and inaccessible-WP paths
(symmetric with the existing semantic-mode contexts), a principal
preload N+1 guard, and an anonymous current_user smoke test that
confirms the static-HTML pipeline doesn't raise when invoked without
an authenticated viewer.
2026-05-26 14:24:02 +03:00
Kabiru Mwenja 666069e126 Cleanup unneeded comments, touch up syntax 2026-05-26 14:19:24 +03:00
Kabiru Mwenja e269fddffc Extract shared static-macro label composition
Reviewer feedback on cd122f867cc: the `parts << status / type / label`
block was duplicated between the regex-driven (`WorkPackages` link
handler) and envelope-driven (`MentionFilter`) static paths, with no
guard against silent desynchronisation. Centralise the composition on
the link handler and document why the two callers pass different
labels — the regex path preserves the alias-as-matched, the envelope
path normalises to the WP's current formatted_id.
2026-05-26 14:19:24 +03:00
Kabiru Mwenja ee4c9aee59 Render WP quickinfo macros as static HTML in mailer notes
The `##N` and `###N` work-package macros emit JS-hydrated
`<opce-macro-wp-quickinfo>` custom elements, which mail clients
collapse to empty bullets. Introduce a `:markdown_as_static_html`
format that shares the rich filter chain but signals
`context[:as_static_html]` so the matcher and `MentionFilter` emit a
server-rendered anchor — formatted_id, type name, subject, and (for
`###`) status name — closely mirroring the in-app widget once
flattened.

Mailer HTML templates (`mentioned`, `watcher_changed`,
`_work_package_details`) opt into the new format. Invisible WPs still
render as plain-text labels, matching the cross-project visibility
policy.

`ResourceLinksMatcher.build_cache` and
`MentionFilter#preload_work_package_mentions` eager-load `:type` and
`:status` only when `:as_static_html` is set, leaving the default web
path's two-SELECT shape untouched. Classic-mode preload now also runs
under `:as_static_html` so the link handler can resolve type/subject
for `##`/`###`.

Renames the internal flag `context[:plain_text]` to `context[:as_text]`
to restore symmetry with the user-facing `:markdown_as_text` format.
2026-05-26 14:19:23 +03:00
Kabiru Mwenja a54dd3dc0b Rename :plain_text format to :markdown_as_text
The format runs the full markdown pipeline and then collapses the DOM
to text — it has nothing to do with the existing `:plain` format,
which strips markdown entirely. Moves the formatter under the Markdown
namespace next to the rich-output formatter whose pipeline it mirrors,
and renames the symbol so the relationship is legible from the
formatter_for case clause.
2026-05-26 14:19:11 +03:00
Kabiru Mwenja 5d8929a7c3 Resolve invisible WP mentions to their current formatted_id
The mention filter previously dropped to the envelope's stored text
when the recipient lacked view permission on the referenced work
package, which left stale identifiers in mailer bodies after a
project rename and diverged from the `#N` text-reference path on
the same render.

Adopts the two-SELECT pattern ResourceLinksMatcher uses for `#N`
references: a single unscoped batched lookup for label resolution
plus a visibility-scoped id pluck for anchor gating. Invisible WPs
render as plain text with the current formatted_id; the per-mention
`WorkPackage.visible.find_by` is gone.
2026-05-26 14:19:11 +03:00
Kabiru Mwenja 8d9aa18ad3 Collapse work-package preload state into one cache value object
Pairs unscoped label resolution and viewer-scoped link gating in a
WorkPackagePreloadCache instead of two RequestStore keys with a
five-method save/restore protocol. Exposes one `current_cache` reader;
consumers ask the cache directly via `fetch` and `visible?`.

Extracts a `text_only?` predicate in the WP link handler so the
`context[:plain_text]` and invisible-WP guards collapse into a single
call site. `SemanticIdentifier.format` renames its parameter to
reflect that the input may or may not be semantic.
2026-05-26 14:19:10 +03:00
Kabiru Mwenja 878048f8e8 Resolve WP labels across visibility boundaries in text macros
The macro preload was visibility-scoped — references to work packages the
recipient cannot see would fall through to the literal `#43` shape, even
when the same reference rendered as `DCP-1` for an author with full view
permission. Notification recipients saw misleading numeric ids for cross-
project references in journal notes.

Splits label resolution from link gating:

- `ResourceLinksMatcher.build_lookup` now does an unscoped fetch for the
  primary identifier and a separate visibility-scoped id pluck. The link
  handler reads `visible_to_current_user?` to decide between a navigable
  anchor and a plain-text label.
- `UpdateAncestorsService#set_journal_note` writes `#display_id` so new
  notes carry the semantic shape at the source; render-time resolution
  heals legacy `#N` content for users with view permission.

Tradeoff: a recipient without view permission now sees the WP's semantic
identifier (e.g. `DCP-1`) as plain text rather than `#43`. The reference's
existence was already disclosed by the stored journal text; the project
identifier is the only new piece of information surfaced, and is not
treated as a secret elsewhere in the system (URLs, exports, API).
2026-05-26 14:18:48 +03:00
Kabiru Mwenja 7c9d15e506 Render WP identifiers per current mode in plain-text mailer notes
The `mentioned` and `watcher_changed` text-mailer bodies surfaced raw
journal markdown — numeric `#42` references stayed numeric in semantic
mode, and `<mention>` envelopes leaked as HTML source.

Introduces `:plain_text` as a sibling format inside the existing Plain
module. The filter chain mirrors the markdown pipeline (markdown,
sanitization, mention, pattern-matcher) and finishes with a new
`PlainTextOutputFilter` that collapses the DOM to text. The
`WorkPackages` link handler and `MentionFilter` get plain-text branches
keyed off `context[:plain_text]` so identifier resolution stays in one
place across rich and plain channels.

Closes https://community.openproject.org/wp/74762
2026-05-26 14:18:17 +03:00
OpenProject Actions CI e38557f12f Merge branch 'release/17.5' into dev 2026-05-26 04:53:23 +00:00
Tomas Hykel 948fa43321 chore: Remove feature flag for project-based work package identifiers 2026-05-25 17:45:06 +02:00
OpenProject Actions CI 9a74b5356b Merge branch 'release/17.4' into release/17.5 2026-05-22 08:36:48 +00:00
Oliver Günther 83c69820d6 Merge remote-tracking branch 'origin/release/17.4' into dev 2026-05-22 10:36:14 +02:00
Oliver Günther d6d7abea19 Merge pull request #23304 from opf/fix/message-pack-safe-buffer-serialization
Allow serialization of safebuffer without encoding issues
2026-05-22 10:35:50 +02:00
Oliver Günther 4a07d490d0 Bumped version to 17.6.0
[ci skip]
2026-05-21 06:55:20 +02:00
Kabiru Mwenja b181e1fde1 Follow-up refactors for the text-macro preload cache (#23285)
* Scope SQL log assertions to target SELECTs in alias-fold-in spec

The N+1 guard was counting the entire QueryRecorder log, which made it
brittle against any incidental Setting/permission query that landed on
the same render path. Switch to scoped greps against the two SELECTs
we actually care about: the work_packages batched query and the
sidecar alias pluck. A regression on either now fails with a clear
message pointing at the offending source.

* Flatten matcher preload wrapping into an iterative fold

The recursive shift-and-recurse shape mutated a duplicated array and
forwarded an anonymous block at every frame, which obscured what the
loop was actually doing: wrap each opt-in matcher's preload hook
around the inner block, first matcher outermost. The iterative form
walks the matcher list once in reverse and rebinds a lambda, so the
nesting order is visible without unwinding a recursion.

* Make the WP preload cache's stringified-key invariant explicit

Both ends of the cache assumed every key was a string, but the
contract lived only in the read site's `identifier.to_s` and an
ambient confidence that the identifier column is text. Normalize at
write time too, swap the safe-navigation lookup for `dig`, and leave
a one-line note at the canonical builder so a future reader doesn't
have to grep the call sites to convince themselves a numeric input
will resolve.
2026-05-20 21:36:09 +03:00
Kabiru Mwenja e2631598c2 Merge pull request #23204 from opf/feature/editor-mention-semantic-id
Render work-package mentions with the current display identifier
2026-05-20 17:26:50 +03:00
Jens Ulferts 0ceaa2617c Merge pull request #23181 from opf/code-maintenance/74768-remove-calculated_value_project_attribute-feature-flag
[#74768] Remove calculated_value_project_attribute feature flag
2026-05-20 16:09:46 +02:00
Oliver Günther 274f7c6e3d Merge pull request #23230 from opf/feat/monthly-meeting-option
Meeting series: Add monthly scheduling options
2026-05-20 16:02:36 +02:00
Dombi Attila f5e08b3d78 Merge pull request #23103 from opf/feature/71305-exclude-certain-work-package-types-from-automated-backlog-per-project
Feature/71305 exclude certain work package types from automated backlog per project
2026-05-20 16:51:06 +03:00
Kabiru Mwenja 70931762d9 Merge remote-tracking branch 'origin/dev' into feature/editor-mention-semantic-id
# Conflicts:
#	frontend/src/vendor/ckeditor/ckeditor.js
#	frontend/src/vendor/ckeditor/ckeditor.js.map
2026-05-20 16:33:47 +03:00
Oliver Günther 6295d90346 Add monthly scheduling options 2026-05-20 15:20:16 +02:00
Kabiru Mwenja a1440eb9bb Merge pull request #23221 from opf/feature/text-macro-preload-cache
Implement doc-level preload cache for WP text macros
2026-05-20 13:19:59 +03:00
Dombi Attila 8a8e47550d Merge branch 'dev' into feature/71305-exclude-certain-work-package-types-from-automated-backlog-per-project 2026-05-20 12:47:25 +03:00
Kabiru Mwenja 506a97f90a Pass visible WP ids to alias fold-in explicitly
The alias fold-in subquery materialised the user's full set of
visible work packages, then IN-tested against it. Threading the
already-loaded WP ids from `build_lookup` collapses that to a
literal IN-list bounded by user input, and makes the visibility
contract explicit at the method signature rather than implicit
in a `lookup.values` invariant.

The end-to-end visibility tests still pin behaviour for aliases
that target inaccessible work packages.
2026-05-20 12:21:36 +03:00
Kabiru Mwenja 559d56ae16 Scope text-macro preload cache to WorkPackage.visible
The lookup cache in `ResourceLinksMatcher` resolved `#42` / `#PROJ-1`
/ `#OLDPROJ-1` references without any permission check, so the link
handler could render `formatted_id` / `display_id` for work packages
the current user had no read access to. Both the main query and the
historical-alias pluck now scope through `WorkPackage.visible`,
matching `MentionFilter` and the other link handlers.

The historical-alias query-count spec now asserts statement count
rather than table-name grep — the visibility CTE references
`work_packages` in both statements, so the old regex over-matched.

Quickinfo macro emission for `##PROJ-1` / `###PROJ-1` is unchanged
and queued as a separate PR. The data-id only echoes user input and
the API endpoints enforce auth, but the Angular custom element still
bootstraps and renders a "not found" error chip for inaccessible
WPs. Fixing that cleanly needs the cache to distinguish `denied`
from `absent`.

Refs #23221.
2026-05-20 12:03:22 +03:00
Oliver Günther 4d61cb5a0f Merge remote-tracking branch 'origin/release/17.4' into dev 2026-05-19 19:55:57 +02:00
Oliver Günther ad94533f4a Fix merge of release/17.3 into 17.4 2026-05-19 19:55:48 +02:00
Oliver Günther e396a5bb53 Merge remote-tracking branch 'origin/release/17.4' into dev 2026-05-19 18:26:45 +02:00
Oliver Günther 637ec7d10f Merge remote-tracking branch 'origin/release/17.3' into release/17.4 2026-05-19 18:26:23 +02:00
Ivan Kuchin b7ee653ade group and cleanup helper custom field class methods 2026-05-19 17:42:45 +02:00
Ivan Kuchin 9b6edd7886 cleanup custom field formats registration 2026-05-19 17:42:45 +02:00
Ivan Kuchin 595291fe82 sort custom field formats once 2026-05-19 17:42:44 +02:00
Ivan Kuchin c8e9f3b700 extract common part of finding formats by cusomizable class 2026-05-19 17:42:44 +02:00
Ivan Kuchin edb6a722ce move format label presence check to for_class_name? 2026-05-19 17:42:43 +02:00
Jan Sandbrink e7bee82f9e Introduce and use ConfidentialCache
It's like OpenProject::Cache, but it encrypts cached
values at rest. Callers that store confidential things in the cache
have been updated to use it, reducing the risk to expose secrets
to an attacker that obtained access to the contents of OpenProject's cache.
2026-05-19 15:56:53 +02:00
Jan Sandbrink 2a52a11f34 Extend interface of OpenProject::Cache
Also expose the #delete method offered by Rails caches.
Adapt the interface of existing methods to stricter follow
the upstream interface. neither #read nor #write accept passing
a block to them.
2026-05-19 14:23:10 +02:00
Tobias Dillmann 0537f52f4b [#71305] Introduce MODULE_ENABLED event and use it for backlogs 2026-05-19 14:21:54 +02:00