diff --git a/13/umbraco-cms/extending/macro-parameter-editors.md b/13/umbraco-cms/extending/macro-parameter-editors.md index bfc0e444a1a..322bea33812 100644 --- a/13/umbraco-cms/extending/macro-parameter-editors.md +++ b/13/umbraco-cms/extending/macro-parameter-editors.md @@ -5,7 +5,7 @@ description: A guide to creating macro property editors in Umbraco # Macro Parameter Editors {% hint style="warning" %} -Macros will be removed in the next version. Consider using Partial Views or Blocks in Rich Text Editor. +Macros will be removed in Umbraco 14. Consider using Partial Views or Blocks in Rich Text Editor. {% endhint %} Every macro can contain parameters. Options for the Editor to set when they insert the Macro to customise the output. There are some useful default types. For example: diff --git a/13/umbraco-cms/fundamentals/design/stylesheets-javascript.md b/13/umbraco-cms/fundamentals/design/stylesheets-javascript.md index 9e8b5074325..ce269421e44 100644 --- a/13/umbraco-cms/fundamentals/design/stylesheets-javascript.md +++ b/13/umbraco-cms/fundamentals/design/stylesheets-javascript.md @@ -86,6 +86,12 @@ You can use whichever tool you are comfortable with for bundling & minification You can create various bundles of your site's CSS or JavaScript files in your code that can be rendered later in your views. There can be a single bundle for the entire site, or a common bundle for the files you want to be loaded on every page, as well as page-specific bundles, just by listing your resources in the order you like. +{% hint style="warning" %} + +Smidge with RunTimeMinification setting is scheduled for removal on Umbraco 14. You can install the package separately if needed and read the [Smidge](https://github.com/Shazwazza/Smidge) documentation on how to get started. + +{% endhint %} + **Step 1:** Create a `INotificationHandler` ```csharp diff --git a/13/umbraco-cms/reference/configuration/runtimeminificationsettings.md b/13/umbraco-cms/reference/configuration/runtimeminificationsettings.md index 0056a4f1ea6..8f6f79184e6 100644 --- a/13/umbraco-cms/reference/configuration/runtimeminificationsettings.md +++ b/13/umbraco-cms/reference/configuration/runtimeminificationsettings.md @@ -4,6 +4,12 @@ description: "Information on the runtime minification settings section" # Runtime minification settings +{% hint style="warning" %} + +Smidge with RunTimeMinification setting is scheduled for removal on Umbraco 14. You can install the package separately if needed and read the [Smidge](https://github.com/Shazwazza/Smidge) documentation on how to get started. + +{% endhint %} + This section allows you to configure the runtime minifications (defaults shown), used by ['Smidge - A lightweight runtime CSS/Javascript minification,combination, compression & management library for ASP.NET'](https://github.com/shazwazza/smidge) ```json @@ -16,6 +22,7 @@ This section allows you to configure the runtime minifications (defaults shown), } } ``` + ## Use 'in memory' cache This setting determines whether Smidge should save it's cached output in memory, or in a file on disk. If set to false, then the folder will be created at the wwwroot of your Umbraco site in a folder called 'Smidge'/ @@ -49,6 +56,7 @@ If you use a CacheBuster setting of "Version" you can add an additional configur } } ``` + The actual 'Version' number will not be visible in the url of the assets, this is because it is combined, along with the Umbraco Version from configuration and the your project assembly dll, and then once combined a 'hash' is generated to obscure these details. in the HTML link thus: `````` (when [`Umbraco:CMS:Hosting:Debug:false`](hostingsettings.md)) diff --git a/13/umbraco-cms/tutorials/creating-a-custom-dashboard/README.md b/13/umbraco-cms/tutorials/creating-a-custom-dashboard/README.md index 749eb352d23..60e3cd23368 100644 --- a/13/umbraco-cms/tutorials/creating-a-custom-dashboard/README.md +++ b/13/umbraco-cms/tutorials/creating-a-custom-dashboard/README.md @@ -35,6 +35,7 @@ At the end of this guide, we should have a friendly welcoming dashboard displayi 3. Add the following HTML to the `WelcomeDashboard.html`: {% code title="WelcomeDashboard.html" lineNumbers="true" %} + ```html

Welcome to Umbraco

@@ -42,6 +43,7 @@ At the end of this guide, we should have a friendly welcoming dashboard displayi

You can put anything here...

``` + {% endcode %} Similar to a property editor you will now register the dashboard in a `package.manifest` file. @@ -49,6 +51,7 @@ Similar to a property editor you will now register the dashboard in a `package.m 4\. Add a new file inside the `~/App_Plugins/CustomWelcomeDashboard` folder called `package.manifest`: {% code title="package.manifest" lineNumbers="true" %} + ```json { "dashboards": [ @@ -65,6 +68,7 @@ Similar to a property editor you will now register the dashboard in a `package.m ] } ``` + {% endcode %} The above configuration is effectively saying: @@ -89,6 +93,7 @@ The `App_Plugins` version of the `Lang` directory is case-sensitive on Linux sys {% endhint %} {% code title="en-US.xml" lineNumbers="true" %} + ```xml @@ -97,6 +102,7 @@ The `App_Plugins` version of the `Lang` directory is case-sensitive on Linux sys ``` + {% endcode %} [Read more about language files](../../extending/language-files.md) @@ -110,6 +116,7 @@ We can apply the same workflow to elements inside the dashboard, not only the na 3\. Extend the translation file `xml` with the following code: {% code title="en-US.xml" lineNumbers="true" %} + ```xml @@ -123,6 +130,7 @@ We can apply the same workflow to elements inside the dashboard, not only the na ``` + {% endcode %} We are adding another area tag with a few keys. we then need to add some HTML to the `WelcomeDashboard`. @@ -130,6 +138,7 @@ We are adding another area tag with a few keys. we then need to add some HTML to 4. Adjust the dashboard HTML with the following code: {% code title="WelcomeDashboard.html" lineNumbers="true" %} + ```html

Default heading

@@ -137,6 +146,7 @@ We are adding another area tag with a few keys. we then need to add some HTML to

Default copyright

``` + {% endcode %} The `localize` tag will be replaced with the translated keywords. We have some default text inside the tags above, which can be removed. It would usually not be visible after the translation is applied. @@ -144,19 +154,23 @@ The `localize` tag will be replaced with the translated keywords. We have some d As for the `localize` tag syntax in HTML, the area alias is combined with the key alias - so if you want to translate: {% code title="en-US.xml" %} + ```html Default heading ``` + {% endcode %} The XML for that specific key will look like this: {% code title="en-US.xml" %} + ```xml Welcome! ``` + {% endcode %} The area and key aliases are combined and an underscore is added in between. @@ -174,6 +188,7 @@ With the above steps completed, our dashboard is all set up to be translated acr To test it out, you can add another language XML file, like `da.xml` for the Danish language. {% code title="da.xml" %} + ```xml @@ -187,6 +202,7 @@ To test it out, you can add another language XML file, like `da.xml` for the Dan ``` + {% endcode %} The backoffice language can be changed in the Users section if you wish to test out the translations. @@ -204,6 +220,7 @@ Inside the package.manifest we add a bit of JSON to describe the dashboard's req 1. Add the following JSON to the `package.manifest` file: {% code title="package.manifest" %} + ```json { "dashboards": [ @@ -226,17 +243,20 @@ Inside the package.manifest we add a bit of JSON to describe the dashboard's req ] } ``` + {% endcode %} 2. Create a stylesheet in our `CustomWelcomeDashboard` folder called `customwelcomedashboard.css`, and add some style: {% code title="CustomWelcomeDashboard.csss" %} + ```css .welcome-dashboard h1 { font-size:4em; color:purple; } ``` + {% endcode %} The stylesheet will be loaded and applied to our dashboard. Add images and HTML markup as required. @@ -253,6 +273,9 @@ One caveat is that the `package.manifest` file is loaded into memory when Umbrac If the title doesn't change color, [Smidge](https://github.com/shazwazza/smidge) may be caching the CSS and JavaScript. To clear the cache and get it to load in the new JavaScript and CSS, you can configure the [Runtime minification settings](../../reference/configuration/runtimeminificationsettings.md) in the `appsettings.json` file. When you reload the page, you'll see the colorful title. For information on creating bundles of your site's CSS or JavaScript files in your code, see the [Bundling & Minification for JavaScript and CSS](../../fundamentals/design/stylesheets-javascript.md#bundling--minification-for-javascript-and-css) section. + +Smidge with RunTimeMinification setting is scheduled for removal on Umbraco 14. You can install the package separately if needed. + {% endhint %} Hopefully, now you can see the potential of what you can provide to an editor as a basic welcome dashboard. @@ -265,20 +288,24 @@ We can add functionality to the dashboard by associating an AngularJS controller 2. Register the AngularJS controller to the Umbraco Angular module: {% code title="customwelcomedashboard.controller.js" %} + ```js angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope) { var vm = this; alert("hello world"); }); ``` + {% endcode %} 3. Update the outer div to wire up the controller to the view In the HTML view: {% code title="WelcomeDashboard.html" %} + ```html
``` + {% endcode %} {% hint style="info" %} @@ -288,6 +315,7 @@ The use of `vm` (short for view model) is to enable communication between the vi 4. Update the `package.manifest` file to load the additional controller JavaScript file when the dashboard is displayed: {% code title="package.manifest" %} + ```json { "dashboards": [ @@ -310,6 +338,7 @@ The use of `vm` (short for view model) is to enable communication between the vi ] } ``` + {% endcode %} Once done, we should receive the 'Hello world' alert every time the dashboard is reloaded in the content section. @@ -323,14 +352,17 @@ The details are in the [Backoffice UI](../../reference/api-documentation.md). Fo 1. Inject the `userService` into our AngularJS controller: {% code title="customwelcomedashboard.controller.js" %} + ```js angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope,userService) { ``` + {% endcode %} 2. Use the `userService's` promise based `getCurrentUser()` method to get the details of the currently logged-in user: {% code title="customwelcomedashboard.controller.js" %} + ```js angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService) { var vm = this; @@ -342,6 +374,7 @@ angular.module("umbraco").controller("CustomWelcomeDashboardController", functio }); }); ``` + {% endcode %} {% hint style="info" %} @@ -351,9 +384,11 @@ Notice you can use `console.log()` to write out to the browser console window wh 3. Update the view to incorporate the current user's name in our Welcome Message: {% code title="WelcomeDashboard.html" %} + ```html

Default heading {{vm.UserName}}

``` + {% endcode %} ![Custom Dashboard Welcome Message With Current User's Name](../../../../10/umbraco-cms/tutorials/images/welcomemessagepersonalised-v10.png) @@ -369,28 +404,34 @@ We add `logResource` to the method and use the `getPagedUserLog` method to retur 1. Inject the `logResource` into our controller: {% code title="customwelcomedashboard.controller.js" %} + ```js angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService, logResource) { ``` + {% endcode %} 2. Add a property on our `ViewModel` to store the log information: {% code title="customwelcomedashboard.controller.js" %} + ```js vm.UserLogHistory = []; ``` + {% endcode %} 3. Add to our `WelcomeDashboard.html` View some markup using angular's `ng-repeat` to display a list of these log entries: {% code title="WelcomeDashboard.html" %} + ```html

We know what you edited last week...

``` + {% endcode %} 4. Populate the array of entries using the `logResource` In our controller. @@ -398,6 +439,7 @@ vm.UserLogHistory = []; The `getPagedUserLog` method expects to receive a `JSON object` containing information to filter the log by: {% code title="customwelcomedashboard.controller.js" %} + ```js var userLogOptions = { pageSize: 10, @@ -406,6 +448,7 @@ var userLogOptions = { sinceDate: new Date(2018, 0, 1) }; ``` + {% endcode %} These options should retrieve the last ten activities for the current user in descending order since the start of 2018. @@ -413,6 +456,7 @@ These options should retrieve the last ten activities for the current user in de 5. Pass the options into the `getPagedUserLog` like so: {% code title="customwelcomedashboard.controller.js" %} + ```js logResource.getPagedUserLog(userLogOptions) .then(function (response) { @@ -420,6 +464,7 @@ logResource.getPagedUserLog(userLogOptions) vm.UserLogHistory = response; }); ``` + {% endcode %} Take a look at the output of console.log of the response in your browser to see the kind of information retrieved from the log: @@ -451,9 +496,11 @@ We can use the `entityResource`, another Umbraco Angular resource to enable us t 6. Inject the following code into our angular controller: {% code title="customwelcomedashboard.controller.js" %} + ```js angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService, logResource, entityResource) { ``` + {% endcode %} We need to loop through the log items from the `logResource`. Since this includes everything, we need to filter out activities we're not interested in eg, Macro Saves, or DocType Saves. Generally, we need the entry in the log to have a `nodeId`, a `logType` of 'save' and an entity type of Media or Content. @@ -476,6 +523,7 @@ This needs to be defined before we loop through the entries. Putting this together it will look like this: {% code title="customwelcomedashboard.controller.js" %} + ```js logResource.getPagedUserLog(userLogOptions) .then(function (response) { @@ -543,11 +591,13 @@ Putting this together it will look like this: // end of function }); ``` + {% endcode %} 7. Update the view to use the additional retrieved entity information: {% code title="WelcomeDashboard.html" %} + ```js

We know what you edited last week...

``` + {% endcode %} We now have a list of recently saved content and media on our Custom Dashboard: @@ -577,6 +628,7 @@ We can add a shortcut to allow the users to add a blog post. To do this we add the following code to our view: {% code title="WelcomeDashboard.html" %} + ```html
@@ -585,6 +637,7 @@ To do this we add the following code to our view:
``` + {% endcode %} `1065` is the `ID` of our blog section and `blogPost` is the alias of the type of document we want to create. @@ -598,6 +651,7 @@ At this point we are done with the tutorial, your files should contain this: CustomWelcomeDashboardController {% code title="customwelcomedashboard.controller.js" %} + ```javascript // Some angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService, logResource, entityResource) { var vm = this; @@ -683,6 +737,7 @@ At this point we are done with the tutorial, your files should contain this: }); ``` + {% endcode %} @@ -692,6 +747,7 @@ At this point we are done with the tutorial, your files should contain this: WelcomeDashboard.html {% code title="WelcomeDashboard.html" %} + ```html

Default heading {{vm.UserName}}

@@ -713,6 +769,7 @@ At this point we are done with the tutorial, your files should contain this:
``` + {% endcode %} diff --git a/14/umbraco-cms/extending-cms/health-check/README.md b/14/umbraco-cms/extending-cms/health-check/README.md index ce8b4b87272..c95ebe5da61 100644 --- a/14/umbraco-cms/extending-cms/health-check/README.md +++ b/14/umbraco-cms/extending-cms/health-check/README.md @@ -92,96 +92,95 @@ This can be anything you can think of, the results and the rectify action are co An example check: ```csharp +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.HealthChecks; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Infrastructure.HostedServices; -using Umbraco.Extensions; -using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Web.HealthCheck.Checks.SEO; [HealthCheck("3A482719-3D90-4BC1-B9F8-910CD9CF5B32", "Robots.txt", - Description = "Create a robots.txt file to block access to system folders.", - Group = "SEO")] -public class RobotsTxt : Cms.Core.HealthChecks.HealthCheck + Description = "Create a robots.txt file to block access to system folders.", + Group = "SEO")] +public class HealthCheckNotifier : Cms.Core.HealthChecks.HealthCheck { - private readonly IHostingEnvironment _hostingEnvironment; - private readonly ILogger _logger; - private readonly ILocalizedTextService _textService; - - public RobotsTxt(ILocalizedTextService textService, IHostingEnvironment hostingEnvironment, - ILogger logger) - { - _textService = textService; - _hostingEnvironment = hostingEnvironment; - _logger = logger; - } - - public override Task> GetStatus() => - Task.FromResult((IEnumerable)new[] { CheckForRobotsTxtFile() }); - - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - { - switch (action.Alias) - { - case "addDefaultRobotsTxtFile": - return AddDefaultRobotsTxtFile(); - default: - throw new InvalidOperationException("Action not supported"); - } - } - - private HealthCheckStatus CheckForRobotsTxtFile() - { - var success = File.Exists(_hostingEnvironment.MapPathContentRoot("~/robots.txt")); - var message = success - ? _textService.Localize("healthcheck", "seoRobotsCheckSuccess") - : _textService.Localize("healthcheck","seoRobotsCheckFailed"); - - var actions = new List(); - - if (success == false) - { - actions.Add(new HealthCheckAction("addDefaultRobotsTxtFile", Id) - // Override the "Rectify" button name and describe what this action will do - { - Name = _textService.Localize("healthcheck","seoRobotsRectifyButtonName"), - Description = _textService.Localize("healthcheck","seoRobotsRectifyDescription") - }); - } - - return - new HealthCheckStatus(message) - { - ResultType = success ? StatusResultType.Success : StatusResultType.Error, Actions = actions - }; - } + private readonly IHostEnvironment _hostEnvironment; + private readonly ILogger _logger; + private readonly ILocalizedTextService _textService; + + public HealthCheckNotifier(ILocalizedTextService textService, IHostEnvironment hostEnvironment, + ILogger logger) + { + _textService = textService; + _hostEnvironment = hostEnvironment; + _logger = logger; + } + + public override Task> GetStatus() => + Task.FromResult((IEnumerable)new[] { CheckForRobotsTxtFile() }); + + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + switch (action.Alias) + { + case "addDefaultRobotsTxtFile": + return AddDefaultRobotsTxtFile(); + default: + throw new InvalidOperationException("Action not supported"); + } + } + + private HealthCheckStatus CheckForRobotsTxtFile() + { + var success = File.Exists(_hostEnvironment.MapPathContentRoot("~/robots.txt")); + var message = success + ? _textService.Localize("healthcheck", "seoRobotsCheckSuccess") + : _textService.Localize("healthcheck", "seoRobotsCheckFailed"); + + var actions = new List(); + + if (success == false) + { + actions.Add(new HealthCheckAction("addDefaultRobotsTxtFile", Id) + // Override the "Rectify" button name and describe what this action will do + { + Name = _textService.Localize("healthcheck", "seoRobotsRectifyButtonName"), + Description = _textService.Localize("healthcheck", "seoRobotsRectifyDescription") + }); + } - private HealthCheckStatus AddDefaultRobotsTxtFile() - { - var success = false; - var message = string.Empty; - const string content = @"# robots.txt for Umbraco + return + new HealthCheckStatus(message) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Error, + Actions = actions + }; + } + + private HealthCheckStatus AddDefaultRobotsTxtFile() + { + var success = false; + var message = string.Empty; + const string content = @"# robots.txt for Umbraco User-agent: * Disallow: /umbraco/"; - try - { - File.WriteAllText(_hostingEnvironment.MapPathContentRoot("~/robots.txt"), content); - success = true; - } - catch (Exception exception) - { - _logger.LogError(exception, "Could not write robots.txt to the root of the site"); - } + try + { + File.WriteAllText(_hostEnvironment.MapPathContentRoot("~/robots.txt"), content); + success = true; + } + catch (Exception exception) + { + _logger.LogError(exception, "Could not write robots.txt to the root of the site"); + } - return - new HealthCheckStatus(message) - { - ResultType = success ? StatusResultType.Success : StatusResultType.Error, - Actions = new List() - }; - } + return + new HealthCheckStatus(message) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Error, + Actions = new List() + }; + } } ``` @@ -196,96 +195,94 @@ The following example shows how the core method for sending notification via ema ```csharp using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Mail; using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Services; -using Umbraco.Extensions; namespace Umbraco.Cms.Core.HealthChecks.NotificationMethods; [HealthCheckNotificationMethod("email")] public class EmailNotificationMethod : NotificationMethodBase { - private readonly ILocalizedTextService? _textService; - private readonly IHostingEnvironment? _hostingEnvironment; - private readonly IEmailSender? _emailSender; - private readonly IMarkdownToHtmlConverter? _markdownToHtmlConverter; - private ContentSettings? _contentSettings; - - public EmailNotificationMethod( - ILocalizedTextService textService, - IHostingEnvironment hostingEnvironment, - IEmailSender emailSender, - IOptionsMonitor healthChecksSettings, - IOptionsMonitor contentSettings, - IMarkdownToHtmlConverter markdownToHtmlConverter) - : base(healthChecksSettings) - { - var recipientEmail = Settings?["RecipientEmail"]; - if (string.IsNullOrWhiteSpace(recipientEmail)) - { - Enabled = false; - return; - } + private readonly ILocalizedTextService? _textService; + private readonly IHostEnvironment? _hostEnvironment; + private readonly IEmailSender? _emailSender; + private readonly IMarkdownToHtmlConverter? _markdownToHtmlConverter; + private ContentSettings? _contentSettings; + + public EmailNotificationMethod( + ILocalizedTextService textService, + IHostEnvironment hostEnvironment, + IEmailSender emailSender, + IOptionsMonitor healthChecksSettings, + IOptionsMonitor contentSettings, + IMarkdownToHtmlConverter markdownToHtmlConverter) + : base(healthChecksSettings) + { + var recipientEmail = Settings?["RecipientEmail"]; + if (string.IsNullOrWhiteSpace(recipientEmail)) + { + Enabled = false; + return; + } - RecipientEmail = recipientEmail; + RecipientEmail = recipientEmail; - _textService = textService ?? throw new ArgumentNullException(nameof(textService)); - _hostingEnvironment = hostingEnvironment; - _emailSender = emailSender; - _markdownToHtmlConverter = markdownToHtmlConverter; - _contentSettings = contentSettings.CurrentValue ?? throw new ArgumentNullException(nameof(contentSettings)); + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); + _hostEnvironment = hostEnvironment; + _emailSender = emailSender; + _markdownToHtmlConverter = markdownToHtmlConverter; + _contentSettings = contentSettings.CurrentValue ?? throw new ArgumentNullException(nameof(contentSettings)); - contentSettings.OnChange(x => _contentSettings = x); - } + contentSettings.OnChange(x => _contentSettings = x); + } - public string? RecipientEmail { get; } + public string? RecipientEmail { get; } - public override async Task SendAsync(HealthCheckResults results) - { - if (ShouldSend(results) == false) - { - return; - } + public override async Task SendAsync(HealthCheckResults results) + { + if (ShouldSend(results) == false) + { + return; + } - if (string.IsNullOrEmpty(RecipientEmail)) - { - return; - } + if (string.IsNullOrEmpty(RecipientEmail)) + { + return; + } - var message = _textService?.Localize("healthcheck","scheduledHealthCheckEmailBody", new[] - { - DateTime.Now.ToShortDateString(), - DateTime.Now.ToShortTimeString(), - _markdownToHtmlConverter?.ToHtml(results, Verbosity) - }); + var message = _textService?.Localize("healthcheck", "scheduledHealthCheckEmailBody", new[] + { + DateTime.Now.ToShortDateString(), + DateTime.Now.ToShortTimeString(), + _markdownToHtmlConverter?.ToHtml(results, Verbosity) + }); - // Include the Umbraco Application URL host in the message subject so that - // you can identify the site that these results are for. - var host = _hostingEnvironment?.ApplicationMainUrl?.ToString(); + // Include the Umbraco Application URL host in the message subject so that + // you can identify the site that these results are for. + var host = _hostEnvironment?.ContentRootPath?.ToString(); - var subject = _textService?.Localize("healthcheck","scheduledHealthCheckEmailSubject", new[] { host }); + var subject = _textService?.Localize("healthcheck", "scheduledHealthCheckEmailSubject", new[] { host }); - var mailMessage = CreateMailMessage(subject, message); - Task? task = _emailSender?.SendAsync(mailMessage, Constants.Web.EmailTypes.HealthCheck); - if (task is not null) - { - await task; - } - } + var mailMessage = CreateMailMessage(subject, message); + Task? task = _emailSender?.SendAsync(mailMessage, Constants.Web.EmailTypes.HealthCheck); + if (task is not null) + { + await task; + } + } - private EmailMessage CreateMailMessage(string? subject, string? message) - { - var to = _contentSettings?.Notifications.Email; + private EmailMessage CreateMailMessage(string? subject, string? message) + { + var to = _contentSettings?.Notifications.Email; - if (string.IsNullOrWhiteSpace(subject)) - subject = "Umbraco Health Check Status"; + if (string.IsNullOrWhiteSpace(subject)) + subject = "Umbraco Health Check Status"; - var isBodyHtml = message.IsNullOrWhiteSpace() == false && message!.Contains("<") && message.Contains(" PropertyEditorAliases => new[] {"BadMediaPicker"}; - - public string ToArtifact(object value, PropertyType propertyType, ICollection dependencies) - { - return value.ToString(); - } - - public object FromArtifact(string value, PropertyType propertyType, object currentValue) - { - return currentValue; - } + private readonly IEntityService _entityService; + private readonly ILogger _logger; + public BadMediaValueConnector(IEntityService entityService, ILogger logger) + { + _entityService = entityService; + _logger = logger; + } + public IEnumerable PropertyEditorAliases => new[] { "BadMediaPicker" }; + + public string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies, IContextCache contextCache) + { + return value.ToString(); + } + + public object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache) + { + return currentValue; + } } ``` +
+ + Legacy v8 uaas.cmd In this case I cloned the Cloud project down using the [uaas.cmd](https://umbra.co/uaas-cmd) tool, which means that I have a class library that I can add the ValueConnector to. This will automatically have some references included and will build a DLL, eg. `projectalias.core.dll`, and put it in the websites bin folder when building. This has no impact on the way you work, but it may help you understand why some things are named the way they are. -At this point I have one clone of the site locally. However, to test this I will push the changes to the Cloud site and then clone it down again. The second clone doesn't need to be cloned with the `uaas.cmd` tool as we aren't developing on it, all we need is to run it locally. +uaas.cmd is a tool that can be used to clone down v8 project and below. +
+ +At this point I have one clone of the site locally. However, to test this I will push the changes to the Cloud site and then clone it down again. At this point I have two local sites: -**Site 1**: Full Visual Studio solution Running on http://localhost:6240/ (Randomly generated) Has the ValueConnector in a class library that is built to a dll and copied to the websites bin on build +**Site 1**: Full Visual Studio solution Running on (Randomly generated) Has the ValueConnector in a class library that is built to a dll and copied to the websites bin on build -**Site 2**: A website served through VS Code (Could be IIS or anything else, doesn't matter) Running on http://localhost:17025/ (Randomly generated) Has the ValueConnector dll in the bin from the clone +**Site 2**: A website served through VS Code (Could be IIS or anything else, doesn't matter) Running on (Randomly generated) Has the ValueConnector dll in the bin from the clone Now we will set up these two identical sites to transfer content between each other. -To do so go to `site1/Config/UmbracoDeploy.config` and edit the live environment url to be Site 2's url (http://localhost:17025/ in my case). Then do the same for Site 2 but put in the domain for Site 1 as the "live" one. +To do so go to `site1/Config/UmbracoDeploy.config` and edit the live environment url to be Site 2's url ( in my case). Then do the same for Site 2 but put in the domain for Site 1 as the "live" one. At this point you should be able to go to the backoffice of either environment and do a Content transfer to live, and it should end up on the other (Assuming no errors from your custom connector). @@ -173,33 +183,31 @@ That is not a good assumption, and you may have noticed that there is a paramete In order to add the image as a dependency for the item being transferred, we will update the code in the `ToArtifact` method: ```csharp -public string ToArtifact(object value, PropertyType propertyType, ICollection dependencies) -{ - var svalue = value as string; - if (string.IsNullOrWhiteSpace(svalue)) - return null; - - if (int.TryParse(svalue, out var intvalue)) - return null; - - var getKeyAttempt = _entityService.GetKey(intvalue, UmbracoObjectTypes.Media); - - if (getKeyAttempt.Success) - { - var udi = new GuidUdi(Constants.UdiEntityType.Media, getKeyAttempt.Result); - // ArtifactDependencyMode can either be "Exist" or "Match" - // Match is an exact match where exist just checks if it is there - dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist)); - - return udi.ToString(); - } - else - { - _logger.Debug($"Couldn't convert integer value #{intvalue} to UDI"); - } - - return null; -} +public string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies, IContextCache contextCache) + { + var svalue = value as string; + if (string.IsNullOrWhiteSpace(svalue)) + return null; + + if (!int.TryParse(svalue, out var intvalue)) + return null; + + var getKeyAttempt = _entityService.GetKey(intvalue, UmbracoObjectTypes.Media); + + if (getKeyAttempt.Success) + { + var udi = new GuidUdi(Constants.UdiEntityType.Media, getKeyAttempt.Result); + dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist)); + + return udi.ToString(); + } + else + { + _logger.LogDebug($"Couldn't convert integer value #{intvalue} to UDI"); + } + + return null; + } ``` You can find references on the methods used here in our API documentation: @@ -220,20 +228,20 @@ Note: Showing the variable values is a feature of [ReSharper](https://www.jetbra By the time we hit `FromArtifact` value of `"umb://media/00c9eff861654f52be7a33367c3561a4"` all that is left to do is convert back to an `int`. ```csharp -public object FromArtifact(string value, PropertyType propertyType, object currentValue) -{ - if (string.IsNullOrWhiteSpace(value)) - return null; +public object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache) + { + if (string.IsNullOrWhiteSpace(value)) + return null; - if (!GuidUdi.TryParse(value, out var udi) || udi.Guid == Guid.Empty) - return null; + if (!UdiParser.TryParse(value, out GuidUdi? udi) || udi.Guid == Guid.Empty) + return null; - var getIdAttempt = _entityService.GetId(udi.Guid, UmbracoObjectTypes.Media); + var getIdAttempt = _entityService.GetId(udi.Guid, UmbracoObjectTypes.Media); - if (!getIdAttempt.Success) return null; + if (!getIdAttempt.Success) return null; - return getIdAttempt.Result.ToString(); -} + return getIdAttempt.Result.ToString(); + } ``` Here is a gif showing the ValueConnector in action. A new image is uploaded, the ID on the node is updated and transferred. Finally the image is on the new environment and the ID is updated: @@ -243,66 +251,63 @@ Here is a gif showing the ValueConnector in action. A new image is uploaded, the The final ValueConnector code will look like this: ```csharp -using System; -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Deploy; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Services; +using Umbraco.Cms.Core.Deploy; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core; namespace valueconnector.Core.Controllers; public class BadMediaValueConnector : IValueConnector { - private readonly IEntityService _entityService; - private readonly ILogger _logger; - public BadMediaValueConnector(IEntityService entityService, ILogger logger) - { - _entityService = entityService; - _logger = logger; - } - public IEnumerable PropertyEditorAliases => new[] {"BadMediaPicker"}; - - public string ToArtifact(object value, PropertyType propertyType, ICollection dependencies) - { - var svalue = value as string; - if (string.IsNullOrWhiteSpace(svalue)) - return null; - - if (!int.TryParse(svalue, out var intvalue)) - return null; - - var getKeyAttempt = _entityService.GetKey(intvalue, UmbracoObjectTypes.Media); - - if (getKeyAttempt.Success) - { - var udi = new GuidUdi(Constants.UdiEntityType.Media, getKeyAttempt.Result); - dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist)); - - return udi.ToString(); - } - else - { - _logger.Debug($"Couldn't convert integer value #{intvalue} to UDI"); - } - - return null; - } - - public object FromArtifact(string value, PropertyType propertyType, object currentValue) - { - if (string.IsNullOrWhiteSpace(value)) - return null; - - if (!GuidUdi.TryParse(value, out var udi) || udi.Guid == Guid.Empty) - return null; - - var getIdAttempt = _entityService.GetId(udi.Guid, UmbracoObjectTypes.Media); - - if (!getIdAttempt.Success) return null; - - return getIdAttempt.Result.ToString(); - } + private readonly IEntityService _entityService; + private readonly ILogger _logger; + public BadMediaValueConnector(IEntityService entityService, ILogger logger) + { + _entityService = entityService; + _logger = logger; + } + public IEnumerable PropertyEditorAliases => new[] { "BadMediaPicker" }; + + public string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies, IContextCache contextCache) + { + var svalue = value as string; + if (string.IsNullOrWhiteSpace(svalue)) + return null; + + if (!int.TryParse(svalue, out var intvalue)) + return null; + + var getKeyAttempt = _entityService.GetKey(intvalue, UmbracoObjectTypes.Media); + + if (getKeyAttempt.Success) + { + var udi = new GuidUdi(Constants.UdiEntityType.Media, getKeyAttempt.Result); + dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist)); + + return udi.ToString(); + } + else + { + _logger.LogDebug($"Couldn't convert integer value #{intvalue} to UDI"); + } + + return null; + } + + public object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + + if (!UdiParser.TryParse(value, out GuidUdi? udi) || udi.Guid == Guid.Empty) + return null; + + var getIdAttempt = _entityService.GetId(udi.Guid, UmbracoObjectTypes.Media); + + if (!getIdAttempt.Success) return null; + + return getIdAttempt.Result.ToString(); + } } ``` diff --git a/14/umbraco-cms/fundamentals/design/stylesheets-javascript.md b/14/umbraco-cms/fundamentals/design/stylesheets-javascript.md index 9e8b5074325..677e8fd6423 100644 --- a/14/umbraco-cms/fundamentals/design/stylesheets-javascript.md +++ b/14/umbraco-cms/fundamentals/design/stylesheets-javascript.md @@ -79,127 +79,3 @@ By default all JavaScript files will be stored in the `wwwroot/scripts` folder i {% hint style="info" %} If you are working locally, you can create CSS and JS files outside of the Backoffice - as long as they are placed in appropriate folders (`css` and `scripts`), they will show up in the Backoffice when you right-click on the folder and then pick reload. {% endhint %} - -## Bundling & Minification for JavaScript and CSS - -You can use whichever tool you are comfortable with for bundling & minification by implementing the `IRuntimeMinifier` interface in your custom minifier class, though it is worth noting that Umbraco 9+ ships with Smidge which offers lightweight runtime bundling and minification. - -You can create various bundles of your site's CSS or JavaScript files in your code that can be rendered later in your views. There can be a single bundle for the entire site, or a common bundle for the files you want to be loaded on every page, as well as page-specific bundles, just by listing your resources in the order you like. - -**Step 1:** Create a `INotificationHandler` - -```csharp -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.WebAssets; - -namespace Umbraco.Docs.Samples.Web.Stylesheets_Javascript; - -public class CreateBundlesNotificationHandler : INotificationHandler -{ - private readonly IRuntimeMinifier _runtimeMinifier; - private readonly IRuntimeState _runtimeState; - - public CreateBundlesNotificationHandler(IRuntimeMinifier runtimeMinifier, IRuntimeState runtimeState) { - _runtimeMinifier = runtimeMinifier; - _runtimeState = runtimeState; - } - public void Handle(UmbracoApplicationStartingNotification notification) - { - if (_runtimeState.Level == RuntimeLevel.Run) - { - _runtimeMinifier.CreateJsBundle("registered-js-bundle", - BundlingOptions.NotOptimizedAndComposite, - new[] { "~/scripts/test-script1.js", "~/scripts/test-script2.js" }); - - _runtimeMinifier.CreateCssBundle("registered-css-bundle", - BundlingOptions.NotOptimizedAndComposite, - new[] { "~/css/test-style.css" }); - } - } -} -``` - -{% hint style="info" %} -See below for the different [Bundling Options](stylesheets-javascript.md#bundling-options). -{% endhint %} - -**Step 2:** Register the `INotificationHandler` in the `Program.cs` file. - -```csharp -builder.CreateUmbracoBuilder() - .AddBackOffice() - .AddWebsite() - .AddDeliveryApi() - .AddComposers() - .AddNotificationHandler() - .Build(); -``` - -**Step 3:** Render the bundles by the bundle name in a view template file using the Smidge TagHelper: - -```csharp - - - - - - -``` - -### Bundling Options - -The following bundling options can be set when creating your bundles: - -```csharp -BundlingOptions.OptimizedAndComposite // The files will be minified and bundled into an individual file -BundlingOptions.OptimizedNotComposite // The files will be minified but not bundled into an individual file -BundlingOptions.NotOptimizedNotComposite // The files will not be minified and will not be bundled into an individual file -BundlingOptions.NotOptimizedAndComposite // The files will not be minified but will be bundled into an individual file -``` - -## Rendering - -### Using Smidge TagHelper - -{% hint style="info" %} -The Smidge TagHelper does not consider the value of `Umbraco:CMS:Hosting:Debug` set in your appsettings file. - -If you do need to debug bundles you can inject `hostingSettings` and add the `debug` attribute as shown below. -{% endhint %} - -```csharp -@using Microsoft.Extensions.Options -@using Umbraco.Cms.Core.Configuration.Models -@inject IOptions hostingSettings -@{ - var debugMode = hostingSettings.Value.Debug; -} -``` - -```csharp - - -``` - -### Using IRuntimeMinifier - -{% hint style="info" %} -In case you are in Debug mode, your bundles won't be minified or bundled, so you would need to set `Umbraco:CMS:Hosting:Debug: false` in your appsettings file. -{% endhint %} - -```csharp -@using Umbraco.Cms.Core.WebAssets -@inject IRuntimeMinifier runtimeMinifier - - - - @Html.Raw(await runtimeMinifier.RenderJsHereAsync("registered-js-bundle")) - @Html.Raw(await runtimeMinifier.RenderCssHereAsync("registered-css-bundle")) - - -``` - -Full details about Smidge can be found here: [https://github.com/Shazwazza/Smidge](https://github.com/Shazwazza/Smidge) diff --git a/14/umbraco-cms/fundamentals/setup/server-setup/runtime-modes.md b/14/umbraco-cms/fundamentals/setup/server-setup/runtime-modes.md index 60d31b1ef78..343fe387268 100644 --- a/14/umbraco-cms/fundamentals/setup/server-setup/runtime-modes.md +++ b/14/umbraco-cms/fundamentals/setup/server-setup/runtime-modes.md @@ -69,7 +69,6 @@ This mode disables both in-memory ModelsBuilder generation (see [Development mod * The application is built/published in Release mode (with JIT optimization enabled), e.g. using `dotnet publish --configuration Release`; * `Umbraco:CMS:WebRouting:UmbracoApplicationUrl` is set to a valid URL; * `Umbraco:CMS:Global:UseHttps` is enabled; -* `Umbraco:CMS:RuntimeMinification:CacheBuster` is set to a fixed cache buster like `Version` or `AppDomain`; * `Umbraco:CMS:ModelsBuilder:ModelsMode` is set to `Nothing`. {% hint style="info" %} @@ -123,7 +122,6 @@ Validation of the above-mentioned settings is done when determining the runtime * `JITOptimizerValidator` - Ensure the application is built/published in Release mode (with JIT optimization enabled) when in production runtime mode, e.g. using `dotnet publish --configuration Release`; * `UmbracoApplicationUrlValidator` - ensure `Umbraco:CMS:WebRouting:UmbracoApplicationUrl` is configured when in production runtime mode; * `UseHttpsValidator` - ensure `Umbraco:CMS:Global:UseHttps` is enabled when in production runtime mode; -* `RuntimeMinificationValidator` - ensure `Umbraco:CMS:RuntimeMinification:CacheBuster` is set to a fixed cache buster like `Version` or `AppDomain` when in production runtime mode; * `ModelsBuilderModeValidator` - ensure `Umbraco:CMS:ModelsBuilder:ModelsMode` is not set to `InMemoryAuto` when in development runtime mode and set to `Nothing` when in production runtime mode. The following example removes the default `UmbracoApplicationUrlValidator` and adds a new custom `DisableElectionForSingleServerValidator`: diff --git a/14/umbraco-cms/fundamentals/setup/upgrading/version-specific/README.md b/14/umbraco-cms/fundamentals/setup/upgrading/version-specific/README.md index 9e4c85de365..732a46b6165 100644 --- a/14/umbraco-cms/fundamentals/setup/upgrading/version-specific/README.md +++ b/14/umbraco-cms/fundamentals/setup/upgrading/version-specific/README.md @@ -1,8 +1,6 @@ --- description: >- - This document covers specific upgrade steps if a version requires them. Most - versions do not require specific upgrade steps. In most cases, you will be - able to upgrade directly from your current versi + This document covers specific upgrade steps if a version requires them. Most versions do not require specific upgrade steps and you will be able to upgrade directly from your current version. --- # Version Specific Upgrades @@ -37,7 +35,7 @@ Below you can find the list of breaking changes introduced in Umbraco 14 CMS. * [Macros and Partial View Macros have been removed](https://github.com/umbraco/Announcements/issues/14). Use partial views and/or blocks in the Rich Text Editor. * XPath has been removed. An alternative is using the Dynamic Roots in the Multinode Treepicker and for ContentXPath the alternative is [IContentLastChanceFinder](../../../../tutorials/custom-error-page.md). * [package-manifest is now umbraco.package.json](../../../../extending-backoffice/package-manifest.md) -* [Smidge has been removed from default installation](https://github.com/umbraco/Umbraco-CMS/pull/15788). Can be manually installed if needed. +* [Smidge has been removed from default installation](https://github.com/umbraco/Umbraco-CMS/pull/15788) along with RuntimeMinification setting. Smidge can be manually installed if needed and you can read the [Smidge](https://github.com/Shazwazza/Smidge) documentation on how to setup a similar setting to [RuntimeMinification](https://github.com/umbraco/UmbracoDocs/blob/umbraco-eol-versions/11/umbraco-cms/reference/configuration/runtimeminificationsettings.md). * New login screen * **Light, Dark or Contract Mode** option has been added in the backoffice. You can choose your preffered mode from your profile information. * [UI Library and UI API](../../../../extending-backoffice/ui-library.md) external documentations. diff --git a/14/umbraco-cms/reference/common-pitfalls.md b/14/umbraco-cms/reference/common-pitfalls.md index a0e54042c15..40e2cd1b0f5 100644 --- a/14/umbraco-cms/reference/common-pitfalls.md +++ b/14/umbraco-cms/reference/common-pitfalls.md @@ -10,7 +10,7 @@ This section describes many common pitfalls that developers fall into. Some of t Generally speaking, if you are writing software these days you should be using Dependency Injection principles. If you do this, you probably aren't using [Singletons](https://en.wikipedia.org/wiki/Singleton\_pattern) or [Statics](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members) (and for the most part you shouldn't be!). Since Umbraco 9 comes with dependency injection out of the box, there really isn't any reason to use singletons or statics. It makes your code very difficult to test but more importantly using Singletons and Statics in your code makes it very hard to manage, APIs become leaky, and ultimately you'll end up with more problems than when you started. -Dependency injection is available everywhere, and you can register your own services as well, additionally, some resources are available through properties on certain base classes. For example, all Razor views that Umbraco creates expose an `UmbracoHelper` property you can access through `@Umbraco`, as well as a `SmidgeHelper` property. The other base classes that expose some things you might need like `UmbracoContext` are things like `SurfaceController`, but even here the services are initially gotten through DI, and you can inject further Umbraco and custom services that you might need. +Dependency injection is available everywhere, and you can register your own services as well, additionally, some resources are available through properties on certain base classes. For example, all Razor views that Umbraco creates expose an `UmbracoHelper` property you can access through `@Umbraco`. The other base classes that expose some things you might need like `UmbracoContext` are things like `SurfaceController`, but even here the services are initially gotten through DI, and you can inject further Umbraco and custom services that you might need. For more information about consuming and registering your own dependencies have a look at the [Dependency Injection](using-ioc.md) documentation @@ -108,11 +108,11 @@ You create a menu on your Home page like: ```csharp ``` @@ -124,11 +124,11 @@ This can be re-written as: ```csharp ``` @@ -142,11 +142,11 @@ Here's a common pitfall that is seen. Let's continue the menu example, in this e ```csharp ``` @@ -154,14 +154,14 @@ The syntax `@Model.Root()` is shorthand for doing this: `Model.AncestorOrSelf(1) ```csharp @{ - var root = Model.Root(); + var root = Model.Root(); } ``` @@ -178,11 +178,11 @@ Your views should rely only on the read-only data services such as `UmbracoHelpe @inject IContentService _contentService @{ - // Services access in your views :( - var dontDoThis = _contentService.GetById(1234); - - // Content cache access in your views - var doThis = Umbraco.Content(1234); + // Services access in your views :( + var dontDoThis = _contentService.GetById(1234); + + // Content cache access in your views + var doThis = Umbraco.Content(1234); } ``` @@ -284,17 +284,17 @@ You then run the following code to show to show the favorites ```csharp @var recipeNode = Umbraco.TypedContent(3251); @{ - var recipeNode = Umbraco.Content(1234); + var recipeNode = Umbraco.Content(1234); }
    - @foreach (var recipe in recipeNode.Children - .Select(x => new RecipeModel(x, _publishedValueFallback)) - .OrderByDescending(x => x.Votes) - .Take(10)) - { -
  • @recipe.Name
  • - } + @foreach (var recipe in recipeNode.Children + .Select(x => new RecipeModel(x, _publishedValueFallback)) + .OrderByDescending(x => x.Votes) + .Take(10)) + { +
  • @recipe.Name
  • + }
``` @@ -345,16 +345,16 @@ This is still not great though. There really isn't much reason to create a `Reci ```csharp @{ - var recipeNode = Umbraco.Content(1234); + var recipeNode = Umbraco.Content(1234); }
    - @foreach (var recipe in recipeNode.Children - .OrderByDescending(x => x.Value("votes")) - .Take(10)) - { -
  • @recipe.Name
  • - } + @foreach (var recipe in recipeNode.Children + .OrderByDescending(x => x.Value("votes")) + .Take(10)) + { +
  • @recipe.Name
  • + }
``` diff --git a/14/umbraco-cms/reference/configuration/README.md b/14/umbraco-cms/reference/configuration/README.md index afc52464e9a..4b340d2dcf7 100644 --- a/14/umbraco-cms/reference/configuration/README.md +++ b/14/umbraco-cms/reference/configuration/README.md @@ -128,7 +128,6 @@ A complete list of all the configuration sections included in Umbraco, by defaul * [Plugins settings](pluginssettings.md) * [Request handler settings](requesthandlersettings.md) * [Rich text editor settings](richtexteditorsettings.md) -* [Runtime minification settings](runtimeminificationsettings.md) * [Runtime settings](runtimesettings.md) * [Security settings](securitysettings.md) * [Serilog settings](serilog.md) diff --git a/14/umbraco-cms/reference/configuration/filesystemproviders.md b/14/umbraco-cms/reference/configuration/filesystemproviders.md index 523410fe4c4..0bac9982caa 100644 --- a/14/umbraco-cms/reference/configuration/filesystemproviders.md +++ b/14/umbraco-cms/reference/configuration/filesystemproviders.md @@ -8,7 +8,6 @@ Filesystem providers are configured via code, you can either configure it in a c ```csharp using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Infrastructure.DependencyInjection; using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; @@ -17,21 +16,22 @@ namespace FilesystemProviders; public class FilesystemComposer : IComposer { - public void Compose(IUmbracoBuilder builder) => - builder.SetMediaFileSystem(factory => - { - IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); - var folderLocation = "~/CustomMediaFolder"; - var rootPath = hostingEnvironment.MapPathWebRoot(folderLocation); - var rootUrl = hostingEnvironment.ToAbsolute(folderLocation); - - return new PhysicalFileSystem( - factory.GetRequiredService(), - hostingEnvironment, - factory.GetRequiredService>(), - rootPath, - rootUrl); - }); + public void Compose(IUmbracoBuilder builder) => + builder.SetMediaFileSystem(factory => + { + IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); + IWebHostEnvironment webHostEnvironment = factory.GetRequiredService(); + var folderLocation = "~/CustomMediaFolder"; + var rootPath = webHostEnvironment.MapPathWebRoot(folderLocation); + var rootUrl = hostingEnvironment.ToAbsolute(folderLocation); + + return new PhysicalFileSystem( + factory.GetRequiredService(), + hostingEnvironment, + factory.GetRequiredService>(), + rootPath, + rootUrl); + }); } ``` @@ -69,35 +69,31 @@ app.UseStaticFiles(new StaticFileOptions Now you can register the folder as the media filesystem ```csharp -using System.IO; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Infrastructure.DependencyInjection; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace FilesystemProviders; public class FilesystemComposer : IComposer { - public void Compose(IUmbracoBuilder builder) - { - builder.SetMediaFileSystem((factory) => - { - IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); - var rootPath = Path.Combine("C:", "storage", "umbracoMedia"); - var rootUrl = hostingEnvironment.ToAbsolute("/CustomPath"); - - return new PhysicalFileSystem( - factory.GetRequiredService(), - hostingEnvironment, - factory.GetRequiredService>(), - rootPath, - rootUrl); - }); - } + public void Compose(IUmbracoBuilder builder) + { + builder.SetMediaFileSystem((factory) => + { + IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); + var rootPath = Path.Combine("C:", "storage", "umbracoMedia"); + var rootUrl = hostingEnvironment.ToAbsolute("/CustomPath"); + + return new PhysicalFileSystem( + factory.GetRequiredService(), + hostingEnvironment, + factory.GetRequiredService>(), + rootPath, + rootUrl); + }); + } } ``` @@ -120,14 +116,12 @@ If you want all your media files in the same location, you have to copy all pre- ## Get the contents of a file as a stream -The recommended approach to obtain a file's content as a stream is to utilize the `MediaFileManager`. It is advised to avoid reading the file directly from the server using methods like `Server.MapPath`. This will ensure that, regardless of the file system provider, the stream will be returned correctly. TThis example demonstrates using MediaFileManager to validate file existence and stream it back from a controller. +The recommended approach to obtain a file's content as a stream is to utilize the `MediaFileManager`. It is advised to avoid reading the file directly from the server using methods like `Server.MapPath`. This will ensure that, regardless of the file system provider, the stream will be returned correctly. This example demonstrates using MediaFileManager to validate file existence and stream it back from a controller. ```csharp -using System.IO; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Routing; @@ -140,44 +134,43 @@ namespace FilesystemProviders; public class MediaController : SurfaceController { - private readonly MediaFileManager _mediaFileManager; - private readonly IHostingEnvironment _hostingEnvironment; - - public MediaController( - IUmbracoContextAccessor umbracoContextAccessor, - IUmbracoDatabaseFactory databaseFactory, - ServiceContext services, - AppCaches appCaches, - IProfilingLogger profilingLogger, - IPublishedUrlProvider publishedUrlProvider, - MediaFileManager mediaFileManager, - IHostingEnvironment hostingEnvironment) - : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) - { - _mediaFileManager = mediaFileManager; - _hostingEnvironment = hostingEnvironment; - } - - public IActionResult Index(string id, string file) - { - var path = _hostingEnvironment.MapPathWebRoot($"/media/{id}/{file}"); - - if (_mediaFileManager.FileSystem.FileExists(path)) - { - var stream = _mediaFileManager.FileSystem.OpenFile(path); - stream.Seek(0, SeekOrigin.Begin); - - var provider = new FileExtensionContentTypeProvider(); - string contentType; - if (!provider.TryGetContentType(file, out contentType)) - { - contentType = "application/octet-stream"; - } - - return new FileStreamResult(stream, contentType); - } - - return new NotFoundResult(); - } + private readonly MediaFileManager _mediaFileManager; + private readonly IWebHostEnvironment _webHostEnvironment; + + public MediaController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider, + MediaFileManager mediaFileManager, + IWebHostEnvironment webHostEnvironment) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + _mediaFileManager = mediaFileManager; + _webHostEnvironment = webHostEnvironment; + } + + public IActionResult Index(string id, string file) + { + var path = _webHostEnvironment.MapPathWebRoot($"/media/{id}/{file}"); + if (_mediaFileManager.FileSystem.FileExists(path) == false) + { + return new NotFoundResult(); + } + + var stream = _mediaFileManager.FileSystem.OpenFile(path); + stream.Seek(0, SeekOrigin.Begin); + + var provider = new FileExtensionContentTypeProvider(); + if (!provider.TryGetContentType(file, out var contentType)) + { + contentType = "application/octet-stream"; + } + + return new FileStreamResult(stream, contentType); + + } } ``` diff --git a/14/umbraco-cms/reference/configuration/runtimeminificationsettings.md b/14/umbraco-cms/reference/configuration/runtimeminificationsettings.md deleted file mode 100644 index 0056a4f1ea6..00000000000 --- a/14/umbraco-cms/reference/configuration/runtimeminificationsettings.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -description: "Information on the runtime minification settings section" ---- - -# Runtime minification settings - -This section allows you to configure the runtime minifications (defaults shown), used by ['Smidge - A lightweight runtime CSS/Javascript minification,combination, compression & management library for ASP.NET'](https://github.com/shazwazza/smidge) - -```json -"Umbraco": { - "CMS": { - "RuntimeMinification": { - "UseInMemoryCache": false, - "CacheBuster": "Version" - } - } -} -``` -## Use 'in memory' cache - -This setting determines whether Smidge should save it's cached output in memory, or in a file on disk. If set to false, then the folder will be created at the wwwroot of your Umbraco site in a folder called 'Smidge'/ - -{% hint style="info" %} -It is not possible to disable in memory caching when `Timestamp` is chosen as `CacheBuster` method. -{% endhint %} - -## Cache buster - -Specifies mechanism for cache invalidation. - -The options are: - -* Version - Caches will be busted when your assembly version changes, when the upstream Umbraco version changes and when the version string specified in Configuration changes. -* AppDomain - Caches will be busted when the app restarts. -* Timestamp - Caches will be busted based on a timestamp of the bundled files. - -## Manually changing the Cache buster version - -If you use a CacheBuster setting of "Version" you can add an additional configuration option, also called 'Version' , which allows you to set a value that you can incremement manually, or via a build server to make sure the version number changes for Smidge and busts the cache. - -```json -"Umbraco": { - "CMS": { - "RuntimeMinification": { - "UseInMemoryCache": true, - "CacheBuster": "Version", - "Version": "1234" - } - } -} -``` -The actual 'Version' number will not be visible in the url of the assets, this is because it is combined, along with the Umbraco Version from configuration and the your project assembly dll, and then once combined a 'hash' is generated to obscure these details. - -in the HTML link thus: `````` (when [`Umbraco:CMS:Hosting:Debug:false`](hostingsettings.md)) - -So if you increased the Version in the configuration by 1 to 1235, all you would see is a different hash! - -{% hint style="info" %} -For production environments, it's recommended to set Cache Buster to 'Version' (you don't actually need to supply a version number, but if you do, you can control when the cache breaks, eg if a package has installed new assets) or to 'AppDomain'. -{% endhint %} - -Another configuration option (of Smidge) is the `"dataFolder`" setting, this setting specifies what folder Smidge will use for its temporary data, it should not be necessary to change this either, it will only be used if UseInMemoryCache is set to false. diff --git a/14/umbraco-cms/tutorials/creating-a-custom-dashboard/README.md b/14/umbraco-cms/tutorials/creating-a-custom-dashboard/README.md index 9b5f6a9e4e8..814cade0513 100644 --- a/14/umbraco-cms/tutorials/creating-a-custom-dashboard/README.md +++ b/14/umbraco-cms/tutorials/creating-a-custom-dashboard/README.md @@ -35,6 +35,7 @@ At the end of this guide, we should have a friendly welcoming dashboard displayi 3. Add the following HTML to the `WelcomeDashboard.html`: {% code title="WelcomeDashboard.html" lineNumbers="true" %} + ```html

Welcome to Umbraco

@@ -42,6 +43,7 @@ At the end of this guide, we should have a friendly welcoming dashboard displayi

You can put anything here...

``` + {% endcode %} Similar to a property editor you will now register the dashboard in a `package.manifest` file. @@ -49,6 +51,7 @@ Similar to a property editor you will now register the dashboard in a `package.m 4\. Add a new file inside the `~/App_Plugins/CustomWelcomeDashboard` folder called `package.manifest`: {% code title="package.manifest" lineNumbers="true" %} + ```json { "dashboards": [ @@ -65,6 +68,7 @@ Similar to a property editor you will now register the dashboard in a `package.m ] } ``` + {% endcode %} The above configuration is effectively saying: @@ -89,6 +93,7 @@ The `App_Plugins` version of the `Lang` directory is case-sensitive on Linux sys {% endhint %} {% code title="en-US.xml" lineNumbers="true" %} + ```xml @@ -97,6 +102,7 @@ The `App_Plugins` version of the `Lang` directory is case-sensitive on Linux sys ``` + {% endcode %} [Read more about language files](../../extending/language-files.md) @@ -110,6 +116,7 @@ We can apply the same workflow to elements inside the dashboard, not only the na 3\. Extend the translation file `xml` with the following code: {% code title="en-US.xml" lineNumbers="true" %} + ```xml @@ -123,6 +130,7 @@ We can apply the same workflow to elements inside the dashboard, not only the na ``` + {% endcode %} We are adding another area tag with a few keys. we then need to add some HTML to the `WelcomeDashboard`. @@ -130,6 +138,7 @@ We are adding another area tag with a few keys. we then need to add some HTML to 4. Adjust the dashboard HTML with the following code: {% code title="WelcomeDashboard.html" lineNumbers="true" %} + ```html

Default heading

@@ -137,6 +146,7 @@ We are adding another area tag with a few keys. we then need to add some HTML to

Default copyright

``` + {% endcode %} The `localize` tag will be replaced with the translated keywords. We have some default text inside the tags above, which can be removed. It would usually not be visible after the translation is applied. @@ -144,19 +154,23 @@ The `localize` tag will be replaced with the translated keywords. We have some d As for the `localize` tag syntax in HTML, the area alias is combined with the key alias - so if you want to translate: {% code title="en-US.xml" %} + ```html Default heading ``` + {% endcode %} The XML for that specific key will look like this: {% code title="en-US.xml" %} + ```xml Welcome! ``` + {% endcode %} The area and key aliases are combined and an underscore is added in between. @@ -174,6 +188,7 @@ With the above steps completed, our dashboard is all set up to be translated acr To test it out, you can add another language XML file, like `da.xml` for the Danish language. {% code title="da.xml" %} + ```xml @@ -187,6 +202,7 @@ To test it out, you can add another language XML file, like `da.xml` for the Dan ``` + {% endcode %} The backoffice language can be changed in the Users section if you wish to test out the translations. @@ -204,6 +220,7 @@ Inside the package.manifest we add a bit of JSON to describe the dashboard's req 1. Add the following JSON to the `package.manifest` file: {% code title="package.manifest" %} + ```json { "dashboards": [ @@ -226,17 +243,20 @@ Inside the package.manifest we add a bit of JSON to describe the dashboard's req ] } ``` + {% endcode %} 2. Create a stylesheet in our `CustomWelcomeDashboard` folder called `customwelcomedashboard.css`, and add some style: {% code title="CustomWelcomeDashboard.csss" %} + ```css .welcome-dashboard h1 { font-size:4em; color:purple; } ``` + {% endcode %} The stylesheet will be loaded and applied to our dashboard. Add images and HTML markup as required. @@ -247,14 +267,6 @@ The stylesheet will be loaded and applied to our dashboard. Add images and HTML One caveat is that the `package.manifest` file is loaded into memory when Umbraco starts up. If you are adding a new stylesheet or JavaScript file you will need to start and stop your application for it to be loaded. {% endhint %} -{% hint style="info" %} -**For version 9 and above** - -If the title doesn't change color, [Smidge](https://github.com/shazwazza/smidge) may be caching the CSS and JavaScript. To clear the cache and get it to load in the new JavaScript and CSS, you can configure the [Runtime minification settings](../../reference/configuration/runtimeminificationsettings.md) in the `appsettings.json` file. When you reload the page, you'll see the colorful title. - -For information on creating bundles of your site's CSS or JavaScript files in your code, see the [Bundling & Minification for JavaScript and CSS](../../fundamentals/design/stylesheets-javascript.md#bundling--minification-for-javascript-and-css) section. -{% endhint %} - Hopefully, now you can see the potential of what you can provide to an editor as a basic welcome dashboard. ## Step 4: Adding functionality @@ -265,20 +277,24 @@ We can add functionality to the dashboard by associating an AngularJS controller 2. Register the AngularJS controller to the Umbraco Angular module: {% code title="customwelcomedashboard.controller.js" %} + ```js angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope) { var vm = this; alert("hello world"); }); ``` + {% endcode %} 3. Update the outer div to wire up the controller to the view In the HTML view: {% code title="WelcomeDashboard.html" %} + ```html
``` + {% endcode %} {% hint style="info" %} @@ -288,6 +304,7 @@ The use of `vm` (short for view model) is to enable communication between the vi 4. Update the `package.manifest` file to load the additional controller JavaScript file when the dashboard is displayed: {% code title="package.manifest" %} + ```json { "dashboards": [ @@ -310,6 +327,7 @@ The use of `vm` (short for view model) is to enable communication between the vi ] } ``` + {% endcode %} Once done, we should receive the 'Hello world' alert every time the dashboard is reloaded in the content section. @@ -323,14 +341,17 @@ The details are in the [Backoffice UI](../../reference/api-documentation.md). Fo 1. Inject the `userService` into our AngularJS controller: {% code title="customwelcomedashboard.controller.js" %} + ```js angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope,userService) { ``` + {% endcode %} 2. Use the `userService's` promise based `getCurrentUser()` method to get the details of the currently logged-in user: {% code title="customwelcomedashboard.controller.js" %} + ```js angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService) { var vm = this; @@ -342,6 +363,7 @@ angular.module("umbraco").controller("CustomWelcomeDashboardController", functio }); }); ``` + {% endcode %} {% hint style="info" %} @@ -351,9 +373,11 @@ Notice you can use `console.log()` to write out to the browser console window wh 3. Update the view to incorporate the current user's name in our Welcome Message: {% code title="WelcomeDashboard.html" %} + ```html

Default heading {{vm.UserName}}

``` + {% endcode %} ![Custom Dashboard Welcome Message With Current User's Name](../../../../10/umbraco-cms/tutorials/images/welcomemessagepersonalised-v10.png) @@ -369,28 +393,34 @@ We add `logResource` to the method and use the `getPagedUserLog` method to retur 1. Inject the `logResource` into our controller: {% code title="customwelcomedashboard.controller.js" %} + ```js angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService, logResource) { ``` + {% endcode %} 2. Add a property on our `ViewModel` to store the log information: {% code title="customwelcomedashboard.controller.js" %} + ```js vm.UserLogHistory = []; ``` + {% endcode %} 3. Add to our `WelcomeDashboard.html` View some markup using angular's `ng-repeat` to display a list of these log entries: {% code title="WelcomeDashboard.html" %} + ```html

We know what you edited last week...

  • {{logEntry.nodeId}} - {{logEntry.logType}} - {{logEntry.timestamp | date:'medium'}}
``` + {% endcode %} 4. Populate the array of entries using the `logResource` In our controller. @@ -398,6 +428,7 @@ vm.UserLogHistory = []; The `getPagedUserLog` method expects to receive a `JSON object` containing information to filter the log by: {% code title="customwelcomedashboard.controller.js" %} + ```js var userLogOptions = { pageSize: 10, @@ -406,6 +437,7 @@ var userLogOptions = { sinceDate: new Date(2018, 0, 1) }; ``` + {% endcode %} These options should retrieve the last ten activities for the current user in descending order since the start of 2018. @@ -413,6 +445,7 @@ These options should retrieve the last ten activities for the current user in de 5. Pass the options into the `getPagedUserLog` like so: {% code title="customwelcomedashboard.controller.js" %} + ```js logResource.getPagedUserLog(userLogOptions) .then(function (response) { @@ -420,6 +453,7 @@ logResource.getPagedUserLog(userLogOptions) vm.UserLogHistory = response; }); ``` + {% endcode %} Take a look at the output of console.log of the response in your browser to see the kind of information retrieved from the log: @@ -451,9 +485,11 @@ We can use the `entityResource`, another Umbraco Angular resource to enable us t 6. Inject the following code into our angular controller: {% code title="customwelcomedashboard.controller.js" %} + ```js angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService, logResource, entityResource) { ``` + {% endcode %} We need to loop through the log items from the `logResource`. Since this includes everything, we need to filter out activities we're not interested in such as DocType Saves. Generally, we need the entry in the log to have a `nodeId`, a `logType` of 'save' and an entity type of Media or Content. @@ -476,6 +512,7 @@ This needs to be defined before we loop through the entries. Putting this together it will look like this: {% code title="customwelcomedashboard.controller.js" %} + ```js logResource.getPagedUserLog(userLogOptions) .then(function (response) { @@ -543,11 +580,13 @@ Putting this together it will look like this: // end of function }); ``` + {% endcode %} 7. Update the view to use the additional retrieved entity information: {% code title="WelcomeDashboard.html" %} + ```js

We know what you edited last week...

    @@ -556,6 +595,7 @@ Putting this together it will look like this:
``` + {% endcode %} We now have a list of recently saved content and media on our Custom Dashboard: @@ -577,6 +617,7 @@ We can add a shortcut to allow the users to add a blog post. To do this we add the following code to our view: {% code title="WelcomeDashboard.html" %} + ```html ``` + {% endcode %} `1065` is the `ID` of our blog section and `blogPost` is the alias of the type of document we want to create. @@ -598,6 +640,7 @@ At this point we are done with the tutorial, your files should contain this: CustomWelcomeDashboardController {% code title="customwelcomedashboard.controller.js" %} + ```javascript // Some angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService, logResource, entityResource) { var vm = this; @@ -683,6 +726,7 @@ At this point we are done with the tutorial, your files should contain this: }); ``` + {% endcode %} @@ -692,6 +736,7 @@ At this point we are done with the tutorial, your files should contain this: WelcomeDashboard.html {% code title="WelcomeDashboard.html" %} + ```html

Default heading {{vm.UserName}}

@@ -713,6 +758,7 @@ At this point we are done with the tutorial, your files should contain this:
``` + {% endcode %}