When editing a color palette via the new page introduced in
https://github.com/discourse/discourse/pull/31742, it should apply the
color changes for the admin making the change automatically upon save.
Internal topic: t/148628/12.
When multiple admins are working in the review queue, it's quite easy for two people to try and handle the same reviewable at the same time. This change addresses the two major situations where this can occur.
The `ReviewableClaimedTopic` model has been extended to allow the system to mark a reviewable as claimed as soon as the first moderator starts handling the reviewable, even when the `reviewable_claiming` setting is disabled. This ensures that reviewable actions with client-site activity (for example, `agree_and_suspend`) will lock the reviewable before another moderator starts working on it.
When someone handles handles a reviewable, we now use `MessageBus` to inform other moderators that it's changed. If any of the other moderator have that reviewable open (either individually, or on the list screen), it will automatically refresh that data.
Fixes the custom homepage crawler output to include links to the site's
top menu.

This also provides a way to override that content via a plugin. Example
usage in a `plugin.rb` file:
```
register_html_builder("server:custom-homepage-crawler-view") do |c|
"<div>override</div>"
end
```
The service worker isn't served via normal asset paths or the CDN.
Instead, the ERB was being compiled by sprockets, fished out of the
`public/` directory by the static_controller, and then the
sprockets-specific stuff like `sourceMappingUrl` was being removed.
Instead, we can put the ERB under `views/static/`, and have it evaluate
at runtime. There are only a couple of super-cheap interpolations, plus
the route is cached in nginx, so there is no performance concern.
This takes us one step closer to removing sprockets.
There are a number of minor changes in this commit :
1. Combine the "Themes" and "Components" links in the admin sidebar into
a single tab labelled "Themes and components"
2. The combined tab links to the `/admin/config/customize/themes` page
(titled as "Themes and components")
3. Add a new "Components" tab to the "Themes and components" page.
There's already an existing "Themes" tab
4. Add a "back to" link at the top of individual theme/component page to
navigate back to the respective tab in the "Themes and components" page
5. Remove the themes/components list/sidebar that currently serves for
navigating between themes/components
6. Remove the header in the theme/component page
Changes 4–6 apply only if the admin sidebar is enabled; they have no
effect otherwise.
Internal topic: t/146006.
A new user joining a community via DiscourseHub and logging in via oauth
goes through this process. This would break down for two reasons.
Reason 1: in some cases, especially on Safari mobile, the redirect in
the omniauth callback was happening too early. A new user may not be
signed in yet by that point, which means the redirect to
`/user-api-key/new` triggers a redirect to `/login` which ends up in a
bit of an infinite loop. Not all browsers exhibited this behaviour, but
Safari definitely did.
Reason 2: `/user-api-key/new` is gated via group membership using the
`user_api_key_allowed_groups` site setting. By default that is set to
include `trust_level_0`, however, auto group assignment wasn't taking
place for all user `create` events (only some that go through staged
users).
This list of all reports is needed in the admin search
controller as well, so this commit refactors it into
a service, adds specs, and also updates the admin
search code to use this new service & avoid a second
AJAX call to the server.
When a tag has a description defined, we should use that in the `<meta
name="description"...` tag on the tagged topic list page. This matches
the behaviour on the equivalent category pages.
We are developing our new composer, and it would be useful
if we could know how posts are being created by members.
To this end, we are going to start storing the following
on post_stats, which are created at the same time as a post
is created:
* writing_device - Based on `BrowserDetection.device`, which in
turn is based on user agent. Will store .e.g iphone, android,
mac, windows etc.
* writing_user_agent - Stores the full user agent (truncated at
400 chars) of the device/browser the member used to write the post.
* composer_version - Either `1` for our old composer, or
`2` if the new rich composer is enabled in site settings and
the user has toggled it on
Redesigned page to update site logos. `AdminBrandingLogoFormComponent`
is attached to the old logos page and the new branding page. In the next
steps, branding will replace the logos page.
A new `AdminConfigAreaCardSection` component was added hidden and less
frequently used settings.
An image placeholder was also needed because many additional logos have
a fallback to the site logo.
Finally, `twitter_summary_large_image` was renamed to
`x_summary_large_image`.
Desktop

Mobile

This commit moves most of emoji logic into the discourse-emojis gem:
https://github.com/discourse/discourse-emojis/
Most notably:
- images are now symlinked from the gem
- the gem provides path to the json files
Search aliases have also been made asynchronous and memoized. When you
will search for an emoji we will now load the aliases and store the list
for future use.
---------
Co-authored-by: David Taylor <david@taylorhq.com>
This change standardises the `User-Agent` header that Discourse will send when talking to other sites.
`Discourse.user_agent` is now the authority on what the user agent value should be. For Onebox requests, this changes the user agent from their existing value to match the new value (unless overridden).
For all other requests, `Net::HTTPHeader` is monkey-patched to add a default `User-Agent` header when one hasn't been provided.
Followup e26a1175d7c33746bddbc858ad89e68cc14beefe
Adds extra functionality and tests for the admin search modal.
* Show third level plugin config pages in search, e.g. AI Usage
* Remember last used search filters
* Allow navigating search results with keyboard, using tab or up/down
and enter to go to result
* Add a placeholder beneath search input to tell the admin what to do
* Add a full page search at `/admin/search` which can be reached from
pressing Enter on the search input
* Add specs for modal and full page search
* Change admin sidebar filter "no results found" to point to full page
search
* Add keyboard shortcut help to modal for admin search
This reverts commit 38de3d7bd1f503743c5d0237bc8a8d9d89effb8e. This
changed seemed to be blocking our own AI helper as well if it has the
“Search” tool.
This change detects if a crawler is trying to load a search results page, and returns a simple response that should indicate to them that there's no content of interest available there.
When an email is sent by sendgrid to an email address with an invalid
host, the webhook payload does not contain the "status" field:
```
[
{
"bounce_classification": "Unclassified",
"email": "noemail@this.does.not.exist.tld",
"event": "bounce",
"reason": "unable to get mx info: failed to get IPs from PTR record: lookup <nil>: unrecognized address",
"sg_event_id": "Ym91bmNlLTQtNTA0ODUxOTUtZXVvMmlLeGRTYXlQRjRZRTQtLUk3QS0w",
"sg_message_id": "euo2iKxdSayPF4YE4--I7A.recvd-5f54b5d587-pczjm-1-67BADEEA-6.0",
"smtp-id": "<870b3a2a-160c-4fc8-bc9a-bd0d5b943b81@forum.umbraco.com>",
"timestamp": 1740300320,
"tls": 0,
"type": "blocked"
}
]
```
When the `status` field is missing, it results in a `NoMethodError
(undefined method `[]' for nil:NilClass)`
error in the controller method. In this commit, we will specifically
handle the webhook event from sendgrid when the email address's domain
is invalid.
Co-Authored-By: @nul800sebastiaan
Previously the rendered locale was based on the current session's
locale. Now that we're routing requests via the CDN, we can't rely on
the user's session, and should instead include the locale name in the
URL. Also adds a `.js` suffix for parity with our other JS assets.
CDNs are often configured to strip query params, which means that the
`?v=` parameter wasn't reaching the Rails app, and therefore the
cache-control header was not being set correctly. Having a 40 character
sha1 digest in the **path** is the approach we take for other similar
assets like stylesheets and theme-javascripts.
Also adds a spec for the fix in 573fbeef64f052decc47e740cbe01a3c298c20b5
Followup to 1b5e4b6b0fef9811839490f2ee3b9f31d5fbed3b
When running through a CDN in a multisite environment, the site hostname
needs to be included in the path or query or the URL so that the
'current db' can be set correctly by rails multisite. Without this, the
response will assume the default site in the cluster.
Previously, the SiteSetting::Update service allowed to update of a
single site setting. In the About controller, we were using the loop
through all settings -
https://github.com/discourse/discourse/blob/main/app/controllers/admin/config/about_controller.rb#L39
It is suboptimal because if the 3 first settings are saved and the
fourth is invalid, we will end with partially updated data.
Changing SiteSetting::Update to accept hash means that we will check
upfront if none of the settings are hidden or invalid and update all or
none.
Custom policies are used to report which settings are failing.
Code/translations for the admin panel and wizard are not considered
sensitive, so there's no need for access control checks here. Once
they're removed, we can cache in NGINX and the CDN, and thereby improve
server and client-load performance.
This totally separate SCSS and i18n compilation pipelines only existed
so that we could run `ember exam` in CI without starting Rails.
Now that our CI has such heavy caching of Ruby dependencies and database
migrations, the speed benefit of this is not worth the cost of
maintaining these separate pipelines.
Therefore, this commit removes that system, and updates CI to use
`bin/rake qunit:test`. That will start up a Rails server and proxy
stylesheet/locale requests to it. This strategy was already used for our
theme and plugin qunit test runs.
This feature allows admins to find what they are
looking for in the admin interface via a search modal.
This replaces the admin sidebar filter
as the focus of the Ctrl+/ command, but the sidebar
filter can also still be used. Perhaps at some point
we may remove it or change the shortcut.
The search modal presents the following data for filtering:
* A list of all admin pages, the same as the sidebar,
except also showing "third level" pages like
"Email > Skipped"
* All site settings
* Themes
* Components
* Reports
Admins can also filter which types of items are shown in the modal,
for example hiding Settings if they know they are looking for a Page.
In this PR, I also have the following fixes:
* Site setting filters now clear when moving between
filtered site setting pages, previously it was super
sticky from Ember
* Many translations were moved around, instead of being
in various namespaces for the sidebar links and the admin
page titles and descriptions, now everything is under
`admin.config` namespace, this makes it way easier to reuse
this text for pages, search, and sidebar, and if you change it
in one place then it is changed everywhere.
---------
Co-authored-by: Ella <ella.estigoy@gmail.com>
This change adds a new `type_source` field to the `Reviewable` model, indicating whether the Reviewable type was registered by `core`, a plugin, or an `unknown` source.
When a plugin that registered a Reviewable type is disabled, this allows us to tell the user which plugin they need to re-enable to handle any orphan reviewable items.
...loading an invite link that points to a topic they already have
access to.
This "feature" was removed in 07ef1a80a1461123d602c57e366974aed265a91e
as part of the security fix.
Internal ref - t/145628
Currently, if creating an API key in "granular" mode, and not selecting any scopes, a globally scoped API key is created. This can be surprising and is not ideal. Having a key with no scopes isn't useful in the first place, so this PR adds client- and server side validations to check that at least one scope is selected if using "granular" mode.
This fixes an issue where the topic invitation rate limiter
for invites for the 1 minute period was incorrectly using
1 day as the length of time the limit should be applied over.
The default for `max_topic_invitations_per_minute` is 5,
so this would be very easy to exceed, then the user gets
a very confusing warning message saying they have to wait
23 hours to send more invites.
This commit also makes other `RateLimiter` period parameters
more consistent by always using the form `N.PERIOD` instead
of things like `86_400` hardcoded seconds per day.
6fd577d97d3923cec3d2458f45ebd2704703fd22 widened the scope of
`use_email_for_username_and_name_suggestions` (default false) to include
invites, which means that it fell back to a generic username like
`user1`.
This commit makes it bail out earlier in this situation, so that no
suggestion is attempted.
This commit makes the
[color-scheme-toggle](https://github.com/discourse/discourse-color-scheme-toggle)
theme component a core feature with improvements and bug fixes. The
theme component will be updated to become a no-op if the core feature is
enabled.
Noteworthy changes:
* the color mode selector has a new "Auto" option that makes the site
render in the same color mode as the user's system preference
* the splash screen respects the color mode selected by the user
* dark/light variants of category logos and background images are now
picked correctly based on the selected color mode
* a new `interface_color_selector` site setting to disable the selector
or choose its location between the sidebar footer or header
Internal topic: t/139465.
---------
Co-authored-by: Ella <ella.estigoy@gmail.com>
Plugins like for example AI or Akismet create reviewable items. When the
plugin is disabled, then we cannot properly handle those items.
In that situation, we should display warnings about unhandled types.
Instruct admin to reenable plugins. In addition, we should allow the
admin to delete all pending reviews from disabled plugins.
This commit drops the `before_action :preload_json` callback in `ApplicationController` as it adds unnecessary complexity to `ApplicationController` as well as other controllers which has to skip this callback. The source of the complexity comes mainly from the following two conditionals in the `preload_json` method:
```
# We don't preload JSON on xhr or JSON request
return if request.xhr? || request.format.json?
# if we are posting in makes no sense to preload
return if request.method != "GET"
```
Basically, the conditionals solely exists for optimization purposes to ensure that we don't run the preloading code when the request is not a GET request and the response is not expected to be HTML. The key problem here is that the conditionals are trying to expect what the content type of the response will be and this has proven to be hard to get right. Instead, we can simplify this problem by running the preloading code in a more deterministic way which is to preload only when the `application` layout is being rendered and this is main change that this commit introduces.
This change adds a sidebar link for each plugin that fulfils the following criteria:
- Does not have an explicit admin route defined in the plugin.
- Has at least one site setting (not including enabled/disabled.)
That sidebar link leads to the automatically generated plugin show settings page.
This commit adds a new Localization config page for
admins, as a basic filtered site setting page similar
to Legal and Notifications. Included settings are:
* default locale
* allow user locale
* set locale from accept langauge header
* onebox locale
* display local time in user card
* discourse local dates enabled
* support mixed text direction
* unicode usernames
* allowed unicode username characters