Merge branch 'v12/dev' into v13/dev
# Conflicts: # global.json # src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml # src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html # src/Umbraco.Core/EmbeddedResources/Lang/da.xml # src/Umbraco.Core/EmbeddedResources/Lang/en.xml # src/Umbraco.Core/EmbeddedResources/Lang/tr.xml # src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs # src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs # src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs # src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs # src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js # src/Umbraco.Web.UI.Client/src/common/services/editor.service.js # src/Umbraco.Web.UI/Startup.cs # tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Login/login.spec.ts # tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs (cherry picked from commit ef3f2c48198d8baed9d3340f926cd07e80720428)
This commit is contained in:
258
.github/CONTRIBUTING.md
vendored
258
.github/CONTRIBUTING.md
vendored
@@ -4,268 +4,56 @@
|
||||
|
||||
These contribution guidelines are mostly just that - guidelines, not rules. This is what we've found to work best over the years, but if you choose to ignore them, we still love you! 💖 Use your best judgement, and feel free to propose changes to this document in a pull request.
|
||||
|
||||
## Coding not your thing? Or want more ways to contribute?
|
||||
## Getting Started
|
||||
We have a guide on [what to consider before you start](contributing-before-you-start.md) and more detailed guides at the end of this article.
|
||||
|
||||
This document covers contributing to the codebase of the CMS but [the community site has plenty of inspiration for other ways to get involved.][get involved]
|
||||
|
||||
If you don't feel you'd like to make code changes here, you can visit our [documentation repository][docs repo] and use your experience to contribute to making the docs we have, even better.
|
||||
|
||||
We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Collaborators and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Before you start](#before-you-start)
|
||||
* [Code of Conduct](#code-of-conduct)
|
||||
* [What can I contribute?](#what-can-i-contribute)
|
||||
+ [Making larger changes](#making-larger-changes)
|
||||
+ [Pull request or package?](#pull-request-or-package)
|
||||
+ [Unwanted changes](#unwanted-changes)
|
||||
+ [Ownership and copyright](#ownership-and-copyright)
|
||||
- [Finding your first issue: Up for grabs](#finding-your-first-issue-up-for-grabs)
|
||||
- [Making your changes](#making-your-changes)
|
||||
+ [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
|
||||
+ [Style guide](#style-guide)
|
||||
+ [Questions?](#questions)
|
||||
- [Creating a pull request](#creating-a-pull-request)
|
||||
- [The review process](#the-review-process)
|
||||
* [Dealing with requested changes](#dealing-with-requested-changes)
|
||||
+ [No longer available?](#no-longer-available)
|
||||
* [The Core Collaborators team](#the-core-collaborators-team)
|
||||
|
||||
## Before you start
|
||||
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
This project and everyone participating in it, is governed by the [our Code of Conduct][code of conduct].
|
||||
|
||||
### What can I contribute?
|
||||
|
||||
We categorise pull requests (PRs) into two categories:
|
||||
|
||||
| PR type | Definition |
|
||||
| --------- | ------------------------------------------------------------ |
|
||||
| Small PRs | Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files. |
|
||||
| Large PRs | New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.). |
|
||||
|
||||
We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process][review process].
|
||||
|
||||
We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations.
|
||||
|
||||
Not all changes are wanted, so on occasion we might close a PR without merging it but if we do, we will give you feedback why we can't accept your changes. **So make sure to [talk to us before making large changes][making larger changes]**, so we can ensure that you don't put all your hard work into something we would not be able to merge.
|
||||
|
||||
#### Making larger changes
|
||||
|
||||
[making larger changes]: #making-larger-changes
|
||||
|
||||
Please make sure to describe your larger ideas in an [issue (bugs)][issues] or [discussion (new features)][discussions], it helps to put in mock up screenshots or videos. If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented.
|
||||
|
||||
If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the small PRs process above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress.
|
||||
|
||||
#### Pull request or package?
|
||||
|
||||
[pr or package]: #pull-request-or-package
|
||||
|
||||
If you're unsure about whether your changes belong in the core Umbraco CMS or if you should turn your idea into a package instead, make sure to [talk to us][making larger changes].
|
||||
|
||||
If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs. Eventually, a package could "graduate" to be included in the CMS.
|
||||
|
||||
#### Unwanted changes
|
||||
While most changes are welcome, there are certain types of changes that are discouraged and might get your pull request refused.
|
||||
Of course this will depend heavily on the specific change, but please take the following examples in mind.
|
||||
|
||||
- **Breaking changes (code and/or behavioral) 💥** - sometimes it can be a bit hard to know if a change is breaking or not. Fortunately, if it relates to code, the build will fail and warn you.
|
||||
- **Large refactors 🤯** - the larger the refactor, the larger the probability of introducing new bugs/issues.
|
||||
- **Changes to obsolete code and/or property editors ✍️**
|
||||
- **Adding new config options 🦾** - while having more flexibility is (most of the times) better, having too many options can also become overwhelming/confusing, especially if there are other (good/simple) ways to achieve it.
|
||||
- **Whitespace changes 🫥** - while some of our files might not follow the formatting/whitespace rules (mostly old ones), changing several of them in one go would cause major merge conflicts with open pull requests or other work in progress. Do feel free to fix these when you are working on another issue/feature and end up "touching" those files!
|
||||
- **Adding new extension/helper methods ✋** - keep in mind that more code also means more to maintain, so if a helper is only meaningful for a few, it might not be worth adding it to the core.
|
||||
|
||||
While these are only a few examples, it is important to ask yourself these questions before making a pull request:
|
||||
|
||||
- How many will benefit from this change?
|
||||
- Are there other ways to achieve this? And if so, how do they compare?
|
||||
- How maintainable is the change?
|
||||
- What would be the effort to test it properly?
|
||||
- Do the benefits outweigh the risks?
|
||||
|
||||
#### Ownership and copyright
|
||||
|
||||
It is your responsibility to make sure that you're allowed to share the code you're providing us. For example, you should have permission from your employer or customer to share code.
|
||||
|
||||
Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS.
|
||||
|
||||
If you're not sure, leave a note on your contribution and we will be happy to guide you.
|
||||
|
||||
When your contribution has been accepted, it will be [MIT licensed][MIT license] from that time onwards.
|
||||
|
||||
## Finding your first issue: Up for grabs
|
||||
|
||||
Umbraco HQ will regularly mark newly created issues on the issue tracker with [the `community/up-for-grabs` tag][up for grabs issues]. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
|
||||
|
||||
If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
|
||||
|
||||
## Making your changes
|
||||
|
||||
Great question! The short version goes like this:
|
||||
The following steps are a quick-start guide:
|
||||
|
||||
1. **Fork**
|
||||
|
||||
Create a fork of [`Umbraco-CMS` on GitHub][Umbraco CMS repo]
|
||||
Create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS)
|
||||
|
||||

|
||||
|
||||
1. **Clone**
|
||||
2. **Clone**
|
||||
|
||||
When GitHub has created your fork, you can clone it in your favorite Git tool
|
||||
When GitHub has created your fork, you can clone it in your favorite Git tool or on the command line with `git clone https://github.com/[YourUsername]/Umbraco-CMS`.
|
||||
|
||||

|
||||
|
||||
1. **Switch to the correct branch**
|
||||
3. **Switch to the correct branch**
|
||||
|
||||
Switch to the `contrib` branch
|
||||
|
||||
1. **Build**
|
||||
4. **Build**
|
||||
|
||||
Build your fork of Umbraco locally as described in the build documentation: you can [debug with Visual Studio Code][build - debugging with code] or [with Visual Studio][build - debugging with vs].
|
||||
Build your fork of Umbraco locally [as described in the build documentation](BUILD.md), you can build with any IDE that supports dotnet or the command line.
|
||||
|
||||
1. **Branch**
|
||||
5. **Branch**
|
||||
|
||||
Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first.
|
||||
Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp/12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first.
|
||||
|
||||
1. **Change**
|
||||
6. **Change**
|
||||
|
||||
Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback][questions].
|
||||
Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](contributing-first-issue.md#questions).
|
||||
|
||||
1. **Commit and push**
|
||||
7. **Commit and push**
|
||||
|
||||
Done? Yay! 🎉
|
||||
|
||||
Remember to commit to your new `temp` branch, and don't commit to `contrib`. Then you can push the changes up to your fork on GitHub.
|
||||
|
||||
#### Keeping your Umbraco fork in sync with the main repository
|
||||
[sync fork]: #keeping-your-umbraco-fork-in-sync-with-the-main-repository
|
||||
8. **Create pull request**
|
||||
|
||||
Once you've already got a fork and cloned your fork locally, you can skip steps 1 and 2 going forward. Just remember to keep your fork up to date before making further changes.
|
||||
On GitHub, in your forked repository (`https://github.com/[YourUsername]/Umbraco-CMS`) you will see a banner saying that you pushed a new branch and a button to make a pull request. Tap the button and follow the instuctions.
|
||||
|
||||
To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once:
|
||||
Want to read further? [Creating a pull request and what happens next](contributing-creating-a-pr.md).
|
||||
|
||||
```
|
||||
git remote add upstream https://github.com/umbraco/Umbraco-CMS.git
|
||||
```
|
||||
## Further contribution guides
|
||||
|
||||
Then when you want to get the changes from the main repository:
|
||||
- [Before you start](contributing-before-you-start.md)
|
||||
- [Finding your first issue: Up for grabs](contributing-before-you-start.md)
|
||||
- [Contributing to the new backoffice](https://docs.umbraco.com/umbraco-backoffice/)
|
||||
- [Unwanted changes](contributing-unwanted-changes.md)
|
||||
- [Other ways to contribute](contributing-other-ways-to-contribute.md)
|
||||
|
||||
```
|
||||
git fetch upstream
|
||||
git rebase upstream/contrib
|
||||
```
|
||||
|
||||
In this command we're syncing with the `contrib` branch, but you can of course choose another one if needed.
|
||||
|
||||
[More information on how this works can be found on the thoughtbot blog.][sync fork ext]
|
||||
|
||||
#### Style guide
|
||||
|
||||
To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
|
||||
|
||||
That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
|
||||
|
||||
#### Questions?
|
||||
[questions]: #questions
|
||||
|
||||
You can get in touch with [the core contributors team][core collabs] in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
|
||||
|
||||
- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
|
||||
- If you want to ask questions on some code you've already written you can create a draft pull request, [detailed in a GitHub blog post][draft prs].
|
||||
- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"][contrib forum] forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
|
||||
|
||||
## Creating a pull request
|
||||
|
||||
Exciting! You're ready to show us your changes.
|
||||
|
||||
We recommend you to [sync with our repository][sync fork] before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
|
||||
|
||||
GitHub will have picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
|
||||

|
||||
|
||||
We like to use [git flow][git flow] as much as possible, but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to `contrib`. This is the branch you should be targeting.
|
||||
|
||||
Please note: we are no longer accepting features for v8 and below but will continue to merge security fixes as and when they arise.
|
||||
|
||||
## The review process
|
||||
[review process]: #the-review-process
|
||||
|
||||
You've sent us your first contribution - congratulations! Now what?
|
||||
|
||||
The [Core Collaborators team][Core collabs] can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes.
|
||||
|
||||
You will get an initial automated reply from our [Friendly Umbraco Robot, Umbrabot][Umbrabot], to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. You can take this opportunity to double check everything is in order based off the handy checklist Umbrabot provides.
|
||||
|
||||
You will get feedback as soon as the [Core Collaborators team][Core collabs] can after opening the PR. You’ll most likely get feedback within a couple of weeks. Then there are a few possible outcomes:
|
||||
|
||||
- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
|
||||
- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
|
||||
- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see
|
||||
- Your proposed change is awesome but... not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!). See [making larger changes][making larger changes] and [pull request or package?][pr or package]
|
||||
|
||||
### Dealing with requested changes
|
||||
|
||||
If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
|
||||
|
||||
#### No longer available?
|
||||
|
||||
We understand you have other things to do and can't just drop everything to help us out.
|
||||
|
||||
So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time.
|
||||
|
||||
If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it.
|
||||
|
||||
There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository.
|
||||
|
||||
### The Core Collaborators team
|
||||
[Core collabs]: #the-core-collaborators-team
|
||||
|
||||
The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan][Sebastiaan], who gets assistance from the following community members who have committed to volunteering their free time:
|
||||
|
||||
- [Busra Sengul][Busra Sengul]
|
||||
- [Emma Garland][Emma Garland]
|
||||
- [George Bidder][George Bidder]
|
||||
- [Jason Elkin][Jason Elkin]
|
||||
- [Laura Neto][Laura Neto]
|
||||
- [Michael Latouche][Michael Latouche]
|
||||
- [Sebastiaan][Sebastiaan]
|
||||
|
||||
|
||||
These wonderful people aim to provide you with a reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. HQ will have final sign-off and will check the work again before it is merged.
|
||||
|
||||
<!-- Reference links for easy updating -->
|
||||
|
||||
<!-- Local -->
|
||||
|
||||
[MIT license]: ../LICENSE.md "Umbraco's license declaration"
|
||||
[build - debugging with vs]: BUILD.md#debugging-with-visual-studio "Details on building and debugging Umbraco with Visual Studio"
|
||||
[build - debugging with code]: BUILD.md#debugging-with-vs-code "Details on building and debugging Umbraco with Visual Studio Code"
|
||||
|
||||
<!-- External -->
|
||||
|
||||
[Busra Sengul]: https://github.com/busrasengul "Busra's GitHub profile"
|
||||
[Emma Garland]: https://github.com/emmagarland "Emma's GitHub profile"
|
||||
[George Bidder]: https://github.com/georgebid "George's GitHub profile"
|
||||
[Jason Elkin]: https://github.com/jasonelkin "Jason's GitHub profile"
|
||||
[Kyle Eck]: https://github.com/teckspeed "Kyle's GitHub profile"
|
||||
[Laura Neto]: https://github.com/lauraneto "Laura's GitHub profile"
|
||||
[Michael Latouche]: https://github.com/mikecp "Michael's GitHub profile"
|
||||
[Sebastiaan]: https://github.com/nul800sebastiaan "Sebastiaan's GitHub profile"
|
||||
[ Umbrabot ]: https://github.com/umbrabot
|
||||
[git flow]: https://jeffkreeftmeijer.com/git-flow/ "An explanation of git flow"
|
||||
[sync fork ext]: http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated "Details on keeping a git fork updated"
|
||||
[draft prs]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ "Github's blog post providing details on draft pull requests"
|
||||
[contrib forum]: https://our.umbraco.com/forum/contributing-to-umbraco-cms/
|
||||
[get involved]: https://community.umbraco.com/get-involved/
|
||||
[docs repo]: https://github.com/umbraco/UmbracoDocs
|
||||
[code of conduct]: https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md
|
||||
[up for grabs issues]: https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs
|
||||
[Umbraco CMS repo]: https://github.com/umbraco/Umbraco-CMS
|
||||
[issues]: https://github.com/umbraco/Umbraco-CMS/issues
|
||||
[discussions]: https://github.com/umbraco/Umbraco-CMS/discussions
|
||||
|
||||
54
.github/contributing-before-you-start.md
vendored
Normal file
54
.github/contributing-before-you-start.md
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
## Before you start
|
||||
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
This project and everyone participating in it, is governed by the [our Code of Conduct][code of conduct].
|
||||
|
||||
### What can I contribute?
|
||||
|
||||
We categorise pull requests (PRs) into two categories:
|
||||
|
||||
| PR type | Definition |
|
||||
| --------- | ------------------------------------------------------------ |
|
||||
| Small PRs | Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files. |
|
||||
| Large PRs | New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.). |
|
||||
|
||||
We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process][review process].
|
||||
|
||||
We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations.
|
||||
|
||||
Not all changes are wanted, so on occasion we might close a PR without merging it but if we do, we will give you feedback why we can't accept your changes. **So make sure to [talk to us before making large changes][making larger changes]**, so we can ensure that you don't put all your hard work into something we would not be able to merge.
|
||||
|
||||
#### Making larger changes
|
||||
|
||||
[making larger changes]: #making-larger-changes
|
||||
|
||||
Please make sure to describe your larger ideas in an [issue (bugs)][issues] or [discussion (new features)][discussions], it helps to put in mock up screenshots or videos. If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented.
|
||||
|
||||
If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the small PRs process above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress.
|
||||
|
||||
#### Pull request or package?
|
||||
|
||||
[pr or package]: #pull-request-or-package
|
||||
|
||||
If you're unsure about whether your changes belong in the core Umbraco CMS or if you should turn your idea into a package instead, make sure to [talk to us][making larger changes].
|
||||
|
||||
If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs. Eventually, a package could "graduate" to be included in the CMS.
|
||||
|
||||
#### Ownership and copyright
|
||||
|
||||
It is your responsibility to make sure that you're allowed to share the code you're providing us. For example, you should have permission from your employer or customer to share code.
|
||||
|
||||
Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS.
|
||||
|
||||
If you're not sure, leave a note on your contribution and we will be happy to guide you.
|
||||
|
||||
When your contribution has been accepted, it will be [MIT licensed][MIT license] from that time onwards.
|
||||
|
||||
|
||||
[MIT license]: ../LICENSE.md "Umbraco's license declaration"
|
||||
|
||||
|
||||
[issues]: https://github.com/umbraco/Umbraco-CMS/issues
|
||||
[discussions]: https://github.com/umbraco/Umbraco-CMS/discussions
|
||||
28
.github/contributing-core-collabs-team.md
vendored
Normal file
28
.github/contributing-core-collabs-team.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
### The Core Collaborators team
|
||||
[Core collabs]: #the-core-collaborators-team
|
||||
|
||||
The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan][Sebastiaan], who gets assistance from the following community members who have committed to volunteering their free time:
|
||||
|
||||
- [Busra Sengul][Busra Sengul]
|
||||
- [Emma Garland][Emma Garland]
|
||||
- [George Bidder][George Bidder]
|
||||
- [Jason Elkin][Jason Elkin]
|
||||
- [Laura Neto][Laura Neto]
|
||||
- [Kyle Eck][Kyle Eck]
|
||||
- [Michael Latouche][Michael Latouche]
|
||||
- [Sebastiaan][Sebastiaan]
|
||||
|
||||
|
||||
These wonderful people aim to provide you with a reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. HQ will have final sign-off and will check the work again before it is merged.
|
||||
|
||||
|
||||
<!-- External -->
|
||||
|
||||
[Busra Sengul]: https://github.com/busrasengul "Busra's GitHub profile"
|
||||
[Emma Garland]: https://github.com/emmagarland "Emma's GitHub profile"
|
||||
[George Bidder]: https://github.com/georgebid "George's GitHub profile"
|
||||
[Jason Elkin]: https://github.com/jasonelkin "Jason's GitHub profile"
|
||||
[Kyle Eck]: https://github.com/teckspeed "Kyle's GitHub profile"
|
||||
[Laura Neto]: https://github.com/lauraneto "Laura's GitHub profile"
|
||||
[Michael Latouche]: https://github.com/mikecp "Michael's GitHub profile"
|
||||
[Sebastiaan]: https://github.com/nul800sebastiaan "Sebastiaan's GitHub profile"
|
||||
51
.github/contributing-creating-a-pr.md
vendored
Normal file
51
.github/contributing-creating-a-pr.md
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
## Creating a pull request
|
||||
|
||||
Exciting! You're ready to show us your changes.
|
||||
|
||||
We recommend you to [sync with our repository][sync fork] before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
|
||||
|
||||
GitHub will have picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
|
||||

|
||||
|
||||
We like to use [git flow][git flow] as much as possible, but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to `contrib`. This is the branch you should be targeting.
|
||||
|
||||
Please note: we are no longer accepting features for v8 and below but will continue to merge security fixes as and when they arise.
|
||||
|
||||
## The review process
|
||||
[review process]: #the-review-process
|
||||
|
||||
You've sent us your contribution - congratulations! Now what?
|
||||
|
||||
The [Core Collaborators team][Core collabs] can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes.
|
||||
|
||||
You will get an initial automated reply from our [Friendly Umbraco Robot, Umbrabot][Umbrabot], to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. You can take this opportunity to double check everything is in order based off the handy checklist Umbrabot provides.
|
||||
|
||||
You will get feedback as soon as the [Core Collaborators team][Core collabs] can after opening the PR. You’ll most likely get feedback within a couple of weeks. Then there are a few possible outcomes:
|
||||
|
||||
- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
|
||||
- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
|
||||
- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see
|
||||
- Your proposed change is awesome but... not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!). See [making larger changes][making larger changes] and [pull request or package?][pr or package]
|
||||
|
||||
### Dealing with requested changes
|
||||
|
||||
If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
|
||||
|
||||
#### No longer available?
|
||||
|
||||
We understand you have other things to do and can't just drop everything to help us out.
|
||||
|
||||
So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time.
|
||||
|
||||
If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it.
|
||||
|
||||
There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository.
|
||||
|
||||
|
||||
[ Umbrabot ]: https://github.com/umbrabot
|
||||
[git flow]: https://jeffkreeftmeijer.com/git-flow/ "An explanation of git flow"
|
||||
|
||||
|
||||
[making larger changes]: contributing-before-you-start.md#making-large-changes
|
||||
[pr or package]: contributing-before-you-start.md#pull-request-or-package
|
||||
[Core collabs]: contributing-core-collabs-team.md
|
||||
93
.github/contributing-first-issue.md
vendored
Normal file
93
.github/contributing-first-issue.md
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
## Finding your first issue: Up for grabs
|
||||
|
||||
Umbraco HQ will regularly mark newly created issues on the issue tracker with [the `community/up-for-grabs` tag][up for grabs issues]. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
|
||||
|
||||
If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
|
||||
|
||||
## Making your changes
|
||||
|
||||
Great question! The short version goes like this:
|
||||
|
||||
1. **Fork**
|
||||
|
||||
Create a fork of [`Umbraco-CMS` on GitHub][Umbraco CMS repo]
|
||||
|
||||

|
||||
|
||||
1. **Clone**
|
||||
|
||||
When GitHub has created your fork, you can clone it in your favorite Git tool
|
||||
|
||||

|
||||
|
||||
1. **Switch to the correct branch**
|
||||
|
||||
Switch to the `contrib` branch
|
||||
|
||||
1. **Build**
|
||||
|
||||
Build your fork of Umbraco locally as described in the build documentation: you can [debug with Visual Studio Code][build - debugging with code] or [with Visual Studio][build - debugging with vs].
|
||||
|
||||
1. **Branch**
|
||||
|
||||
Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `contrib`, create a new branch first.
|
||||
|
||||
1. **Change**
|
||||
|
||||
Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback][questions].
|
||||
|
||||
1. **Commit and push**
|
||||
|
||||
Done? Yay! 🎉
|
||||
|
||||
Remember to commit to your new `temp` branch, and don't commit to `contrib`. Then you can push the changes up to your fork on GitHub.
|
||||
|
||||
#### Keeping your Umbraco fork in sync with the main repository
|
||||
[sync fork]: #keeping-your-umbraco-fork-in-sync-with-the-main-repository
|
||||
|
||||
Once you've already got a fork and cloned your fork locally, you can skip steps 1 and 2 going forward. Just remember to keep your fork up to date before making further changes.
|
||||
|
||||
To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once:
|
||||
|
||||
```
|
||||
git remote add upstream https://github.com/umbraco/Umbraco-CMS.git
|
||||
```
|
||||
|
||||
Then when you want to get the changes from the main repository:
|
||||
|
||||
```
|
||||
git fetch upstream
|
||||
git rebase upstream/contrib
|
||||
```
|
||||
|
||||
In this command we're syncing with the `contrib` branch, but you can of course choose another one if needed.
|
||||
|
||||
[More information on how this works can be found on the thoughtbot blog.][sync fork ext]
|
||||
|
||||
#### Style guide
|
||||
|
||||
To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
|
||||
|
||||
That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
|
||||
|
||||
#### Questions?
|
||||
[questions]: #questions
|
||||
|
||||
You can get in touch with [the core contributors team][core collabs] in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
|
||||
|
||||
- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
|
||||
- If you want to ask questions on some code you've already written you can create a draft pull request, [detailed in a GitHub blog post][draft prs].
|
||||
- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"][contrib forum] forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
|
||||
|
||||
|
||||
<!-- Local -->
|
||||
|
||||
[build - debugging with vs]: BUILD.md#debugging-with-visual-studio "Details on building and debugging Umbraco with Visual Studio"
|
||||
[build - debugging with code]: BUILD.md#debugging-with-vs-code "Details on building and debugging Umbraco with Visual Studio Code"
|
||||
|
||||
|
||||
[sync fork ext]: http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated "Details on keeping a git fork updated"
|
||||
[draft prs]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ "Github's blog post providing details on draft pull requests"
|
||||
[contrib forum]: https://our.umbraco.com/forum/contributing-to-umbraco-cms/
|
||||
[Umbraco CMS repo]: https://github.com/umbraco/Umbraco-CMS
|
||||
[up for grabs issues]: https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs
|
||||
10
.github/contributing-other-ways-to-contribute.md
vendored
Normal file
10
.github/contributing-other-ways-to-contribute.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
## Coding not your thing? Or want more ways to contribute?
|
||||
|
||||
This document covers contributing to the codebase of the CMS but [the community site has plenty of inspiration for other ways to get involved.][get involved]
|
||||
|
||||
If you don't feel you'd like to make code changes here, you can visit our [documentation repository][docs repo] and use your experience to contribute to making the docs we have, even better.
|
||||
|
||||
We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Collaborators and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.
|
||||
|
||||
[get involved]: https://community.umbraco.com/get-involved/
|
||||
[docs repo]: https://github.com/umbraco/UmbracoDocs
|
||||
18
.github/contributing-unwanted-changes.md
vendored
Normal file
18
.github/contributing-unwanted-changes.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
## Unwanted changes
|
||||
While most changes are welcome, there are certain types of changes that are discouraged and might get your pull request refused.
|
||||
Of course this will depend heavily on the specific change, but please take the following examples in mind.
|
||||
|
||||
- **Breaking changes (code and/or behavioral) 💥** - sometimes it can be a bit hard to know if a change is breaking or not. Fortunately, if it relates to code, the build will fail and warn you.
|
||||
- **Large refactors 🤯** - the larger the refactor, the larger the probability of introducing new bugs/issues.
|
||||
- **Changes to obsolete code and/or property editors ✍️**
|
||||
- **Adding new config options 🦾** - while having more flexibility is (most of the times) better, having too many options can also become overwhelming/confusing, especially if there are other (good/simple) ways to achieve it.
|
||||
- **Whitespace changes 🫥** - while some of our files might not follow the formatting/whitespace rules (mostly old ones), changing several of them in one go would cause major merge conflicts with open pull requests or other work in progress. Do feel free to fix these when you are working on another issue/feature and end up "touching" those files!
|
||||
- **Adding new extension/helper methods ✋** - keep in mind that more code also means more to maintain, so if a helper is only meaningful for a few, it might not be worth adding it to the core.
|
||||
|
||||
While these are only a few examples, it is important to ask yourself these questions before making a pull request:
|
||||
|
||||
- How many will benefit from this change?
|
||||
- Are there other ways to achieve this? And if so, how do they compare?
|
||||
- How maintainable is the change?
|
||||
- What would be the effort to test it properly?
|
||||
- Do the benefits outweigh the risks?
|
||||
@@ -1,119 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
</configSections>
|
||||
|
||||
<appSettings>
|
||||
<add key="Umbraco.Core.ConfigurationStatus" value="6.0.0"/>
|
||||
<add key="Umbraco.Core.ReservedUrls" value="~/config/splashes/booting.aspx,~/install/default.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd,~/.well-known" />
|
||||
<add key="Umbraco.Core.ReservedPaths" value="~/install/"/>
|
||||
<add key="Umbraco.Core.Path" value="~/umbraco"/>
|
||||
<add key="Umbraco.Core.HideTopLevelNodeFromPath" value="true"/>
|
||||
<add key="Umbraco.Core.TimeOutInMinutes" value="20"/>
|
||||
<add key="Umbraco.Core.DefaultUILanguage" value="en"/>
|
||||
<add key="Umbraco.Core.UseHttps" value="false"/>
|
||||
<add key="dataAnnotations:dataTypeAttribute:disableRegEx" value="false"/>
|
||||
</appSettings>
|
||||
|
||||
<connectionStrings>
|
||||
<add name="umbracoDbDSN" connectionString="Datasource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1;" providerName="System.Data.SqlServerCe.4.0"/>
|
||||
</connectionStrings>
|
||||
|
||||
<system.data>
|
||||
<DbProviderFactories>
|
||||
<remove invariant="System.Data.SqlServerCe.4.0"/>
|
||||
<add name="Microsoft SQL Server Compact Data Provider 4.0" invariant="System.Data.SqlServerCe.4.0" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91"/>
|
||||
</DbProviderFactories>
|
||||
</system.data>
|
||||
|
||||
<system.web>
|
||||
<httpRuntime targetFramework="4.5"/>
|
||||
<compilation defaultLanguage="c#" debug="true" batch="false" targetFramework="4.0"></compilation>
|
||||
<machineKey validationKey="5E7B955FCE36F5F2A867C2A0D85DC61E7FEA9E15F1561E8386F78BFE9EE23FF18B21E6A44AA17300B3B9D5DBEB37AA61A2C73884A5BBEDA6D3B14BA408A7A8CD" decryptionKey="116B853D031219E404E088FCA0986D6CF2DFA77E1957B59FCC9404B8CA3909A1" validation="SHA1" decryption="AES"/>
|
||||
<!--<trust level="Medium" originUrl=".*"/>-->
|
||||
<!-- Sitemap provider-->
|
||||
<siteMap defaultProvider="UmbracoSiteMapProvider" enabled="true">
|
||||
<providers>
|
||||
<clear/>
|
||||
<add name="UmbracoSiteMapProvider" type="umbraco.presentation.nodeFactory.UmbracoSiteMapProvider" defaultDescriptionAlias="description" securityTrimmingEnabled="true"/>
|
||||
</providers>
|
||||
</siteMap>
|
||||
<!-- Membership Provider -->
|
||||
<membership defaultProvider="UmbracoMembershipProvider" userIsOnlineTimeWindow="15">
|
||||
<providers>
|
||||
<clear/>
|
||||
<add name="UmbracoMembershipProvider" type="Umbraco.Web.Security.Providers.MembersMembershipProvider, Umbraco.Web" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="false" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Member" passwordFormat="Hashed"/>
|
||||
</providers>
|
||||
</membership>
|
||||
</system.web>
|
||||
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
||||
</startup>
|
||||
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.2.5.0" newVersion="1.2.5.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ComponentModel.Annotations" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.2.1.0" newVersion="4.2.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="4.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="4.0.0.0-4.0.1.1" newVersion="4.0.1.1"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="4.0.0.0-4.1.4.0" newVersion="4.1.4.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
||||
</configuration>
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Umbraco.Tests")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Umbraco.Tests")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2012")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("33ddf9b7-505c-4a12-8370-7fee9de5df6d")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
||||
// Internals must be visible to DynamicProxyGenAssembly2
|
||||
// in order to mock loggers loggers with types from the assembly
|
||||
// I.E. Mock.Of<ILogger<TestControllerFactory>>()
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
@@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Tests.Published
|
||||
{
|
||||
[TestFixture]
|
||||
public class ModelTypeTests
|
||||
{
|
||||
|
||||
//TODO these is not easy to move to the Unittest project due to underlysing NotImplementedException of Type.IsSZArray
|
||||
[Test]
|
||||
public void ModelTypeToStringTests()
|
||||
{
|
||||
var modelType = ModelType.For("alias1");
|
||||
var modelTypeArray = modelType.MakeArrayType();
|
||||
|
||||
Assert.AreEqual("{alias1}", modelType.ToString());
|
||||
|
||||
// there's an "*" there because the arrays are not true SZArray - but that changes when we map
|
||||
|
||||
Assert.AreEqual("{alias1}[*]", modelTypeArray.ToString());
|
||||
var enumArray = typeof(IEnumerable<>).MakeGenericType(modelTypeArray);
|
||||
Assert.AreEqual("System.Collections.Generic.IEnumerable`1[{alias1}[*]]", enumArray.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ModelTypeFullNameTests()
|
||||
{
|
||||
Assert.AreEqual("{alias1}", ModelType.For("alias1").FullName);
|
||||
|
||||
Type type = ModelType.For("alias1");
|
||||
Assert.AreEqual("{alias1}", type.FullName);
|
||||
|
||||
// there's an "*" there because the arrays are not true SZArray - but that changes when we map
|
||||
Assert.AreEqual("{alias1}[*]", ModelType.For("alias1").MakeArrayType().FullName);
|
||||
// note the inner assembly qualified name
|
||||
Assert.AreEqual("System.Collections.Generic.IEnumerable`1[[{alias1}[*], Umbraco.Core, Version=0.5.0.0, Culture=neutral, PublicKeyToken=null]]", typeof(IEnumerable<>).MakeGenericType(ModelType.For("alias1").MakeArrayType()).FullName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
{
|
||||
public abstract class BaseUrlProviderTest : BaseWebTest
|
||||
{
|
||||
protected IUmbracoContextAccessor UmbracoContextAccessor { get; } = new TestUmbracoContextAccessor();
|
||||
|
||||
protected abstract bool HideTopLevelNodeFromPath { get; }
|
||||
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
Builder.Services.AddTransient<ISiteDomainMapper, SiteDomainMapper>();
|
||||
}
|
||||
|
||||
protected override void ComposeSettings()
|
||||
{
|
||||
var contentSettings = new ContentSettings();
|
||||
var userPasswordConfigurationSettings = new UserPasswordConfigurationSettings();
|
||||
|
||||
Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(contentSettings));
|
||||
Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(userPasswordConfigurationSettings));
|
||||
}
|
||||
|
||||
protected IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext, DefaultUrlProvider urlProvider)
|
||||
{
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
return new UrlProvider(
|
||||
new TestUmbracoContextAccessor(umbracoContext),
|
||||
Microsoft.Extensions.Options.Options.Create(webRoutingSettings),
|
||||
new UrlProviderCollection(new[] { urlProvider }),
|
||||
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
|
||||
Mock.Of<IVariationContextAccessor>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Tests.PublishedContent;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
|
||||
public class MediaUrlProviderTests : BaseWebTest
|
||||
{
|
||||
private DefaultMediaUrlProvider _mediaUrlProvider;
|
||||
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
|
||||
var loggerFactory = NullLoggerFactory.Instance;
|
||||
var mediaFileManager = new MediaFileManager(Mock.Of<IFileSystem>(), Mock.Of<IMediaPathScheme>(),
|
||||
loggerFactory.CreateLogger<MediaFileManager>(), Mock.Of<IShortStringHelper>());
|
||||
var contentSettings = new ContentSettings();
|
||||
var dataTypeService = Mock.Of<IDataTypeService>();
|
||||
var propertyEditors = new MediaUrlGeneratorCollection(new IMediaUrlGenerator[]
|
||||
{
|
||||
new FileUploadPropertyEditor(DataValueEditorFactory, mediaFileManager, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, LocalizedTextService, UploadAutoFillProperties, ContentService),
|
||||
new ImageCropperPropertyEditor(DataValueEditorFactory, loggerFactory, mediaFileManager, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, IOHelper, UploadAutoFillProperties, ContentService),
|
||||
});
|
||||
_mediaUrlProvider = new DefaultMediaUrlProvider(propertyEditors, UriUtility);
|
||||
}
|
||||
|
||||
public override void TearDown()
|
||||
{
|
||||
base.TearDown();
|
||||
|
||||
_mediaUrlProvider = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Resolves_Url_From_Upload_Property_Editor()
|
||||
{
|
||||
const string expected = "/media/rfeiw584/test.jpg";
|
||||
|
||||
var umbracoContext = GetUmbracoContext("/");
|
||||
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null);
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto);
|
||||
|
||||
Assert.AreEqual(expected, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Resolves_Url_From_Image_Cropper_Property_Editor()
|
||||
{
|
||||
const string expected = "/media/rfeiw584/test.jpg";
|
||||
|
||||
var configuration = new ImageCropperConfiguration();
|
||||
var imageCropperValue = JsonConvert.SerializeObject(new ImageCropperValue
|
||||
{
|
||||
Src = expected
|
||||
});
|
||||
|
||||
var umbracoContext = GetUmbracoContext("/");
|
||||
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration);
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto);
|
||||
|
||||
Assert.AreEqual(expected, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Can_Resolve_Absolute_Url()
|
||||
{
|
||||
const string mediaUrl = "/media/rfeiw584/test.jpg";
|
||||
var expected = $"http://localhost{mediaUrl}";
|
||||
|
||||
var umbracoContext = GetUmbracoContext("http://localhost");
|
||||
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, mediaUrl, null);
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Absolute);
|
||||
|
||||
Assert.AreEqual(expected, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Returns_Absolute_Url_If_Stored_Url_Is_Absolute()
|
||||
{
|
||||
const string expected = "http://localhost/media/rfeiw584/test.jpg";
|
||||
|
||||
var umbracoContext = GetUmbracoContext("http://localhost");
|
||||
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null);
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Relative);
|
||||
|
||||
Assert.AreEqual(expected, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Returns_Empty_String_When_PropertyType_Is_Not_Supported()
|
||||
{
|
||||
var umbracoContext = GetUmbracoContext("/");
|
||||
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.Boolean, "0", null);
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Absolute, propertyAlias: "test");
|
||||
|
||||
Assert.AreEqual(string.Empty, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Can_Resolve_Variant_Property_Url()
|
||||
{
|
||||
var umbracoContext = GetUmbracoContext("http://localhost");
|
||||
|
||||
var umbracoFilePropertyType = CreatePropertyType(Constants.PropertyEditors.Aliases.UploadField, null, ContentVariation.Culture);
|
||||
|
||||
const string enMediaUrl = "/media/rfeiw584/en.jpg";
|
||||
const string daMediaUrl = "/media/uf8ewud2/da.jpg";
|
||||
|
||||
var property = new SolidPublishedPropertyWithLanguageVariants
|
||||
{
|
||||
Alias = "umbracoFile",
|
||||
PropertyType = umbracoFilePropertyType,
|
||||
};
|
||||
|
||||
property.SetSourceValue("en", enMediaUrl, true);
|
||||
property.SetSourceValue("da", daMediaUrl);
|
||||
|
||||
var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), new [] { umbracoFilePropertyType }, ContentVariation.Culture);
|
||||
var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}};
|
||||
|
||||
var resolvedUrl = GetPublishedUrlProvider(umbracoContext).GetMediaUrl(publishedContent, UrlMode.Auto, "da");
|
||||
Assert.AreEqual(daMediaUrl, resolvedUrl);
|
||||
}
|
||||
|
||||
private IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext)
|
||||
{
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
return new UrlProvider(
|
||||
new TestUmbracoContextAccessor(umbracoContext),
|
||||
Microsoft.Extensions.Options.Options.Create(webRoutingSettings),
|
||||
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
|
||||
new MediaUrlProviderCollection(new []{_mediaUrlProvider}),
|
||||
Mock.Of<IVariationContextAccessor>()
|
||||
);
|
||||
}
|
||||
|
||||
private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, string propertyValue, object dataTypeConfiguration)
|
||||
{
|
||||
var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing);
|
||||
|
||||
var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(),
|
||||
new[] {umbracoFilePropertyType}, ContentVariation.Nothing);
|
||||
|
||||
return new SolidPublishedContent(contentType)
|
||||
{
|
||||
Id = 1234,
|
||||
Key = Guid.NewGuid(),
|
||||
Properties = new[]
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "umbracoFile",
|
||||
SolidSourceValue = propertyValue,
|
||||
SolidHasValue = true,
|
||||
PropertyType = umbracoFilePropertyType
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static PublishedPropertyType CreatePropertyType(string propertyEditorAlias, object dataTypeConfiguration, ContentVariation variation)
|
||||
{
|
||||
var uploadDataType = new PublishedDataType(1234, propertyEditorAlias, new Lazy<object>(() => dataTypeConfiguration));
|
||||
|
||||
var propertyValueConverters = new PropertyValueConverterCollection(new IPropertyValueConverter[]
|
||||
{
|
||||
new UploadPropertyConverter(),
|
||||
new ImageCropperValueConverter(Mock.Of<ILogger<ImageCropperValueConverter>>()),
|
||||
});
|
||||
|
||||
var publishedModelFactory = Mock.Of<IPublishedModelFactory>();
|
||||
var publishedContentTypeFactory = new Mock<IPublishedContentTypeFactory>();
|
||||
publishedContentTypeFactory.Setup(x => x.GetDataType(It.IsAny<int>()))
|
||||
.Returns(uploadDataType);
|
||||
|
||||
return new PublishedPropertyType("umbracoFile", 42, true, variation, propertyValueConverters, publishedModelFactory, publishedContentTypeFactory.Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{5D3B8245-ADA6-453F-A008-50ED04BFE770}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Umbraco.Tests</RootNamespace>
|
||||
<AssemblyName>Umbraco.Tests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.Entity.Design" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IO" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Runtime.Caching" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Text.Encoding" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.ApplicationServices" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
<Reference Include="System.Web.Services" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Castle.Core" Version="4.4.1" />
|
||||
<PackageReference Include="Examine.Core">
|
||||
<Version>2.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Castle.Core" Version="4.3.1" />
|
||||
<PackageReference Include="Examine" Version="2.0.0" />
|
||||
<PackageReference Include="HtmlAgilityPack">
|
||||
<Version>1.11.31</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Lucene.Net.Contrib" Version="3.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.3" />
|
||||
<PackageReference Include="Microsoft.AspNet.Mvc" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Owin" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.SelfHost" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Tracing" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.WebHost" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions">
|
||||
<Version>5.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console">
|
||||
<Version>5.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Owin" Version="4.1.1" />
|
||||
<PackageReference Include="Microsoft.Owin.Hosting" Version="4.1.1" />
|
||||
<PackageReference Include="Microsoft.Owin.Security" Version="4.1.1" />
|
||||
<PackageReference Include="Microsoft.Owin.Testing" Version="4.1.1" />
|
||||
<PackageReference Include="Microsoft.Web.Infrastructure" Version="1.0.0.0" />
|
||||
<PackageReference Include="MiniProfiler" Version="4.2.22" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="MiniProfiler" Version="4.0.138" />
|
||||
<PackageReference Include="Moq" Version="4.10.1" />
|
||||
<PackageReference Include="Moq" Version="4.14.5" />
|
||||
<PackageReference Include="NPoco" Version="3.9.4" />
|
||||
<PackageReference Include="NUnit" Version="3.11.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.12.0" />
|
||||
<PackageReference Include="NPoco" Version="4.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
<PackageReference Include="Owin" Version="1.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="Umbraco.SqlServerCE" Version="4.0.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Published\ModelTypeTests.cs" />
|
||||
<Compile Include="Routing\BaseUrlProviderTest.cs" />
|
||||
<Compile Include="Routing\MediaUrlProviderTests.cs" />
|
||||
<Compile Include="Scoping\ScopedNuCacheTests.cs" />
|
||||
<Compile Include="Web\Controllers\AuthenticationControllerTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="unit-test-logger.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Umbraco.Core\Umbraco.Core.csproj">
|
||||
<Project>{29aa69d9-b597-4395-8d42-43b1263c240a}</Project>
|
||||
<Name>Umbraco.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Umbraco.Examine.Lucene\Umbraco.Examine.Lucene.csproj">
|
||||
<Project>{0fad7d2a-d7dd-45b1-91fd-488bb6cdacea}</Project>
|
||||
<Name>Umbraco.Examine.Lucene</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="...\..\srcUmbraco.Infrastructure\Umbraco.Infrastructure.csproj">
|
||||
<Project>{3ae7bf57-966b-45a5-910a-954d7c554441}</Project>
|
||||
<Name>Umbraco.Infrastructure</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Umbraco.Persistence.SqlCe\Umbraco.Persistence.SqlCe.csproj">
|
||||
<Project>{33085570-9bf2-4065-a9b0-a29d920d13ba}</Project>
|
||||
<Name>Umbraco.Persistence.SqlCe</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Umbraco.PublishedCache.NuCache\Umbraco.PublishedCache.NuCache.csproj">
|
||||
<Project>{f6de8da0-07cc-4ef2-8a59-2bc81dbb3830}</Project>
|
||||
<Name>Umbraco.PublishedCache.NuCache</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Umbraco.Tests.Common\Umbraco.Tests.Common.csproj">
|
||||
<Project>{a499779c-1b3b-48a8-b551-458e582e6e96}</Project>
|
||||
<Name>Umbraco.Tests.Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\Umbraco.Web\Umbraco.Web.csproj">
|
||||
<Project>{651E1350-91B6-44B7-BD60-7207006D7003}</Project>
|
||||
<Name>Umbraco.Web</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<!-- get NuGet packages directory -->
|
||||
<PropertyGroup>
|
||||
<NuGetPackages>$(NuGetPackageFolders.Split(';')[0])</NuGetPackages>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="BeforeBuild">
|
||||
<Message Text="-BeforeBuild-" Importance="high" />
|
||||
<Message Text="MSBuildExtensionsPath: $(MSBuildExtensionsPath)" Importance="high" />
|
||||
<Message Text="WebPublishingTasks: $(WebPublishingTasks)" Importance="high" />
|
||||
<Message Text="NuGetPackageFolders: $(NuGetPackageFolders)" Importance="high" />
|
||||
<Message Text="NuGetPackages: $(NuGetPackages)" Importance="high" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,122 +0,0 @@
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Features;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing;
|
||||
|
||||
namespace Umbraco.Tests.Web.Controllers
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.None)]
|
||||
public class AuthenticationControllerTests : TestWithDatabaseBase
|
||||
{
|
||||
protected override void ComposeApplication(bool withApplication)
|
||||
{
|
||||
base.ComposeApplication(withApplication);
|
||||
//if (!withApplication) return;
|
||||
|
||||
// replace the true IUserService implementation with a mock
|
||||
// so that each test can configure the service to their liking
|
||||
Builder.Services.AddUnique(f => Mock.Of<IUserService>());
|
||||
|
||||
// kill the true IEntityService too
|
||||
Builder.Services.AddUnique(f => Mock.Of<IEntityService>());
|
||||
|
||||
Builder.Services.AddUnique<UmbracoFeatures>();
|
||||
}
|
||||
|
||||
|
||||
// TODO Reintroduce when moved to .NET Core
|
||||
// [Test]
|
||||
// public async System.Threading.Tasks.Task GetCurrentUser_Fips()
|
||||
// {
|
||||
// ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
// {
|
||||
// //setup some mocks
|
||||
// var userServiceMock = Mock.Get(ServiceContext.UserService);
|
||||
// userServiceMock.Setup(service => service.GetUserById(It.IsAny<int>()))
|
||||
// .Returns(() => null);
|
||||
//
|
||||
// if (Thread.GetDomain().GetData(".appPath") != null)
|
||||
// {
|
||||
// HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter()));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var baseDir = IOHelper.MapPath("").TrimEnd(Path.DirectorySeparatorChar);
|
||||
// HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter()));
|
||||
// }
|
||||
//
|
||||
// var usersController = new AuthenticationController(
|
||||
// new TestUserPasswordConfig(),
|
||||
// Factory.GetInstance<IGlobalSettings>(),
|
||||
// Factory.GetInstance<IHostingEnvironment>(),
|
||||
// umbracoContextAccessor,
|
||||
// Factory.GetInstance<ISqlContext>(),
|
||||
// Factory.GetInstance<ServiceContext>(),
|
||||
// Factory.GetInstance<AppCaches>(),
|
||||
// Factory.GetInstance<IProfilingLogger>(),
|
||||
// Factory.GetInstance<IRuntimeState>(),
|
||||
// Factory.GetInstance<UmbracoMapper>(),
|
||||
// Factory.GetInstance<ISecuritySettings>(),
|
||||
// Factory.GetInstance<IPublishedUrlProvider>(),
|
||||
// Factory.GetInstance<IRequestAccessor>(),
|
||||
// Factory.GetInstance<IEmailSender>()
|
||||
// );
|
||||
// return usersController;
|
||||
// }
|
||||
//
|
||||
// Mock.Get(Current.SqlContext)
|
||||
// .Setup(x => x.Query<IUser>())
|
||||
// .Returns(new Query<IUser>(Current.SqlContext));
|
||||
//
|
||||
// var syntax = new SqlCeSyntaxProvider();
|
||||
//
|
||||
// Mock.Get(Current.SqlContext)
|
||||
// .Setup(x => x.SqlSyntax)
|
||||
// .Returns(syntax);
|
||||
//
|
||||
// var mappers = new MapperCollection(new[]
|
||||
// {
|
||||
// new UserMapper(new Lazy<ISqlContext>(() => Current.SqlContext), new ConcurrentDictionary<Type, ConcurrentDictionary<string, string>>())
|
||||
// });
|
||||
//
|
||||
// Mock.Get(Current.SqlContext)
|
||||
// .Setup(x => x.Mappers)
|
||||
// .Returns(mappers);
|
||||
//
|
||||
// // Testing what happens if the system were configured to only use FIPS-compliant algorithms
|
||||
// var typ = typeof(CryptoConfig);
|
||||
// var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
|
||||
// var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy");
|
||||
// var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy");
|
||||
// var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms;
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// if (!originalFipsValue)
|
||||
// {
|
||||
// haveFld.SetValue(null, true);
|
||||
// isFld.SetValue(null, true);
|
||||
// }
|
||||
//
|
||||
// var runner = new TestRunner(CtrlFactory);
|
||||
// var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get);
|
||||
//
|
||||
// var obj = JsonConvert.DeserializeObject<UserDetail>(response.Item2);
|
||||
// Assert.AreEqual(-1, obj.UserId);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// if (!originalFipsValue)
|
||||
// {
|
||||
// haveFld.SetValue(null, false);
|
||||
// isFld.SetValue(null, false);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<log4net>
|
||||
<root>
|
||||
<priority value="OFF"/>
|
||||
</root>
|
||||
</log4net>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<!-- Global Log Level event -->
|
||||
<add key="serilog:minimum-level" value="Warning" />
|
||||
|
||||
<!-- Write to console -->
|
||||
<add key="serilog:using:Console" value="Serilog.Sinks.Console" />
|
||||
<add key="serilog:write-to:Console.theme" value="Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console" />
|
||||
<add key="serilog:write-to:Console.outputTemplate" value="[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}" />
|
||||
|
||||
<!-- Namespace log levels -->
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.Publishing.PublishingStrategy" value="Warning" />
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.TypeLoader" value="Warning" />
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.Persistence.UmbracoDatabase" value="Debug" />
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.Persistence.Migrations.Initial.DatabaseSchemaCreation" value="Warning" />
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.Persistence.Migrations.Initial.BaseDataCreation" value="Warning" />
|
||||
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -12,6 +12,8 @@ public class EnumSchemaFilter : ISchemaFilter
|
||||
{
|
||||
if (context.Type.IsEnum)
|
||||
{
|
||||
model.Type = "string";
|
||||
model.Format = null;
|
||||
model.Enum.Clear();
|
||||
foreach (var name in Enum.GetNames(context.Type))
|
||||
{
|
||||
|
||||
@@ -10,7 +10,8 @@ public abstract class QueryOptionBase
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly IRequestRoutingService _requestRoutingService;
|
||||
|
||||
public QueryOptionBase(IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
public QueryOptionBase(
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
IRequestRoutingService requestRoutingService)
|
||||
{
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
|
||||
@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Persistence.EFCore.Migrations;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Cms.Persistence.EFCore;
|
||||
|
||||
namespace Umbraco.Cms.Persistence.EFCore.Sqlite;
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Umbraco.Cms.Persistence.EFCore;
|
||||
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class ProviderNames
|
||||
{
|
||||
public const string SQLLite = "Microsoft.Data.Sqlite";
|
||||
|
||||
public const string SQLServer = "Microsoft.Data.SqlClient";
|
||||
}
|
||||
}
|
||||
@@ -21,5 +21,5 @@
|
||||
|
||||
</style>
|
||||
|
||||
<div class="text" ng-click="block.edit()" ng-focus="block.focus" ng-bind-html="block.data.richText.markup" style="margin: 0 20px;">
|
||||
<div class="text" ng-click="block.edit()" ng-focus="block.focus" ng-bind-html="block.data.richText.markup | safe_html" style="margin: 0 20px;">
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@ public class TypeFinder : ITypeFinder
|
||||
"ServiceStack.", "SqlCE4Umbraco,", "Superpower,", // used by Serilog
|
||||
"System.", "TidyNet,", "TidyNet.", "WebDriver,", "itextsharp,", "mscorlib,", "NUnit,", "NUnit.", "NUnit3.",
|
||||
"Selenium.", "ImageProcessor", "MiniProfiler.", "Owin,", "SQLite",
|
||||
"ReSharperTestRunner32", "ReSharperTestRunner64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension
|
||||
"ReSharperTestRunner", "ReSharperTestRunner32", "ReSharperTestRunner64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension
|
||||
};
|
||||
|
||||
private static readonly ConcurrentDictionary<string, Type?> TypeNamesCache = new();
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key alias="createGroup">Opret gruppe</key>
|
||||
<key alias="delete">Slet</key>
|
||||
<key alias="disable">Deaktivér</key>
|
||||
<key alias="editSettings">Edit settings</key>
|
||||
<key alias="editSettings">Redigér indstillinger</key>
|
||||
<key alias="emptyrecyclebin">Tøm papirkurv</key>
|
||||
<key alias="enable">Aktivér</key>
|
||||
<key alias="exportDocumentType">Eksportér dokumenttype</key>
|
||||
@@ -26,7 +26,7 @@
|
||||
<key alias="liveEdit">Redigér i Canvas</key>
|
||||
<key alias="logout">Log af</key>
|
||||
<key alias="move">Flyt</key>
|
||||
<key alias="notify">Notificeringer</key>
|
||||
<key alias="notify">Notifikationer</key>
|
||||
<key alias="protect">Offentlig adgang</key>
|
||||
<key alias="publish">Udgiv</key>
|
||||
<key alias="unpublish">Afpublicér</key>
|
||||
@@ -84,7 +84,7 @@
|
||||
<key alias="translate">Tillad adgang til at oversætte en node</key>
|
||||
<key alias="update">Tillad adgang til at gemme en node</key>
|
||||
<key alias="createblueprint">Tillad adgang til at oprette en indholdsskabelon</key>
|
||||
<key alias="notify">Tillad adgang til at oprette notificeringer for noder</key>
|
||||
<key alias="notify">Tillad adgang til at oprette notifikationer for noder</key>
|
||||
</area>
|
||||
<area alias="apps">
|
||||
<key alias="umbContent">Indhold</key>
|
||||
@@ -105,10 +105,10 @@
|
||||
<key alias="domainUpdated">Domænet '%0%' er nu opdateret</key>
|
||||
<key alias="orEdit">eller rediger nuværende domæner</key>
|
||||
<key alias="domainHelpWithVariants"><![CDATA[Gyldige domænenavne er: "example.com", "www.example.com", "example.com:8080" eller "https://www.example.com/".
|
||||
Yderlgiere understøttes også første niveau af stien efter domænet, f.eks. "Example.com/en" eller "/en". ]]></key>
|
||||
Yderligere understøttes også første niveau af stien efter domænet, f.eks. "example.com/en" eller "/en". ]]></key>
|
||||
<key alias="inherit">Nedarv</key>
|
||||
<key alias="setLanguage">Sprog</key>
|
||||
<key alias="setLanguageHelp"><![CDATA[Indstil sproget for noder under den aktuelle node,<br /> eller nedarv sprog fra forældre noder. Gælder også<br />
|
||||
<key alias="setLanguageHelp"><![CDATA[Indstil sproget for noder under den aktuelle node,<br /> eller nedarv sprog fra forældernoder. Gælder også<br />
|
||||
for den aktuelle node, medmindre et domæne nedenfor også indstiller et sprog.]]></key>
|
||||
<key alias="setDomains">Domæner</key>
|
||||
</area>
|
||||
@@ -120,7 +120,7 @@
|
||||
<key alias="deindent">Fortryd indryk afsnit</key>
|
||||
<key alias="formFieldInsert">Indsæt formularfelt</key>
|
||||
<key alias="graphicHeadline">Indsæt grafisk overskrift</key>
|
||||
<key alias="htmlEdit">Redigér Html</key>
|
||||
<key alias="htmlEdit">Redigér HTML</key>
|
||||
<key alias="indent">Indryk afsnit</key>
|
||||
<key alias="italic">Kursiv</key>
|
||||
<key alias="justifyCenter">Centrér</key>
|
||||
@@ -154,7 +154,7 @@
|
||||
<key alias="deleteTag">Slet tag</key>
|
||||
<key alias="confirmActionCancel">Fortryd</key>
|
||||
<key alias="confirmActionConfirm">Bekræft</key>
|
||||
<key alias="morePublishingOptions">Flere publiseringsmuligheder</key>
|
||||
<key alias="morePublishingOptions">Flere publiceringsmuligheder</key>
|
||||
<key alias="submitChanges">Indsæt</key>
|
||||
</area>
|
||||
<area alias="auditTrailsMedia">
|
||||
@@ -243,21 +243,21 @@
|
||||
<key alias="parentNotPublishedAnomaly">Ups: dette dokument er udgivet, men er ikke i cachen (intern fejl)</key>
|
||||
<key alias="getUrlException">Kunne ikke hente URL'en</key>
|
||||
<key alias="routeError">Dette dokument er udgivet, men dets URL ville kollidere med indholdet %0%</key>
|
||||
<key alias="routeErrorCannotRoute">Dette dokument er udgivet, men dets URL kan ikke dirigeres</key>
|
||||
<key alias="routeErrorCannotRoute">Dette dokument er udgivet, men dets URL kan ikke genereres</key>
|
||||
<key alias="publish">Udgiv</key>
|
||||
<key alias="published">Udgivet</key>
|
||||
<key alias="publishedPendingChanges">Udgivet (Ventede ændringer)</key>
|
||||
<key alias="publishStatus">Udgivelsesstatus</key>
|
||||
<key alias="publishDescendantsHelp">
|
||||
<![CDATA[Klik <em>Udgiv med undersider</em> for at udgive <strong>%0%</strong> og alle sider under og dermed gøre deres indhold offentligt tilgængelige.]]></key>
|
||||
<![CDATA[Klik <em>Udgiv med undersider</em> for at udgive <strong>%0%</strong> og alle sider under, og dermed gøre deres indhold offentligt tilgængeligt.]]></key>
|
||||
<key alias="publishDescendantsWithVariantsHelp">
|
||||
<![CDATA[Klik <em>Udgiv med undersider</em> for at udgive <strong>de valgte sprog</strong> og de samme sprog for sider under og dermed gøre deres indhold offentligt tilgængelige.]]></key>
|
||||
<![CDATA[Klik <em>Udgiv med undersider</em> for at udgive <strong>de valgte sprog</strong> og de samme sprog for sider under, og dermed gøre deres indhold offentligt tilgængeligt.]]></key>
|
||||
<key alias="releaseDate">Udgivelsesdato</key>
|
||||
<key alias="unpublishDate">Afpubliceringsdato</key>
|
||||
<key alias="removeDate">Fjern dato</key>
|
||||
<key alias="setDate">Vælg dato</key>
|
||||
<key alias="sortDone">Sorteringsrækkefølgen er opdateret</key>
|
||||
<key alias="sortHelp">For at sortere, træk siderne eller klik på en af kolonnehovederne. Du kan vælge flere sider
|
||||
<key alias="sortHelp">For at sortere, træk siderne eller klik på en af kolonneoverskrifterne. Du kan vælge flere sider
|
||||
ved at holde "shift" eller "control" nede mens du vælger.
|
||||
</key>
|
||||
<key alias="statistics">Statistik</key>
|
||||
@@ -298,8 +298,8 @@
|
||||
<key alias="removeTextBox">Fjern denne tekstboks</key>
|
||||
<key alias="contentRoot">Indholdsrod</key>
|
||||
<key alias="includeUnpublished">Inkluder ikke-udgivet indhold.</key>
|
||||
<key alias="isSensitiveValue">Denne værdi er skjult.Hvis du har brug for adgang til at se denne værdi, bedes du
|
||||
kontakte din web-administrator.
|
||||
<key alias="isSensitiveValue">Denne værdi er skjult. Hvis du har brug for adgang til at se denne værdi, bedes du
|
||||
kontakte din administrator.
|
||||
</key>
|
||||
<key alias="isSensitiveValue_short">Denne værdi er skjult.</key>
|
||||
<key alias="languagesToPublish">Hvilke sprog vil du gerne udgive?</key>
|
||||
@@ -334,11 +334,11 @@
|
||||
<area alias="media">
|
||||
<key alias="clickToUpload">Klik for at uploade</key>
|
||||
<key alias="orClickHereToUpload">eller klik her for at vælge filer</key>
|
||||
<key alias="disallowedFileType">Kan ikke uploade denne fil, den har ikke en godkendt filtype</key>
|
||||
<key alias="maxFileSize">Maks filstørrelse er</key>
|
||||
<key alias="disallowedFileType">Kan ikke uploade denne fil; den har ikke en godkendt filtype</key>
|
||||
<key alias="maxFileSize">Maksimal filstørrelse er</key>
|
||||
<key alias="mediaRoot">Medie rod</key>
|
||||
<key alias="moveToSameFolderFailed">Overordnet og destinations mappe kan ikke være den samme</key>
|
||||
<key alias="createFolderFailed">Oprettelse af mappen under parent med id %0% fejlede</key>
|
||||
<key alias="moveToSameFolderFailed">Overordnet og destinationsmappe kan ikke være den samme</key>
|
||||
<key alias="createFolderFailed">Oprettelse af mappen under node med id %0% fejlede</key>
|
||||
<key alias="renameFolderFailed">Omdøbning af mappen med id %0% fejlede</key>
|
||||
<key alias="dragAndDropYourFilesIntoTheArea">Træk dine filer ind i dropzonen for, at uploade dem til
|
||||
mediebiblioteket.
|
||||
@@ -348,7 +348,7 @@
|
||||
<area alias="member">
|
||||
<key alias="createNewMember">Opret et nyt medlem</key>
|
||||
<key alias="allMembers">Alle medlemmer</key>
|
||||
<key alias="memberGroupNoProperties">Medlemgrupper har ingen yderligere egenskaber til redigering.</key>
|
||||
<key alias="memberGroupNoProperties">Medlemsgrupper har ingen yderligere egenskaber til redigering.</key>
|
||||
<key alias="2fa">Totrinsbekræftelse</key>
|
||||
</area>
|
||||
<area alias="contentType">
|
||||
@@ -358,7 +358,7 @@
|
||||
<area alias="mediaType">
|
||||
<key alias="copyFailed">Kopiering af medietypen fejlede</key>
|
||||
<key alias="moveFailed">Flytning af medietypen fejlede</key>
|
||||
<key alias="autoPickMediaType">Auto vælg</key>
|
||||
<key alias="autoPickMediaType">Vælg automatisk</key>
|
||||
</area>
|
||||
<area alias="memberType">
|
||||
<key alias="copyFailed">Kopiering af medlemstypen fejlede</key>
|
||||
@@ -370,18 +370,18 @@
|
||||
<key alias="enterFolderName">Angiv et navn for mappen</key>
|
||||
<key alias="updateData">Vælg en type og skriv en titel</key>
|
||||
<key alias="noDocumentTypes" version="7.0">
|
||||
<![CDATA[Der kunne ikke findes nogen tilladte dokumenttyper. Du skal tillade disse i indstillinger under <strong>"dokumenttyper"</strong>.]]></key>
|
||||
<![CDATA[Der kunne ikke findes nogen tilladte dokumenttyper. Du skal tillade disse i <strong>Indstillinger</strong> under <strong>"Dokumenttyper"</strong>.]]></key>
|
||||
<key alias="noDocumentTypesAtRoot">
|
||||
<![CDATA[There are no document types available for creating content here. You must create these in <strong>Document Types</strong> within the <strong>Settings</strong> section.]]></key>
|
||||
<![CDATA[Der er ingen dokumenttyper tilgængelige til at oprette indhold her. Du skal oprette disse under <strong>Dokumenttyper</strong> i <strong>Indstillinger</strong>-sektionen.]]></key>
|
||||
<key alias="noDocumentTypesWithNoSettingsAccess">Den valgte side i træet tillader ikke at sider oprettes under
|
||||
den.
|
||||
</key>
|
||||
<key alias="noDocumentTypesEditPermissions">Rediger tilladelser for denne dokumenttype.</key>
|
||||
<key alias="noDocumentTypesCreateNew">Opret en ny dokumenttype</key>
|
||||
<key alias="noDocumentTypesAllowedAtRoot">
|
||||
<![CDATA[Der er ingen tilladte Dokumenttyper tilgængelige for at lave indhold her. Du skal tillade dette i <strong>Dokumenttyper</strong> inde i <strong>Indstillinger</strong> sektionen, ved at ændre <strong>Tillad på rodniveau</strong> indestillingen under <strong>Permissions</strong>.]]></key>
|
||||
<![CDATA[Der er ingen tilladte Dokumenttyper tilgængelige for at lave indhold her. Du skal tillade dette i <strong>Dokumenttyper</strong> inde i <strong>Indstillinger</strong> sektionen, ved at ændre <strong>Tillad på rodniveau</strong> indstillingen under <strong>Permissions</strong>.]]></key>
|
||||
<key alias="noMediaTypes" version="7.0">
|
||||
<![CDATA[Der kunne ikke findes nogen tilladte media typer. Du skal tillade disse i indstillinger under <strong>"media typer"</strong>.]]></key>
|
||||
<![CDATA[Der kunne ikke findes nogen tilladte media typer. Du skal tillade disse i indstillinger under <strong>"Medietyper"</strong>.]]></key>
|
||||
<key alias="noMediaTypesWithNoSettingsAccess">Det valgte medie i træet tillader ikke at medier oprettes under det.
|
||||
</key>
|
||||
<key alias="noMediaTypesEditPermissions">Rediger tilladelser for denne medietype.</key>
|
||||
@@ -436,7 +436,7 @@
|
||||
</key>
|
||||
<key alias="confirmListViewPublish">Udgivelse vil gøre de valgte sider synlige på sitet.</key>
|
||||
<key alias="confirmListViewUnpublish">Afpublicering vil fjerne de valgte sider og deres undersider fra sitet.</key>
|
||||
<key alias="confirmUnpublish">Afpublicering vil fjerne denne side og alle dets undersider fra websitet.</key>
|
||||
<key alias="confirmUnpublish">Afpublicering vil fjerne denne side og alle dens undersider fra websitet.</key>
|
||||
<key alias="doctypeChangeWarning">Du har ikke-gemte ændringer. Hvis du ændrer dokumenttype, kasseres ændringerne.
|
||||
</key>
|
||||
</area>
|
||||
@@ -871,6 +871,7 @@
|
||||
<key alias="avatar">Avatar til</key>
|
||||
<key alias="header">Overskrift</key>
|
||||
<key alias="systemField">system felt</key>
|
||||
<key alias="primary">Primær</key>
|
||||
</area>
|
||||
<area alias="colors">
|
||||
<key alias="blue">Blå</key>
|
||||
@@ -1084,8 +1085,8 @@
|
||||
<key alias="relateToOriginal">Relater det kopierede element til originalen</key>
|
||||
</area>
|
||||
<area alias="notifications">
|
||||
<key alias="editNotifications"><![CDATA[Vælg dine notificeringer for <strong>%0%</strong>]]></key>
|
||||
<key alias="notificationsSavedFor">Notificeringer er gemt for</key>
|
||||
<key alias="editNotifications"><![CDATA[Vælg dine notifikationer for <strong>%0%</strong>]]></key>
|
||||
<key alias="notificationsSavedFor">Notifikationer er gemt for</key>
|
||||
<key alias="mailBody"><![CDATA[
|
||||
Hej %0%
|
||||
|
||||
@@ -1105,7 +1106,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<div style="margin: 8px 0; padding: 8px; display: block;"> <br />
|
||||
<a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/#/content/content/edit/%5%"> RET </a> <br /> </div> <p> <h3>Opdateringssammendrag:</h3> <table style="width: 100%;"> %6% </table> </p> <div style="margin: 8px 0; padding: 8px; display: block;"> <br /> <a style="color: white; font-weight: bold; background-color: #66cc66; text-decoration : none; margin-right: 20px; border: 8px solid #66cc66; width: 150px;" href="http://%4%/actions/publish.aspx?id=%5%"> PUBLISÉR </a> <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/#/content/content/edit/%5%"> RET </a> <a style="color: white; font-weight: bold; background-color: #ca4a4a; text-decoration : none; margin-right: 20px; border: 8px solid #ca4a4a; width: 150px;" href="http://%4%/actions/delete.aspx?id=%5%"> SLET </a> <br /> </div> <p>Hav en fortsat god dag!<br /><br /> De bedste hilsner fra Umbraco robotten </p>]]></key>
|
||||
<key alias="mailSubject">[%0%] Notificering om %1% udført på %2%</key>
|
||||
<key alias="notifications">Notificeringer</key>
|
||||
<key alias="notifications">Notifikationer</key>
|
||||
</area>
|
||||
<area alias="packager">
|
||||
<key alias="actions">Handlinger</key>
|
||||
@@ -2104,25 +2105,25 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="checkPassed">Test bestået</key>
|
||||
<key alias="checkFailed">Test fejlet</key>
|
||||
<key alias="openBackofficeSearch">Åben backoffice søgning</key>
|
||||
<key alias="openCloseBackofficeHelp">Åben/Luk backoffice hjælp</key>
|
||||
<key alias="openCloseBackofficeProfileOptions">Åben/Luk dine profil indstillinger</key>
|
||||
<key alias="openCloseBackofficeHelp">Åben/luk backoffice hjælp</key>
|
||||
<key alias="openCloseBackofficeProfileOptions">Åben/luk dine profilindstillinger</key>
|
||||
<key alias="assignDomainDescription">Tilføj domæne på %0%</key>
|
||||
<key alias="createDescription">Opret ny node under %0%</key>
|
||||
<key alias="protectDescription">Opsæt offentlig adgang på %0%</key>
|
||||
<key alias="rightsDescription">Opsæt rettigheder på %0%</key>
|
||||
<key alias="sortDescription">Juster soterings rækkefølgen for %0%</key>
|
||||
<key alias="createblueprintDescription">Opret indholds skabelon baseret på %0%</key>
|
||||
<key alias="openContextMenu">Åben kontext menu for</key>
|
||||
<key alias="sortDescription">Juster sorteringsrækkefølgen for %0%</key>
|
||||
<key alias="createblueprintDescription">Opret indholdsskabelon baseret på %0%</key>
|
||||
<key alias="openContextMenu">Åben kontekstmenu for</key>
|
||||
<key alias="currentLanguage">Aktivt sprog</key>
|
||||
<key alias="switchLanguage">Skift sprog til</key>
|
||||
<key alias="createNewFolder">Opret ny mappe</key>
|
||||
<key alias="newPartialView">Delvist View</key>
|
||||
<key alias="newPartialViewMacro">Delvist View Macro</key>
|
||||
<key alias="newPartialView">Partial View</key>
|
||||
<key alias="newPartialViewMacro">Partial View Macro</key>
|
||||
<key alias="newMember">Medlem</key>
|
||||
<key alias="newDataType">Data type</key>
|
||||
<key alias="redirectDashboardSearchLabel">Søg i viderestillings dashboardet</key>
|
||||
<key alias="userGroupSearchLabel">Søg i brugergruppe sektionen</key>
|
||||
<key alias="userSearchLabel">Søg i bruger sektionen</key>
|
||||
<key alias="userGroupSearchLabel">Søg i brugergruppesektionen</key>
|
||||
<key alias="userSearchLabel">Søg i brugersektionen</key>
|
||||
<key alias="createItem">Opret element</key>
|
||||
<key alias="create">Opret</key>
|
||||
<key alias="edit">Rediger</key>
|
||||
@@ -2146,16 +2147,16 @@ Mange hilsner fra Umbraco robotten
|
||||
</area>
|
||||
<area alias="references">
|
||||
<key alias="tabName">Referencer</key>
|
||||
<key alias="DataTypeNoReferences">Denne Data Type har ingen referencer.</key>
|
||||
<key alias="labelUsedByMediaTypes">Brugt i Medie Typer</key>
|
||||
<key alias="labelUsedByMemberTypes">Brugt i Medlems Typer</key>
|
||||
<key alias="usedByProperties">Brugt af</key>
|
||||
<key alias="labelUsedByDocuments">Brugt i Dokumenter</key>
|
||||
<key alias="labelUsedByMembers">Brugt i Medlemmer</key>
|
||||
<key alias="labelUsedByMedia">Brugt i Medier</key>
|
||||
<key alias="DataTypeNoReferences">Denne Datatype har ingen referencer.</key>
|
||||
<key alias="labelUsedByMediaTypes">Bruges i Medietyper</key>
|
||||
<key alias="labelUsedByMemberTypes">Bruges i Medlemstyper</key>
|
||||
<key alias="usedByProperties">Bruges af</key>
|
||||
<key alias="labelUsedByDocuments">Bruges i Dokumenter</key>
|
||||
<key alias="labelUsedByMembers">Bruges i Medlemmer</key>
|
||||
<key alias="labelUsedByMedia">Bruges i Medier</key>
|
||||
</area>
|
||||
<area alias="logViewer">
|
||||
<key alias="deleteSavedSearch">Slet gemte søgning</key>
|
||||
<key alias="deleteSavedSearch">Slet gemt søgning</key>
|
||||
<key alias="logLevels">Log type</key>
|
||||
<key alias="selectAllLogLevelFilters">Vælg alle</key>
|
||||
<key alias="deselectAllLogLevelFilters">Fravælg alle</key>
|
||||
@@ -2187,7 +2188,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="deleteThisSearch">Slet denne søgning</key>
|
||||
<key alias="findLogsWithRequestId">Find logs med request Id</key>
|
||||
<key alias="findLogsWithNamespace">Find logs med Namespace</key>
|
||||
<key alias="findLogsWithMachineName">Find logs med maskin navn</key>
|
||||
<key alias="findLogsWithMachineName">Find logs med maskinnavn</key>
|
||||
<key alias="open">Åben</key>
|
||||
<key alias="polling">Henter</key>
|
||||
<key alias="every2">Hver 2 sekunder</key>
|
||||
@@ -2224,19 +2225,19 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="headlineEditorAppearance">Redigerings udseende</key>
|
||||
<key alias="headlineDataModels">Data modeller</key>
|
||||
<key alias="headlineCatalogueAppearance">katalog udseende</key>
|
||||
<key alias="labelBackgroundColor">Baggrunds farve</key>
|
||||
<key alias="labelBackgroundColor">Baggrundsfarve</key>
|
||||
<key alias="labelIconColor">Ikon farve</key>
|
||||
<key alias="labelContentElementType">Indholds model</key>
|
||||
<key alias="labelLabelTemplate">Label</key>
|
||||
<key alias="labelCustomView">Speciel visning</key>
|
||||
<key alias="labelCustomViewInfoTitle">Vis speciel visning beskrivelsen</key>
|
||||
<key alias="labelCustomViewDescription">Overskrift hvordan denne block præsenteres i backoffice interfacet. Vælg en
|
||||
.html fil der indeholder din præsensation.
|
||||
<key alias="labelCustomViewDescription">Overskriv hvordan denne blok præsenteres i backoffice. Vælg en
|
||||
.html fil der indeholder din visning.
|
||||
</key>
|
||||
<key alias="labelSettingsElementType">Indstillings model</key>
|
||||
<key alias="labelEditorSize">Rederings lagets størrelse</key>
|
||||
<key alias="labelEditorSize">Redigeringsvinduets størrelse</key>
|
||||
<key alias="addCustomView">Tilføj speciel visning</key>
|
||||
<key alias="addSettingsElementType">Tilføj instillinger</key>
|
||||
<key alias="addSettingsElementType">Tilføj indstillinger</key>
|
||||
<key alias="confirmDeleteBlockMessage">
|
||||
<![CDATA[Er du sikker på at du vil slette indholdet <strong>%0%</strong>?]]></key>
|
||||
<key alias="confirmDeleteBlockTypeMessage">
|
||||
@@ -2261,7 +2262,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="forceHideContentEditor">Skjul indholdseditoren</key>
|
||||
<key alias="forceHideContentEditorHelp">Skjul indholds redigerings knappen samt indholdseditoren i Blok Redigerings vinduet</key>
|
||||
<key alias="girdInlineEditing">Direkte redigering</key>
|
||||
<key alias="girdInlineEditingHelp">Tilføjer direkte redigering a det første felt. Yderligere felter optræder kun i redigerings vinduet.</key>
|
||||
<key alias="girdInlineEditingHelp">Tilføjer direkte redigering af det første felt. Yderligere felter optræder kun i redigerings vinduet.</key>
|
||||
<key alias="blockHasChanges">Du har lavet ændringer til dette indhold. Er du sikker på at du vil kassere dem?</key>
|
||||
<key alias="confirmCancelBlockCreationHeadline">Annuller oprettelse?</key>
|
||||
<key alias="confirmCancelBlockCreationMessage"><![CDATA[Er du sikker på at du vil annullere oprettelsen.]]></key>
|
||||
@@ -2284,11 +2285,11 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="confirmDeleteBlockAreaNotice">Alle blokke, der er oprettet i dette område, vil blive slettet.</key>
|
||||
<key alias="layoutOptions">Layout-opsætning</key>
|
||||
<key alias="structuralOptions">Struktur</key>
|
||||
<key alias="sizeOptions">Størrelses opsætning</key>
|
||||
<key alias="sizeOptions">Størrelsesopsætning</key>
|
||||
<key alias="allowedBlockColumns">Tilgængelige kolonne-størrelser</key>
|
||||
<key alias="allowedBlockColumnsHelp">Vælg de forskellige antal kolonner denne blok må optage i layoutet. Dette forhindre ikke blokken i at optræde i et mindre område.</key>
|
||||
<key alias="allowedBlockRows">TIlgængelige række-størrelser</key>
|
||||
<key alias="allowedBlockRowsHelp">Vælg hvor mange rækker denne blok på optage i layoutet.</key>
|
||||
<key alias="allowedBlockColumnsHelp">Vælg de forskellige antal kolonner denne blok må optage i layoutet. Dette forhindrer ikke blokken i at optræde i et mindre område.</key>
|
||||
<key alias="allowedBlockRows">Tilgængelige række-størrelser</key>
|
||||
<key alias="allowedBlockRowsHelp">Vælg hvor mange rækker denne blok må optage i layoutet.</key>
|
||||
<key alias="allowBlockInRoot">Tillad på rodniveau</key>
|
||||
<key alias="allowBlockInRootHelp">Gør denne blok tilgængelig i layoutets rodniveau. Hvis dette ikke er valgt, kan denne blok kun bruges inden for andre blokkes definerede områder.</key>
|
||||
<key alias="areas">Blok-områder</key>
|
||||
@@ -2302,11 +2303,11 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="confirmPasteDisallowedNestedBlockMessage">
|
||||
<![CDATA[Det indsatte indhold bestod af ikke tilladt del-indhold, disse dele er blevet afvist. Vil du beholde det resterene alligevel?]]></key>
|
||||
<key alias="areaAliasHelp">
|
||||
<![CDATA[Dette alias skrives ud via GetBlockGridHTML(), brug aliaset til at fange det element der repræsentere dette område. F.eks.. .umb-block-grid__area[data-area-alias="MitOmraadeAlias"] { ... }]]></key>
|
||||
<![CDATA[Dette alias skrives ud via GetBlockGridHTML(), brug aliaset til at fange det element der repræsenterer dette område. F.eks.. .umb-block-grid__area[data-area-alias="MitOmraadeAlias"] { ... }]]></key>
|
||||
<key alias="scaleHandlerButtonTitle">Træk for at skalere</key>
|
||||
<key alias="areaCreateLabelTitle">Tilføj indhold label</key>
|
||||
<key alias="areaCreateLabelHelp">Overskriv labellen for tilføj indholds knappen i dette område.</key>
|
||||
<key alias="showSizeOptions">Tilføj skalerings muligheder</key>
|
||||
<key alias="areaCreateLabelHelp">Overskriv labelen for tilføj indholds knappen i dette område.</key>
|
||||
<key alias="showSizeOptions">Tilføj skaleringsmuligheder</key>
|
||||
<key alias="addBlockType">Tilføj Blok</key>
|
||||
<key alias="addBlockGroup">Tilføj gruppe</key>
|
||||
<key alias="pickSpecificAllowance">Tilføj gruppe eller Blok</key>
|
||||
@@ -2319,14 +2320,14 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="tabAdvanced">Avanceret</key>
|
||||
<key alias="headlineAllowance">Tilladelser</key>
|
||||
<key alias="getSampleHeadline">Installer demo konfiguration</key>
|
||||
<key alias="getSampleDescription"><![CDATA[Dette tilføjer basale og hjælper dig til at komme igang med Block Grid Editor.<br/>Dette indeholder Blokke for Overskrift, Beriget-Tekst, Billede og To-Koloners-Layout.]]></key>
|
||||
<key alias="getSampleDescription"><![CDATA[Dette tilføjer en basal opsætning og hjælper dig til at komme igang med Block Grid Editor.<br/>Dette indeholder Blokke for Overskrift, Formateret Tekst, Billede og To-Kolonners-Layout.]]></key>
|
||||
<key alias="getSampleButton">Installer</key>
|
||||
<key alias="actionEnterSortMode">Sortings tilstand</key>
|
||||
<key alias="actionExitSortMode">Afslut sortings tilstand</key>
|
||||
<key alias="actionEnterSortMode">Sortingstilstand</key>
|
||||
<key alias="actionExitSortMode">Afslut sortingstilstand</key>
|
||||
<key alias="areaAliasIsNotUnique">Dette område alias skal være unikt sammenlignet med andre områder af denne Blok.</key>
|
||||
<key alias="configureArea">Konfigurer område</key>
|
||||
<key alias="deleteArea">Slet område</key>
|
||||
<key alias="addColumnSpanOption">Tilføj mulighed for %0% koloner</key>
|
||||
<key alias="addColumnSpanOption">Tilføj mulighed for %0% kolonner</key>
|
||||
<key alias="insertBlock">Indsæt Blok</key>
|
||||
<key alias="labelInlineMode">Vis på linje med tekst</key>
|
||||
</area>
|
||||
@@ -2358,12 +2359,12 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="openWebsiteLabel">Vis i nyt vindue</key>
|
||||
<key alias="openWebsiteTitle">Åben forhåndsvisning i nyt vindue</key>
|
||||
<key alias="returnToPreviewHeadline">Forhåndsvisning af indholdet?</key>
|
||||
<key alias="returnToPreviewDescription">Du har afslutet forhåndsvisning, vil du starte forhåndsvisning igen for at
|
||||
<key alias="returnToPreviewDescription">Du har afsluttet forhåndsvisning, vil du starte forhåndsvisning igen for at
|
||||
se seneste gemte version af indholdet?
|
||||
</key>
|
||||
<key alias="returnToPreviewDeclineButton">Se udgivet indhold</key>
|
||||
<key alias="viewPublishedContentHeadline">Se udgivet indhold?</key>
|
||||
<key alias="viewPublishedContentDescription">Du er i forhåndsvisning, vil du afslutte for at se den udgivet
|
||||
<key alias="viewPublishedContentDescription">Du er i forhåndsvisning, vil du afslutte for at se den udgivne
|
||||
version?
|
||||
</key>
|
||||
<key alias="viewPublishedContentAcceptButton">Se udgivet version</key>
|
||||
@@ -2373,7 +2374,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="FolderCreation">Mappeoprettelse</key>
|
||||
<key alias="FileWritingForPackages">Filskrivning for pakker</key>
|
||||
<key alias="FileWriting">Filskrivning</key>
|
||||
<key alias="MediaFolderCreation">Medie mappeoprettelse</key>
|
||||
<key alias="MediaFolderCreation">Mediemappeoprettelse</key>
|
||||
</area>
|
||||
<area alias="treeSearch">
|
||||
<key alias="searchResult">resultat</key>
|
||||
|
||||
@@ -56,6 +56,8 @@
|
||||
<key alias="createblueprint">Create Content Template</key>
|
||||
<key alias="resendInvite">Resend Invitation</key>
|
||||
<key alias="toggleHideUnavailable">Hide unavailable options</key>
|
||||
<key alias="changeDataType">Change Data Type</key>
|
||||
<key alias="editContent">Edit content</key>
|
||||
</area>
|
||||
<area alias="actionCategories">
|
||||
<key alias="content">Content</key>
|
||||
@@ -158,6 +160,7 @@
|
||||
<key alias="confirmActionConfirm">Confirm</key>
|
||||
<key alias="morePublishingOptions">More publishing options</key>
|
||||
<key alias="submitChanges">Submit</key>
|
||||
<key alias="generateModelsAndClose">Generate models and close</key>
|
||||
</area>
|
||||
<area alias="auditTrailsMedia">
|
||||
<key alias="delete">Media deleted</key>
|
||||
@@ -198,6 +201,8 @@
|
||||
<key alias="smallContentVersionPreventCleanup">Save</key>
|
||||
<key alias="smallContentVersionEnableCleanup">Save</key>
|
||||
<key alias="historyIncludingVariants">History (all variants)</key>
|
||||
<key alias="unpublishvariant">Content unpublished for languages: %0%</key>
|
||||
<key alias="smallUnpublishVariant">Unpublish</key>
|
||||
</area>
|
||||
<area alias="codefile">
|
||||
<key alias="createFolderIllegalChars">The folder name cannot contain illegal characters.</key>
|
||||
@@ -325,6 +330,12 @@
|
||||
<key alias="createEmpty">Create new</key>
|
||||
<key alias="createFromClipboard">Paste from clipboard</key>
|
||||
<key alias="nodeIsInTrash">This item is in the Recycle Bin</key>
|
||||
<key alias="noProperties">No content can be added for this item</key>
|
||||
<key alias="variantSaveNotAllowed">Save is not allowed</key>
|
||||
<key alias="variantPublishNotAllowed">Publish is not allowed</key>
|
||||
<key alias="variantSendForApprovalNotAllowed">Send for approval is not allowed</key>
|
||||
<key alias="variantScheduleNotAllowed">Schedule is not allowed</key>
|
||||
<key alias="variantUnpublishNotAllowed">Unpublish is not allowed</key>
|
||||
</area>
|
||||
<area alias="blueprints">
|
||||
<key alias="createBlueprintFrom"><![CDATA[Create a new Content Template from <em>%0%</em>]]></key>
|
||||
@@ -349,12 +360,19 @@
|
||||
<key alias="renameFolderFailed">Failed to rename the folder with id %0%</key>
|
||||
<key alias="dragAndDropYourFilesIntoTheArea">Drag and drop your file(s) into the area</key>
|
||||
<key alias="fileSecurityValidationFailure">One or more file security validations have failed</key>
|
||||
<key alias="moveToSameFolderFailed">Parent and destination folders cannot be the same</key>
|
||||
<key alias="uploadNotAllowed">Upload is not allowed in this location.</key>
|
||||
</area>
|
||||
<area alias="member">
|
||||
<key alias="createNewMember">Create a new member</key>
|
||||
<key alias="allMembers">All Members</key>
|
||||
<key alias="memberGroupNoProperties">Member groups have no additional properties for editing.</key>
|
||||
<key alias="2fa">Two-Factor Authentication</key>
|
||||
<key alias="duplicateMemberLogin">A member with this login already exists</key>
|
||||
<key alias="memberHasGroup">The member is already in group '%0%'</key>
|
||||
<key alias="memberHasPassword">The member already has a password set</key>
|
||||
<key alias="memberLockoutNotEnabled">Lockout is not enabled for this member</key>
|
||||
<key alias="memberNotInGroup">The member is not in group '%0%'</key>
|
||||
</area>
|
||||
<area alias="contentType">
|
||||
<key alias="copyFailed">Failed to copy content type</key>
|
||||
@@ -476,7 +494,6 @@
|
||||
<key alias="urlLinkPicker">Link</key>
|
||||
<key alias="anchorLinkPicker">Anchor / querystring</key>
|
||||
<key alias="anchorInsert">Name</key>
|
||||
<key alias="assignDomain">Manage hostnames</key>
|
||||
<key alias="closeThisWindow">Close this window</key>
|
||||
<key alias="confirmdelete">Are you sure you want to delete</key>
|
||||
<key alias="confirmdeleteNumberOfItems"><![CDATA[Are you sure you want to delete <strong>%0%</strong> of <strong>%1%</strong> items]]></key>
|
||||
@@ -581,6 +598,7 @@
|
||||
<key alias="yesRemove">Yes, remove</key>
|
||||
<key alias="deleteLayout">You are deleting the layout</key>
|
||||
<key alias="deletingALayout">Modifying layout will result in loss of data for any existing content that is based on this configuration.</key>
|
||||
<key alias="selectEditorConfiguration">Select configuration</key>
|
||||
</area>
|
||||
<area alias="dictionary">
|
||||
<key alias="importDictionaryItemHelp">
|
||||
@@ -689,6 +707,9 @@
|
||||
<key alias="selectFolder">Select the folder to move</key>
|
||||
<key alias="inTheTree">to in the tree structure below</key>
|
||||
<key alias="wasMoved">was moved underneath</key>
|
||||
<key alias="canChangePropertyEditorHelp">Changing a property editor on a data type with stored values is disabled. To allow this you can change the Umbraco:CMS:DataTypes:CanBeChanged setting in appsettings.json.</key>
|
||||
<key alias="hasReferencesDeleteConsequence"><![CDATA[Deleting <strong>%0%</strong> will delete the properties and their data from the following items]]></key>
|
||||
<key alias="acceptDeleteConsequence">I understand this action will delete the properties and data based on this Data Type</key>
|
||||
</area>
|
||||
<area alias="errorHandling">
|
||||
<key alias="errorButDataWasSaved">Your data has been saved, but before you can publish this page there are some
|
||||
@@ -729,6 +750,8 @@
|
||||
<key alias="tableColMergeLeft">Please place cursor at the left of the two cells you wish to merge</key>
|
||||
<key alias="tableSplitNotSplittable">You cannot split a cell that hasn't been merged.</key>
|
||||
<key alias="propertyHasErrors">This property is invalid</key>
|
||||
<key alias="defaultError">An unknown failure has occurred</key>
|
||||
<key alias="concurrencyError">Optimistic concurrency failure, object has been modified</key>
|
||||
</area>
|
||||
<area alias="general">
|
||||
<key alias="about">About</key>
|
||||
@@ -893,6 +916,13 @@
|
||||
<key alias="lastUpdated">Last Updated</key>
|
||||
<key alias="skipToMenu">Skip to menu</key>
|
||||
<key alias="skipToContent">Skip to content</key>
|
||||
<key alias="primary">Primary</key>
|
||||
<key alias="change">Change</key>
|
||||
<key alias="cropSection">Crop section</key>
|
||||
<key alias="generic">Generic</key>
|
||||
<key alias="media">Media</key>
|
||||
<key alias="revert">Revert</key>
|
||||
<key alias="validate">Validate</key>
|
||||
</area>
|
||||
<area alias="colors">
|
||||
<key alias="blue">Blue</key>
|
||||
@@ -919,6 +949,7 @@
|
||||
<key alias="generalHeader">General</key>
|
||||
<key alias="editorHeader">Editor</key>
|
||||
<key alias="toggleAllowCultureVariants">Toggle allow culture variants</key>
|
||||
<key alias="addTab">Add tab</key>
|
||||
</area>
|
||||
<area alias="graphicheadline">
|
||||
<key alias="backgroundcolor">Background colour</key>
|
||||
@@ -1368,6 +1399,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
so uninstall with caution. If in doubt, contact the package author.]]></key>
|
||||
<key alias="packageVersion">Package version</key>
|
||||
<key alias="verifiedToWorkOnUmbracoCloud">Verified to work on Umbraco Cloud</key>
|
||||
<key alias="packageMigrationsComplete">Package migrations have successfully completed.</key>
|
||||
<key alias="packageMigrationsNonePending">All package migrations have successfully completed.</key>
|
||||
</area>
|
||||
<area alias="paste">
|
||||
<key alias="doNothing">Paste with full formatting (Not recommended)</key>
|
||||
@@ -1425,6 +1458,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="publishHelp"><![CDATA[Click <em>Publish</em> to publish <strong>%0%</strong> and thereby making its content publicly available.<br/><br />
|
||||
You can publish this page and all its subpages by checking <em>Include unpublished subpages</em> below.
|
||||
]]> </key>
|
||||
<key alias="invalidPublishBranchPermissions">Insufficient user permissions to publish all descendant documents</key>
|
||||
<key alias="contentPublishedFailedIsTrashed">%0% could not be published because the item is in the recycle bin.</key>
|
||||
<key alias="contentPublishedFailedReqCultureValidationError">Validation failed for required language '%0%'. This language was saved but not published.</key>
|
||||
</area>
|
||||
<area alias="colorpicker">
|
||||
<key alias="noColors">You have not configured any approved colours</key>
|
||||
@@ -1517,25 +1553,22 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="rollbackTo">Rollback to</key>
|
||||
<key alias="selectVersion">Select version</key>
|
||||
<key alias="view">View</key>
|
||||
<key alias="pagination"><![CDATA[Showing version %0% to %1% of %2% versions]]></key>
|
||||
<key alias="versions">Versions</key>
|
||||
<key alias="currentDraftVersion">Current draft version</key>
|
||||
<key alias="currentPublishedVersion">Current published version</key>
|
||||
</area>
|
||||
<area alias="scripts">
|
||||
<key alias="editscript">Edit script file</key>
|
||||
</area>
|
||||
<area alias="sections">
|
||||
<key alias="concierge">Concierge</key>
|
||||
<key alias="content">Content</key>
|
||||
<key alias="courier">Courier</key>
|
||||
<key alias="developer">Developer</key>
|
||||
<key alias="forms">Forms</key>
|
||||
<key alias="help" version="7.0">Help</key>
|
||||
<key alias="installer">Umbraco Configuration Wizard</key>
|
||||
<key alias="media">Media</key>
|
||||
<key alias="member">Members</key>
|
||||
<key alias="newsletters">Newsletters</key>
|
||||
<key alias="packages">Packages</key>
|
||||
<key alias="marketplace">Marketplace</key>
|
||||
<key alias="settings">Settings</key>
|
||||
<key alias="statistics">Statistics</key>
|
||||
<key alias="translation">Translation</key>
|
||||
<key alias="users">Users</key>
|
||||
</area>
|
||||
@@ -1576,6 +1609,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
column headers to sort the entire collection of items
|
||||
</key>
|
||||
<key alias="sortPleaseWait"><![CDATA[Please wait. Items are being sorted, this can take a while.]]></key>
|
||||
<key alias="sortEmptyState">This node has no child nodes to sort</key>
|
||||
</area>
|
||||
<area alias="speechBubbles">
|
||||
<key alias="validationFailedHeader">Validation</key>
|
||||
@@ -1587,7 +1621,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="operationCancelledText">Operation was cancelled by a 3rd party add-in</key>
|
||||
<key alias="folderUploadNotAllowed">This file is being uploaded as part of a folder, but creating a new folder is not allowed here</key>
|
||||
<key alias="folderCreationNotAllowed">Creating a new folder is not allowed here</key>
|
||||
<key alias="contentPublishedFailedByEvent">Publishing was cancelled by a 3rd party add-in</key>
|
||||
<key alias="contentTypeDublicatePropertyType">Property type already exists</key>
|
||||
<key alias="contentTypePropertyTypeCreated">Property type created</key>
|
||||
<key alias="contentTypePropertyTypeCreatedText"><![CDATA[Name: %0% <br /> DataType: %1%]]></key>
|
||||
@@ -1601,7 +1634,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="cssSavedText">Stylesheet saved without any errors</key>
|
||||
<key alias="dataTypeSaved">Datatype saved</key>
|
||||
<key alias="dictionaryItemSaved">Dictionary item saved</key>
|
||||
<key alias="editContentPublishedFailedByParent">Publishing failed because the parent page isn't published</key>
|
||||
<key alias="editContentPublishedHeader">Content published</key>
|
||||
<key alias="editContentPublishedText">and visible on the website</key>
|
||||
<key alias="editBlueprintSavedHeader">Content Template saved</key>
|
||||
@@ -1668,7 +1700,25 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
</key>
|
||||
<key alias="copySuccessMessage">Your system information has successfully been copied to the clipboard</key>
|
||||
<key alias="cannotCopyInformation">Could not copy your system information to the clipboard</key>
|
||||
<key alias="webhookSaved">Webhook saved</key>
|
||||
<key alias="webhookSaved">Webhook saved</key>
|
||||
<key alias="operationSavedHeaderReloadUser">Saved. To view the changes please reload your browser</key>
|
||||
<key alias="editMultiContentPublishedText">%0% documents published and visible on the website</key>
|
||||
<key alias="editVariantPublishedText">%0% published and visible on the website</key>
|
||||
<key alias="editMultiVariantPublishedText">%0% documents published for languages %1% and visible on the website</key>
|
||||
<key alias="editContentScheduledSavedText">A schedule for publishing has been updated</key>
|
||||
<key alias="editVariantSavedText">%0% saved</key>
|
||||
<key alias="editVariantSendToPublishText">%0% changes have been sent for approval</key>
|
||||
<key alias="contentCultureUnpublished">Content variation %0% unpublished</key>
|
||||
<key alias="contentMandatoryCultureUnpublished">The mandatory language '%0%' was unpublished. All languages for this content item are now unpublished.</key>
|
||||
<key alias="contentReqCulturePublishError">Cannot publish the document since the required '%0%' is not published</key>
|
||||
<key alias="contentCultureValidationError">Validation failed for language '%0%'</key>
|
||||
<key alias="scheduleErrReleaseDate1">The release date cannot be in the past</key>
|
||||
<key alias="scheduleErrReleaseDate2">Cannot schedule the document for publishing since the required '%0%' is not published</key>
|
||||
<key alias="scheduleErrReleaseDate3">Cannot schedule the document for publishing since the required '%0%' has a publish date later than a non mandatory language</key>
|
||||
<key alias="scheduleErrExpireDate1">The expire date cannot be in the past</key>
|
||||
<key alias="scheduleErrExpireDate2">The expire date cannot be before the release date</key>
|
||||
<key alias="preventCleanupEnableError">An error occurred while enabling version cleanup for %0%</key>
|
||||
<key alias="preventCleanupDisableError">An error occurred while disabling version cleanup for %0%</key>
|
||||
</area>
|
||||
<area alias="stylesheet">
|
||||
<key alias="addRule">Add style</key>
|
||||
@@ -1932,6 +1982,18 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="historyCleanupPreventCleanup">Prevent cleanup</key>
|
||||
<key alias="historyCleanupEnableCleanup">Enable cleanup</key>
|
||||
<key alias="historyCleanupGloballyDisabled"><![CDATA[<strong>NOTE!</strong> The cleanup of historically content versions are disabled globally. These settings will not take effect before it is enabled.]]></key>
|
||||
<key alias="groupReorderSameAliasError">You can't move the group %0% to this tab because the group will get the same alias as a tab: "%1%". Rename the group to continue.</key>
|
||||
<key alias="searchResultSettings">Available configurations</key>
|
||||
<key alias="searchResultEditors">Create a new configuration</key>
|
||||
<key alias="confirmDeleteTabMessage"><![CDATA[Are you sure you want to delete the tab <strong>%0%</strong>?]]></key>
|
||||
<key alias="confirmDeleteGroupMessage"><![CDATA[Are you sure you want to delete the group <strong>%0%</strong>?]]></key>
|
||||
<key alias="confirmDeletePropertyMessage"><![CDATA[Are you sure you want to delete the property <strong>%0%</strong>?]]></key>
|
||||
<key alias="confirmDeleteTabNotice">This will also delete all items below this tab.</key>
|
||||
<key alias="confirmDeleteGroupNotice">This will also delete all items below this group.</key>
|
||||
<key alias="addTab">Add tab</key>
|
||||
<key alias="convertToTab">Convert to tab</key>
|
||||
<key alias="tabDirectPropertiesDropZone">Drag properties here to place directly on the tab</key>
|
||||
<key alias="changeDataTypeHelpText">Changing a data type with stored values is disabled. To allow this you can change the Umbraco:CMS:DataTypes:CanBeChanged setting in appsettings.json.</key>
|
||||
</area>
|
||||
<area alias="webhooks">
|
||||
<key alias="addWebhook">Create webhook</key>
|
||||
@@ -1967,6 +2029,11 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
</key>
|
||||
<key alias="fallbackLanguage">Fall back language</key>
|
||||
<key alias="none">none</key>
|
||||
<key alias="invariantPropertyUnlockHelp"><![CDATA[<strong>%0%</strong> is shared across languages and segments.]]></key>
|
||||
<key alias="invariantCulturePropertyUnlockHelp"><![CDATA[<strong>%0%</strong> is shared across all languages.]]></key>
|
||||
<key alias="invariantSegmentPropertyUnlockHelp"><![CDATA[<strong>%0%</strong> is shared across all segments.]]></key>
|
||||
<key alias="invariantLanguageProperty">Shared: Languages</key>
|
||||
<key alias="invariantSegmentProperty">Shared: Segments</key>
|
||||
</area>
|
||||
<area alias="macro">
|
||||
<key alias="addParameter">Add parameter</key>
|
||||
@@ -2320,10 +2387,22 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="2faProviderIsDisabledMsg">This two-factor provider is now disabled</key>
|
||||
<key alias="2faProviderIsNotDisabledMsg">Something went wrong with trying to disable this two-factor provider</key>
|
||||
<key alias="2faDisableForUser">Do you want to disable this two-factor provider for this user?</key>
|
||||
<key alias="duplicateLogin">A user with this login already exists</key>
|
||||
<key alias="passwordRequiresDigit">The password must have at least one digit ('0'-'9')</key>
|
||||
<key alias="passwordRequiresLower">The password must have at least one lowercase ('a'-'z')</key>
|
||||
<key alias="passwordRequiresNonAlphanumeric">The password must have at least one non alphanumeric character</key>
|
||||
<key alias="passwordRequiresUniqueChars">The password must use at least %0% different characters</key>
|
||||
<key alias="passwordRequiresUpper">The password must have at least one uppercase ('A'-'Z')</key>
|
||||
<key alias="passwordTooShort">The password must be at least %0% characters long</key>
|
||||
<key alias="allowAccessToAllLanguages">Allow access to all languages</key>
|
||||
<key alias="userHasPassword">The user already has a password set</key>
|
||||
<key alias="userHasGroup">The user is already in group '%0%'</key>
|
||||
<key alias="userLockoutNotEnabled">Lockout is not enabled for this user</key>
|
||||
<key alias="userNotInGroup">The user is not in group '%0%'</key>
|
||||
<key alias="configureTwoFactor">Configure Two-Factor</key>
|
||||
</area>
|
||||
<area alias="validation">
|
||||
<key alias="validation">Validation</key>
|
||||
<key alias="noValidation">No validation</key>
|
||||
<key alias="validateAsEmail">Validate as an email address</key>
|
||||
<key alias="validateAsNumber">Validate as a number</key>
|
||||
<key alias="validateAsUrl">Validate as a URL</key>
|
||||
@@ -2350,6 +2429,14 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
|
||||
<key alias="invalidMemberGroupName">Invalid member group name</key>
|
||||
<key alias="invalidUserGroupName">Invalid user group name</key>
|
||||
<key alias="invalidToken">Invalid token</key>
|
||||
<key alias="invalidUsername">Invalid username</key>
|
||||
<key alias="duplicateEmail">Email '%0%' is already taken</key>
|
||||
<key alias="duplicateUserGroupName">User group name '%0%' is already taken</key>
|
||||
<key alias="duplicateMemberGroupName">Member group name '%0%' is already taken</key>
|
||||
<key alias="duplicateUsername">Username '%0%' is already taken</key>
|
||||
</area>
|
||||
<area alias="healthcheck">
|
||||
<!-- The following keys get these tokens passed in:
|
||||
@@ -2482,6 +2569,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="disableError">Error disabling the URL tracker, more information can be found in your log file.</key>
|
||||
<key alias="enabledConfirm">URL tracker has now been enabled.</key>
|
||||
<key alias="enableError">Error enabling the URL tracker, more information can be found in your log file.</key>
|
||||
<key alias="culture">Culture</key>
|
||||
</area>
|
||||
<area alias="emptyStates">
|
||||
<key alias="emptyDictionaryTree">No Dictionary items to choose from</key>
|
||||
@@ -2530,6 +2618,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="settingsProfiler">Profiling</key>
|
||||
<key alias="memberIntro">Getting Started</key>
|
||||
<key alias="formsInstall">Install Umbraco Forms</key>
|
||||
<key alias="settingsAnalytics">Telemetry data</key>
|
||||
</area>
|
||||
<area alias="visuallyHiddenTexts">
|
||||
<key alias="goBack">Go back</key>
|
||||
@@ -2600,6 +2689,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="unpublishWarning">This item or its descendants is being used. Unpublishing can lead to broken links on your website. Please take the appropriate actions.</key>
|
||||
<key alias="deleteDisabledWarning">This item or its descendants is being used. Therefore, deletion has been disabled.</key>
|
||||
<key alias="listViewDialogWarning">The following items you are trying to %0% are used by other content.</key>
|
||||
<key alias="labelUsedByItems">Referenced by the following items</key>
|
||||
<key alias="labelDependsOnThis">The following items depend on this</key>
|
||||
<key alias="labelDependentDescendants">The following descending items have dependencies</key>
|
||||
</area>
|
||||
<area alias="logViewer">
|
||||
<key alias="deleteSavedSearch">Delete Saved Search</key>
|
||||
@@ -2957,4 +3049,29 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="searchResult">item returned</key>
|
||||
<key alias="searchResults">items returned</key>
|
||||
</area>
|
||||
<area alias="propertyEditorPicker">
|
||||
<key alias="title">Select Property Editor</key>
|
||||
<key alias="openPropertyEditorPicker">Select Property Editor</key>
|
||||
</area>
|
||||
<area alias="analytics">
|
||||
<key alias="consentForAnalytics">Consent for telemetry data</key>
|
||||
<key alias="analyticsLevelSavedSuccess">Telemetry level saved!</key>
|
||||
<key alias="analyticsDescription"><![CDATA[In order to improve Umbraco and add new functionality based on as relevant information as possible,
|
||||
<br>we would like to collect system- and usage information from your installation.
|
||||
<br>Aggregate data will be shared on a regular basis as well as learnings from these metrics.
|
||||
<br>Hopefully, you will help us collect some valuable data.
|
||||
<br>
|
||||
<br>We <strong>WILL NOT</strong> collect any personal data such as content, code, user information, and all data will be fully anonymized.]]></key>
|
||||
<key alias="minimalLevelDescription">We will only send an anonymized site ID to let us know that the site exists.</key>
|
||||
<key alias="basicLevelDescription">We will send an anonymized site ID, Umbraco version, and packages installed</key>
|
||||
<key alias="detailedLevelDescription"><![CDATA[We will send:
|
||||
<ul>
|
||||
<li>Anonymized site ID, Umbraco version, and packages installed.</li>
|
||||
<li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, and Property Editors in use.</li>
|
||||
<li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li>
|
||||
<li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, whether the delivery API is enabled, and allows public access, and if you are in debug mode.</li>
|
||||
</ul>
|
||||
<em>We might change what we send on the Detailed level in the future. If so, it will be listed above.
|
||||
<br>By choosing "Detailed" you agree to current and future anonymized information being collected.</em>]]></key>
|
||||
</area>
|
||||
</language>
|
||||
|
||||
@@ -923,6 +923,7 @@
|
||||
<key alias="lastUpdated">Last Updated</key>
|
||||
<key alias="skipToMenu">Skip to menu</key>
|
||||
<key alias="skipToContent">Skip to content</key>
|
||||
<key alias="primary">Primary</key>
|
||||
</area>
|
||||
<area alias="colors">
|
||||
<key alias="blue">Blue</key>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<key alias="restore" version="7.3.0">Récupérer</key>
|
||||
<key alias="chooseWhereToCopy">Choisissez où copier</key>
|
||||
<key alias="chooseWhereToMove">Choisissez où déplacer</key>
|
||||
<key alias="chooseWhereToImport">Choisissez où importer</key>
|
||||
<key alias="toInTheTreeStructureBelow">dans l'arborescence ci-dessous</key>
|
||||
<key alias="wasMovedTo">a été déplacé vers</key>
|
||||
<key alias="wasCopiedTo">a été copié vers</key>
|
||||
@@ -476,7 +477,14 @@
|
||||
<key alias="variantdeletewarning">Ceci supprimera le noeud et toutes ses langues. Si vous souhaitez supprimer une langue spécifique, vous devriez plutôt supprimer la publication du noeud dans cette langue-là.</key>
|
||||
</area>
|
||||
<area alias="dictionary">
|
||||
<key alias="noItems">Il n'y a pas d'éléments dans le dictionnaire.</key>
|
||||
<key alias="importDictionaryItemHelp">
|
||||
Pour importer un élément de dictionnaire, trouvez le fichier ".udt" sur votre ordinateur en cliquant sur le bouton "Importer" (une confirmation vous sera demandée dans l'écran suivant)
|
||||
</key>
|
||||
<key alias="itemDoesNotExists">L'élément de dictionnaire n'existe pas.</key>
|
||||
<key alias="parentDoesNotExists">L'élément parent n'existe pas.</key>
|
||||
<key alias="noItems">Il n'y a pas d'élément dans le dictionnaire.</key>
|
||||
<key alias="noItemsInFile">Il n'y a pas d'élément de dictionnaire dans ce fichier.</key>
|
||||
<key alias="createNew">Créer un élément de dictionnaire</key>
|
||||
</area>
|
||||
<area alias="dictionaryItem">
|
||||
<key alias="description"><![CDATA[
|
||||
@@ -690,6 +698,7 @@
|
||||
<key alias="pleasewait">Un moment s'il vous plaît...</key>
|
||||
<key alias="previous">Précédent</key>
|
||||
<key alias="properties">Propriétés</key>
|
||||
<key alias="readMore">En savoir plus</key>
|
||||
<key alias="rebuild">Reconstruire</key>
|
||||
<key alias="reciept">Email de réception des données de formulaire</key>
|
||||
<key alias="recycleBin">Corbeille</key>
|
||||
@@ -709,6 +718,7 @@
|
||||
<key alias="noItemsInList">Aucun élément n'a été ajouté</key>
|
||||
<key alias="server">Serveur</key>
|
||||
<key alias="settings">Paramètres</key>
|
||||
<key alias="shared">Partagé</key>
|
||||
<key alias="show">Montrer</key>
|
||||
<key alias="showPageOnSend">Afficher la page à l'envoi</key>
|
||||
<key alias="size">Taille</key>
|
||||
@@ -1263,6 +1273,10 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à
|
||||
<key alias="rollbackTo">Revenir à</key>
|
||||
<key alias="selectVersion">Choisissez une version</key>
|
||||
<key alias="view">Voir</key>
|
||||
<key alias="pagination"><![CDATA[Afficher les versions %0% à %1% des %2% versions]]></key>
|
||||
<key alias="versions">Versions</key>
|
||||
<key alias="currentDraftVersion">Version de travail</key>
|
||||
<key alias="currentPublishedVersion">Version publiée</key>
|
||||
</area>
|
||||
<area alias="scripts">
|
||||
<key alias="editscript">Editer le fichier de script</key>
|
||||
@@ -1388,6 +1402,9 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à
|
||||
<key alias="contentCultureValidationError">La validation a échoué pour la langue '%0%'</key>
|
||||
<key alias="documentTypeExportedSuccess">Le Type de Document a été exporté dans le fichier</key>
|
||||
<key alias="documentTypeExportedError">Une erreur est survenue durant l'export du type de document</key>
|
||||
<key alias="dictionaryItemExportedSuccess">Les éléments de dictionnaire ont été exportés vers le fichier</key>
|
||||
<key alias="dictionaryItemExportedError">Une erreur est survenue lors de l'export des éléments de dictionnaire.</key>
|
||||
<key alias="dictionaryItemImported">Les éléments de dictionnaire suivants ont été importés!</key>
|
||||
<key alias="scheduleErrReleaseDate1">La date de publication ne peut pas être dans le passé</key>
|
||||
<key alias="scheduleErrReleaseDate2">Impossible de planifier la publication du document car la langue obligatoire '%0%' n'est pas publiée</key>
|
||||
<key alias="scheduleErrReleaseDate3">Impossible de planifier la publication du document car la langue obligatoire '%0%' a une date de publication postérieure à celle d'une langue non obligatoire</key>
|
||||
@@ -1584,6 +1601,13 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à
|
||||
<key alias="elementDescription">Un Type d'Elément est destiné à être utilisé par exemple dans Nested Content, et pas dans l'arborescence.</key>
|
||||
<key alias="elementDoesNotSupport">Ceci n'est pas d'application pour un Type d'Elément</key>
|
||||
<key alias="propertyHasChanges">Vous avez apporté des modifications à cette propriété. Etes-vous certain.e de vouloir les annuler?</key>
|
||||
<key alias="historyCleanupHeading">Nettoyer l'historique</key>
|
||||
<key alias="historyCleanupDescription">Autoriser le remplacement des paramètres globaux de nettoyage de l'historique.</key>
|
||||
<key alias="historyCleanupKeepAllVersionsNewerThanDays">Garder toutes les versions plus récentes que jours</key>
|
||||
<key alias="historyCleanupKeepLatestVersionPerDayForDays">Garder la dernière version quotidienne pendant jours</key>
|
||||
<key alias="historyCleanupPreventCleanup">Empêcher le nettoyage</key>
|
||||
<key alias="historyCleanupEnableCleanup">Activer le nettoyage</key>
|
||||
<key alias="historyCleanupGloballyDisabled"><![CDATA[<strong>REMARQUE!</strong> Le nettoyage de l'historique des versions de contenu est désactvé globalement. Ces paramètres ne prendront pas effet avant qu'il ne soit activé.]]></key>
|
||||
</area>
|
||||
<area alias="webhooks">
|
||||
<key alias="addWebhook">Créer un webhook</key>
|
||||
@@ -2100,6 +2124,9 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à
|
||||
<key alias="newPartialViewMacro">Macro de Partial View</key>
|
||||
<key alias="newMember">Membre</key>
|
||||
<key alias="searchContentTree">Chercher dans l'arborescence de contenu</key>
|
||||
<key alias="maxAmount">Quantité maximum</key>
|
||||
<key alias="expandChildItems">Afficher les éléments enfant pour</key>
|
||||
<key alias="openContextNode">Ouvrir le noeud de contexte pour</key>
|
||||
</area>
|
||||
<area alias="references">
|
||||
<key alias="tabName">Références</key>
|
||||
|
||||
@@ -322,19 +322,19 @@
|
||||
</area>
|
||||
<area alias="create">
|
||||
<key alias="chooseNode">Yeni %0% 'i nerede oluşturmak istiyorsunuz</key>
|
||||
<key alias="createUnder">Altında bir öğe oluşturun</key>
|
||||
<key alias="createUnder">Şu dizin altında bir öğe oluşturun: </key>
|
||||
<key alias="createContentBlueprint">İçerik şablonu yapmak istediğiniz belge türünü seçin</key>
|
||||
<key alias="enterFolderName">Bir klasör adı girin</key>
|
||||
<key alias="updateData">Bir tür ve başlık seçin</key>
|
||||
<key alias="noDocumentTypes" version="7.0"><![CDATA[Burada içerik oluşturmak için izin verilen doküman türü yok. Bunları, <strong>İzinler</strong> altında <strong>İzin verilen alt düğüm türlerini</strong> düzenleyerek <strong>Ayarlar</strong> bölümündeki <strong>Belge Türleri</strong> 'nde etkinleştirmelisiniz.]]></key>
|
||||
<key alias="noDocumentTypesAtRoot"><![CDATA[Burada içerik oluşturmak için uygun belge türü yok. Bunları <strong>Ayarlar</strong> bölümündeki <strong>Belge Türleri</strong> 'nde oluşturmanız gerekir.]]></key>
|
||||
<key alias="noDocumentTypesAtRoot"><![CDATA[Burada içerik oluşturmak için uygun bir belge türü yok. Öncelikle belge türlerini <strong>Ayarlar</strong> bölümündeki <strong>Belge Türleri</strong> 'nde oluşturmanız gerekiyor.]]></key>
|
||||
<key alias="noDocumentTypesWithNoSettingsAccess">İçerik ağacında seçilen sayfa, altında herhangi bir sayfanın oluşturulmasına izin vermiyor.</key>
|
||||
<key alias="noDocumentTypesEditPermissions">Bu belge türü için izinleri düzenleyin</key>
|
||||
<key alias="noDocumentTypesEditPermissions">Şu belge türü için izinleri düzenleyin</key>
|
||||
<key alias="noDocumentTypesCreateNew">Yeni bir belge türü oluşturun</key>
|
||||
<key alias="noDocumentTypesAllowedAtRoot"><![CDATA[Burada içerik oluşturmak için izin verilen belge türü yok. Bunları, <strong>İzinler</strong> altında <strong>Kök olarak izin ver</strong> seçeneğini değiştirerek, <strong>Ayarlar</strong> bölümündeki <strong>Belge Türleri</strong> 'nde etkinleştirmeniz gerekir. ]]></key>
|
||||
<key alias="noMediaTypes" version="7.0"><![CDATA[Burada medya oluşturmak için izin verilen medya türü yok. Bunları, <strong>İzinler</strong> altında <strong>İzin verilen alt düğüm türlerini</strong> düzenleyerek <strong>Ayarlar</strong> bölümündeki <strong>Medya Türleri</strong> 'nde etkinleştirmelisiniz. .]]></key>
|
||||
<key alias="noMediaTypesWithNoSettingsAccess">Ağaçtaki seçili ortam, altında başka bir ortamın oluşturulmasına izin vermiyor.</key>
|
||||
<key alias="noMediaTypesEditPermissions">Bu medya türü için izinleri düzenleyin</key>
|
||||
<key alias="noMediaTypesEditPermissions">Şu medya türü için izinleri düzenleyin:</key>
|
||||
<key alias="documentTypeWithoutTemplate">Şablonsuz Belge Türü</key>
|
||||
<key alias="newFolder">Yeni klasör</key>
|
||||
<key alias="newDataType">Yeni veri türü</key>
|
||||
@@ -360,11 +360,11 @@
|
||||
<key alias="stay">Kal</key>
|
||||
<key alias="discardChanges">Değişiklikleri sil</key>
|
||||
<key alias="unsavedChanges">Kaydedilmemiş değişiklikleriniz var</key>
|
||||
<key alias="unsavedChangesWarning">Bu sayfadan ayrılmak istediğinizden emin misiniz? - kaydedilmemiş değişiklikleriniz var</key>
|
||||
<key alias="unsavedChangesWarning">Bu sayfadan ayrılmak istediğinizden emin misiniz? - Kaydedilmemiş değişiklikleriniz var</key>
|
||||
<key alias="confirmListViewPublish">Yayınlama, seçili öğelerin sitede görünür olmasını sağlar.</key>
|
||||
<key alias="confirmListViewUnpublish">Yayından kaldırıldığında, seçili öğeler ve bunların tüm alt öğeleri siteden kaldırılır.</key>
|
||||
<key alias="confirmUnpublish">Yayından kaldırıldığında bu sayfa ve tüm soyundan gelenler siteden kaldırılır.</key>
|
||||
<key alias="doctypeChangeWarning">Kaydedilmemiş değişiklikleriniz var. Belge Türünde değişiklik yapmak değişiklikleri geçersiz kılacaktır.</key>
|
||||
<key alias="confirmListViewUnpublish">Yayından kaldırma, seçili öğeler ile birlikte bu öğelerin tüm alt öğelerini de siteden kaldırır.</key>
|
||||
<key alias="confirmUnpublish">Yayından kaldırma, bu seçili öğe ile birlikte onun tüm alt öğelerini de siteden kaldırır.</key>
|
||||
<key alias="doctypeChangeWarning">Kaydedilmemiş değişiklikleriniz var. Belge Türü'nde değişiklik yapmak bu kaydedilmemiş değişikliklerinizi geçersiz kılacaktır.</key>
|
||||
</area>
|
||||
<area alias="bulk">
|
||||
<key alias="done">Bitti</key>
|
||||
@@ -428,7 +428,7 @@
|
||||
<key alias="permissionsHelp">İzinlerini ayarlamak istediğiniz kullanıcı gruplarını seçin</key>
|
||||
<key alias="recycleBinDeleting">Geri dönüşüm kutusundaki öğeler artık siliniyor. Lütfen bu işlem yapılırken bu pencereyi kapatmayın</key>
|
||||
<key alias="recycleBinIsEmpty">Geri dönüşüm kutusu artık boş</key>
|
||||
<key alias="recycleBinWarning">Öğeler geri dönüşüm kutusundan silindiğinde sonsuza kadar kaybolacaklar</key>
|
||||
<key alias="recycleBinWarning">Öğeler geri dönüşüm kutusundan silindiğinde bir daha geri gelmeyecek şekilde ortadan kaybolacaklar</key>
|
||||
<key alias="regexSearchError"><![CDATA[<a target='_ blank' href='http: //regexlib.com'> regexlib.com </a> 'un web hizmeti şu anda bazı sorunlar yaşıyor ve biz üzerinde kontrol yok. Bu rahatsızlıktan dolayı çok üzgünüz.]]></key>
|
||||
<key alias="regexSearchHelp">Bir form alanına doğrulama eklemek için normal ifade arayın. Örnek: 'e-posta,'posta kodu','URL'</key>
|
||||
<key alias="removeMacro">Makroyu Kaldır</key>
|
||||
@@ -608,15 +608,16 @@
|
||||
<key alias="propertyHasErrors">Bu özellik geçersiz</key>
|
||||
</area>
|
||||
<area alias="general">
|
||||
<key alias="options">Seçenekler</key>
|
||||
<key alias="about">Hakkında</key>
|
||||
<key alias="action">İşlem</key>
|
||||
<key alias="actions">İşlemler</key>
|
||||
<key alias="add">Ekle</key>
|
||||
<key alias="alias">Takma reklam</key>
|
||||
<key alias="alias">Takma isim</key>
|
||||
<key alias="all">Tümü</key>
|
||||
<key alias="areyousure">Emin misiniz?</key>
|
||||
<key alias="back">Geri</key>
|
||||
<key alias="backToOverview">Genel bakışa dön</key>
|
||||
<key alias="backToOverview">Gözden geçirmeye geri dön</key>
|
||||
<key alias="border">Kenarlık</key>
|
||||
<key alias="cancel">İptal</key>
|
||||
<key alias="cellMargin">Hücre kenar boşluğu</key>
|
||||
@@ -624,14 +625,16 @@
|
||||
<key alias="clear">Temizle</key>
|
||||
<key alias="close">Kapat</key>
|
||||
<key alias="closewindow">Pencereyi Kapat</key>
|
||||
<key alias="closepane">Pencereyi Kapat</key>
|
||||
<key alias="comment">Yorum</key>
|
||||
<key alias="confirm">Onayla</key>
|
||||
<key alias="confirm">Doğrula</key>
|
||||
<key alias="constrain">Sınırla</key>
|
||||
<key alias="constrainProportions">Oranları sınırlayın</key>
|
||||
<key alias="content">İçerik</key>
|
||||
<key alias="continue">Devam et</key>
|
||||
<key alias="copy">Kopyala</key>
|
||||
<key alias="create">Oluştur</key>
|
||||
<key alias="cropSection">Bölümü kırp</key>
|
||||
<key alias="database">Veritabanı</key>
|
||||
<key alias="date">Tarih</key>
|
||||
<key alias="default">Varsayılan</key>
|
||||
@@ -641,7 +644,7 @@
|
||||
<key alias="design">Tasarım</key>
|
||||
<key alias="dictionary">Sözlük</key>
|
||||
<key alias="dimensions">Boyutlar</key>
|
||||
<key alias="discard">Sil</key>
|
||||
<key alias="discard">Gözden çıkar</key>
|
||||
<key alias="down">Aşağı</key>
|
||||
<key alias="download">İndir</key>
|
||||
<key alias="edit">Düzenle</key>
|
||||
@@ -654,6 +657,7 @@
|
||||
<key alias="first">İlk</key>
|
||||
<key alias="focalPoint">Odak noktası</key>
|
||||
<key alias="general">Genel</key>
|
||||
<key alias="generic">Genel</key>
|
||||
<key alias="groups">Gruplar</key>
|
||||
<key alias="group">Grup</key>
|
||||
<key alias="height">Yükseklik</key>
|
||||
@@ -661,7 +665,7 @@
|
||||
<key alias="hide">Gizle</key>
|
||||
<key alias="history">Geçmiş</key>
|
||||
<key alias="icon">Simge</key>
|
||||
<key alias="id">Kimlik</key>
|
||||
<key alias="id">Belirleyici Numara</key>
|
||||
<key alias="import">İçe Aktar</key>
|
||||
<key alias="excludeFromSubFolders">Yalnızca bu klasörü ara</key>
|
||||
<key alias="info">Bilgi</key>
|
||||
@@ -692,7 +696,6 @@
|
||||
<key alias="off">Kapalı</key>
|
||||
<key alias="ok">Tamam</key>
|
||||
<key alias="open">Aç</key>
|
||||
<key alias="options">Seçenekler</key>
|
||||
<key alias="on">Açık</key>
|
||||
<key alias="or">veya</key>
|
||||
<key alias="orderBy">Sıralama ölçütü</key>
|
||||
@@ -854,12 +857,12 @@
|
||||
Umbraco'yu çalıştırmaya ve paketleri kurmaya hazırsınız!]]>
|
||||
</key>
|
||||
<key alias="permissionsResolveFolderIssues">Klasör sorununu çözme</key>
|
||||
<key alias="permissionsResolveFolderIssuesLink">ASP.NET ile ilgili sorunlar ve klasör oluşturma hakkında daha fazla bilgi için bu bağlantıyı izleyin</key>
|
||||
<key alias="permissionsResolveFolderIssuesLink">ASP.NET ile ilgili sorunlar ve klasör oluşturma hakkında daha fazla bilgi için bu bağlantıyı takip edin</key>
|
||||
<key alias="permissionsSettingUpPermissions">Klasör izinlerini ayarlama</key>
|
||||
<key alias="permissionsText">
|
||||
<![CDATA[
|
||||
Resimler ve PDF'ler gibi dosyaları depolamak için Umbraco'nun belirli dizinlere yazma / değiştirme erişimine ihtiyacı vardır.
|
||||
Ayrıca, web sitenizin performansını artırmak için geçici verileri (aka: önbellek) depolar.
|
||||
Ayrıca, web sitenizin performansını artırmak için geçici verileri önbellekte tutar.
|
||||
]]>
|
||||
</key>
|
||||
<key alias="runwayFromScratch">Sıfırdan başlamak istiyorum</key>
|
||||
@@ -895,12 +898,12 @@
|
||||
]]>
|
||||
</key>
|
||||
<key alias="runwayWhatIsRunway">Runway Nedir</key>
|
||||
<key alias="step1">Adım 1/5 Lisansı kabul edin</key>
|
||||
<key alias="step1">Adım 1/5 Lisansın kabulü</key>
|
||||
<key alias="step2">Adım 2/5: Veritabanı yapılandırması</key>
|
||||
<key alias="step3">Adım 3/5: Dosya İzinlerini Doğrulama</key>
|
||||
<key alias="step4">4. Adım: Umbraco güvenliğini kontrol edin</key>
|
||||
<key alias="step5">Adım 5/5: Umbraco başlamanıza hazır</key>
|
||||
<key alias="thankYou">Umbraco'yu seçtiğiniz için teşekkür ederiz</key>
|
||||
<key alias="step3">Adım 3/5: Dosya izinlerini doğrulama</key>
|
||||
<key alias="step4">Adım 4/5: Umbraco güvenliğinin kontrolü</key>
|
||||
<key alias="step5">Adım 5/5: Umbraco, üzerinde çalışmaya başlamanız için hazır</key>
|
||||
<key alias="thankYou">Umbraco'yu seçtiğiniz için teşekkürler</key>
|
||||
<key alias="theEndBrowseSite">
|
||||
<![CDATA[<h3> Yeni sitenize göz atın </h3>
|
||||
Runway'i kurdunuz, öyleyse neden yeni web sitenizin nasıl göründüğüne bakmıyorsunuz.]]>
|
||||
@@ -933,7 +936,7 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey
|
||||
<key alias="displayName">Kültür Adı</key>
|
||||
</area>
|
||||
<area alias="lockout">
|
||||
<key alias="lockoutWillOccur">Boştaydınız ve çıkış otomatik olarak içinde gerçekleşecek</key>
|
||||
<key alias="lockoutWillOccur">Herhangi bir işlem yapmadınız ve sistemden çıkış otomatik olarak şu belirtilen süre içinde gerçekleşecek</key>
|
||||
<key alias="renewSession">Çalışmanızı kaydetmek için şimdi yenileyin</key>
|
||||
</area>
|
||||
<area alias="login">
|
||||
@@ -949,15 +952,15 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey
|
||||
<key alias="timeout">Oturum zaman aşımına uğradı</key>
|
||||
<key alias="bottomText"><![CDATA[<p style="text-align: right;">© 2015 - %0% <br /> <a href="https://umbraco.com" style="text-decoration: none" target="_blank" rel="noopener">Umbraco.com </a> </p>]]></key>
|
||||
<key alias="forgottenPassword">Şifrenizi mi unuttunuz?</key>
|
||||
<key alias="forgottenPasswordInstruction">Parolanızı sıfırlamak için bir bağlantıyla belirtilen adrese bir e-posta gönderilecektir</key>
|
||||
<key alias="requestPasswordResetConfirmation">Kayıtlarımızla eşleşirse, şifre sıfırlama talimatlarını içeren bir e-posta, belirtilen adrese gönderilecektir</key>
|
||||
<key alias="forgottenPasswordInstruction">Şifrenizi yenilemeniz için belirtilen adrese bir bağlantı bilgisi gönderilecektir</key>
|
||||
<key alias="requestPasswordResetConfirmation">Eğer kayıtlarımızla eşleşirse, şifre yenileme talimatlarını içeren bir e-posta belirtilen adrese gönderilecektir</key>
|
||||
<key alias="showPassword">Şifreyi göster</key>
|
||||
<key alias="hidePassword">Şifreyi gizle</key>
|
||||
<key alias="returnToLogin">Giriş formuna dön</key>
|
||||
<key alias="setPasswordInstruction">Lütfen yeni bir şifre girin</key>
|
||||
<key alias="setPasswordConfirmation">Şifreniz güncellendi</key>
|
||||
<key alias="resetCodeExpired">Tıkladığınız bağlantı geçersiz veya süresi dolmuş</key>
|
||||
<key alias="resetPasswordEmailCopySubject">Umbraco: Şifreyi Sıfırla</key>
|
||||
<key alias="resetPasswordEmailCopySubject">Umbraco: Şifreyi Yenile</key>
|
||||
<key alias="resetPasswordEmailCopyFormat">
|
||||
<![CDATA[
|
||||
<html>
|
||||
@@ -996,7 +999,7 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey
|
||||
<tr>
|
||||
<td style='line-height: 24px; font-family: sans-serif; font-size: 14px; vertical-align: top;' valign='top'>
|
||||
<h1 style='color: #392F54; font-family: sans-serif; font-weight: bold; line-height: 1.4; font-size: 24px; text-align: left; text-transform: capitalize; margin: 0 0 30px;' align='left'>
|
||||
Şifre sıfırlama istendi
|
||||
Şifre yenileme istendi
|
||||
</h1>
|
||||
<p style='color: #392F54; font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0 0 15px;'>
|
||||
Umbraco arka ofisinde oturum açmak için kullanıcı adınız: <strong>%0%</strong>
|
||||
@@ -1007,7 +1010,7 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey
|
||||
<tr>
|
||||
<td style='font-family: sans-serif; font-size: 14px; vertical-align: top; border-radius: 5px; text-align: center; background: #35C786;' align='center' bgcolor='#35C786' valign='top'>
|
||||
<a href='%1%' target='_blank' rel='noopener' style='color: #FFFFFF; text-decoration: none; -ms-word-break: break-all; word-break: break-all; border-radius: 5px; box-sizing: border-box; cursor: pointer; display: inline-block; font-size: 14px; font-weight: bold; text-transform: capitalize; background: #35C786; margin: 0; padding: 12px 30px; border: 1px solid #35c786;'>
|
||||
Şifrenizi sıfırlamak için bu bağlantıyı tıklayın
|
||||
Şifrenizi yenilemek için bu bağlantıyı tıklayın
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -1041,38 +1044,46 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey
|
||||
</html>
|
||||
]]>
|
||||
</key>
|
||||
<key alias="mfaSecurityCodeSubject">Umbraco: Güvenlik Kodu</key>
|
||||
<key alias="mfaSecurityCodeMessage">Güvenlik kodunuz: %0%</key>
|
||||
<key alias="2faTitle">Son adım</key>
|
||||
<key alias="2faText">2-aşamalı kimlik doğrulamayı etkinleştirdiniz ve kimliğinizi doğrulamanız gerekmekte.</key>
|
||||
<key alias="2faMultipleText">Lütfen bir tane 2-aşamalı kimlik doğrulama hizmet sağlayıcı seçin</key>
|
||||
<key alias="2faCodeInput">Doğrulama kodu</key>
|
||||
<key alias="2faCodeInputHelp">Lütfen doğrulama kodunu girin</key>
|
||||
<key alias="2faInvalidCode">Geçersiz kod girildi</key>
|
||||
</area>
|
||||
<area alias="main">
|
||||
<key alias="dashboard">Dashboard</key>
|
||||
<key alias="dashboard">Gösterge Paneli</key>
|
||||
<key alias="sections">Bölümler</key>
|
||||
<key alias="tree">İçerik</key>
|
||||
</area>
|
||||
<area alias="moveOrCopy">
|
||||
<key alias="choose">Yukarıdaki sayfayı seçin ...</key>
|
||||
<key alias="copyDone">%0%,%1% konumuna kopyalandı</key>
|
||||
<key alias="copyTo">%0% belgesinin aşağıya kopyalanacağı yeri seçin</key>
|
||||
<key alias="copyTo">%0% belgesinin kopyalanacağı yeri aşağıdan seçin</key>
|
||||
<key alias="moveDone">%0%,%1% konumuna taşındı</key>
|
||||
<key alias="moveTo">%0% belgesinin aşağıya taşınacağı yeri seçin</key>
|
||||
<key alias="moveTo">%0% belgesinin taşınacağı yeri aşağıdan seçin</key>
|
||||
<key alias="nodeSelected">, yeni içeriğinizin kökü olarak seçildi, aşağıdaki 'tamam'ı tıklayın.</key>
|
||||
<key alias="noNodeSelected">Henüz düğüm seçilmedi, lütfen 'tamam'ı tıklamadan önce yukarıdaki listeden bir düğüm seçin</key>
|
||||
<key alias="notAllowedByContentType">Geçerli düğüme, türü nedeniyle seçilen düğüm altında izin verilmiyor</key>
|
||||
<key alias="notAllowedByContentType">Geçerli düğüme, türü nedeniyle, seçilen düğüm altında izin verilmiyor</key>
|
||||
<key alias="notAllowedByPath">Geçerli düğüm, alt sayfalarından birine taşınamaz</key>
|
||||
<key alias="notAllowedAtRoot">Geçerli düğüm kökte bulunamaz</key>
|
||||
<key alias="notAllowedAtRoot">Geçerli düğüm kök düğüm seviyesinde bulunamaz</key>
|
||||
<key alias="notValid">1 veya daha fazla alt belge üzerinde yetersiz izniniz olduğundan işleme izin verilmiyor.</key>
|
||||
<key alias="relateToOriginal">Kopyalanan öğeleri orijinalle ilişkilendir</key>
|
||||
<key alias="relateToOriginal">Kopyalanan öğeleri asıl öğe ile ilişkilendir</key>
|
||||
</area>
|
||||
<area alias="notifications">
|
||||
<key alias="editNotifications"><![CDATA[<strong>%0%</strong>]]> için bildiriminizi seçin</key>
|
||||
<key alias="notificationsSavedFor">için bildirim ayarları kaydedildi</key>
|
||||
<key alias="notificationsSavedFor">Bildirim ayarları bu belirtilen öğe için kaydedildi</key>
|
||||
<key alias="mailBody">
|
||||
<![CDATA[
|
||||
Merhaba %0%
|
||||
|
||||
Bu, '%1%' görevinin size bildirilmesi için otomatik bir postadır.
|
||||
'%2%' sayfasında gerçekleştirildi
|
||||
'%3%' kullanıcısı tarafından
|
||||
Bu, '%1%' görevinin '%2%' sayfasında '%3%' kullanıcısı tarafından
|
||||
gerçekleştirildiğini bildiren
|
||||
otomatik bir e-postadır.
|
||||
|
||||
Düzenlemek için http://%4%/#/content/content/edit/%5% adresine gidin.
|
||||
Düzenleme yapmak için http://%4%/#/content/content/edit/%5% adresine gidiniz.
|
||||
|
||||
%6%
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Web;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
@@ -89,7 +90,7 @@ public class ContentFinderByRedirectUrl : IContentFinder
|
||||
}
|
||||
|
||||
frequest
|
||||
.SetRedirectPermanent(url)
|
||||
.SetRedirectPermanent(HttpUtility.UrlPathEncode(url))
|
||||
|
||||
// From: http://stackoverflow.com/a/22468386/5018
|
||||
// See http://issues.umbraco.org/issue/U4-8361#comment=67-30532
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core;
|
||||
|
||||
@@ -6,7 +6,7 @@ public static class UriUtilityCore
|
||||
{
|
||||
#region Uri string utilities
|
||||
|
||||
public static bool HasScheme(string uri) => uri.IndexOf("://") > 0;
|
||||
public static bool HasScheme(string uri) => uri.IndexOf("://", StringComparison.InvariantCulture) > 0;
|
||||
|
||||
public static string StartWithScheme(string uri) => StartWithScheme(uri, null);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Examine;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
@@ -8,7 +10,6 @@ using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Examine;
|
||||
|
||||
@@ -28,6 +29,7 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
private readonly IUserService _userService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly ILogger<ContentValueSetBuilder> _logger;
|
||||
|
||||
public ContentValueSetBuilder(
|
||||
PropertyEditorCollection propertyEditors,
|
||||
@@ -37,7 +39,8 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
ICoreScopeProvider scopeProvider,
|
||||
bool publishedValuesOnly,
|
||||
ILocalizationService localizationService,
|
||||
IContentTypeService contentTypeService)
|
||||
IContentTypeService contentTypeService,
|
||||
ILogger<ContentValueSetBuilder> logger)
|
||||
: base(propertyEditors, publishedValuesOnly)
|
||||
{
|
||||
_urlSegmentProviders = urlSegmentProviders;
|
||||
@@ -46,6 +49,30 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
_scopeProvider = scopeProvider;
|
||||
_localizationService = localizationService;
|
||||
_contentTypeService = contentTypeService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
|
||||
public ContentValueSetBuilder(
|
||||
PropertyEditorCollection propertyEditors,
|
||||
UrlSegmentProviderCollection urlSegmentProviders,
|
||||
IUserService userService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
ICoreScopeProvider scopeProvider,
|
||||
bool publishedValuesOnly,
|
||||
ILocalizationService localizationService,
|
||||
IContentTypeService contentTypeService)
|
||||
: this(
|
||||
propertyEditors,
|
||||
urlSegmentProviders,
|
||||
userService,
|
||||
shortStringHelper,
|
||||
scopeProvider,
|
||||
publishedValuesOnly,
|
||||
localizationService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILogger<ContentValueSetBuilder>>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
|
||||
@@ -65,9 +92,9 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
scopeProvider,
|
||||
publishedValuesOnly,
|
||||
localizationService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>())
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILogger<ContentValueSetBuilder>>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
|
||||
@@ -86,7 +113,8 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
scopeProvider,
|
||||
publishedValuesOnly,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>())
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILogger<ContentValueSetBuilder>>())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -190,13 +218,34 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
{
|
||||
if (!property.PropertyType.VariesByCulture())
|
||||
{
|
||||
AddPropertyValue(property, null, null, values, availableCultures, contentTypeDictionary);
|
||||
try
|
||||
{
|
||||
AddPropertyValue(property, null, null, values, availableCultures, contentTypeDictionary);
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to add property '{PropertyAlias}' to index for content {ContentId}", property.Alias, c.Id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var culture in c.AvailableCultures)
|
||||
{
|
||||
AddPropertyValue(property, culture.ToLowerInvariant(), null, values, availableCultures, contentTypeDictionary);
|
||||
try
|
||||
{
|
||||
AddPropertyValue(property, culture.ToLowerInvariant(), null, values, availableCultures, contentTypeDictionary);
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Failed to add property '{PropertyAlias}' to index for content {ContentId} in culture {Culture}",
|
||||
property.Alias,
|
||||
c.Id,
|
||||
culture);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,9 @@ public class MemberPasswordHasher : UmbracoPasswordHasher<MemberIdentityUser>
|
||||
switch (algorithmName)
|
||||
{
|
||||
case "AES":
|
||||
algorithm = new AesCryptoServiceProvider { Key = StringToByteArray(decryptionKey), IV = new byte[16] };
|
||||
algorithm = Aes.Create();
|
||||
algorithm.Key = StringToByteArray(decryptionKey);
|
||||
algorithm.IV = new byte[16];
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"The algorithm ({algorithmName}) is not supported");
|
||||
|
||||
@@ -58,11 +58,13 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
|
||||
private long _domainGen;
|
||||
private SnapDictionary<int, Domain> _domainStore = null!;
|
||||
private IAppCache? _elementsCache;
|
||||
private bool _isReadSet;
|
||||
|
||||
private bool _isReadSet;
|
||||
private bool _isReady;
|
||||
private object? _isReadyLock;
|
||||
|
||||
private bool _mainDomRegistered;
|
||||
|
||||
private BPlusTree<int, ContentNodeKit>? _localContentDb;
|
||||
private bool _localContentDbExists;
|
||||
private BPlusTree<int, ContentNodeKit>? _localMediaDb;
|
||||
@@ -511,9 +513,20 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
|
||||
// it will not be able to close the stores until we are done populating (if the store is empty)
|
||||
lock (_storesLock)
|
||||
{
|
||||
SyncBootState bootState = _syncBootStateAccessor.GetSyncBootState();
|
||||
|
||||
if (!_options.IgnoreLocalDb)
|
||||
{
|
||||
_mainDom.Register(MainDomRegister, MainDomRelease);
|
||||
if (!_mainDomRegistered)
|
||||
{
|
||||
_mainDom.Register(MainDomRegister, MainDomRelease);
|
||||
}
|
||||
else
|
||||
{
|
||||
// MainDom is already registered, so we must be retrying to load cache data
|
||||
// We can't trust the localdb state, so always perform a cold boot
|
||||
bootState = SyncBootState.ColdBoot;
|
||||
}
|
||||
|
||||
// stores are created with a db so they can write to it, but they do not read from it,
|
||||
// stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to
|
||||
@@ -562,8 +575,6 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
|
||||
var okContent = false;
|
||||
var okMedia = false;
|
||||
|
||||
SyncBootState bootState = _syncBootStateAccessor.GetSyncBootState();
|
||||
|
||||
try
|
||||
{
|
||||
if (bootState != SyncBootState.ColdBoot && _localContentDbExists)
|
||||
@@ -635,6 +646,8 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
|
||||
_localContentDb = BTree.GetTree(localContentDbPath, _localContentDbExists, _config, _contentDataSerializer);
|
||||
_localMediaDb = BTree.GetTree(localMediaDbPath, _localMediaDbExists, _config, _contentDataSerializer);
|
||||
|
||||
_mainDomRegistered = true;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Registered with MainDom, localContentDbExists? {LocalContentDbExists}, localMediaDbExists? {LocalMediaDbExists}",
|
||||
_localContentDbExists,
|
||||
@@ -720,21 +733,10 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
|
||||
_localContentDb?.Clear();
|
||||
|
||||
// IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder
|
||||
try
|
||||
{
|
||||
IEnumerable<ContentNodeKit> kits = _publishedContentService.GetAllContentSources();
|
||||
return onStartup
|
||||
? _contentStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true)
|
||||
: _contentStore.SetAllLocked(kits, _config.KitBatchSize, true);
|
||||
}
|
||||
catch (ThreadAbortException tae)
|
||||
{
|
||||
// Caught a ThreadAbortException, most likely from a database timeout.
|
||||
// If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment).
|
||||
_logger.LogWarning(tae, tae.Message);
|
||||
}
|
||||
|
||||
return false;
|
||||
IEnumerable<ContentNodeKit> kits = _publishedContentService.GetAllContentSources();
|
||||
return onStartup
|
||||
? _contentStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true)
|
||||
: _contentStore.SetAllLocked(kits, _config.KitBatchSize, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,21 +785,10 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
|
||||
}
|
||||
|
||||
// IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder
|
||||
try
|
||||
{
|
||||
IEnumerable<ContentNodeKit> kits = _publishedContentService.GetAllMediaSources();
|
||||
return onStartup
|
||||
? _mediaStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true)
|
||||
: _mediaStore.SetAllLocked(kits, _config.KitBatchSize, true);
|
||||
}
|
||||
catch (ThreadAbortException tae)
|
||||
{
|
||||
// Caught a ThreadAbortException, most likely from a database timeout.
|
||||
// If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment).
|
||||
_logger.LogWarning(tae, tae.Message);
|
||||
}
|
||||
|
||||
return false;
|
||||
IEnumerable<ContentNodeKit> kits = _publishedContentService.GetAllMediaSources();
|
||||
return onStartup
|
||||
? _mediaStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true)
|
||||
: _mediaStore.SetAllLocked(kits, _config.KitBatchSize, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -376,7 +376,10 @@ public class MemberController : ContentControllerBase
|
||||
}
|
||||
|
||||
// map the custom properties - this will already be set for new entities in our member binder
|
||||
contentItem.PersistedContent.IsApproved = contentItem.IsApproved;
|
||||
if (_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() ?? false)
|
||||
{
|
||||
contentItem.PersistedContent.IsApproved = contentItem.IsApproved;
|
||||
}
|
||||
contentItem.PersistedContent.Email = contentItem.Email.Trim();
|
||||
contentItem.PersistedContent.Username = contentItem.Username;
|
||||
}
|
||||
@@ -548,6 +551,13 @@ public class MemberController : ContentControllerBase
|
||||
}
|
||||
}
|
||||
}
|
||||
//thoese properties defaulting to sensitive, change the value of the contentItem model to the persisted value
|
||||
if (contentItem.PersistedContent is not null)
|
||||
{
|
||||
contentItem.IsApproved = contentItem.PersistedContent.IsApproved;
|
||||
contentItem.IsLockedOut = contentItem.PersistedContent.IsLockedOut;
|
||||
}
|
||||
contentItem.IsTwoFactorEnabled = await _twoFactorLoginService.IsTwoFactorEnabledAsync(contentItem.Key);
|
||||
}
|
||||
|
||||
if (contentItem.PersistedContent is not null)
|
||||
|
||||
@@ -532,7 +532,7 @@ public class UsersController : BackOfficeNotificationsController
|
||||
{
|
||||
// first validate the username if we're showing it
|
||||
ActionResult<IUser?> userResult = CheckUniqueUsername(userSave.Username,
|
||||
u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue);
|
||||
u => u.UserState != UserState.Invited);
|
||||
if (userResult.Result is not null)
|
||||
{
|
||||
return userResult.Result;
|
||||
@@ -540,7 +540,7 @@ public class UsersController : BackOfficeNotificationsController
|
||||
}
|
||||
|
||||
IUser? user = CheckUniqueEmail(userSave.Email,
|
||||
u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue);
|
||||
u => u.UserState != UserState.Invited);
|
||||
|
||||
if (ModelState.IsValid == false)
|
||||
{
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"title": "Create your first Document Type",
|
||||
"content": "<p>Step 1 of any site is to create a <strong>Document Type</strong>.<br> A Document Type is a template for content. For each <em>type</em> of content you want to create you'll create a Document Type. This will define where content based on this Document Type can be created, how many properties it holds and what the input method should be for these properties.</p><p>When you have at least one Document Type in place you can start creating content and this content can then be used in a template.</p><p>In this tour you will learn how to set up a basic Document Type with a property to enter a short text.</p>",
|
||||
"content": "<p>Step 1 of any site is to create a <strong>Document Type</strong>.<br> A Document Type is a template for content. For each <em>type</em> of content you want to create, you will need to create a Document Type. This will define where content based on this Document Type can be created, how many properties it holds and what the input method should be for these properties.</p><p>When you have at least one Document Type in place you can start creating content and this content can then be used in a template.</p><p>In this tour you will learn how to set up a basic Document Type with a property to enter a short text.</p>",
|
||||
"type": "intro"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -227,7 +227,6 @@ public static partial class UmbracoBuilderExtensions
|
||||
builder.Services.AddSingleton(RecurringBackgroundJobHostedService.CreateHostedServiceFactory);
|
||||
builder.Services.AddHostedService<RecurringBackgroundJobHostedServiceRunner>();
|
||||
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ Use this directive to render a group of toggle buttons.
|
||||
function link(scope, el, attr, ctrl) {
|
||||
for(let i = 0; i < scope.items.length; i++) {
|
||||
scope.items[i].inputId = "umb-toggle-group-item_" + String.CreateGuid();
|
||||
scope.items[i].labelId = "umb-toggle-group-item_" + String.CreateGuid();
|
||||
}
|
||||
scope.change = function(item) {
|
||||
if (item.disabled) {
|
||||
|
||||
@@ -124,7 +124,8 @@
|
||||
}
|
||||
else if(defaultFocusedElement === null ){
|
||||
// If the first focusable elements are either items from the umb-sub-views-nav menu or the umb-button-ellipsis we most likely want to start the focus on the second item
|
||||
var avoidStartElm = focusableElements.findIndex(elm => elm.classList.contains('umb-button-ellipsis') || elm.classList.contains('umb-sub-views-nav-item__action') || elm.classList.contains('umb-tab-button'));
|
||||
// We don't want to focus the second button if it's in a tab otherwise the second tab is highlighted as well as the first tab
|
||||
var avoidStartElm = focusableElements.findIndex(elm => elm.classList.contains('umb-button-ellipsis') || elm.classList.contains('umb-sub-views-nav-item__action') || (elm.classList.contains('umb-tab-button') && !elm.parent.classList.contains('umb-tab')));
|
||||
|
||||
if(avoidStartElm === 0) {
|
||||
focusableElements[1].focus();
|
||||
|
||||
@@ -509,6 +509,7 @@
|
||||
scope.openContentType = (contentTypeId) => {
|
||||
const editor = {
|
||||
id: contentTypeId,
|
||||
entityType: scope.contentType,
|
||||
submit: () => {
|
||||
const args = { node: scope.model };
|
||||
eventsService.emit("editors.documentType.reload", args);
|
||||
@@ -518,8 +519,8 @@
|
||||
editorService.close();
|
||||
}
|
||||
};
|
||||
editorService.documentTypeEditor(editor);
|
||||
|
||||
editorService.contentTypeEditor(editor);
|
||||
};
|
||||
|
||||
/* ---------- TABS ---------- */
|
||||
|
||||
@@ -282,6 +282,7 @@ Use this directive to generate a thumbnail grid of media items.
|
||||
var flexStyle = {
|
||||
"flex": flex + " 1 " + imageMinFlexWidth + "px",
|
||||
"max-width": mediaItem.width + "px",
|
||||
"max-height": itemMaxHeight + "px",
|
||||
"min-width": itemMinWidth + "px",
|
||||
"min-height": itemMinHeight + "px"
|
||||
};
|
||||
|
||||
@@ -323,7 +323,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
|
||||
"contentTypeApiBaseUrl",
|
||||
"DeleteContainer",
|
||||
[{ id: id }])),
|
||||
'Failed to delete content type contaier');
|
||||
'Failed to delete content type container');
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -166,7 +166,7 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter, locali
|
||||
"mediaTypeApiBaseUrl",
|
||||
"DeleteContainer",
|
||||
[{ id: id }])),
|
||||
'Failed to delete content type contaier');
|
||||
'Failed to delete content type container');
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function blockEditorModelObjectFactory($interpolate, $q, udiService, contentResource, localizationService, umbRequestHelper, clipboardService, notificationsService, $compile) {
|
||||
function blockEditorModelObjectFactory($interpolate, $q, udiService, contentResource, localizationService, umbRequestHelper, clipboardService, notificationsService, $compile, editorState) {
|
||||
|
||||
/**
|
||||
* Simple mapping from property model content entry to editing model,
|
||||
@@ -31,7 +31,8 @@
|
||||
for (var p = 0; p < tab.properties.length; p++) {
|
||||
var prop = tab.properties[p];
|
||||
|
||||
prop.value = dataModel[prop.alias];
|
||||
if (typeof (dataModel[prop.alias]) !== 'undefined')
|
||||
prop.value = dataModel[prop.alias];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +398,10 @@
|
||||
scaffoldKeys = scaffoldKeys.filter((value, index, self) => self.indexOf(value) === index);
|
||||
|
||||
if(scaffoldKeys.length > 0) {
|
||||
tasks.push(contentResource.getScaffoldByKeys(-20, scaffoldKeys).then(scaffolds => {
|
||||
var currentPage = editorState.getCurrent();
|
||||
var currentPageId = currentPage ? (currentPage.id > 0 ? currentPage.id : currentPage.parentId) : null || -20;
|
||||
|
||||
tasks.push(contentResource.getScaffoldByKeys(currentPageId, scaffoldKeys).then(scaffolds => {
|
||||
Object.values(scaffolds).forEach(scaffold => {
|
||||
// self.scaffolds might not exists anymore, this happens if this instance has been destroyed before the load is complete.
|
||||
if (self.scaffolds) {
|
||||
|
||||
@@ -391,12 +391,44 @@ When building a custom infinite editor view you can use the same components as a
|
||||
* Opens a content type picker in infinite editing, the submit callback returns an array of selected items
|
||||
*
|
||||
* @param {object} editor rendering options.
|
||||
* @param {string} editor.entityType Entity type to open - default is document type.
|
||||
* @param {boolean} editor.multiPicker Pick one or multiple items.
|
||||
* @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object.
|
||||
* @param {function} editor.close Callback function when the close button is clicked.
|
||||
* @returns {object} editor object
|
||||
*/
|
||||
function contentTypePicker(editor) {
|
||||
|
||||
if (!editor.entityType) editor.entityType = "documentType";
|
||||
|
||||
switch (editor.entityType) {
|
||||
case "documentType":
|
||||
documentTypePicker(editor);
|
||||
break;
|
||||
case "mediaType":
|
||||
mediaTypePicker(editor);
|
||||
break;
|
||||
case "memberType":
|
||||
memberTypePicker(editor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.editorService#documentTypePicker
|
||||
* @methodOf umbraco.services.editorService
|
||||
*
|
||||
* @description
|
||||
* Opens a document type picker in infinite editing, the submit callback returns an array of selected items
|
||||
*
|
||||
* @param {object} editor rendering options.
|
||||
* @param {boolean} editor.multiPicker Pick one or multiple items.
|
||||
* @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object.
|
||||
* @param {function} editor.close Callback function when the close button is clicked.
|
||||
* @returns {object} editor object
|
||||
*/
|
||||
function documentTypePicker(editor) {
|
||||
editor.view = "views/common/infiniteeditors/treepicker/treepicker.html";
|
||||
if (!editor.size) editor.size = "small";
|
||||
editor.section = "settings";
|
||||
@@ -635,6 +667,39 @@ When building a custom infinite editor view you can use the same components as a
|
||||
open(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.editorService#contentTypeEditor
|
||||
* @methodOf umbraco.services.editorService
|
||||
*
|
||||
* @description
|
||||
* Opens the content type editor in infinite editing, the submit callback returns the alias of the saved content type.
|
||||
*
|
||||
* @param {object} editor rendering options.
|
||||
* @param {string} editor.entityType Entity type to open - default document type.
|
||||
* @param {number} editor.id Indicates the ID of the content type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the content type editor for creating a new content type.
|
||||
* @param {boolean} editor.create Set to `true` to open the content type editor for creating a new content type.
|
||||
* @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object.
|
||||
* @param {function} editor.close Callback function when the close button is clicked.
|
||||
* @returns {object} editor object.
|
||||
*/
|
||||
function contentTypeEditor(editor) {
|
||||
|
||||
if (!editor.entityType) editor.entityType = "documentType";
|
||||
|
||||
switch (editor.entityType) {
|
||||
case "documentType":
|
||||
documentTypeEditor(editor);
|
||||
break;
|
||||
case "mediaType":
|
||||
mediaTypeEditor(editor);
|
||||
break;
|
||||
case "memberType":
|
||||
memberTypeEditor(editor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.editorService#documentTypeEditor
|
||||
@@ -852,7 +917,6 @@ When building a custom infinite editor view you can use the same components as a
|
||||
if (!editor.size) editor.size = "small";
|
||||
open(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.editorService#sectionPicker
|
||||
@@ -1149,10 +1213,12 @@ When building a custom infinite editor view you can use the same components as a
|
||||
open: open,
|
||||
close: close,
|
||||
closeAll: closeAll,
|
||||
mediaEditor: mediaEditor,
|
||||
contentEditor: contentEditor,
|
||||
mediaEditor: mediaEditor,
|
||||
memberEditor: memberEditor,
|
||||
contentPicker: contentPicker,
|
||||
contentTypePicker: contentTypePicker,
|
||||
documentTypePicker: documentTypePicker,
|
||||
mediaTypePicker: mediaTypePicker,
|
||||
memberTypePicker: memberTypePicker,
|
||||
copy: copy,
|
||||
@@ -1164,6 +1230,7 @@ When building a custom infinite editor view you can use the same components as a
|
||||
linkPicker: linkPicker,
|
||||
mediaPicker: mediaPicker,
|
||||
iconPicker: iconPicker,
|
||||
contentTypeEditor: contentTypeEditor,
|
||||
documentTypeEditor: documentTypeEditor,
|
||||
mediaTypeEditor: mediaTypeEditor,
|
||||
memberTypeEditor: memberTypeEditor,
|
||||
@@ -1184,7 +1251,6 @@ When building a custom infinite editor view you can use the same components as a
|
||||
macroPicker: macroPicker,
|
||||
memberGroupPicker: memberGroupPicker,
|
||||
memberPicker: memberPicker,
|
||||
memberEditor: memberEditor,
|
||||
mediaCropDetails,
|
||||
eventPicker : eventPicker
|
||||
};
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
*/
|
||||
function navigationService($routeParams, $location, $q, $injector, eventsService, umbModelMapper, treeService, appState, backdropService) {
|
||||
|
||||
//the promise that will be resolved when the navigation is ready
|
||||
var activeElement = undefined;
|
||||
var navReadyPromise = $q.defer();
|
||||
|
||||
//the main tree's API reference, this is acquired when the tree has initialized
|
||||
@@ -82,7 +82,6 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
|
||||
if (appState.getGlobalState("isTablet") === true) {
|
||||
appState.setGlobalState("showNavigation", false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -134,9 +133,20 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
|
||||
backdropService.close();
|
||||
leftColumn.classList.remove(aboveClass);
|
||||
}
|
||||
|
||||
returnFocusToTriggerElement();
|
||||
}
|
||||
}
|
||||
|
||||
function returnFocusToTriggerElement() {
|
||||
if(!activeElement) return;
|
||||
|
||||
const elementToFocus = activeElement.querySelector(".umb-tree-item__inner .umb-button-ellipsis");
|
||||
document.body.classList.add("tabbing-active");
|
||||
elementToFocus.style.backgroundColor = "hsla(0,0%,100%,.8)";
|
||||
elementToFocus.focus();
|
||||
}
|
||||
|
||||
function showBackdrop() {
|
||||
var backDropOptions = {
|
||||
'element': document.getElementById('leftcolumn')
|
||||
@@ -680,9 +690,12 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
|
||||
if (appState.getMenuState("allowHideMenuDialog") === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (showMenu) {
|
||||
this.showMenu({ skipDefault: true, node: appState.getMenuState("currentNode") });
|
||||
} else {
|
||||
activeElement = document.querySelector("#tree .active");
|
||||
|
||||
closeBackdrop();
|
||||
setMode("default");
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ function dateHelper() {
|
||||
const localOffset = new Date().getTimezoneOffset();
|
||||
const serverTimeNeedsOffsetting = -serverOffset !== localOffset;
|
||||
if (serverTimeNeedsOffsetting) {
|
||||
dateVal = this.convertToLocalMomentTime(date, serverOffset, format);
|
||||
dateVal = this.convertToLocalMomentTime(date, serverOffset);
|
||||
} else {
|
||||
dateVal = moment(date, parsingFormat);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<umb-navigation></umb-navigation>
|
||||
|
||||
<section id="contentwrapper">
|
||||
<div id="contentcolumn" ng-view>
|
||||
</div>
|
||||
<main role="main" id="contentcolumn" ng-view>
|
||||
</main>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -86,6 +86,13 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f
|
||||
}
|
||||
};
|
||||
|
||||
$scope.togglePassword = function () {
|
||||
var elem = $("form[name='installerForm'] input[name='installer.current.model.password']");
|
||||
elem.attr("type", (elem.attr("type") === "text" ? "password" : "text"));
|
||||
elem.focus();
|
||||
$(".password-text.show, .password-text.hide").toggle();
|
||||
}
|
||||
|
||||
function onChangeConsent(values) {
|
||||
const result = Math.round(Number(values[0]) - 1);
|
||||
|
||||
|
||||
@@ -49,9 +49,9 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="password">Password</label>
|
||||
<div class="controls">
|
||||
<!-- why isn't this masked: https://www.nngroup.com/articles/stop-password-masking/ -->
|
||||
|
||||
<input
|
||||
type="text"
|
||||
type="password"
|
||||
name="installer.current.model.password"
|
||||
ng-minlength="{{installer.current.model.minCharLength}}"
|
||||
ng-pattern="passwordPattern"
|
||||
@@ -63,6 +63,14 @@
|
||||
id="password"
|
||||
spellcheck="false"
|
||||
/>
|
||||
|
||||
<div class="password-toggle">
|
||||
<button type="button" class="btn-reset" ng-click="togglePassword()">
|
||||
<small class="password-text show">Show password</small>
|
||||
<small class="password-text hide">Hide password</small>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<small class="inline-help"
|
||||
>At least {{installer.current.model.minCharLength}} characters
|
||||
long</small
|
||||
|
||||
@@ -84,6 +84,12 @@ body.umb-drawer-is-visible #mainwrapper{
|
||||
&.above-backdrop{
|
||||
z-index: 7501;
|
||||
}
|
||||
|
||||
// force behind TinyMCE when it's in fullscreen mode
|
||||
.tox-fullscreen & {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#navigation {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
}
|
||||
|
||||
.umb-dashboard__content {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@@ -312,3 +312,31 @@ select {
|
||||
#consentSliderWrapper {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
float: right;
|
||||
user-select: none;
|
||||
|
||||
button {
|
||||
opacity: .5;
|
||||
display: inline-block;
|
||||
z-index: 1;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
.password-text {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 18px;
|
||||
background-position: 0 1px;
|
||||
padding-left: 24px;
|
||||
|
||||
&.show {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath fill='%23444' d='M16 6C9 6 3 10 0 16c3 6 9 10 16 10s13-4 16-10c-3-6-9-10-16-10zm8 5.3c1.8 1.2 3.4 2.8 4.6 4.7-1.2 2-2.8 3.5-4.7 4.7-3 1.5-6 2.3-8 2.3s-6-.8-8-2.3C6 19.5 4 18 3 16c1.5-2 3-3.5 5-4.7l.6-.2C8 12 8 13 8 14c0 4.5 3.5 8 8 8s8-3.5 8-8c0-1-.3-2-.6-2.6l.4.3zM16 13c0 1.7-1.3 3-3 3s-3-1.3-3-3 1.3-3 3-3 3 1.3 3 3z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
&.hide {
|
||||
display: none;
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath fill='%23444' d='M29.6.4C29 0 28 0 27.4.4L21 6.8c-1.4-.5-3-.8-5-.8C9 6 3 10 0 16c1.3 2.6 3 4.8 5.4 6.5l-5 5c-.5.5-.5 1.5 0 2 .3.4.7.5 1 .5s1 0 1.2-.4l27-27C30 2 30 1 29.6.4zM13 10c1.3 0 2.4 1 2.8 2L12 15.8c-1-.4-2-1.5-2-2.8 0-1.7 1.3-3 3-3zm-9.6 6c1.2-2 2.8-3.5 4.7-4.7l.7-.2c-.4 1-.6 2-.6 3 0 1.8.6 3.4 1.6 4.7l-2 2c-1.6-1.2-3-2.7-4-4.4zM24 13.8c0-.8 0-1.7-.4-2.4l-10 10c.7.3 1.6.4 2.4.4 4.4 0 8-3.6 8-8z'/%3E%3Cpath fill='%23444' d='M26 9l-2.2 2.2c2 1.3 3.6 3 4.8 4.8-1.2 2-2.8 3.5-4.7 4.7-2.7 1.5-5.4 2.3-8 2.3-1.4 0-2.6 0-3.8-.4L10 25c2 .6 4 1 6 1 7 0 13-4 16-10-1.4-2.8-3.5-5.2-6-7z'/%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +353,7 @@
|
||||
// Try to avoid customizing these :)
|
||||
@zIndexEditor: 100;
|
||||
@zIndexTree: 100;
|
||||
@zIndexBlockActions: 500;
|
||||
@zIndexBlockActions: 90; // Should be less than "zIndexEditor" to stay behind editor overlay in infinite mode.
|
||||
@zindexDropdown: 1000;
|
||||
@zindexPopover: 1010;
|
||||
@zindexTooltip: 1030;
|
||||
|
||||
@@ -33,6 +33,8 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
|
||||
selectedSearchResults: []
|
||||
};
|
||||
|
||||
$scope.userLinkNameInput = '';
|
||||
|
||||
$scope.showTarget = $scope.model.hideTarget !== true;
|
||||
$scope.showAnchor = $scope.model.hideAnchor !== true;
|
||||
|
||||
@@ -143,7 +145,9 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
|
||||
$scope.currentNode.selected = true;
|
||||
$scope.model.target.id = args.node.id;
|
||||
$scope.model.target.udi = args.node.udi;
|
||||
$scope.model.target.name = args.node.name;
|
||||
if ($scope.oKToUpdateLinkTargetName()) {
|
||||
$scope.model.target.name = args.node.name;
|
||||
}
|
||||
|
||||
if (args.node.id < 0) {
|
||||
$scope.model.target.url = "/";
|
||||
@@ -167,7 +171,18 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
|
||||
openMiniListView(args.node);
|
||||
}
|
||||
}
|
||||
$scope.trackUserInput = function (userInput) {
|
||||
$scope.userLinkNameInput = userInput;
|
||||
|
||||
}
|
||||
$scope.oKToUpdateLinkTargetName = function () {
|
||||
if (!$scope.userLinkNameInput || !$scope.model.target.name) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$scope.switchToMediaPicker = function () {
|
||||
userService.getCurrentUser().then(function (userData) {
|
||||
|
||||
@@ -190,8 +205,10 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
|
||||
|
||||
$scope.model.target.id = media.id;
|
||||
$scope.model.target.udi = media.udi;
|
||||
$scope.model.target.isMedia = true;
|
||||
$scope.model.target.isMedia = true;
|
||||
if ($scope.oKToUpdateLinkTargetName()) {
|
||||
$scope.model.target.name = media.name;
|
||||
}
|
||||
$scope.model.target.url = media.image;
|
||||
|
||||
editorService.close();
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
placeholder="@placeholders_entername"
|
||||
class="umb-property-editor umb-textstring"
|
||||
ng-model="model.target.name"
|
||||
ng-change="trackUserInput(model.target.name)"
|
||||
id="nodeNameLinkPicker"/>
|
||||
</umb-control-group>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<div class="umb-app-header">
|
||||
<header role="banner" class="umb-app-header">
|
||||
|
||||
<button type="button" class="umb-app-header__skip-button" ng-click="skipToMenu()"><localize key="general_skipToMenu">Go to menu</localize></button>
|
||||
<button type="button" class="umb-app-header__skip-button" ng-click="skipToContent()"><localize key="general_skipToContent">Go to content</localize></button>
|
||||
@@ -128,5 +128,5 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div id="leftcolumn" ng-controller="Umbraco.NavigationController" ng-mouseleave="leaveTree($event)" ng-mouseenter="enterTree($event)" on-outside-click="onOutsideClick()">
|
||||
|
||||
<!-- navigation container -->
|
||||
<div id="navigation" ng-show="showNavigation" class="fill umb-modalcolumn" ng-animate="'slide'" nav-resize
|
||||
ng-class="{'--notInFront': infiniteMode}">
|
||||
<nav role="navigation" id="navigation" ng-show="showNavigation" class="fill umb-modalcolumn" ng-animate="'slide'" nav-resize
|
||||
ng-class="{'--notInFront': infiniteMode}" localize="aria-label" aria-label="@actionCategories_content">
|
||||
|
||||
<div class="navigation-inner-container">
|
||||
|
||||
@@ -64,6 +64,6 @@
|
||||
|
||||
<div class="umb-editor__overlay"></div>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<form class="flex items-center mb0" role="search" prevent-default>
|
||||
<label for="app-search" class="umb-search__label">
|
||||
<umb-icon icon="icon-search" class="umb-search-input-icon"></umb-icon>
|
||||
<span class="sr-only">
|
||||
@@ -42,7 +42,7 @@
|
||||
<button ng-show="vm.searchQuery.length > 0" class="umb-search-input-clear umb-animated" ng-click="vm.clearSearch()">
|
||||
<localize key="general_clear">Clear</localize>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="umb-search-results">
|
||||
<div class="umb-search-group" ng-repeat="(key, group) in vm.searchResults" id="search-results" role="listbox">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<div id="applications" ng-class="{faded:stickyNavigation}">
|
||||
<nav role="navigation" id="applications" ng-class="{faded:stickyNavigation}" localize="aria-label" aria-label="@general_primary">
|
||||
<ul class="sections" data-element="sections">
|
||||
|
||||
<li data-element="section-{{section.alias}}" ng-repeat="section in sections | limitTo: visibleSections" ng-class="{current: section.alias == currentSection}">
|
||||
@@ -37,6 +37,6 @@
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.umb-block-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
grid-auto-rows: minmax(30px, auto);
|
||||
grid-auto-rows: minmax(150px, auto);
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ umb-block-card {
|
||||
font-size: 14px;
|
||||
color: @ui-action-type;
|
||||
margin: 0 16px -1px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.__subname {
|
||||
color: @gray-4;
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
checked="item.checked"
|
||||
disabled="item.disabled"
|
||||
input-id="{{item.inputId}}"
|
||||
aria-labelledby="{{item.labelId}}"
|
||||
on-click="change(item)">
|
||||
</umb-toggle>
|
||||
<div class="umb-toggle-group-item__content" ng-click="change(item)">
|
||||
<div><label for="{{item.inputId}}">{{ item.name }}</label> </div>
|
||||
<div><label id="{{item.labelId}}" for="{{item.inputId}}">{{ item.name }}</label> </div>
|
||||
<div class="umb-toggle-group-item__description">{{ item.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
max-height: 50vh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
<div class="umb-table-cell">
|
||||
|
||||
<button type="button" class="umb-table__action" ng-show="vm.allowSelectAll" ng-click="vm.selectAll()">
|
||||
<umb-checkmark checked="vm.isSelectedAll()" size="xs"></umb-checkmark>
|
||||
<span class="sr-only"><localize key="general_selectAll">Select all</localize></span>
|
||||
<umb-checkmark checked="vm.isSelectedAll()" size="xs"></umb-checkmark>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</p>
|
||||
|
||||
<!-- Drag and drop illustration -->
|
||||
<img class="illustration" src="assets/img/uploader/upload-illustration.svg" alt="" draggable="false" />
|
||||
<img class="illustration" src="assets/img/uploader/upload-illustration.svg" alt=" " draggable="false" />
|
||||
|
||||
<!-- Select files -->
|
||||
<button type="button"
|
||||
|
||||
@@ -60,6 +60,7 @@ function DataTypeInfoController($scope, $routeParams, dataTypeResource, $timeout
|
||||
|
||||
const editor = {
|
||||
id: id,
|
||||
entityType: type,
|
||||
submit: function (model) {
|
||||
editorService.close();
|
||||
vm.view.loading = true;
|
||||
@@ -71,17 +72,7 @@ function DataTypeInfoController($scope, $routeParams, dataTypeResource, $timeout
|
||||
}
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case "documentType":
|
||||
editorService.documentTypeEditor(editor);
|
||||
break;
|
||||
case "mediaType":
|
||||
editorService.mediaTypeEditor(editor);
|
||||
break;
|
||||
case "memberType":
|
||||
editorService.memberTypeEditor(editor);
|
||||
break;
|
||||
}
|
||||
editorService.contentTypeEditor(editor);
|
||||
}
|
||||
|
||||
loadRelations();
|
||||
|
||||
@@ -50,12 +50,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="item in vm.filtered = (vm.items | filter: { 'name': vm.filter.searchTerm })" class="cursor-pointer">
|
||||
<tr ng-repeat="item in vm.filtered = (vm.items | filter: { 'name': vm.filter.searchTerm })" ng-click="vm.clickItem(item.id)" class="cursor-pointer">
|
||||
<th>
|
||||
<button type="button" ng-style="item.style" class="btn-reset bold" ng-click="vm.clickItem(item.id)">{{item.name}}</button>
|
||||
<span ng-style="item.style" class="bold">{{item.name}}</span>
|
||||
</th>
|
||||
<td ng-repeat="column in item.translations | orderBy:'displayName'">
|
||||
<button type="button" class="btn-reset" ng-click="vm.clickItem(item.id)">
|
||||
<div>
|
||||
<umb-icon icon="{{ column.hasTranslation ? 'icon-check' : 'icon-alert' }}"
|
||||
class="{{ column.hasTranslation ? 'color-green' : 'color-red' }}">
|
||||
</umb-icon>
|
||||
@@ -63,7 +63,7 @@
|
||||
<localize ng-if="column.hasTranslation" key="visuallyHiddenTexts_hasTranslation">Has translation</localize>
|
||||
<localize ng-if="!column.hasTranslation" key="visuallyHiddenTexts_noTranslation">Missing translation</localize>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState, localizationService) {
|
||||
|
||||
$scope.model = {
|
||||
folderName: "",
|
||||
creatingFolder: false
|
||||
allowCreateFolder: $scope.currentNode.parentId === null || $scope.currentNode.nodeType === 'container',
|
||||
folderName: "",
|
||||
creatingFolder: false
|
||||
};
|
||||
|
||||
var node = $scope.currentNode;
|
||||
|
||||
@@ -10,20 +10,21 @@
|
||||
</h5>
|
||||
|
||||
<ul class="umb-actions umb-actions-child">
|
||||
<li class="umb-action">
|
||||
<button class="umb-action-link umb-outline btn-reset" ng-click="createMediaType()" type="button"
|
||||
umb-auto-focus>
|
||||
<li data-element="action-mediaType" class="umb-action">
|
||||
<button type="button" class="umb-action-link umb-outline btn-reset" ng-click="createMediaType()" umb-auto-focus>
|
||||
<umb-icon class="icon large" icon="icon-item-arrangement"></umb-icon>
|
||||
<span class="menu-label">
|
||||
<localize key="general_new">New</localize>
|
||||
<localize key="content_mediatype">Media type</localize>
|
||||
</span>
|
||||
<localize key="general_new">New</localize>
|
||||
<localize key="content_mediatype">Media type</localize>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="umb-action">
|
||||
<button class="umb-action-link umb-outline btn-reset" ng-click="showCreateFolder()" type="button">
|
||||
<li data-element="action-folder" class="umb-action" ng-if="model.allowCreateFolder">
|
||||
<button type="button" class="umb-action-link umb-outline btn-reset" ng-click="showCreateFolder()">
|
||||
<umb-icon class="icon large" icon="icon-folder"></umb-icon>
|
||||
<span class="menu-label"><localize key="general_folder">Folder</localize>...</span>
|
||||
<span class="menu-label">
|
||||
<localize key="general_folder">Folder</localize>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -35,6 +36,7 @@
|
||||
<localize key="buttons_somethingElse">Do something else</localize>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-cloak ng-show="model.creatingFolder">
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
function MemberTypesCreateController($scope, $location, navigationService, memberTypeResource, formHelper, appState, localizationService) {
|
||||
|
||||
$scope.model = {
|
||||
folderName: "",
|
||||
creatingFolder: false
|
||||
allowCreateFolder: $scope.currentNode.parentId === null || $scope.currentNode.nodeType === 'container',
|
||||
folderName: "",
|
||||
creatingFolder: false
|
||||
};
|
||||
|
||||
var node = $scope.currentNode;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div ng-controller="Umbraco.Editors.MemberTypes.CreateController">
|
||||
|
||||
<div class="umbracoDialog umb-dialog-body with-footer">
|
||||
<div class="umb-pane" ng-if="!model.creatingFolder">
|
||||
<h5>
|
||||
@@ -6,7 +7,16 @@
|
||||
</h5>
|
||||
|
||||
<ul class="umb-actions umb-actions-child">
|
||||
<li class="umb-action">
|
||||
<li data-element="action-memberType" class="umb-action">
|
||||
<button type="button" class="umb-action-link umb-outline btn-reset" ng-click="createMemberType()">
|
||||
<umb-icon icon="icon-item-arrangement" class="icon large"></umb-icon>
|
||||
<span class="menu-label">
|
||||
<localize key="general_new">New</localize>
|
||||
<localize key="content_memberType">Member type</localize>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li data-element="action-folder" class="umb-action">
|
||||
<button type="button" class="umb-action-link umb-outline btn-reset" ng-click="showCreateFolder()">
|
||||
<umb-icon icon="icon-folder" class="icon large"></umb-icon>
|
||||
<span class="menu-label">
|
||||
@@ -14,15 +24,6 @@
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="umb-action">
|
||||
<button type="button" class="umb-action-link umb-outline btn-reset" ng-click="createMemberType()">
|
||||
<umb-icon icon="icon-item-arrangement" class="icon large"></umb-icon>
|
||||
<span class="menu-label">
|
||||
<localize key="general_new">New</localize>
|
||||
<localize key="content_memberType">Member type</localize>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -199,12 +199,9 @@
|
||||
|
||||
localizationService.localize("blockEditor_headlineCreateBlock").then(function(localizedTitle) {
|
||||
|
||||
const contentTypePicker = {
|
||||
const dialog = {
|
||||
title: localizedTitle,
|
||||
section: "settings",
|
||||
treeAlias: "documentTypes",
|
||||
entityType: "documentType",
|
||||
isDialog: true,
|
||||
filter: function (node) {
|
||||
if (node.metaData.isElement === true) {
|
||||
var key = udiService.getKey(node.udi);
|
||||
@@ -238,8 +235,8 @@
|
||||
}
|
||||
]
|
||||
};
|
||||
editorService.treePicker(contentTypePicker);
|
||||
|
||||
editorService.contentTypePicker(dialog);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
.umb-block-grid-block-configuration {
|
||||
margin-bottom: 20px;
|
||||
|
||||
margin-bottom: 20px;
|
||||
uui-button {
|
||||
--uui-button-border-radius: 6px;
|
||||
font-weight: 700;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
uui-button {
|
||||
font-weight: 700;
|
||||
--uui-button-border-radius: 6px;
|
||||
min-height: 80px;
|
||||
.__get-sample-box {
|
||||
position: relative;
|
||||
border: 1px solid @gray-10;
|
||||
border-radius: 6px;
|
||||
box-shadow: 3px 3px 6px rgba(0, 0, 0, .07);
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
padding-top: 15px;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 40px;
|
||||
max-width: 480px;
|
||||
|
||||
umb-button {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
display: block;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.__get-sample-box {
|
||||
position:relative;
|
||||
border: 1px solid @gray-10;
|
||||
border-radius: 6px;
|
||||
box-shadow: 3px 3px 6px rgba(0, 0, 0, .07);
|
||||
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
padding-top: 15px;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 40px;
|
||||
max-width: 480px;
|
||||
|
||||
umb-button {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
display: block;
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
border-radius: @baseBorderRadius;
|
||||
transition: color 120ms;
|
||||
&.--hideText {
|
||||
color: white;
|
||||
color: @ui-action-discreet-type;
|
||||
}
|
||||
&:hover, &:focus {
|
||||
color: @ui-action-discreet-type-hover;
|
||||
|
||||
@@ -103,12 +103,9 @@
|
||||
|
||||
localizationService.localize("blockEditor_headlineCreateBlock").then(localizedTitle => {
|
||||
|
||||
const contentTypePicker = {
|
||||
const dialog = {
|
||||
title: localizedTitle,
|
||||
section: "settings",
|
||||
treeAlias: "documentTypes",
|
||||
entityType: "documentType",
|
||||
isDialog: true,
|
||||
filter: function (node) {
|
||||
if (node.metaData.isElement === true) {
|
||||
var key = udiService.getKey(node.udi);
|
||||
@@ -142,7 +139,7 @@
|
||||
]
|
||||
};
|
||||
|
||||
editorService.treePicker(contentTypePicker);
|
||||
editorService.contentTypePicker(dialog);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
</div>
|
||||
</umb-block-card>
|
||||
|
||||
<button type="button" id="{{model.alias}}" class="btn-reset __add-button" ng-click="vm.openAddDialog($event)">
|
||||
<localize key="general_add">Add</localize>
|
||||
</button>
|
||||
<uui-button id="{{model.alias}}" look="{{model.value.length === 0 ? 'primary' : 'placeholder'}}" color="primary" ng-click="vm.openAddDialog($event)">
|
||||
<localize key="blockEditor_addBlockType">Add Block</localize>
|
||||
</uui-button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
.umb-block-list-block-configuration {
|
||||
|
||||
uui-button {
|
||||
--uui-button-border-radius: 6px;
|
||||
font-weight: 700;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.__add-button {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
&.--noValue {
|
||||
text-align: center;
|
||||
border-radius: @baseBorderRadius;
|
||||
color: white;
|
||||
color: @ui-action-discreet-type;
|
||||
transition: color 120ms;
|
||||
&:hover, &:focus {
|
||||
color: @ui-action-discreet-type-hover;
|
||||
|
||||
@@ -1,38 +1,46 @@
|
||||
<div class="umb-property-editor umb-prevalues-multivalues umb-colors" ng-controller="Umbraco.PrevalueEditors.MultiColorPickerController as vm">
|
||||
<div class="control-group umb-prevalues-multivalues__add color-picker-preval">
|
||||
<div class="umb-prevalues-multivalues__left">
|
||||
<div class="control-group umb-prevalues-multivalues__add color-picker-preval">
|
||||
<div class="umb-prevalues-multivalues__left">
|
||||
|
||||
<umb-color-picker
|
||||
ng-model="newColor"
|
||||
options="options"
|
||||
on-hide="vm.hide(color)"
|
||||
on-show="vm.show(color)"
|
||||
on-change="vm.change(color)">
|
||||
</umb-color-picker>
|
||||
<umb-color-picker ng-model="newColor"
|
||||
options="options"
|
||||
on-hide="vm.hide(color)"
|
||||
on-show="vm.show(color)"
|
||||
on-change="vm.change(color)">
|
||||
</umb-color-picker>
|
||||
|
||||
<label val-highlight="{{hasError}}">#{{newColor}}</label>
|
||||
<input type="text" name="newLabel" ng-model="newLabel" focus-when="{{focusOnNew}}" class="umb-property-editor color-label" localize="placeholder" placeholder="@general_label" ng-show="vm.labelEnabled" />
|
||||
</div>
|
||||
<div class="umb-prevalues-multivalues__right">
|
||||
<button type="button" class="btn btn-info add" ng-if="vm.editItem == null" ng-click="vm.add($event)"><localize key="general_add">Add</localize></button>
|
||||
<button type="button" class="btn btn-info add" ng-if="vm.editItem != null" ng-click="vm.add($event)"><localize key="general_update">Update</localize></button>
|
||||
<button type="button" class="btn btn-link" ng-if="vm.editItem != null" ng-click="vm.cancel($event)"><localize key="general_cancel">Cancel</localize></button>
|
||||
</div>
|
||||
<label val-highlight="{{colorHasError}}">#{{newColor}}</label>
|
||||
<input type="text" name="newLabel"
|
||||
ng-model="newLabel"
|
||||
focus-when="{{focusOnNew}}"
|
||||
class="umb-property-editor color-label"
|
||||
localize="placeholder"
|
||||
placeholder="@general_label"
|
||||
ng-show="vm.labelEnabled"
|
||||
ng-keydown="vm.addOnEnter($event)"
|
||||
ng-keyup="vm.validateLabel()"
|
||||
val-highlight="{{labelHasError}}" />
|
||||
</div>
|
||||
<div ui-sortable="sortableOptions" ng-model="model.value">
|
||||
<div class="control-group umb-prevalues-multivalues__listitem color-picker-preval" ng-repeat="item in model.value track by $id(item)">
|
||||
<umb-icon icon="icon-navigation" class="icon handle"></umb-icon>
|
||||
<div class="umb-prevalues-multivalues__left">
|
||||
<div class="thumbnail span1" hex-bg-color="{{item.value}}" hex-bg-orig="transparent"></div>
|
||||
<div class="color-picker-prediv">
|
||||
<pre>#{{item.value}}</pre>
|
||||
<span ng-bind="item.label" ng-if="vm.labelEnabled"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="umb-prevalues-multivalues__right">
|
||||
<button type="button" class="umb-node-preview__action umb-node-preview__action--red" ng-click="vm.edit(item, $event)"><localize key="general_edit">Edit</localize></button>
|
||||
<button type="button" class="umb-node-preview__action umb-node-preview__action--red" ng-click="vm.remove(item, $event)"><localize key="general_remove">Remove</localize></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="umb-prevalues-multivalues__right">
|
||||
<button type="button" class="btn btn-info add" ng-if="vm.editItem == null" ng-click="vm.add($event)"><localize key="general_add">Add</localize></button>
|
||||
<button type="button" class="btn btn-info add" ng-if="vm.editItem != null" ng-click="vm.add($event)"><localize key="general_update">Update</localize></button>
|
||||
<button type="button" class="btn btn-link" ng-if="vm.editItem != null" ng-click="vm.cancel($event)"><localize key="general_cancel">Cancel</localize></button>
|
||||
</div>
|
||||
</div>
|
||||
<div ui-sortable="sortableOptions" ng-model="model.value">
|
||||
<div class="control-group umb-prevalues-multivalues__listitem color-picker-preval" ng-repeat="item in model.value track by $id(item)">
|
||||
<umb-icon icon="icon-navigation" class="icon handle"></umb-icon>
|
||||
<div class="umb-prevalues-multivalues__left">
|
||||
<div class="thumbnail span1" hex-bg-color="{{item.value}}" hex-bg-orig="transparent"></div>
|
||||
<div class="color-picker-prediv">
|
||||
<pre>#{{item.value}}</pre>
|
||||
<span ng-bind="item.label" ng-if="vm.labelEnabled"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="umb-prevalues-multivalues__right">
|
||||
<button type="button" class="umb-node-preview__action umb-node-preview__action--red" ng-click="vm.edit(item, $event)"><localize key="general_edit">Edit</localize></button>
|
||||
<button type="button" class="umb-node-preview__action umb-node-preview__action--red" ng-click="vm.remove(item, $event)"><localize key="general_remove">Remove</localize></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,191 +1,228 @@
|
||||
angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiColorPickerController",
|
||||
function ($scope, angularHelper, $element, eventsService) {
|
||||
function ($scope, angularHelper, $element, eventsService) {
|
||||
|
||||
const vm = this;
|
||||
const vm = this;
|
||||
|
||||
vm.add = add;
|
||||
vm.remove = remove;
|
||||
vm.edit = edit;
|
||||
vm.cancel = cancel;
|
||||
vm.add = add;
|
||||
vm.addOnEnter = addOnEnter;
|
||||
vm.validateLabel = validateLabel;
|
||||
vm.remove = remove;
|
||||
vm.edit = edit;
|
||||
vm.cancel = cancel;
|
||||
|
||||
vm.show = show;
|
||||
vm.hide = hide;
|
||||
vm.change = change;
|
||||
vm.show = show;
|
||||
vm.hide = hide;
|
||||
vm.change = change;
|
||||
|
||||
vm.labelEnabled = false;
|
||||
vm.editItem = null;
|
||||
vm.labelEnabled = false;
|
||||
vm.editItem = null;
|
||||
|
||||
// NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive.
|
||||
const defaultColor = "000000";
|
||||
const defaultLabel = null;
|
||||
// NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive.
|
||||
const defaultColor = "000000";
|
||||
const defaultLabel = null;
|
||||
|
||||
$scope.newColor = defaultColor;
|
||||
$scope.newLabel = defaultLabel;
|
||||
$scope.hasError = false;
|
||||
$scope.focusOnNew = false;
|
||||
$scope.newColor = defaultColor;
|
||||
$scope.newLabel = defaultLabel;
|
||||
$scope.colorHasError = false;
|
||||
$scope.labelHasError = false;
|
||||
$scope.focusOnNew = false;
|
||||
|
||||
$scope.options = {
|
||||
type: "color",
|
||||
color: defaultColor,
|
||||
allowEmpty: false,
|
||||
showAlpha: false
|
||||
};
|
||||
$scope.options = {
|
||||
type: "color",
|
||||
color: defaultColor,
|
||||
allowEmpty: false,
|
||||
showAlpha: false
|
||||
};
|
||||
|
||||
function hide() {
|
||||
// show the add button
|
||||
$element.find(".btn.add").show();
|
||||
function hide() {
|
||||
// show the add button
|
||||
$element.find(".btn.add").show();
|
||||
}
|
||||
|
||||
function show() {
|
||||
// hide the add button
|
||||
$element.find(".btn.add").hide();
|
||||
}
|
||||
|
||||
function change(color) {
|
||||
angularHelper.safeApply($scope, function () {
|
||||
if (color) {
|
||||
$scope.newColor = color.toHexString().trimStart("#");
|
||||
$scope.colorHasError = !colorIsValid();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function show() {
|
||||
// hide the add button
|
||||
$element.find(".btn.add").hide();
|
||||
}
|
||||
|
||||
function change(color) {
|
||||
angularHelper.safeApply($scope, function () {
|
||||
if (color) {
|
||||
$scope.newColor = color.toHexString().trimStart("#");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var evts = [];
|
||||
evts.push(eventsService.on("toggleValue", function (e, args) {
|
||||
if (args.inputId === "useLabel") {
|
||||
vm.labelEnabled = args.value;
|
||||
}
|
||||
}));
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
for (var e in evts) {
|
||||
eventsService.unsubscribe(evts[e]);
|
||||
}
|
||||
});
|
||||
|
||||
if (!Utilities.isArray($scope.model.value)) {
|
||||
//make an array from the dictionary
|
||||
var items = [];
|
||||
for (var i in $scope.model.value) {
|
||||
var oldValue = $scope.model.value[i];
|
||||
if (Object.prototype.hasOwnProperty.call(oldValue, "value")) {
|
||||
items.push({
|
||||
value: oldValue.value,
|
||||
label: oldValue.label,
|
||||
sortOrder: oldValue.sortOrder,
|
||||
id: i
|
||||
});
|
||||
} else {
|
||||
items.push({
|
||||
value: oldValue,
|
||||
label: oldValue,
|
||||
sortOrder: oldValue.sortOrder,
|
||||
id: i
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//ensure the items are sorted by the provided sort order
|
||||
items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
|
||||
|
||||
//now make the editor model the array
|
||||
$scope.model.value = items;
|
||||
}
|
||||
|
||||
// ensure labels
|
||||
for (var ii = 0; ii < $scope.model.value.length; ii++) {
|
||||
var item = $scope.model.value[ii];
|
||||
item.label = Object.prototype.hasOwnProperty.call(item, "label") ? item.label : item.value;
|
||||
}
|
||||
|
||||
function validLabel(label) {
|
||||
return label !== null && typeof label !== "undefined" && label !== "" && label.length && label.length > 0;
|
||||
}
|
||||
|
||||
function remove(item, evt) {
|
||||
|
||||
evt.preventDefault();
|
||||
|
||||
$scope.model.value = _.reject($scope.model.value, function (x) {
|
||||
return x.value === item.value && x.label === item.label;
|
||||
});
|
||||
|
||||
setDirty();
|
||||
}
|
||||
|
||||
function add(evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
if ($scope.newColor) {
|
||||
var newLabel = validLabel($scope.newLabel) ? $scope.newLabel : $scope.newColor;
|
||||
var exists = _.find($scope.model.value, function (item) {
|
||||
return item != vm.editItem && (item.value.toUpperCase() === $scope.newColor.toUpperCase() || item.label.toUpperCase() === newLabel.toUpperCase());
|
||||
});
|
||||
if (!exists) {
|
||||
if (vm.editItem == null) {
|
||||
$scope.model.value.push({
|
||||
value: $scope.newColor,
|
||||
label: newLabel
|
||||
});
|
||||
} else {
|
||||
|
||||
if(vm.editItem.value === vm.editItem.label && vm.editItem.value === newLabel) {
|
||||
vm.editItem.label = $scope.newColor;
|
||||
|
||||
}
|
||||
else {
|
||||
vm.editItem.label = newLabel;
|
||||
}
|
||||
|
||||
vm.editItem.value = $scope.newColor;
|
||||
|
||||
vm.editItem = null;
|
||||
}
|
||||
|
||||
$scope.newLabel = "";
|
||||
$scope.hasError = false;
|
||||
$scope.focusOnNew = true;
|
||||
setDirty();
|
||||
return;
|
||||
}
|
||||
|
||||
// there was an error, do the highlight (will be set back by the directive)
|
||||
$scope.hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
function edit(item, evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
vm.editItem = item;
|
||||
|
||||
$scope.newColor = item.value;
|
||||
$scope.newLabel = item.label;
|
||||
}
|
||||
|
||||
function cancel(evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
vm.editItem = null;
|
||||
$scope.newColor = defaultColor;
|
||||
$scope.newLabel = defaultLabel;
|
||||
}
|
||||
|
||||
function setDirty() {
|
||||
if (vm.modelValueForm) {
|
||||
vm.modelValueForm.selectedColor.$setDirty();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.sortableOptions = {
|
||||
axis: 'y',
|
||||
containment: 'parent',
|
||||
cursor: 'move',
|
||||
//handle: ".handle, .thumbnail",
|
||||
items: '> div.control-group',
|
||||
tolerance: 'pointer',
|
||||
update: function () {
|
||||
setDirty();
|
||||
}
|
||||
};
|
||||
var evts = [];
|
||||
evts.push(eventsService.on("toggleValue", function (e, args) {
|
||||
if (args.inputId === "useLabel") {
|
||||
vm.labelEnabled = args.value;
|
||||
}
|
||||
}));
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
for (var e in evts) {
|
||||
eventsService.unsubscribe(evts[e]);
|
||||
}
|
||||
});
|
||||
|
||||
if (!Utilities.isArray($scope.model.value)) {
|
||||
//make an array from the dictionary
|
||||
var items = [];
|
||||
for (var i in $scope.model.value) {
|
||||
var oldValue = $scope.model.value[i];
|
||||
if (Object.prototype.hasOwnProperty.call(oldValue, "value")) {
|
||||
items.push({
|
||||
value: oldValue.value,
|
||||
label: oldValue.label,
|
||||
sortOrder: oldValue.sortOrder,
|
||||
id: i
|
||||
});
|
||||
} else {
|
||||
items.push({
|
||||
value: oldValue,
|
||||
label: oldValue,
|
||||
sortOrder: oldValue.sortOrder,
|
||||
id: i
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//ensure the items are sorted by the provided sort order
|
||||
items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
|
||||
|
||||
//now make the editor model the array
|
||||
$scope.model.value = items;
|
||||
}
|
||||
|
||||
// ensure labels
|
||||
for (var ii = 0; ii < $scope.model.value.length; ii++) {
|
||||
var item = $scope.model.value[ii];
|
||||
item.label = Object.prototype.hasOwnProperty.call(item, "label") ? item.label : item.value;
|
||||
}
|
||||
|
||||
function remove(item, evt) {
|
||||
|
||||
evt.preventDefault();
|
||||
|
||||
$scope.model.value = _.reject($scope.model.value, function (x) {
|
||||
return x.value === item.value && x.label === item.label;
|
||||
});
|
||||
|
||||
setDirty();
|
||||
}
|
||||
|
||||
function colorIsValid() {
|
||||
var colorExists = _.find($scope.model.value, function (item) {
|
||||
return item != vm.editItem && item.value.toUpperCase() === $scope.newColor.toUpperCase();
|
||||
});
|
||||
|
||||
return colorExists ? false : true;
|
||||
}
|
||||
|
||||
function getLabel() {
|
||||
var validLabel = $scope.newLabel !== null && typeof $scope.newLabel !== "undefined" && $scope.newLabel !== "" && $scope.newLabel.length && $scope.newLabel.length > 0;
|
||||
return validLabel ? $scope.newLabel : $scope.newColor;
|
||||
}
|
||||
|
||||
function labelIsValid() {
|
||||
var label = getLabel();
|
||||
label = label.toUpperCase();
|
||||
|
||||
var labelExists = _.find($scope.model.value, function (item) {
|
||||
return item != vm.editItem && item.label.toUpperCase() === label;
|
||||
});
|
||||
|
||||
return labelExists ? false : true;
|
||||
}
|
||||
|
||||
function validateLabel() {
|
||||
$scope.labelHasError = !labelIsValid();
|
||||
}
|
||||
|
||||
function addOnEnter(evt) {
|
||||
if (evt.keyCode === 13) {
|
||||
add(evt);
|
||||
}
|
||||
}
|
||||
|
||||
function add(evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
if ($scope.newColor) {
|
||||
|
||||
$scope.colorHasError = !colorIsValid();
|
||||
$scope.labelHasError = !labelIsValid();
|
||||
|
||||
if ($scope.labelHasError || $scope.colorHasError) {
|
||||
return;
|
||||
}
|
||||
|
||||
var newLabel = getLabel();
|
||||
|
||||
if (vm.editItem == null) {
|
||||
$scope.model.value.push({
|
||||
value: $scope.newColor,
|
||||
label: newLabel
|
||||
});
|
||||
} else {
|
||||
|
||||
if (vm.editItem.value === vm.editItem.label && vm.editItem.value === newLabel) {
|
||||
vm.editItem.label = $scope.newColor;
|
||||
|
||||
}
|
||||
else {
|
||||
vm.editItem.label = newLabel;
|
||||
}
|
||||
|
||||
vm.editItem.value = $scope.newColor;
|
||||
|
||||
vm.editItem = null;
|
||||
}
|
||||
|
||||
$scope.newLabel = "";
|
||||
$scope.colorHasError = false;
|
||||
$scope.labelHasError = false;
|
||||
$scope.focusOnNew = true;
|
||||
setDirty();
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function edit(item, evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
vm.editItem = item;
|
||||
|
||||
$scope.newColor = item.value;
|
||||
$scope.newLabel = item.label;
|
||||
}
|
||||
|
||||
function cancel(evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
vm.editItem = null;
|
||||
$scope.newColor = defaultColor;
|
||||
$scope.newLabel = defaultLabel;
|
||||
}
|
||||
|
||||
function setDirty() {
|
||||
if (vm.modelValueForm) {
|
||||
vm.modelValueForm.selectedColor.$setDirty();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.sortableOptions = {
|
||||
axis: 'y',
|
||||
containment: 'parent',
|
||||
cursor: 'move',
|
||||
//handle: ".handle, .thumbnail",
|
||||
items: '> div.control-group',
|
||||
tolerance: 'pointer',
|
||||
update: function () {
|
||||
setDirty();
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -20,8 +20,9 @@ angular.module('umbraco')
|
||||
|
||||
var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
|
||||
$scope.acceptFileExt = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes);
|
||||
|
||||
/**
|
||||
* Called when the umgImageGravity component updates the focal point value
|
||||
* Called when the umbImageGravity component updates the focal point value
|
||||
* @param {any} left
|
||||
* @param {any} top
|
||||
*/
|
||||
@@ -81,7 +82,7 @@ angular.module('umbraco')
|
||||
function imageLoaded(isCroppable, hasDimensions) {
|
||||
$scope.isCroppable = isCroppable;
|
||||
$scope.hasDimensions = hasDimensions;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the file collection changes
|
||||
|
||||
@@ -73,7 +73,7 @@ public class UmbTwoFactorLoginController : SurfaceController
|
||||
MemberIdentityUser? user = await _memberSignInManager.GetTwoFactorAuthenticationUserAsync();
|
||||
if (user == null!)
|
||||
{
|
||||
_logger.LogWarning("PostVerify2FACode :: No verified member found, returning 404");
|
||||
_logger.LogWarning("Verify2FACode :: No verified member found, returning 404");
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Threading;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Extensions;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
@@ -99,7 +100,7 @@ public class LoadTestController : Controller
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IFileService _fileService;
|
||||
private readonly IHostApplicationLifetime _hostApplicationLifetime;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IHostEnvironment _hostEnvironment;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
|
||||
public LoadTestController(
|
||||
@@ -108,7 +109,7 @@ public class LoadTestController : Controller
|
||||
IDataTypeService dataTypeService,
|
||||
IFileService fileService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IHostEnvironment hostEnvironment,
|
||||
IHostApplicationLifetime hostApplicationLifetime)
|
||||
{
|
||||
_contentTypeService = contentTypeService;
|
||||
@@ -116,7 +117,7 @@ public class LoadTestController : Controller
|
||||
_dataTypeService = dataTypeService;
|
||||
_fileService = fileService;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_hostEnvironment = hostEnvironment;
|
||||
_hostApplicationLifetime = hostApplicationLifetime;
|
||||
}
|
||||
|
||||
@@ -321,7 +322,7 @@ public class LoadTestController : Controller
|
||||
public IActionResult ColdBootRestart()
|
||||
{
|
||||
Directory.Delete(
|
||||
_hostingEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.TempData, "DistCache")),
|
||||
_hostEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.TempData, "DistCache")),
|
||||
true);
|
||||
|
||||
DoRestart();
|
||||
|
||||
60
tests/Umbraco.Tests.AcceptanceTest/package-lock.json
generated
60
tests/Umbraco.Tests.AcceptanceTest/package-lock.json
generated
@@ -23,7 +23,7 @@
|
||||
"prompt": "^1.2.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.8.3",
|
||||
"wait-on": "^6.0.1"
|
||||
"wait-on": "^7.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@colors/colors": {
|
||||
@@ -177,12 +177,14 @@
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
|
||||
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.7"
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
@@ -364,9 +366,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -563,15 +565,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/joi": {
|
||||
"version": "17.6.3",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.6.3.tgz",
|
||||
"integrity": "sha512-YlQsIaS9MHYekzf1Qe11LjTkNzx9qhYluK3172z38RxYoAUf82XMX1p1DG1H4Wtk2ED/vPdSn9OggqtDu+aTow==",
|
||||
"version": "17.11.0",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz",
|
||||
"integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.0.0",
|
||||
"@hapi/topo": "^5.0.0",
|
||||
"@sideway/address": "^4.1.3",
|
||||
"@sideway/formula": "^3.0.0",
|
||||
"@sideway/formula": "^3.0.1",
|
||||
"@sideway/pinpoint": "^2.0.0"
|
||||
}
|
||||
},
|
||||
@@ -635,9 +637,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
|
||||
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -777,6 +779,12 @@
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -867,9 +875,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.5.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz",
|
||||
"integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
@@ -930,22 +938,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wait-on": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz",
|
||||
"integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz",
|
||||
"integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"axios": "^0.25.0",
|
||||
"joi": "^17.6.0",
|
||||
"axios": "^1.6.1",
|
||||
"joi": "^17.11.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimist": "^1.2.5",
|
||||
"rxjs": "^7.5.4"
|
||||
"minimist": "^1.2.8",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"bin": {
|
||||
"wait-on": "bin/wait-on"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"del": "^6.0.0",
|
||||
"ncp": "^2.0.0",
|
||||
"prompt": "^1.2.0",
|
||||
"wait-on": "^6.0.1"
|
||||
"wait-on": "^7.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "^1.0.6",
|
||||
|
||||
@@ -36,6 +36,19 @@ test("Check the youtube link works as expected", async ({ page, umbracoUi }) =>
|
||||
|
||||
} else {
|
||||
await expect(page).toHaveURL(/.*UmbracoLearningBase/);
|
||||
await page.close();
|
||||
}
|
||||
});
|
||||
test("Check the Our Umbraco link works as expected", async ({ page, umbracoUi }) => {
|
||||
// Action
|
||||
await umbracoUi.clickElement(umbracoUi.getGlobalHelp());
|
||||
let ourUmbracoLink = await page.locator('[key="help_umbracoForum"]');
|
||||
await ourUmbracoLink.click();
|
||||
let ourUmbraco = page.waitForEvent("popup");
|
||||
let ourUmbracoPopup = await ourUmbraco;
|
||||
|
||||
//Assert
|
||||
await expect(ourUmbracoPopup).toHaveURL(/.*our.umbraco.com/);
|
||||
await ourUmbracoPopup.close();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,611 @@
|
||||
using Examine;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Sync;
|
||||
using Umbraco.Cms.Infrastructure.Examine;
|
||||
using Umbraco.Cms.Infrastructure.Examine.DependencyInjection;
|
||||
using Umbraco.Cms.Infrastructure.HostedServices;
|
||||
using Umbraco.Cms.Infrastructure.Search;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
|
||||
using Umbraco.Cms.Web.BackOffice.Security;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine;
|
||||
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
public class BackOfficeExamineSearcherTests : ExamineBaseTest
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.InternalIndexName));
|
||||
TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.ExternalIndexName));
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.RequestServices = Services;
|
||||
Mock.Get(TestHelper.GetHttpContextAccessor()).Setup(x => x.HttpContext).Returns(httpContext);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// When disposing examine, it does a final write, which ends up locking the file if the indexing is not done yet. So we have this wait to circumvent that.
|
||||
Thread.Sleep(1500);
|
||||
// Sometimes we do not dispose all services in time and the test fails because the log file is locked. Resulting in all other tests failing as well
|
||||
Services.DisposeIfDisposable();
|
||||
}
|
||||
|
||||
private IBackOfficeExamineSearcher BackOfficeExamineSearcher => GetRequiredService<IBackOfficeExamineSearcher>();
|
||||
|
||||
private IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();
|
||||
|
||||
private ILocalizationService LocalizationService => GetRequiredService<ILocalizationService>();
|
||||
|
||||
private ContentService ContentService => (ContentService)GetRequiredService<IContentService>();
|
||||
|
||||
private IUserStore<BackOfficeIdentityUser> BackOfficeUserStore =>
|
||||
GetRequiredService<IUserStore<BackOfficeIdentityUser>>();
|
||||
|
||||
private IBackOfficeSignInManager BackOfficeSignInManager => GetRequiredService<IBackOfficeSignInManager>();
|
||||
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddUnique<IBackOfficeExamineSearcher, BackOfficeExamineSearcher>();
|
||||
builder.Services.AddUnique<IServerMessenger, ContentEventsTests.LocalServerMessenger>();
|
||||
builder
|
||||
.AddNotificationHandler<ContentTreeChangeNotification,
|
||||
ContentTreeChangeDistributedCacheNotificationHandler>();
|
||||
builder.AddNotificationHandler<ContentCacheRefresherNotification, ContentIndexingNotificationHandler>();
|
||||
builder.AddExamineIndexes();
|
||||
builder.AddBackOfficeIdentity();
|
||||
builder.Services.AddHostedService<QueuedHostedService>();
|
||||
}
|
||||
|
||||
private IEnumerable<ISearchResult> BackOfficeExamineSearch(string query, int pageSize = 20, int pageIndex = 0) =>
|
||||
BackOfficeExamineSearcher.Search(query, UmbracoEntityTypes.Document,
|
||||
pageSize, pageIndex, out _, ignoreUserStartNodes: true);
|
||||
|
||||
private async Task SetupUserIdentity(string userId)
|
||||
{
|
||||
var identity =
|
||||
await BackOfficeUserStore.FindByIdAsync(userId, CancellationToken.None);
|
||||
await BackOfficeSignInManager.SignInAsync(identity, false);
|
||||
}
|
||||
|
||||
private async Task<PublishResult> CreateDefaultPublishedContent(string contentName)
|
||||
{
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithId(0)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
var content = new ContentBuilder()
|
||||
.WithId(0)
|
||||
.WithName(contentName)
|
||||
.WithContentType(contentType)
|
||||
.Build();
|
||||
|
||||
var createdContent = await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
return createdContent;
|
||||
}
|
||||
|
||||
private async Task<PublishResult> CreateDefaultPublishedContentWithTwoLanguages(string englishNodeName, string danishNodeName)
|
||||
{
|
||||
const string usIso = "en-US";
|
||||
const string dkIso = "da";
|
||||
|
||||
var langDa = new LanguageBuilder()
|
||||
.WithCultureInfo(dkIso)
|
||||
.Build();
|
||||
LocalizationService.Save(langDa);
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithId(0)
|
||||
.WithContentVariation(ContentVariation.Culture)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
var content = new ContentBuilder()
|
||||
.WithId(0)
|
||||
.WithCultureName(usIso, englishNodeName)
|
||||
.WithCultureName(dkIso, danishNodeName)
|
||||
.WithContentType(contentType)
|
||||
.Build();
|
||||
var createdContent = await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
return createdContent;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_Published_Content_With_Empty_Query()
|
||||
{
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
// Arrange
|
||||
const string contentName = "TestContent";
|
||||
await CreateDefaultPublishedContent(contentName);
|
||||
|
||||
string query = string.Empty;
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, actual.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_Published_Content_With_Query_By_Content_Name()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string contentName = "TestContent";
|
||||
await CreateDefaultPublishedContent(contentName);
|
||||
|
||||
string query = contentName;
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
IEnumerable<ISearchResult> searchResults = actual.ToArray();
|
||||
Assert.AreEqual(1, searchResults.Count());
|
||||
Assert.AreEqual(searchResults.First().Values["nodeName"], contentName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_Published_Content_With_Query_By_Non_Existing_Content_Name()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string contentName = "TestContent";
|
||||
await CreateDefaultPublishedContent(contentName);
|
||||
|
||||
string query = "ContentTest";
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, actual.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_Published_Content_With_Query_By_Content_Id()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string contentName = "RandomContentName";
|
||||
PublishResult createdContent = await CreateDefaultPublishedContent(contentName);
|
||||
|
||||
string contentId = createdContent.Content.Id.ToString();
|
||||
|
||||
string query = contentId;
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
IEnumerable<ISearchResult> searchResults = actual.ToArray();
|
||||
Assert.AreEqual(1, searchResults.Count());
|
||||
Assert.AreEqual(searchResults.First().Values["nodeName"], contentName);
|
||||
Assert.AreEqual(searchResults.First().Id, contentId);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_Two_Published_Content_With_Similar_Names_By_Name()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string contentName = "TestName Original";
|
||||
const string secondContentName = "TestName Copy";
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithId(0)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
var firstContent = new ContentBuilder()
|
||||
.WithId(0)
|
||||
.WithName(contentName)
|
||||
.WithContentType(contentType)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(firstContent), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
var secondContent = new ContentBuilder()
|
||||
.WithId(0)
|
||||
.WithName(secondContentName)
|
||||
.WithContentType(contentType)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(secondContent), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
string query = contentName;
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
IEnumerable<ISearchResult> searchResults = actual.ToArray();
|
||||
Assert.AreEqual(2, searchResults.Count());
|
||||
// Checks if the first content in the search is the original content
|
||||
Assert.AreEqual(searchResults.First().Id, firstContent.Id.ToString());
|
||||
// Checks if the score for the original name is higher than the score for the copy
|
||||
Assert.Greater(searchResults.First().Score, searchResults.Last().Score);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_For_Child_Published_Content_With_Query_By_Content_Name()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string contentName = "ParentTestContent";
|
||||
const string childContentName = "ChildTestContent";
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithName("Document")
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
var content = new ContentBuilder()
|
||||
.WithName(contentName)
|
||||
.WithContentType(contentType)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
var childContent = new ContentBuilder()
|
||||
.WithName(childContentName)
|
||||
.WithContentType(contentType)
|
||||
.WithParentId(content.Id)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(childContent), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
string parentQuery = content.Id.ToString();
|
||||
|
||||
string childQuery = childContent.Id.ToString();
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> parentContentActual = BackOfficeExamineSearch(parentQuery);
|
||||
IEnumerable<ISearchResult> childContentActual = BackOfficeExamineSearch(childQuery);
|
||||
|
||||
// Assert
|
||||
IEnumerable<ISearchResult> contentActual = parentContentActual.ToArray();
|
||||
IEnumerable<ISearchResult> searchResults = childContentActual.ToArray();
|
||||
Assert.AreEqual(1, contentActual.Count());
|
||||
Assert.AreEqual(1, searchResults.Count());
|
||||
Assert.AreEqual(contentActual.First().Values["nodeName"], contentName);
|
||||
Assert.AreEqual(searchResults.First().Values["nodeName"], childContentName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_For_Child_In_Child_Published_Content_With_Query_By_Content_Name()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string contentName = "ParentTestContent";
|
||||
const string childContentName = "ChildTestContent";
|
||||
const string childChildContentName = "ChildChildTestContent";
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithName("Document")
|
||||
.Build();
|
||||
ContentTypeService.Save(contentType);
|
||||
|
||||
var content = new ContentBuilder()
|
||||
.WithName(contentName)
|
||||
.WithContentType(contentType)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
var childContent = new ContentBuilder()
|
||||
.WithName(childContentName)
|
||||
.WithContentType(contentType)
|
||||
.WithParentId(content.Id)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(childContent), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
var childChildContent = new ContentBuilder()
|
||||
.WithName(childChildContentName)
|
||||
.WithContentType(contentType)
|
||||
.WithParentId(childContent.Id)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(childChildContent), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
string parentQuery = content.Id.ToString();
|
||||
string childQuery = childContent.Id.ToString();
|
||||
string childChildQuery = childChildContent.Id.ToString();
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> parentContentActual = BackOfficeExamineSearch(parentQuery);
|
||||
IEnumerable<ISearchResult> childContentActual = BackOfficeExamineSearch(childQuery);
|
||||
IEnumerable<ISearchResult> childChildContentActual = BackOfficeExamineSearch(childChildQuery);
|
||||
|
||||
IEnumerable<ISearchResult> parentSearchResults = parentContentActual.ToArray();
|
||||
IEnumerable<ISearchResult> childSearchResults = childContentActual.ToArray();
|
||||
IEnumerable<ISearchResult> childChildSearchResults = childChildContentActual.ToArray();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, parentSearchResults.Count());
|
||||
Assert.AreEqual(1, childSearchResults.Count());
|
||||
Assert.AreEqual(1, childChildSearchResults.Count());
|
||||
Assert.AreEqual(parentSearchResults.First().Values["nodeName"], contentName);
|
||||
Assert.AreEqual(childSearchResults.First().Values["nodeName"], childContentName);
|
||||
Assert.AreEqual(childChildSearchResults.First().Values["nodeName"], childChildContentName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_Published_Content_With_Query_With_Content_Name_No_User_Logged_In()
|
||||
{
|
||||
// Arrange
|
||||
const string contentName = "TestContent";
|
||||
|
||||
PublishResult createdContent = await CreateDefaultPublishedContent(contentName);
|
||||
|
||||
string query = createdContent.Content.Id.ToString();
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, actual.Count());
|
||||
}
|
||||
|
||||
// Multiple Languages
|
||||
[Test]
|
||||
public async Task Search_Published_Content_By_Content_Name_With_Two_Languages()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string usIso = "en-US";
|
||||
const string dkIso = "da";
|
||||
const string englishNodeName = "EnglishNode";
|
||||
const string danishNodeName = "DanishNode";
|
||||
|
||||
var langDa = new LanguageBuilder()
|
||||
.WithCultureInfo(dkIso)
|
||||
.Build();
|
||||
LocalizationService.Save(langDa);
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithId(0)
|
||||
.WithContentVariation(ContentVariation.Culture)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentTypeService.Save(contentType), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
var content = new ContentBuilder()
|
||||
.WithId(0)
|
||||
.WithCultureName(usIso, englishNodeName)
|
||||
.WithCultureName(dkIso, danishNodeName)
|
||||
.WithContentType(contentType)
|
||||
.Build();
|
||||
PublishResult createdContent = await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
string query = createdContent.Content.Id.ToString();
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
IEnumerable<ISearchResult> searchResults = actual.ToArray();
|
||||
Assert.AreEqual(1, searchResults.Count());
|
||||
var nodeNameEn = searchResults.First().Values["nodeName_en-us"];
|
||||
var nodeNameDa = searchResults.First().Values["nodeName_da"];
|
||||
Assert.AreEqual(englishNodeName, nodeNameEn);
|
||||
Assert.AreEqual(danishNodeName, nodeNameDa);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_For_Published_Content_Name_With_Two_Languages_By_Default_Language_Content_Name()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string englishNodeName = "EnglishNode";
|
||||
const string danishNodeName = "DanishNode";
|
||||
|
||||
await CreateDefaultPublishedContentWithTwoLanguages(englishNodeName, danishNodeName);
|
||||
|
||||
string query = englishNodeName;
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
IEnumerable<ISearchResult> searchResults = actual.ToArray();
|
||||
Assert.AreEqual(1, searchResults.Count());
|
||||
var nodeNameEn = searchResults.First().Values["nodeName_en-us"];
|
||||
var nodeNameDa = searchResults.First().Values["nodeName_da"];
|
||||
Assert.AreEqual(englishNodeName, nodeNameEn);
|
||||
Assert.AreEqual(danishNodeName, nodeNameDa);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_For_Published_Content_Name_With_Two_Languages_By_Non_Default_Language_Content_Name()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string englishNodeName = "EnglishNode";
|
||||
const string danishNodeName = "DanishNode";
|
||||
|
||||
await CreateDefaultPublishedContentWithTwoLanguages(englishNodeName, danishNodeName);
|
||||
|
||||
string query = danishNodeName;
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
IEnumerable<ISearchResult> searchResults = actual.ToArray();
|
||||
Assert.AreEqual(1, searchResults.Count());
|
||||
var nodeNameDa = searchResults.First().Values["nodeName_da"];
|
||||
var nodeNameEn = searchResults.First().Values["nodeName_en-us"];
|
||||
Assert.AreEqual(englishNodeName, nodeNameEn);
|
||||
Assert.AreEqual(danishNodeName, nodeNameDa);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_Published_Content_With_Two_Languages_By_Id()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string englishNodeName = "EnglishNode";
|
||||
const string danishNodeName = "DanishNode";
|
||||
|
||||
var contentNode = await CreateDefaultPublishedContentWithTwoLanguages(englishNodeName, danishNodeName);
|
||||
|
||||
string query = contentNode.Content.Id.ToString();
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
IEnumerable<ISearchResult> searchResults = actual.ToArray();
|
||||
Assert.AreEqual(1, searchResults.Count());
|
||||
var nodeNameDa = searchResults.First().Values["nodeName_da"];
|
||||
var nodeNameEn = searchResults.First().Values["nodeName_en-us"];
|
||||
Assert.AreEqual(englishNodeName, nodeNameEn);
|
||||
Assert.AreEqual(danishNodeName, nodeNameDa);
|
||||
}
|
||||
|
||||
// Check All Indexed Values
|
||||
[Test]
|
||||
public async Task Check_All_Indexed_Values_For_Published_Content_With_No_Properties()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string contentName = "TestContent";
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithId(0)
|
||||
.Build();
|
||||
ContentTypeService.Save(contentType);
|
||||
|
||||
var contentNode = new ContentBuilder()
|
||||
.WithId(0)
|
||||
.WithName(contentName)
|
||||
.WithContentType(contentType)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(contentNode), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
string query = contentName;
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
IEnumerable<ISearchResult> searchResults = actual.ToArray();
|
||||
Assert.AreEqual(1, searchResults.Count());
|
||||
|
||||
string contentNodePublish = string.Empty;
|
||||
if (contentNode.Published)
|
||||
{
|
||||
contentNodePublish = "y";
|
||||
}
|
||||
|
||||
string contentTypeCultureVariations = string.Empty;
|
||||
|
||||
if (contentType.Variations == ContentVariation.Nothing)
|
||||
{
|
||||
contentTypeCultureVariations = "n";
|
||||
}
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(searchResults.First().Values["__NodeId"], contentNode.Id.ToString());
|
||||
Assert.AreEqual(searchResults.First().Values["__IndexType"], "content");
|
||||
Assert.AreEqual(searchResults.First().Values["__NodeTypeAlias"], contentNode.ContentType.Alias);
|
||||
Assert.AreEqual(searchResults.First().Values["__Published"], contentNodePublish);
|
||||
Assert.AreEqual(searchResults.First().Values["id"], contentNode.Id.ToString());
|
||||
Assert.AreEqual(searchResults.First().Values["__Key"], contentNode.Key.ToString());
|
||||
Assert.AreEqual(searchResults.First().Values["parentID"], contentNode.ParentId.ToString());
|
||||
Assert.AreEqual(searchResults.First().Values["nodeName"], contentNode.Name);
|
||||
Assert.AreEqual(searchResults.First().Values["__VariesByCulture"], contentTypeCultureVariations);
|
||||
Assert.AreEqual(searchResults.First().Values["__Icon"], contentNode.ContentType.Icon);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Check_All_Indexed_Values_For_Published_Content_With_Properties()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
const string contentName = "TestContent";
|
||||
const string propertyEditorName = "TestBox";
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithId(0)
|
||||
.AddPropertyType()
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox)
|
||||
.WithName(propertyEditorName)
|
||||
.WithAlias("testBox")
|
||||
.Done()
|
||||
.Build();
|
||||
ContentTypeService.Save(contentType);
|
||||
|
||||
var contentNode = new ContentBuilder()
|
||||
.WithId(0)
|
||||
.WithName(contentName)
|
||||
.WithContentType(contentType)
|
||||
.WithPropertyValues(new { testBox = "TestValue" })
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(contentNode), Constants.UmbracoIndexes.InternalIndexName);
|
||||
|
||||
string query = contentName;
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = BackOfficeExamineSearch(query);
|
||||
|
||||
// Assert
|
||||
IEnumerable<ISearchResult> searchResults = actual.ToArray();
|
||||
Assert.AreEqual(1, searchResults.Count());
|
||||
|
||||
string contentNodePublish = string.Empty;
|
||||
string contentTypeCultureVariations = string.Empty;
|
||||
|
||||
if (contentNode.Published)
|
||||
{
|
||||
contentNodePublish = "y";
|
||||
}
|
||||
|
||||
if (contentType.Variations == ContentVariation.Nothing)
|
||||
{
|
||||
contentTypeCultureVariations = "n";
|
||||
}
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(searchResults.First().Values["__NodeId"], contentNode.Id.ToString());
|
||||
Assert.AreEqual(searchResults.First().Values["__IndexType"], "content");
|
||||
Assert.AreEqual(searchResults.First().Values["__NodeTypeAlias"], contentNode.ContentType.Alias);
|
||||
Assert.AreEqual(searchResults.First().Values["__Published"], contentNodePublish);
|
||||
Assert.AreEqual(searchResults.First().Values["id"], contentNode.Id.ToString());
|
||||
Assert.AreEqual(searchResults.First().Values["__Key"], contentNode.Key.ToString());
|
||||
Assert.AreEqual(searchResults.First().Values["parentID"], contentNode.ParentId.ToString());
|
||||
Assert.AreEqual(searchResults.First().Values["nodeName"], contentNode.Name);
|
||||
Assert.AreEqual(searchResults.First().Values["__VariesByCulture"], contentTypeCultureVariations);
|
||||
Assert.AreEqual(searchResults.First().Values["__Icon"], contentNode.ContentType.Icon);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Data;
|
||||
using Examine;
|
||||
using Examine.Lucene.Providers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
@@ -25,6 +26,8 @@ public abstract class ExamineBaseTest : UmbracoIntegrationTest
|
||||
|
||||
protected IRuntimeState RunningRuntimeState { get; } = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run);
|
||||
|
||||
protected IExamineManager ExamineManager => GetRequiredService<IExamineManager>();
|
||||
|
||||
protected override void ConfigureTestServices(IServiceCollection services)
|
||||
=> services.AddSingleton<IndexInitializer>();
|
||||
|
||||
@@ -113,6 +116,43 @@ public abstract class ExamineBaseTest : UmbracoIntegrationTest
|
||||
return new DisposableWrapper(syncMode, index, luceneDir);
|
||||
}
|
||||
|
||||
private AutoResetEvent indexingHandle = new(false);
|
||||
|
||||
protected async Task ExecuteAndWaitForIndexing(Action indexUpdatingAction, string indexName) =>
|
||||
await ExecuteAndWaitForIndexing<int?>(
|
||||
() =>
|
||||
{
|
||||
indexUpdatingAction();
|
||||
return null;
|
||||
}, indexName);
|
||||
|
||||
/// <summary>
|
||||
/// Performs and action and waits for the specified index to be done indexing.
|
||||
/// </summary>
|
||||
/// <param name="indexUpdatingAction">The action that causes the index to be updated.</param>
|
||||
/// <param name="indexName">The name of the index to wait for rebuild.</param>
|
||||
/// <typeparam name="T">The type returned from the action.</typeparam>
|
||||
/// <returns>The result of the action.</returns>
|
||||
protected async Task<T> ExecuteAndWaitForIndexing<T> (Func<T> indexUpdatingAction, string indexName)
|
||||
{
|
||||
// Set up an action to release the handle when the index is populated.
|
||||
if (ExamineManager.TryGetIndex(indexName, out IIndex index) is false)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not find index: {indexName}");
|
||||
}
|
||||
|
||||
index.IndexOperationComplete += (_, _) =>
|
||||
{
|
||||
indexingHandle.Set();
|
||||
};
|
||||
|
||||
// Perform the action, and wait for the handle to be freed, meaning the index is done populating.
|
||||
var result = indexUpdatingAction();
|
||||
await indexingHandle.WaitOneAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private class DisposableWrapper : IDisposable
|
||||
{
|
||||
private readonly IDisposable[] _disposables;
|
||||
@@ -127,4 +167,10 @@ public abstract class ExamineBaseTest : UmbracoIntegrationTest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected string GetIndexPath(string indexName)
|
||||
{
|
||||
var root = TestContext.CurrentContext.TestDirectory.Split("Umbraco.Tests.Integration")[0];
|
||||
return Path.Combine(root, "Umbraco.Tests.Integration", "umbraco", "Data", "TEMP", "ExamineIndexes", indexName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Examine;
|
||||
using Examine.Search;
|
||||
using Lucene.Net.QueryParsers.Classic;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Examine;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine;
|
||||
|
||||
public class ExamineExternalIndexSearcherTest : IExamineExternalIndexSearcherTest
|
||||
{
|
||||
private readonly AppCaches _appCaches;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly IExamineManager _examineManager;
|
||||
private readonly ILocalizationService _languageService;
|
||||
private readonly IPublishedUrlProvider _publishedUrlProvider;
|
||||
private readonly IUmbracoTreeSearcherFields _treeSearcherFields;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public ExamineExternalIndexSearcherTest(
|
||||
IExamineManager examineManager,
|
||||
ILocalizationService languageService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IEntityService entityService,
|
||||
IUmbracoTreeSearcherFields treeSearcherFields,
|
||||
AppCaches appCaches,
|
||||
IUmbracoMapper umbracoMapper,
|
||||
IPublishedUrlProvider publishedUrlProvider)
|
||||
{
|
||||
_examineManager = examineManager;
|
||||
_languageService = languageService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_entityService = entityService;
|
||||
_treeSearcherFields = treeSearcherFields;
|
||||
_appCaches = appCaches;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_publishedUrlProvider = publishedUrlProvider;
|
||||
}
|
||||
|
||||
public IEnumerable<ISearchResult> Search(
|
||||
string query,
|
||||
UmbracoEntityTypes entityType,
|
||||
int pageSize,
|
||||
long pageIndex,
|
||||
out long totalFound,
|
||||
string? searchFrom = null,
|
||||
bool ignoreUserStartNodes = false)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
string type;
|
||||
var indexName = Constants.UmbracoIndexes.ExternalIndexName;
|
||||
var fields = _treeSearcherFields.GetBackOfficeFields().ToList();
|
||||
|
||||
ISet<string> fieldsToLoad = new HashSet<string>(_treeSearcherFields.GetBackOfficeFieldsToLoad());
|
||||
|
||||
// TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string
|
||||
// manipulation for things like start paths, member types, etc...
|
||||
//if (Examine.ExamineExtensions.TryParseLuceneQuery(query))
|
||||
//{
|
||||
|
||||
//}
|
||||
|
||||
//special GUID check since if a user searches on one specifically we need to escape it
|
||||
if (Guid.TryParse(query, out Guid g))
|
||||
{
|
||||
query = "\"" + g + "\"";
|
||||
}
|
||||
|
||||
IUser? currentUser = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser;
|
||||
|
||||
switch (entityType)
|
||||
{
|
||||
case UmbracoEntityTypes.Member:
|
||||
indexName = Constants.UmbracoIndexes.MembersIndexName;
|
||||
type = "member";
|
||||
fields.AddRange(_treeSearcherFields.GetBackOfficeMembersFields());
|
||||
foreach (var field in _treeSearcherFields.GetBackOfficeMembersFieldsToLoad())
|
||||
{
|
||||
fieldsToLoad.Add(field);
|
||||
}
|
||||
|
||||
if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId &&
|
||||
searchFrom.Trim() != "-1")
|
||||
{
|
||||
sb.Append("+__NodeTypeAlias:");
|
||||
sb.Append(searchFrom);
|
||||
sb.Append(" ");
|
||||
}
|
||||
|
||||
break;
|
||||
case UmbracoEntityTypes.Media:
|
||||
type = "media";
|
||||
fields.AddRange(_treeSearcherFields.GetBackOfficeMediaFields());
|
||||
foreach (var field in _treeSearcherFields.GetBackOfficeMediaFieldsToLoad())
|
||||
{
|
||||
fieldsToLoad.Add(field);
|
||||
}
|
||||
|
||||
var allMediaStartNodes = currentUser != null
|
||||
? currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches)
|
||||
: Array.Empty<int>();
|
||||
AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService);
|
||||
break;
|
||||
case UmbracoEntityTypes.Document:
|
||||
type = "content";
|
||||
fields.AddRange(_treeSearcherFields.GetBackOfficeDocumentFields());
|
||||
foreach (var field in _treeSearcherFields.GetBackOfficeDocumentFieldsToLoad())
|
||||
{
|
||||
fieldsToLoad.Add(field);
|
||||
}
|
||||
|
||||
var allContentStartNodes = currentUser != null
|
||||
? currentUser.CalculateContentStartNodeIds(_entityService, _appCaches)
|
||||
: Array.Empty<int>();
|
||||
AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("The " + typeof(BackOfficeExamineSearcher) +
|
||||
" currently does not support searching against object type " +
|
||||
entityType);
|
||||
}
|
||||
|
||||
if (!_examineManager.TryGetIndex(indexName, out IIndex? index))
|
||||
{
|
||||
throw new InvalidOperationException("No index found by name " + indexName);
|
||||
}
|
||||
|
||||
if (!BuildQuery(sb, query, searchFrom, fields, type))
|
||||
{
|
||||
totalFound = 0;
|
||||
return Enumerable.Empty<ISearchResult>();
|
||||
}
|
||||
|
||||
ISearchResults? result = index.Searcher
|
||||
.CreateQuery()
|
||||
.NativeQuery(sb.ToString())
|
||||
.SelectFields(fieldsToLoad)
|
||||
//only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested
|
||||
.Execute(QueryOptions.SkipTake(Convert.ToInt32(pageSize * pageIndex), pageSize));
|
||||
|
||||
totalFound = result.TotalItemCount;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool BuildQuery(StringBuilder sb, string query, string? searchFrom, List<string> fields, string type)
|
||||
{
|
||||
//build a lucene query:
|
||||
// the nodeName will be boosted 10x without wildcards
|
||||
// then nodeName will be matched normally with wildcards
|
||||
// the rest will be normal without wildcards
|
||||
|
||||
var allLangs = _languageService.GetAllLanguages().Select(x => x.IsoCode.ToLowerInvariant()).ToList();
|
||||
|
||||
// the chars [*-_] in the query will mess everything up so let's remove those
|
||||
// However we cannot just remove - and _ since these signify a space, so we instead replace them with that.
|
||||
query = Regex.Replace(query, "[\\*]", string.Empty);
|
||||
query = Regex.Replace(query, "[\\-_]", " ");
|
||||
|
||||
|
||||
//check if text is surrounded by single or double quotes, if so, then exact match
|
||||
var surroundedByQuotes = Regex.IsMatch(query, "^\".*?\"$")
|
||||
|| Regex.IsMatch(query, "^\'.*?\'$");
|
||||
|
||||
if (surroundedByQuotes)
|
||||
{
|
||||
//strip quotes, escape string, the replace again
|
||||
query = query.Trim(Constants.CharArrays.DoubleQuoteSingleQuote);
|
||||
|
||||
query = QueryParserBase.Escape(query);
|
||||
|
||||
//nothing to search
|
||||
if (searchFrom.IsNullOrWhiteSpace() && query.IsNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//update the query with the query term
|
||||
if (query.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//add back the surrounding quotes
|
||||
query = string.Format("{0}{1}{0}", "\"", query);
|
||||
|
||||
sb.Append("+(");
|
||||
|
||||
AppendNodeNamePhraseWithBoost(sb, query, allLangs);
|
||||
|
||||
foreach (var f in fields)
|
||||
{
|
||||
//additional fields normally
|
||||
sb.Append(f);
|
||||
sb.Append(": (");
|
||||
sb.Append(query);
|
||||
sb.Append(") ");
|
||||
}
|
||||
|
||||
sb.Append(") ");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var trimmed = query.Trim(Constants.CharArrays.DoubleQuoteSingleQuote);
|
||||
|
||||
//nothing to search
|
||||
if (searchFrom.IsNullOrWhiteSpace() && trimmed.IsNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//update the query with the query term
|
||||
if (trimmed.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
query = QueryParserBase.Escape(query);
|
||||
|
||||
var querywords = query.Split(Constants.CharArrays.Space, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
sb.Append("+(");
|
||||
|
||||
AppendNodeNameExactWithBoost(sb, query, allLangs);
|
||||
|
||||
AppendNodeNameWithWildcards(sb, querywords, allLangs);
|
||||
|
||||
foreach (var f in fields)
|
||||
{
|
||||
var queryWordsReplaced = new string[querywords.Length];
|
||||
|
||||
// when searching file names containing hyphens we need to replace the hyphens with spaces
|
||||
if (f.Equals(UmbracoExamineFieldNames.UmbracoFileFieldName))
|
||||
{
|
||||
for (var index = 0; index < querywords.Length; index++)
|
||||
{
|
||||
queryWordsReplaced[index] =
|
||||
querywords[index].Replace("\\-", " ").Replace("_", " ").Trim(" ");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queryWordsReplaced = querywords;
|
||||
}
|
||||
|
||||
//additional fields normally
|
||||
sb.Append(f);
|
||||
sb.Append(":");
|
||||
sb.Append("(");
|
||||
foreach (var w in queryWordsReplaced)
|
||||
{
|
||||
sb.Append(w.ToLower());
|
||||
sb.Append("* ");
|
||||
}
|
||||
|
||||
sb.Append(")");
|
||||
sb.Append(" ");
|
||||
}
|
||||
|
||||
sb.Append(") ");
|
||||
}
|
||||
}
|
||||
|
||||
//must match index type
|
||||
sb.Append("+__IndexType:");
|
||||
sb.Append(type);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AppendNodeNamePhraseWithBoost(StringBuilder sb, string query, IEnumerable<string> allLangs)
|
||||
{
|
||||
//node name exactly boost x 10
|
||||
sb.Append("nodeName: (");
|
||||
sb.Append(query.ToLower());
|
||||
sb.Append(")^10.0 ");
|
||||
|
||||
//also search on all variant node names
|
||||
foreach (var lang in allLangs)
|
||||
{
|
||||
//node name exactly boost x 10
|
||||
sb.Append($"nodeName_{lang}: (");
|
||||
sb.Append(query.ToLower());
|
||||
sb.Append(")^10.0 ");
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendNodeNameExactWithBoost(StringBuilder sb, string query, IEnumerable<string> allLangs)
|
||||
{
|
||||
//node name exactly boost x 10
|
||||
sb.Append("nodeName:");
|
||||
sb.Append("\"");
|
||||
sb.Append(query.ToLower());
|
||||
sb.Append("\"");
|
||||
sb.Append("^10.0 ");
|
||||
//also search on all variant node names
|
||||
foreach (var lang in allLangs)
|
||||
{
|
||||
//node name exactly boost x 10
|
||||
sb.Append($"nodeName_{lang}:");
|
||||
sb.Append("\"");
|
||||
sb.Append(query.ToLower());
|
||||
sb.Append("\"");
|
||||
sb.Append("^10.0 ");
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendNodeNameWithWildcards(StringBuilder sb, string[] querywords, IEnumerable<string> allLangs)
|
||||
{
|
||||
//node name normally with wildcards
|
||||
sb.Append("nodeName:");
|
||||
sb.Append("(");
|
||||
foreach (var w in querywords)
|
||||
{
|
||||
sb.Append(w.ToLower());
|
||||
sb.Append("* ");
|
||||
}
|
||||
|
||||
sb.Append(") ");
|
||||
//also search on all variant node names
|
||||
foreach (var lang in allLangs)
|
||||
{
|
||||
//node name normally with wildcards
|
||||
sb.Append($"nodeName_{lang}:");
|
||||
sb.Append("(");
|
||||
foreach (var w in querywords)
|
||||
{
|
||||
sb.Append(w.ToLower());
|
||||
sb.Append("* ");
|
||||
}
|
||||
|
||||
sb.Append(") ");
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[]? startNodeIds, string? searchFrom, bool ignoreUserStartNodes, IEntityService entityService)
|
||||
{
|
||||
if (sb == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sb));
|
||||
}
|
||||
|
||||
if (entityService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entityService));
|
||||
}
|
||||
|
||||
UdiParser.TryParse(searchFrom, true, out Udi? udi);
|
||||
searchFrom = udi == null ? searchFrom : entityService.GetId(udi).Result.ToString();
|
||||
|
||||
TreeEntityPath? entityPath =
|
||||
int.TryParse(searchFrom, NumberStyles.Integer, CultureInfo.InvariantCulture, out var searchFromId) &&
|
||||
searchFromId > 0
|
||||
? entityService.GetAllPaths(objectType, searchFromId).FirstOrDefault()
|
||||
: null;
|
||||
if (entityPath != null)
|
||||
{
|
||||
// find... only what's underneath
|
||||
sb.Append("+__Path:");
|
||||
AppendPath(sb, entityPath.Path, false);
|
||||
sb.Append(" ");
|
||||
}
|
||||
else if (startNodeIds?.Length == 0)
|
||||
{
|
||||
// make sure we don't find anything
|
||||
sb.Append("+__Path:none ");
|
||||
}
|
||||
else if (startNodeIds?.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction
|
||||
{
|
||||
IEnumerable<TreeEntityPath> entityPaths = entityService.GetAllPaths(objectType, startNodeIds);
|
||||
|
||||
// for each start node, find the start node, and what's underneath
|
||||
// +__Path:(-1*,1234 -1*,1234,* -1*,5678 -1*,5678,* ...)
|
||||
sb.Append("+__Path:(");
|
||||
var first = true;
|
||||
foreach (TreeEntityPath ep in entityPaths)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(" ");
|
||||
}
|
||||
|
||||
AppendPath(sb, ep.Path, true);
|
||||
}
|
||||
|
||||
sb.Append(") ");
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendPath(StringBuilder sb, string path, bool includeThisNode)
|
||||
{
|
||||
path = path.Replace("-", "\\-").Replace(",", "\\,");
|
||||
if (includeThisNode)
|
||||
{
|
||||
sb.Append(path);
|
||||
sb.Append(" ");
|
||||
}
|
||||
|
||||
sb.Append(path);
|
||||
sb.Append("\\,*");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using Examine;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Sync;
|
||||
using Umbraco.Cms.Infrastructure.Examine.DependencyInjection;
|
||||
using Umbraco.Cms.Infrastructure.HostedServices;
|
||||
using Umbraco.Cms.Infrastructure.Search;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
|
||||
using Umbraco.Cms.Web.BackOffice.Security;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine;
|
||||
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
public class ExamineExternalIndexTests : ExamineBaseTest
|
||||
{
|
||||
private const string ContentName = "TestContent";
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.InternalIndexName));
|
||||
TestHelper.DeleteDirectory(GetIndexPath(Constants.UmbracoIndexes.ExternalIndexName));
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.RequestServices = Services;
|
||||
Mock.Get(TestHelper.GetHttpContextAccessor()).Setup(x => x.HttpContext).Returns(httpContext);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// When disposing examine, it does a final write, which ends up locking the file if the indexing is not done yet. So we have this wait to circumvent that.
|
||||
Thread.Sleep(1500);
|
||||
// Sometimes we do not dispose all services in time and the test fails because the log file is locked. Resulting in all other tests failing as well
|
||||
Services.DisposeIfDisposable();
|
||||
}
|
||||
|
||||
private IExamineExternalIndexSearcherTest ExamineExternalIndexSearcher =>
|
||||
GetRequiredService<IExamineExternalIndexSearcherTest>();
|
||||
|
||||
private IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();
|
||||
|
||||
private ContentService ContentService => (ContentService)GetRequiredService<IContentService>();
|
||||
|
||||
private IUserStore<BackOfficeIdentityUser> BackOfficeUserStore =>
|
||||
GetRequiredService<IUserStore<BackOfficeIdentityUser>>();
|
||||
|
||||
private IBackOfficeSignInManager BackOfficeSignInManager => GetRequiredService<IBackOfficeSignInManager>();
|
||||
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddUnique<IExamineExternalIndexSearcherTest, ExamineExternalIndexSearcherTest>();
|
||||
builder.Services.AddUnique<IServerMessenger, ContentEventsTests.LocalServerMessenger>();
|
||||
builder
|
||||
.AddNotificationHandler<ContentTreeChangeNotification,
|
||||
ContentTreeChangeDistributedCacheNotificationHandler>();
|
||||
builder.AddNotificationHandler<ContentCacheRefresherNotification, ContentIndexingNotificationHandler>();
|
||||
builder.AddExamineIndexes();
|
||||
builder.AddBackOfficeIdentity();
|
||||
builder.Services.AddHostedService<QueuedHostedService>();
|
||||
}
|
||||
|
||||
private IEnumerable<ISearchResult> ExamineExternalIndexSearch(string query, int pageSize = 20, int pageIndex = 0) =>
|
||||
ExamineExternalIndexSearcher.Search(query, UmbracoEntityTypes.Document,
|
||||
pageSize, pageIndex, out _, ignoreUserStartNodes: true);
|
||||
|
||||
private async Task SetupUserIdentity(string userId)
|
||||
{
|
||||
var identity =
|
||||
await BackOfficeUserStore.FindByIdAsync(userId, CancellationToken.None);
|
||||
await BackOfficeSignInManager.SignInAsync(identity, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_Published_Content_With_Query_By_Content_Name()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithId(0)
|
||||
.Build();
|
||||
ContentTypeService.Save(contentType);
|
||||
|
||||
var content = new ContentBuilder()
|
||||
.WithId(0)
|
||||
.WithName(ContentName)
|
||||
.WithContentType(contentType)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentService.SaveAndPublish(content), Constants.UmbracoIndexes.ExternalIndexName);
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = ExamineExternalIndexSearch(ContentName);
|
||||
|
||||
// Assert
|
||||
IEnumerable<ISearchResult> searchResults = actual.ToArray();
|
||||
Assert.AreEqual(1, searchResults.Count());
|
||||
Assert.AreEqual(searchResults.First().Values["nodeName"], ContentName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_Unpublished_Content_With_Query_By_Content_Name()
|
||||
{
|
||||
// Arrange
|
||||
await SetupUserIdentity(Constants.Security.SuperUserIdAsString);
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithId(0)
|
||||
.Build();
|
||||
ContentTypeService.Save(contentType);
|
||||
|
||||
var content = new ContentBuilder()
|
||||
.WithId(0)
|
||||
.WithName(ContentName)
|
||||
.WithContentType(contentType)
|
||||
.Build();
|
||||
await ExecuteAndWaitForIndexing(() => ContentService.Save(content), Constants.UmbracoIndexes.ExternalIndexName);
|
||||
|
||||
// Act
|
||||
IEnumerable<ISearchResult> actual = ExamineExternalIndexSearch(ContentName);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, actual.Count());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Examine;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine;
|
||||
|
||||
public interface IExamineExternalIndexSearcherTest
|
||||
{
|
||||
|
||||
IEnumerable<ISearchResult> Search(
|
||||
string query,
|
||||
UmbracoEntityTypes entityType,
|
||||
int pageSize,
|
||||
long pageIndex,
|
||||
out long totalFound,
|
||||
string? searchFrom = null,
|
||||
bool ignoreUserStartNodes = false);
|
||||
|
||||
}
|
||||
@@ -19,7 +19,6 @@ using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.Examine;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Directory = Lucene.Net.Store.Directory;
|
||||
using StaticServiceProvider = Umbraco.Cms.Core.DependencyInjection.StaticServiceProvider;
|
||||
|
||||
@@ -37,6 +36,7 @@ public class IndexInitializer
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
public IndexInitializer(
|
||||
IShortStringHelper shortStringHelper,
|
||||
@@ -45,7 +45,8 @@ public class IndexInitializer
|
||||
IScopeProvider scopeProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<ContentSettings> contentSettings,
|
||||
ILocalizationService localizationService)
|
||||
ILocalizationService localizationService,
|
||||
IContentTypeService contentTypeService)
|
||||
{
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_propertyEditors = propertyEditors;
|
||||
@@ -54,6 +55,7 @@ public class IndexInitializer
|
||||
_loggerFactory = loggerFactory;
|
||||
_contentSettings = contentSettings;
|
||||
_localizationService = localizationService;
|
||||
_contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
public IndexInitializer(
|
||||
@@ -62,7 +64,7 @@ public class IndexInitializer
|
||||
MediaUrlGeneratorCollection mediaUrlGenerators,
|
||||
IScopeProvider scopeProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<ContentSettings> contentSettings)
|
||||
IOptions<ContentSettings> contentSettings, IContentTypeService contentTypeService)
|
||||
: this(
|
||||
shortStringHelper,
|
||||
propertyEditors,
|
||||
@@ -70,7 +72,7 @@ public class IndexInitializer
|
||||
scopeProvider,
|
||||
loggerFactory,
|
||||
contentSettings,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>())
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>(), contentTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -83,7 +85,9 @@ public class IndexInitializer
|
||||
_shortStringHelper,
|
||||
_scopeProvider,
|
||||
publishedValuesOnly,
|
||||
_localizationService);
|
||||
_localizationService,
|
||||
_contentTypeService,
|
||||
_loggerFactory.CreateLogger<ContentValueSetBuilder>());
|
||||
|
||||
return contentValueSetBuilder;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user