Merge branch 'temp8' into temp8-backoffice-search-with-variants
# Conflicts: # src/Umbraco.Examine/ExamineExtensions.cs
This commit is contained in:
@@ -42,6 +42,17 @@ namespace Umbraco.Core.Composing
|
||||
private static LocalTempStorage _localTempStorage = LocalTempStorage.Unknown;
|
||||
private static string _fileBasePath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypeLoader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="runtimeCache">The application runtime cache.</param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="logger">A profiling logger.</param>
|
||||
/// <remarks>Used by LightInject.</remarks>
|
||||
public TypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, ProfilingLogger logger)
|
||||
: this(runtimeCache, globalSettings, logger, true)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypeLoader"/> class.
|
||||
/// </summary>
|
||||
@@ -49,7 +60,7 @@ namespace Umbraco.Core.Composing
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="logger">A profiling logger.</param>
|
||||
/// <param name="detectChanges">Whether to detect changes using hashes.</param>
|
||||
internal TypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, ProfilingLogger logger, bool detectChanges = true)
|
||||
internal TypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, ProfilingLogger logger, bool detectChanges)
|
||||
{
|
||||
_runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache));
|
||||
_globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings));
|
||||
@@ -401,39 +412,6 @@ namespace Umbraco.Core.Composing
|
||||
return _fileBasePath;
|
||||
}
|
||||
|
||||
//private string GetFilePath(string extension)
|
||||
//{
|
||||
// string path;
|
||||
// switch (_globalSettings.LocalTempStorageLocation)
|
||||
// {
|
||||
// case LocalTempStorage.AspNetTemp:
|
||||
// path = Path.Combine(HttpRuntime.CodegenDir, "UmbracoData", "umbraco-types." + extension);
|
||||
// break;
|
||||
// case LocalTempStorage.EnvironmentTemp:
|
||||
// // include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
|
||||
// // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
|
||||
// // utilizing an old path - assuming we cannot have SHA1 collisions on AppDomainAppId
|
||||
// var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1();
|
||||
// var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", appDomainHash);
|
||||
// path = Path.Combine(cachePath, "umbraco-types." + extension);
|
||||
// break;
|
||||
// case LocalTempStorage.Default:
|
||||
// default:
|
||||
// var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/TypesCache");
|
||||
// path = Path.Combine(tempFolder, "umbraco-types." + NetworkHelper.FileSafeMachineName + "." + extension);
|
||||
// break;
|
||||
// }
|
||||
|
||||
// // ensure that the folder exists
|
||||
// var directory = Path.GetDirectoryName(path);
|
||||
// if (directory == null)
|
||||
// throw new InvalidOperationException($"Could not determine folder for file \"{path}\".");
|
||||
// if (Directory.Exists(directory) == false)
|
||||
// Directory.CreateDirectory(directory);
|
||||
|
||||
// return path;
|
||||
//}
|
||||
|
||||
// internal for tests
|
||||
internal void WriteCache()
|
||||
{
|
||||
|
||||
@@ -202,7 +202,6 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
private static bool IsTimeoutThreadAbortException(Exception exception)
|
||||
{
|
||||
if (!(exception is ThreadAbortException abort)) return false;
|
||||
|
||||
if (abort.ExceptionState == null) return false;
|
||||
|
||||
var stateType = abort.ExceptionState.GetType();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using NPoco;
|
||||
using Umbraco.Core.Migrations.Expressions.Common;
|
||||
using Umbraco.Core.Migrations.Expressions.Execute.Expressions;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Expressions.Execute
|
||||
{
|
||||
@@ -12,7 +13,16 @@ namespace Umbraco.Core.Migrations.Expressions.Execute
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Do() => Expression.Execute();
|
||||
public void Do()
|
||||
{
|
||||
// slightly awkward, but doing it right would mean a *lot*
|
||||
// of changes for MigrationExpressionBase
|
||||
|
||||
if (Expression.SqlObject == null)
|
||||
Expression.Execute();
|
||||
else
|
||||
Expression.ExecuteSqlObject();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IExecutableBuilder Sql(string sqlStatement)
|
||||
@@ -20,5 +30,12 @@ namespace Umbraco.Core.Migrations.Expressions.Execute
|
||||
Expression.SqlStatement = sqlStatement;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IExecutableBuilder Sql(Sql<ISqlContext> sql)
|
||||
{
|
||||
Expression.SqlObject = sql;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -1,4 +1,5 @@
|
||||
using NPoco;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Expressions.Execute.Expressions
|
||||
{
|
||||
@@ -10,6 +11,13 @@ namespace Umbraco.Core.Migrations.Expressions.Execute.Expressions
|
||||
|
||||
public virtual string SqlStatement { get; set; }
|
||||
|
||||
public virtual Sql<ISqlContext> SqlObject { get; set; }
|
||||
|
||||
public void ExecuteSqlObject()
|
||||
{
|
||||
Execute(SqlObject);
|
||||
}
|
||||
|
||||
protected override string GetSql()
|
||||
{
|
||||
return SqlStatement;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Umbraco.Core.Migrations.Expressions.Common;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Migrations.Expressions.Common;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Expressions.Execute
|
||||
{
|
||||
@@ -12,5 +14,10 @@ namespace Umbraco.Core.Migrations.Expressions.Execute
|
||||
/// Specifies the Sql statement to execute.
|
||||
/// </summary>
|
||||
IExecutableBuilder Sql(string sqlStatement);
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the Sql statement to execute.
|
||||
/// </summary>
|
||||
IExecutableBuilder Sql(Sql<ISqlContext> sql);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,56 +534,11 @@ namespace Umbraco.Core.Migrations.Install
|
||||
|
||||
_logger.Info<DatabaseBuilder>("Database upgrade started");
|
||||
|
||||
//var database = scope.Database;
|
||||
//var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
|
||||
|
||||
var message = GetResultMessageForMySql();
|
||||
|
||||
// fixme - remove this code
|
||||
//var schemaResult = ValidateDatabaseSchema();
|
||||
//
|
||||
//var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion());
|
||||
//var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService);
|
||||
//var targetVersion = UmbracoVersion.Current;
|
||||
//
|
||||
////In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will
|
||||
//// return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the
|
||||
//// migrations are run for the target version
|
||||
//if (installedMigrationVersion == new SemVersion(new Version(0, 0, 0)) && installedSchemaVersion > new SemVersion(new Version(0, 0, 0)))
|
||||
//{
|
||||
// //set the installedMigrationVersion to be one less than the target so the latest migrations are guaranteed to execute
|
||||
// installedMigrationVersion = new SemVersion(targetVersion.SubtractRevision());
|
||||
//}
|
||||
//
|
||||
////Figure out what our current installed version is. If the web.config doesn't have a version listed, then we'll use the minimum
|
||||
//// version detected between the schema installed and the migrations listed in the migration table.
|
||||
//// If there is a version in the web.config, we'll take the minimum between the listed migration in the db and what
|
||||
//// is declared in the web.config.
|
||||
//
|
||||
//var currentInstalledVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus)
|
||||
// //Take the minimum version between the detected schema version and the installed migration version
|
||||
// ? new[] { installedSchemaVersion, installedMigrationVersion }.Min()
|
||||
// //Take the minimum version between the installed migration version and the version specified in the config
|
||||
// : new[] { SemVersion.Parse(GlobalSettings.ConfigurationStatus), installedMigrationVersion }.Min();
|
||||
//
|
||||
////Ok, another edge case here. If the current version is a pre-release,
|
||||
//// then we want to ensure all migrations for the current release are executed.
|
||||
//if (currentInstalledVersion.Prerelease.IsNullOrWhiteSpace() == false)
|
||||
//{
|
||||
// currentInstalledVersion = new SemVersion(currentInstalledVersion.GetVersion().SubtractRevision());
|
||||
//}
|
||||
|
||||
// upgrade
|
||||
var upgrader = new UmbracoUpgrader(_scopeProvider, _migrationBuilder, _keyValueService, _postMigrations, _logger);
|
||||
upgrader.Execute();
|
||||
|
||||
// fixme remove this code
|
||||
//var runner = new MigrationRunner(_scopeProvider, builder, migrationEntryService, _logger, currentInstalledVersion, UmbracoVersion.SemanticVersion, Constants.System.UmbracoMigrationName);
|
||||
//var upgraded = runner.Execute(/*upgrade:true*/);
|
||||
//if (upgraded == false)
|
||||
//{
|
||||
// throw new ApplicationException("Upgrading failed, either an error occurred during the upgrade process or an event canceled the upgrade process, see log for full details");
|
||||
//}
|
||||
var upgrader = new UmbracoUpgrader();
|
||||
upgrader.Execute(_scopeProvider, _migrationBuilder, _keyValueService, _logger, _postMigrations);
|
||||
|
||||
message = message + "<p>Upgrade completed!</p>";
|
||||
|
||||
|
||||
@@ -321,9 +321,9 @@ namespace Umbraco.Core.Migrations.Install
|
||||
{
|
||||
// on install, initialize the umbraco migration plan with the final state
|
||||
|
||||
var plan = new UmbracoPlan();
|
||||
var stateValueKey = Upgrader.GetStateValueKey(plan);
|
||||
var finalState = plan.FinalState;
|
||||
var upgrader = new UmbracoUpgrader();
|
||||
var stateValueKey = upgrader.StateValueKey;
|
||||
var finalState = upgrader.Plan.FinalState;
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.KeyValue, "key", false, new KeyValueDto { Key = stateValueKey, Value = finalState, Updated = DateTime.Now });
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -88,6 +87,31 @@ namespace Umbraco.Core.Migrations
|
||||
expression.Execute();
|
||||
}
|
||||
|
||||
protected void Execute(Sql<ISqlContext> sql)
|
||||
{
|
||||
if (_executed)
|
||||
throw new InvalidOperationException("This expression has already been executed.");
|
||||
_executed = true;
|
||||
|
||||
if (sql == null)
|
||||
{
|
||||
Logger.Info(GetType(), $"SQL [{Context.Index}]: <empty>");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info(GetType(), $"SQL [{Context.Index}]: {sql.ToText()}");
|
||||
Database.Execute(sql);
|
||||
}
|
||||
|
||||
Context.Index++;
|
||||
|
||||
if (_expressions == null)
|
||||
return;
|
||||
|
||||
foreach (var expression in _expressions)
|
||||
expression.Execute();
|
||||
}
|
||||
|
||||
private void ExecuteStatement(StringBuilder stmtBuilder)
|
||||
{
|
||||
var stmt = stmtBuilder.ToString();
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Type = System.Type;
|
||||
|
||||
namespace Umbraco.Core.Migrations
|
||||
{
|
||||
@@ -12,8 +13,6 @@ namespace Umbraco.Core.Migrations
|
||||
/// </summary>
|
||||
public class MigrationPlan
|
||||
{
|
||||
private readonly IMigrationBuilder _migrationBuilder;
|
||||
private readonly ILogger _logger;
|
||||
private readonly Dictionary<string, Transition> _transitions = new Dictionary<string, Transition>();
|
||||
|
||||
private string _prevState;
|
||||
@@ -23,64 +22,24 @@ namespace Umbraco.Core.Migrations
|
||||
/// Initializes a new instance of the <see cref="MigrationPlan"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the plan.</param>
|
||||
/// <remarks>The plan cannot be executed. Use this constructor e.g. when only validating the plan,
|
||||
/// or trying to get its final state, without actually needing to execute it.</remarks>
|
||||
public MigrationPlan(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name));
|
||||
Name = name;
|
||||
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
// (accepted)
|
||||
DefinePlan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrationPlan"/> class.
|
||||
/// Gets the transitions.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the plan.</param>
|
||||
/// <param name="migrationBuilder">A migration builder.</param>
|
||||
/// <param name="logger">A logger.</param>
|
||||
/// <remarks>The plan can be executed.</remarks>
|
||||
public MigrationPlan(string name, IMigrationBuilder migrationBuilder, ILogger logger)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name));
|
||||
Name = name;
|
||||
_migrationBuilder = migrationBuilder ?? throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
// (accepted)
|
||||
DefinePlan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the plan.
|
||||
/// </summary>
|
||||
protected virtual void DefinePlan() { }
|
||||
public IReadOnlyDictionary<string, Transition> Transitions => _transitions;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the plan.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds an empty migration from source to target state.
|
||||
/// </summary>
|
||||
public MigrationPlan Add(string sourceState, string targetState)
|
||||
=> Add<NoopMigration>(sourceState, targetState);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a migration from source to target state.
|
||||
/// </summary>
|
||||
public MigrationPlan Add<TMigration>(string sourceState, string targetState)
|
||||
where TMigration : IMigration
|
||||
=> Add(sourceState, targetState, typeof(TMigration));
|
||||
|
||||
/// <summary>
|
||||
/// Adds a migration from source to target state.
|
||||
/// </summary>
|
||||
public MigrationPlan Add(string sourceState, string targetState, Type migration)
|
||||
// adds a transition
|
||||
private MigrationPlan Add(string sourceState, string targetState, Type migration)
|
||||
{
|
||||
if (sourceState == null) throw new ArgumentNullException(nameof(sourceState));
|
||||
if (string.IsNullOrWhiteSpace(targetState)) throw new ArgumentNullOrEmptyException(nameof(targetState));
|
||||
@@ -113,26 +72,26 @@ namespace Umbraco.Core.Migrations
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chains an empty migration from chain to target state.
|
||||
/// </summary>
|
||||
public MigrationPlan Chain(string targetState)
|
||||
=> Chain<NoopMigration>(targetState);
|
||||
/// Adds a transition to a target state through an empty migration.
|
||||
/// </summary>
|
||||
public MigrationPlan To(string targetState)
|
||||
=> To<NoopMigration>(targetState);
|
||||
|
||||
/// <summary>
|
||||
/// Chains a migration from chain to target state.
|
||||
/// Adds a transition to a target state through a migration.
|
||||
/// </summary>
|
||||
public MigrationPlan Chain<TMigration>(string targetState)
|
||||
public MigrationPlan To<TMigration>(string targetState)
|
||||
where TMigration : IMigration
|
||||
=> Chain(targetState, typeof(TMigration));
|
||||
=> To(targetState, typeof(TMigration));
|
||||
|
||||
/// <summary>
|
||||
/// Chains a migration from chain to target state.
|
||||
/// Adds a transition to a target state through a migration.
|
||||
/// </summary>
|
||||
public MigrationPlan Chain(string targetState, Type migration)
|
||||
public MigrationPlan To(string targetState, Type migration)
|
||||
=> Add(_prevState, targetState, migration);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the chain state.
|
||||
/// Sets the starting state.
|
||||
/// </summary>
|
||||
public MigrationPlan From(string sourceState)
|
||||
{
|
||||
@@ -141,19 +100,16 @@ namespace Umbraco.Core.Migrations
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a chain.
|
||||
/// Adds transitions to a target state by copying transitions from a start state to an end state.
|
||||
/// </summary>
|
||||
/// <remarks>Copies the chain going from startState to endState, with new states going from sourceState to targetState.</remarks>
|
||||
public MigrationPlan CopyChain(string sourceState, string startState, string endState, string targetState)
|
||||
public MigrationPlan To(string targetState, string startState, string endState)
|
||||
{
|
||||
if (sourceState == null) throw new ArgumentNullException(nameof(sourceState));
|
||||
if (string.IsNullOrWhiteSpace(startState)) throw new ArgumentNullOrEmptyException(nameof(startState));
|
||||
if (string.IsNullOrWhiteSpace(endState)) throw new ArgumentNullOrEmptyException(nameof(endState));
|
||||
if (string.IsNullOrWhiteSpace(targetState)) throw new ArgumentNullOrEmptyException(nameof(targetState));
|
||||
if (sourceState == targetState) throw new ArgumentException("Source and target states cannot be identical.");
|
||||
|
||||
if (startState == endState) throw new ArgumentException("Start and end states cannot be identical.");
|
||||
|
||||
sourceState = sourceState.Trim();
|
||||
startState = startState.Trim();
|
||||
endState = endState.Trim();
|
||||
targetState = targetState.Trim();
|
||||
@@ -168,26 +124,18 @@ namespace Umbraco.Core.Migrations
|
||||
visited.Add(state);
|
||||
|
||||
if (!_transitions.TryGetValue(state, out var transition))
|
||||
throw new InvalidOperationException($"There is no transition from state \"{sourceState}\".");
|
||||
throw new InvalidOperationException($"There is no transition from state \"{state}\".");
|
||||
|
||||
var newTargetState = transition.TargetState == endState
|
||||
? targetState
|
||||
: Guid.NewGuid().ToString("B").ToUpper();
|
||||
Add(sourceState, newTargetState, transition.MigrationType);
|
||||
sourceState = newTargetState;
|
||||
To(newTargetState, transition.MigrationType);
|
||||
state = transition.TargetState;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a chain.
|
||||
/// </summary>
|
||||
/// <remarks>Copies the chain going from startState to endState, with new states going from chain to targetState.</remarks>
|
||||
public MigrationPlan CopyChain(string startState, string endState, string targetState)
|
||||
=> CopyChain(_prevState, startState, endState, targetState);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the initial state.
|
||||
/// </summary>
|
||||
@@ -260,50 +208,92 @@ namespace Umbraco.Core.Migrations
|
||||
/// </summary>
|
||||
/// <param name="scope">A scope.</param>
|
||||
/// <param name="fromState">The state to start execution at.</param>
|
||||
/// <param name="migrationBuilder">A migration builder.</param>
|
||||
/// <param name="logger">A logger.</param>
|
||||
/// <returns>The final state.</returns>
|
||||
/// <remarks>The plan executes within the scope, which must then be completed.</remarks>
|
||||
public string Execute(IScope scope, string fromState)
|
||||
public string Execute(IScope scope, string fromState, IMigrationBuilder migrationBuilder, ILogger logger)
|
||||
{
|
||||
Validate();
|
||||
|
||||
if (_migrationBuilder == null || _logger == null)
|
||||
throw new InvalidOperationException("Cannot execute a non-executing plan.");
|
||||
if (migrationBuilder == null) throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_logger.Info<MigrationPlan>("Starting '{MigrationName}'...", Name);
|
||||
logger.Info<MigrationPlan>("Starting '{MigrationName}'...", Name);
|
||||
|
||||
var origState = fromState ?? string.Empty;
|
||||
|
||||
_logger.Info<MigrationPlan>("At {OrigState}", string.IsNullOrWhiteSpace(origState) ? "origin": origState);
|
||||
logger.Info<MigrationPlan>("At {OrigState}", string.IsNullOrWhiteSpace(origState) ? "origin": origState);
|
||||
|
||||
if (!_transitions.TryGetValue(origState, out var transition))
|
||||
throw new Exception($"Unknown state \"{origState}\".");
|
||||
|
||||
var context = new MigrationContext(scope.Database, _logger);
|
||||
var context = new MigrationContext(scope.Database, logger);
|
||||
|
||||
while (transition != null)
|
||||
{
|
||||
var migration = _migrationBuilder.Build(transition.MigrationType, context);
|
||||
var migration = migrationBuilder.Build(transition.MigrationType, context);
|
||||
migration.Migrate();
|
||||
|
||||
var nextState = transition.TargetState;
|
||||
origState = nextState;
|
||||
|
||||
_logger.Info<MigrationPlan>("At {OrigState}", origState);
|
||||
logger.Info<MigrationPlan>("At {OrigState}", origState);
|
||||
|
||||
if (!_transitions.TryGetValue(origState, out transition))
|
||||
throw new Exception($"Unknown state \"{origState}\".");
|
||||
}
|
||||
|
||||
_logger.Info<MigrationPlan>("Done (pending scope completion).");
|
||||
logger.Info<MigrationPlan>("Done (pending scope completion).");
|
||||
|
||||
// safety check
|
||||
if (origState != _finalState)
|
||||
throw new Exception($"Internal error, reached state {origState} which is not final state {_finalState}");
|
||||
|
||||
return origState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Follows a path (for tests and debugging).
|
||||
/// </summary>
|
||||
/// <remarks>Does the same thing Execute does, but does not actually execute migrations.</remarks>
|
||||
internal string FollowPath(string fromState, string toState = null)
|
||||
{
|
||||
toState = toState.NullOrWhiteSpaceAsNull();
|
||||
|
||||
Validate();
|
||||
|
||||
var origState = fromState ?? string.Empty;
|
||||
|
||||
if (!_transitions.TryGetValue(origState, out var transition))
|
||||
throw new Exception($"Unknown state \"{origState}\".");
|
||||
|
||||
while (transition != null)
|
||||
{
|
||||
var nextState = transition.TargetState;
|
||||
origState = nextState;
|
||||
|
||||
if (nextState == toState)
|
||||
{
|
||||
transition = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_transitions.TryGetValue(origState, out transition))
|
||||
throw new Exception($"Unknown state \"{origState}\".");
|
||||
}
|
||||
|
||||
// safety check
|
||||
if (origState != (toState ?? _finalState))
|
||||
throw new Exception($"Internal error, reached state {origState} which is not state {toState ?? _finalState}");
|
||||
|
||||
// fixme - what about post-migrations?
|
||||
return origState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a plan transition.
|
||||
/// </summary>
|
||||
private class Transition
|
||||
public class Transition
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Transition"/> class.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Configuration;
|
||||
using Semver;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_7_12_0;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_0_0;
|
||||
|
||||
@@ -18,14 +17,9 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
/// </summary>
|
||||
public UmbracoPlan()
|
||||
: base(Constants.System.UmbracoUpgradePlanName)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoPlan"/> class.
|
||||
/// </summary>
|
||||
public UmbracoPlan(IMigrationBuilder migrationBuilder, ILogger logger)
|
||||
: base(Constants.System.UmbracoUpgradePlanName, migrationBuilder, logger)
|
||||
{ }
|
||||
{
|
||||
DefinePlan();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
@@ -61,8 +55,8 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DefinePlan()
|
||||
// define the plan
|
||||
protected void DefinePlan()
|
||||
{
|
||||
// MODIFYING THE PLAN
|
||||
//
|
||||
@@ -85,65 +79,74 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
// upgrades from 7 to 8, and then takes care of all eventual upgrades
|
||||
//
|
||||
From("{init-7.10.0}");
|
||||
Chain<AddLockObjects>("{7C447271-CA3F-4A6A-A913-5D77015655CB}");
|
||||
Chain<AddContentNuTable>("{CBFF58A2-7B50-4F75-8E98-249920DB0F37}");
|
||||
Chain<RefactorXmlColumns>("{3D18920C-E84D-405C-A06A-B7CEE52FE5DD}");
|
||||
Chain<VariantsMigration>("{FB0A5429-587E-4BD0-8A67-20F0E7E62FF7}");
|
||||
Chain<DropMigrationsTable>("{F0C42457-6A3B-4912-A7EA-F27ED85A2092}");
|
||||
Chain<DataTypeMigration>("{8640C9E4-A1C0-4C59-99BB-609B4E604981}");
|
||||
Chain<TagsMigration>("{DD1B99AF-8106-4E00-BAC7-A43003EA07F8}");
|
||||
Chain<SuperZero>("{9DF05B77-11D1-475C-A00A-B656AF7E0908}");
|
||||
Chain<PropertyEditorsMigration>("{6FE3EF34-44A0-4992-B379-B40BC4EF1C4D}");
|
||||
Chain<LanguageColumns>("{7F59355A-0EC9-4438-8157-EB517E6D2727}");
|
||||
To<AddLockObjects>("{7C447271-CA3F-4A6A-A913-5D77015655CB}");
|
||||
To<AddContentNuTable>("{CBFF58A2-7B50-4F75-8E98-249920DB0F37}");
|
||||
To<RefactorXmlColumns>("{3D18920C-E84D-405C-A06A-B7CEE52FE5DD}");
|
||||
To<VariantsMigration>("{FB0A5429-587E-4BD0-8A67-20F0E7E62FF7}");
|
||||
To<DropMigrationsTable>("{F0C42457-6A3B-4912-A7EA-F27ED85A2092}");
|
||||
To<DataTypeMigration>("{8640C9E4-A1C0-4C59-99BB-609B4E604981}");
|
||||
To<TagsMigration>("{DD1B99AF-8106-4E00-BAC7-A43003EA07F8}");
|
||||
To<SuperZero>("{9DF05B77-11D1-475C-A00A-B656AF7E0908}");
|
||||
To<PropertyEditorsMigration>("{6FE3EF34-44A0-4992-B379-B40BC4EF1C4D}");
|
||||
To<LanguageColumns>("{7F59355A-0EC9-4438-8157-EB517E6D2727}");
|
||||
//To<AddVariationTables1>("{941B2ABA-2D06-4E04-81F5-74224F1DB037}"); // AddVariationTables1 has been superseded by AddVariationTables2
|
||||
To<AddVariationTables2>("{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}");
|
||||
|
||||
// AddVariationTables1 has been superceeded by AddVariationTables2
|
||||
//Chain<AddVariationTables1>("{941B2ABA-2D06-4E04-81F5-74224F1DB037}");
|
||||
Chain<AddVariationTables2>("{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}");
|
||||
// however, provide a path out of the old state
|
||||
Add<AddVariationTables1A>("{941B2ABA-2D06-4E04-81F5-74224F1DB037}", "{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}");
|
||||
// way out of the commented state
|
||||
From("{941B2ABA-2D06-4E04-81F5-74224F1DB037}");
|
||||
To<AddVariationTables1A>("{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}");
|
||||
// resume at {76DF5CD7-A884-41A5-8DC6-7860D95B1DF5} ...
|
||||
|
||||
Chain<RefactorMacroColumns>("{A7540C58-171D-462A-91C5-7A9AA5CB8BFD}");
|
||||
To<RefactorMacroColumns>("{A7540C58-171D-462A-91C5-7A9AA5CB8BFD}");
|
||||
To<UserForeignKeys>("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}"); // shannon added that one - let's keep it as the default path
|
||||
//To<AddTypedLabels>("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}"); // stephan added that one = merge conflict, remove
|
||||
To<AddTypedLabels>("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // but add it after shannon's, with a new target state
|
||||
|
||||
Chain<UserForeignKeys>("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}"); // shannon added that one - let's keep it as the default path
|
||||
//Chain<AddTypedLabels>("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}"); // stephan added that one = merge conflict, remove,
|
||||
Chain<AddTypedLabels>("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // but add it after shannon's, with a new target state,
|
||||
Add<UserForeignKeys>("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}", "{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // and provide a path out of the conflict state
|
||||
// way out of the commented state
|
||||
From("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}");
|
||||
To<UserForeignKeys>("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}");
|
||||
// resume at {4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4} ...
|
||||
|
||||
Chain<ContentVariationMigration>("{1350617A-4930-4D61-852F-E3AA9E692173}");
|
||||
Chain<UpdateUmbracoConsent>("{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); // from 7.12.0
|
||||
//Chain<FallbackLanguage>("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}"); // andy added that one = merge conflict, remove
|
||||
Chain<AddRelationTypeForMediaFolderOnDelete>("{0541A62B-EF87-4CA2-8225-F0EB98ECCC9F}"); // from 7.12.0
|
||||
Chain<IncreaseLanguageIsoCodeColumnLength>("{EB34B5DC-BB87-4005-985E-D983EA496C38}"); // from 7.12.0
|
||||
Chain<RenameTrueFalseField>("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0
|
||||
Chain<SetDefaultTagsStorageType>("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0
|
||||
//Chain<UpdateDefaultMandatoryLanguage>("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}"); // stephan added that one = merge conflict, remove
|
||||
To<ContentVariationMigration>("{1350617A-4930-4D61-852F-E3AA9E692173}");
|
||||
To<UpdateUmbracoConsent>("{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); // from 7.12.0
|
||||
//To<FallbackLanguage>("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}"); // andy added that one = merge conflict, remove
|
||||
To<AddRelationTypeForMediaFolderOnDelete>("{0541A62B-EF87-4CA2-8225-F0EB98ECCC9F}"); // from 7.12.0
|
||||
To<IncreaseLanguageIsoCodeColumnLength>("{EB34B5DC-BB87-4005-985E-D983EA496C38}"); // from 7.12.0
|
||||
To<RenameTrueFalseField>("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0
|
||||
To<SetDefaultTagsStorageType>("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0
|
||||
//To<UpdateDefaultMandatoryLanguage>("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}"); // stephan added that one = merge conflict, remove
|
||||
|
||||
Chain<FallbackLanguage>("{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // add andy's after others, with a new target state
|
||||
From("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}") // and provide a path out of andy's
|
||||
.CopyChain("{39E5B1F7-A50B-437E-B768-1723AEC45B65}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}", "{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // to next
|
||||
To<FallbackLanguage>("{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // add andy's after others, with a new target state
|
||||
|
||||
// way out of andy's
|
||||
From("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}");
|
||||
To("{8B14CEBD-EE47-4AAD-A841-93551D917F11}", "{39E5B1F7-A50B-437E-B768-1723AEC45B65}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}");
|
||||
// resume at {8B14CEBD-EE47-4AAD-A841-93551D917F11} ...
|
||||
|
||||
Chain<UpdateDefaultMandatoryLanguage>("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // add stephan's after others, with a new target state
|
||||
From("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}") // and provide a path out of stephan's
|
||||
.Chain<FallbackLanguage>("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // to next
|
||||
To<UpdateDefaultMandatoryLanguage>("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // add stephan's after others, with a new target state
|
||||
|
||||
// way out of the commented state
|
||||
From("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}");
|
||||
To<FallbackLanguage>("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // to next
|
||||
// resume at {5F4597F4-A4E0-4AFE-90B5-6D2F896830EB} ...
|
||||
|
||||
//Chain<RefactorVariantsModel>("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}");
|
||||
Chain<RefactorVariantsModel>("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
|
||||
From("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}")
|
||||
.Chain<FallbackLanguage>("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
|
||||
//To<RefactorVariantsModel>("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}");
|
||||
To<RefactorVariantsModel>("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
|
||||
|
||||
// way out of the commented state
|
||||
From("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}");
|
||||
To<FallbackLanguage>("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
|
||||
// resume at {290C18EE-B3DE-4769-84F1-1F467F3F76DA}...
|
||||
|
||||
Chain<DropTaskTables>("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}");
|
||||
Chain<FixLockTablePrimaryKey>("{77874C77-93E5-4488-A404-A630907CEEF0}");
|
||||
Chain<AddLogTableColumns>("{8804D8E8-FE62-4E3A-B8A2-C047C2118C38}");
|
||||
Chain<DropPreValueTable>("{23275462-446E-44C7-8C2C-3B8C1127B07D}");
|
||||
Chain<DropDownPropertyEditorsMigration>("{6B251841-3069-4AD5-8AE9-861F9523E8DA}");
|
||||
Chain<TagsMigrationFix>("{EE429F1B-9B26-43CA-89F8-A86017C809A3}");
|
||||
Chain<DropTemplateDesignColumn>("{08919C4B-B431-449C-90EC-2B8445B5C6B1}");
|
||||
Chain<TablesForScheduledPublishing>("{7EB0254C-CB8B-4C75-B15B-D48C55B449EB}");
|
||||
To<DropTaskTables>("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}");
|
||||
To<FixLockTablePrimaryKey>("{77874C77-93E5-4488-A404-A630907CEEF0}");
|
||||
To<AddLogTableColumns>("{8804D8E8-FE62-4E3A-B8A2-C047C2118C38}");
|
||||
To<DropPreValueTable>("{23275462-446E-44C7-8C2C-3B8C1127B07D}");
|
||||
To<DropDownPropertyEditorsMigration>("{6B251841-3069-4AD5-8AE9-861F9523E8DA}");
|
||||
To<TagsMigrationFix>("{EE429F1B-9B26-43CA-89F8-A86017C809A3}");
|
||||
To<DropTemplateDesignColumn>("{08919C4B-B431-449C-90EC-2B8445B5C6B1}");
|
||||
To<TablesForScheduledPublishing>("{7EB0254C-CB8B-4C75-B15B-D48C55B449EB}");
|
||||
To<MakeTagsVariant>("{C39BF2A7-1454-4047-BBFE-89E40F66ED63}");
|
||||
|
||||
//FINAL
|
||||
|
||||
@@ -152,20 +155,20 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
|
||||
// and then, need to support upgrading from more recent 7.x
|
||||
//
|
||||
From("{init-7.10.1}").Chain("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.10.2}").Chain("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.10.3}").Chain("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.10.4}").Chain("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.11.0}").Chain("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.11.1}").Chain("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.10.1}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.10.2}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.10.3}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.10.4}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.11.0}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.11.1}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
|
||||
// 7.12.0 has migrations, define a custom chain which copies the chain
|
||||
// going from {init-7.10.0} to former final (1350617A) , and then goes straight to
|
||||
// main chain, skipping the migrations
|
||||
//
|
||||
From("{init-7.12.0}");
|
||||
// copy from copy to (former final) main chain
|
||||
CopyChain("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}");
|
||||
// target copy from copy to (former final)
|
||||
To("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}", "{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,41 @@ using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Umbraco upgrader.
|
||||
/// </summary>
|
||||
public class UmbracoUpgrader : Upgrader
|
||||
{
|
||||
public UmbracoUpgrader(IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, PostMigrationCollection postMigrations, ILogger logger)
|
||||
: base(scopeProvider, migrationBuilder, keyValueService, postMigrations, logger)
|
||||
private PostMigrationCollection _postMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see ref="UmbracoUpgrader" /> class.
|
||||
/// </summary>
|
||||
public UmbracoUpgrader()
|
||||
: base(new UmbracoPlan())
|
||||
{ }
|
||||
|
||||
protected override MigrationPlan GetPlan()
|
||||
/// <summary>
|
||||
/// Executes.
|
||||
/// </summary>
|
||||
public void Execute(IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, ILogger logger, PostMigrationCollection postMigrations)
|
||||
{
|
||||
return new UmbracoPlan(MigrationBuilder, Logger);
|
||||
_postMigrations = postMigrations;
|
||||
Execute(scopeProvider, migrationBuilder, keyValueService, logger);
|
||||
}
|
||||
|
||||
protected override (SemVersion, SemVersion) GetVersions()
|
||||
/// <inheritdoc />
|
||||
public override void AfterMigrations(IScope scope, ILogger logger)
|
||||
{
|
||||
// assume we have something in web.config that makes some sense
|
||||
if (!SemVersion.TryParse(ConfigurationManager.AppSettings["umbracoConfigurationStatus"], out var currentVersion))
|
||||
// assume we have something in web.config that makes some sense = the origin version
|
||||
if (!SemVersion.TryParse(ConfigurationManager.AppSettings["umbracoConfigurationStatus"], out var originVersion))
|
||||
throw new InvalidOperationException("Could not get current version from web.config umbracoConfigurationStatus appSetting.");
|
||||
|
||||
return (currentVersion, UmbracoVersion.SemanticVersion);
|
||||
// target version is the code version
|
||||
var targetVersion = UmbracoVersion.SemanticVersion;
|
||||
|
||||
foreach (var postMigration in _postMigrations)
|
||||
postMigration.Execute(Name, scope, originVersion, targetVersion, logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,60 @@
|
||||
using System;
|
||||
using Semver;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade
|
||||
{
|
||||
public abstract class Upgrader
|
||||
/// <summary>
|
||||
/// Represents an upgrader.
|
||||
/// </summary>
|
||||
public class Upgrader
|
||||
{
|
||||
private readonly IKeyValueService _keyValueService;
|
||||
private readonly PostMigrationCollection _postMigrations;
|
||||
private MigrationPlan _plan;
|
||||
|
||||
protected Upgrader(IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, PostMigrationCollection postMigrations, ILogger logger)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see ref="Upgrader"/> class.
|
||||
/// </summary>
|
||||
public Upgrader(MigrationPlan plan)
|
||||
{
|
||||
ScopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
|
||||
MigrationBuilder = migrationBuilder ?? throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
_keyValueService = keyValueService ?? throw new ArgumentNullException(nameof(keyValueService));
|
||||
_postMigrations = postMigrations ?? throw new ArgumentNullException(nameof(postMigrations));
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
Plan = plan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the migration plan.
|
||||
/// </summary>
|
||||
public string Name => Plan.Name;
|
||||
|
||||
public string StateValueKey => GetStateValueKey(Plan);
|
||||
/// <summary>
|
||||
/// Gets the migration plan.
|
||||
/// </summary>
|
||||
public MigrationPlan Plan { get; }
|
||||
|
||||
protected IScopeProvider ScopeProvider { get; }
|
||||
/// <summary>
|
||||
/// Gets the key for the state value.
|
||||
/// </summary>
|
||||
public virtual string StateValueKey => "Umbraco.Core.Upgrader.State+" + Name;
|
||||
|
||||
protected IMigrationBuilder MigrationBuilder { get; }
|
||||
|
||||
protected ILogger Logger { get; }
|
||||
|
||||
protected MigrationPlan Plan => _plan ?? (_plan = GetPlan());
|
||||
|
||||
protected abstract MigrationPlan GetPlan();
|
||||
protected abstract (SemVersion, SemVersion) GetVersions();
|
||||
|
||||
public void Execute()
|
||||
/// <summary>
|
||||
/// Executes.
|
||||
/// </summary>
|
||||
/// <param name="scopeProvider">A scope provider.</param>
|
||||
/// <param name="migrationBuilder">A migration builder.</param>
|
||||
/// <param name="keyValueService">A key-value service.</param>
|
||||
/// <param name="logger">A logger.</param>
|
||||
public void Execute(IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, ILogger logger)
|
||||
{
|
||||
if (scopeProvider == null) throw new ArgumentNullException(nameof(scopeProvider));
|
||||
if (migrationBuilder == null) throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
if (keyValueService == null) throw new ArgumentNullException(nameof(keyValueService));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
var plan = Plan;
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
using (var scope = scopeProvider.CreateScope())
|
||||
{
|
||||
BeforeMigrations(scope, logger);
|
||||
|
||||
// read current state
|
||||
var currentState = _keyValueService.GetValue(StateValueKey);
|
||||
var currentState = keyValueService.GetValue(StateValueKey);
|
||||
var forceState = false;
|
||||
|
||||
if (currentState == null)
|
||||
@@ -53,25 +64,33 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
}
|
||||
|
||||
// execute plan
|
||||
var state = plan.Execute(scope, currentState);
|
||||
var state = plan.Execute(scope, currentState, migrationBuilder, logger);
|
||||
if (string.IsNullOrWhiteSpace(state))
|
||||
throw new Exception("Plan execution returned an invalid null or empty state.");
|
||||
|
||||
// save new state
|
||||
if (forceState)
|
||||
_keyValueService.SetValue(StateValueKey, state);
|
||||
keyValueService.SetValue(StateValueKey, state);
|
||||
else if (currentState != state)
|
||||
_keyValueService.SetValue(StateValueKey, currentState, state);
|
||||
keyValueService.SetValue(StateValueKey, currentState, state);
|
||||
|
||||
// run post-migrations
|
||||
(var originVersion, var targetVersion) = GetVersions();
|
||||
foreach (var postMigration in _postMigrations)
|
||||
postMigration.Execute(Name, scope, originVersion, targetVersion, Logger);
|
||||
AfterMigrations(scope, logger);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetStateValueKey(MigrationPlan plan) => "Umbraco.Core.Upgrader.State+" + plan.Name;
|
||||
/// <summary>
|
||||
/// Executes as part of the upgrade scope and before all migrations have executed.
|
||||
/// </summary>
|
||||
public virtual void BeforeMigrations(IScope scope, ILogger logger)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Executes as part of the upgrade scope and after all migrations have executed.
|
||||
/// </summary>
|
||||
public virtual void AfterMigrations(IScope scope, ILogger logger)
|
||||
{ }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
public class MakeTagsVariant : MigrationBase
|
||||
{
|
||||
public MakeTagsVariant(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
AddColumn<TagDto>("languageId");
|
||||
|
||||
Delete.Index($"IX_{Constants.DatabaseSchema.Tables.Tag}").OnTable(Constants.DatabaseSchema.Tables.Tag).Do();
|
||||
Create.Index($"IX_{Constants.DatabaseSchema.Tables.Tag}").OnTable(Constants.DatabaseSchema.Tables.Tag)
|
||||
.OnColumn("group")
|
||||
.Ascending()
|
||||
.OnColumn("tag")
|
||||
.Ascending()
|
||||
.OnColumn("languageId")
|
||||
.Ascending()
|
||||
.WithOptions().Unique()
|
||||
.Do();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,10 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="tags">The tags.</param>
|
||||
/// <param name="merge">A value indicating whether to merge the tags with existing tags instead of replacing them.</param>
|
||||
/// <remarks>Tags do not support variants.</remarks>
|
||||
public static void AssignTags(this IContentBase content, string propertyTypeAlias, IEnumerable<string> tags, bool merge = false)
|
||||
/// <param name="culture">A culture, for multi-lingual properties.</param>
|
||||
public static void AssignTags(this IContentBase content, string propertyTypeAlias, IEnumerable<string> tags, bool merge = false, string culture = null)
|
||||
{
|
||||
content.GetTagProperty(propertyTypeAlias).AssignTags(tags, merge);
|
||||
content.GetTagProperty(propertyTypeAlias).AssignTags(tags, merge, culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -27,10 +27,10 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="tags">The tags.</param>
|
||||
/// <remarks>Tags do not support variants.</remarks>
|
||||
public static void RemoveTags(this IContentBase content, string propertyTypeAlias, IEnumerable<string> tags)
|
||||
/// <param name="culture">A culture, for multi-lingual properties.</param>
|
||||
public static void RemoveTags(this IContentBase content, string propertyTypeAlias, IEnumerable<string> tags, string culture = null)
|
||||
{
|
||||
content.GetTagProperty(propertyTypeAlias).RemoveTags(tags);
|
||||
content.GetTagProperty(propertyTypeAlias).RemoveTags(tags, culture);
|
||||
}
|
||||
|
||||
// gets and validates the property
|
||||
|
||||
@@ -20,6 +20,12 @@ namespace Umbraco.Core.Models
|
||||
[DataMember]
|
||||
string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tag language.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
int? LanguageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of nodes tagged with this tag.
|
||||
/// </summary>
|
||||
|
||||
@@ -38,13 +38,13 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign default tags.
|
||||
/// Assign tags.
|
||||
/// </summary>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="tags">The tags.</param>
|
||||
/// <param name="merge">A value indicating whether to merge the tags with existing tags instead of replacing them.</param>
|
||||
/// <remarks>Tags do not support variants.</remarks>
|
||||
public static void AssignTags(this Property property, IEnumerable<string> tags, bool merge = false)
|
||||
/// <param name="culture">A culture, for multi-lingual properties.</param>
|
||||
public static void AssignTags(this Property property, IEnumerable<string> tags, bool merge = false, string culture = null)
|
||||
{
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
@@ -52,11 +52,11 @@ namespace Umbraco.Core.Models
|
||||
if (configuration == null)
|
||||
throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags.");
|
||||
|
||||
property.AssignTags(tags, merge, configuration.StorageType, configuration.Delimiter);
|
||||
property.AssignTags(tags, merge, configuration.StorageType, configuration.Delimiter, culture);
|
||||
}
|
||||
|
||||
// assumes that parameters are consistent with the datatype configuration
|
||||
internal static void AssignTags(this Property property, IEnumerable<string> tags, bool merge, TagsStorageType storageType, char delimiter)
|
||||
private static void AssignTags(this Property property, IEnumerable<string> tags, bool merge, TagsStorageType storageType, char delimiter, string culture)
|
||||
{
|
||||
// set the property value
|
||||
var trimmedTags = tags.Select(x => x.Trim()).ToArray();
|
||||
@@ -68,11 +68,11 @@ namespace Umbraco.Core.Models
|
||||
switch (storageType)
|
||||
{
|
||||
case TagsStorageType.Csv:
|
||||
property.SetValue(string.Join(delimiter.ToString(), currentTags.Union(trimmedTags))); // csv string
|
||||
property.SetValue(string.Join(delimiter.ToString(), currentTags.Union(trimmedTags)), culture); // csv string
|
||||
break;
|
||||
|
||||
case TagsStorageType.Json:
|
||||
property.SetValue(JsonConvert.SerializeObject(currentTags.Union(trimmedTags).ToArray())); // json array
|
||||
property.SetValue(JsonConvert.SerializeObject(currentTags.Union(trimmedTags).ToArray()), culture); // json array
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -81,23 +81,23 @@ namespace Umbraco.Core.Models
|
||||
switch (storageType)
|
||||
{
|
||||
case TagsStorageType.Csv:
|
||||
property.SetValue(string.Join(delimiter.ToString(), trimmedTags)); // csv string
|
||||
property.SetValue(string.Join(delimiter.ToString(), trimmedTags), culture); // csv string
|
||||
break;
|
||||
|
||||
case TagsStorageType.Json:
|
||||
property.SetValue(JsonConvert.SerializeObject(trimmedTags)); // json array
|
||||
property.SetValue(JsonConvert.SerializeObject(trimmedTags), culture); // json array
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes default tags.
|
||||
/// Removes tags.
|
||||
/// </summary>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="tags">The tags.</param>
|
||||
/// <remarks>Tags do not support variants.</remarks>
|
||||
public static void RemoveTags(this Property property, IEnumerable<string> tags)
|
||||
/// <param name="culture">A culture, for multi-lingual properties.</param>
|
||||
public static void RemoveTags(this Property property, IEnumerable<string> tags, string culture = null)
|
||||
{
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
@@ -105,33 +105,33 @@ namespace Umbraco.Core.Models
|
||||
if (configuration == null)
|
||||
throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags.");
|
||||
|
||||
property.RemoveTags(tags, configuration.StorageType, configuration.Delimiter);
|
||||
property.RemoveTags(tags, configuration.StorageType, configuration.Delimiter, culture);
|
||||
}
|
||||
|
||||
// assumes that parameters are consistent with the datatype configuration
|
||||
private static void RemoveTags(this Property property, IEnumerable<string> tags, TagsStorageType storageType, char delimiter)
|
||||
private static void RemoveTags(this Property property, IEnumerable<string> tags, TagsStorageType storageType, char delimiter, string culture)
|
||||
{
|
||||
// already empty = nothing to do
|
||||
//fixme doesn't take into account variants
|
||||
var value = property.GetValue()?.ToString();
|
||||
var value = property.GetValue(culture)?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(value)) return;
|
||||
|
||||
// set the property value
|
||||
var trimmedTags = tags.Select(x => x.Trim()).ToArray();
|
||||
var currentTags = property.GetTagsValue(storageType, delimiter);
|
||||
var currentTags = property.GetTagsValue(storageType, delimiter, culture);
|
||||
switch (storageType)
|
||||
{
|
||||
case TagsStorageType.Csv:
|
||||
property.SetValue(string.Join(delimiter.ToString(), currentTags.Except(trimmedTags))); // csv string
|
||||
property.SetValue(string.Join(delimiter.ToString(), currentTags.Except(trimmedTags)), culture); // csv string
|
||||
break;
|
||||
|
||||
case TagsStorageType.Json:
|
||||
property.SetValue(JsonConvert.SerializeObject(currentTags.Except(trimmedTags).ToArray())); // json array
|
||||
property.SetValue(JsonConvert.SerializeObject(currentTags.Except(trimmedTags).ToArray()), culture); // json array
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<string> GetTagsValue(this Property property)
|
||||
// used by ContentRepositoryBase
|
||||
internal static IEnumerable<string> GetTagsValue(this Property property, string culture = null)
|
||||
{
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
@@ -139,15 +139,14 @@ namespace Umbraco.Core.Models
|
||||
if (configuration == null)
|
||||
throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags.");
|
||||
|
||||
return property.GetTagsValue(configuration.StorageType, configuration.Delimiter);
|
||||
return property.GetTagsValue(configuration.StorageType, configuration.Delimiter, culture);
|
||||
}
|
||||
|
||||
internal static IEnumerable<string> GetTagsValue(this Property property, TagsStorageType storageType, char delimiter)
|
||||
private static IEnumerable<string> GetTagsValue(this Property property, TagsStorageType storageType, char delimiter, string culture = null)
|
||||
{
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
//fixme doesn't take into account variants
|
||||
var value = property.GetValue()?.ToString();
|
||||
var value = property.GetValue(culture)?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(value)) return Enumerable.Empty<string>();
|
||||
|
||||
switch (storageType)
|
||||
@@ -158,7 +157,6 @@ namespace Umbraco.Core.Models
|
||||
case TagsStorageType.Json:
|
||||
try
|
||||
{
|
||||
//fixme doesn't take into account variants
|
||||
return JsonConvert.DeserializeObject<JArray>(value).Select(x => x.ToString().Trim());
|
||||
}
|
||||
catch (JsonException)
|
||||
@@ -178,34 +176,33 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="value">The property value.</param>
|
||||
/// <param name="tagConfiguration">The datatype configuration.</param>
|
||||
/// <remarks>
|
||||
/// <param name="culture">A culture, for multi-lingual properties.</param>
|
||||
/// <remarks>
|
||||
/// <para>The value is either a string (delimited string) or an enumeration of strings (tag list).</para>
|
||||
/// <para>This is used both by the content repositories to initialize a property with some tag values, and by the
|
||||
/// content controllers to update a property with values received from the property editor.</para>
|
||||
/// </remarks>
|
||||
internal static void SetTagsValue(this Property property, object value, TagConfiguration tagConfiguration)
|
||||
internal static void SetTagsValue(this Property property, object value, TagConfiguration tagConfiguration, string culture)
|
||||
{
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
if (tagConfiguration == null) throw new ArgumentNullException(nameof(tagConfiguration));
|
||||
|
||||
var merge = false; // fixme always!
|
||||
var storageType = tagConfiguration.StorageType;
|
||||
var delimiter = tagConfiguration.Delimiter;
|
||||
|
||||
SetTagsValue(property, value, merge, storageType, delimiter);
|
||||
SetTagsValue(property, value, storageType, delimiter, culture);
|
||||
}
|
||||
|
||||
// assumes that parameters are consistent with the datatype configuration
|
||||
// value can be an enumeration of string, or a serialized value using storageType format
|
||||
// fixme merge always false here?!
|
||||
private static void SetTagsValue(Property property, object value, bool merge, TagsStorageType storageType, char delimiter)
|
||||
private static void SetTagsValue(Property property, object value, TagsStorageType storageType, char delimiter, string culture)
|
||||
{
|
||||
if (value == null) value = Enumerable.Empty<string>();
|
||||
|
||||
// if value is already an enumeration of strings, just use it
|
||||
if (value is IEnumerable<string> tags1)
|
||||
{
|
||||
property.AssignTags(tags1, merge, storageType, delimiter);
|
||||
property.AssignTags(tags1, false, storageType, delimiter, culture);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -214,14 +211,14 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
case TagsStorageType.Csv:
|
||||
var tags2 = value.ToString().Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);
|
||||
property.AssignTags(tags2, merge, storageType, delimiter);
|
||||
property.AssignTags(tags2, false, storageType, delimiter, culture);
|
||||
break;
|
||||
|
||||
case TagsStorageType.Json:
|
||||
try
|
||||
{
|
||||
var tags3 = JsonConvert.DeserializeObject<IEnumerable<string>>(value.ToString());
|
||||
property.AssignTags(tags3 ?? Enumerable.Empty<string>(), merge, storageType, delimiter);
|
||||
property.AssignTags(tags3 ?? Enumerable.Empty<string>(), false, storageType, delimiter, culture);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Umbraco.Core.Models
|
||||
|
||||
private string _group;
|
||||
private string _text;
|
||||
private int? _languageId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Tag"/> class.
|
||||
@@ -26,11 +27,12 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Tag"/> class.
|
||||
/// </summary>
|
||||
public Tag(int id, string group, string text)
|
||||
public Tag(int id, string group, string text, int? languageId = null)
|
||||
{
|
||||
Id = id;
|
||||
Text = text;
|
||||
Group = group;
|
||||
LanguageId = languageId;
|
||||
}
|
||||
|
||||
private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors());
|
||||
@@ -39,6 +41,7 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
public readonly PropertyInfo Group = ExpressionHelper.GetPropertyInfo<Tag, string>(x => x.Group);
|
||||
public readonly PropertyInfo Text = ExpressionHelper.GetPropertyInfo<Tag, string>(x => x.Text);
|
||||
public readonly PropertyInfo LanguageId = ExpressionHelper.GetPropertyInfo<Tag, int?>(x => x.LanguageId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -55,6 +58,13 @@ namespace Umbraco.Core.Models
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _text, Selectors.Text);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? LanguageId
|
||||
{
|
||||
get => _languageId;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _languageId, Selectors.LanguageId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int NodeCount { get; internal set; }
|
||||
}
|
||||
|
||||
@@ -5,10 +5,13 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Represents a tagged entity.
|
||||
/// </summary>
|
||||
/// <remarks>Note that it is the properties of an entity (like Content, Media, Members, etc.) that is tagged,
|
||||
/// which is why this class is composed of a list of tagged properties and an Id reference to the actual entity.</remarks>
|
||||
/// <remarks>Note that it is the properties of an entity (like Content, Media, Members, etc.) that are tagged,
|
||||
/// which is why this class is composed of a list of tagged properties and the identifier the actual entity.</remarks>
|
||||
public class TaggedEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TaggedEntity"/> class.
|
||||
/// </summary>
|
||||
public TaggedEntity(int entityId, IEnumerable<TaggedProperty> taggedProperties)
|
||||
{
|
||||
EntityId = entityId;
|
||||
@@ -16,13 +19,13 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Id of the entity, which is tagged
|
||||
/// Gets the identifier of the entity.
|
||||
/// </summary>
|
||||
public int EntityId { get; private set; }
|
||||
public int EntityId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An enumerable list of tagged properties
|
||||
/// Gets the tagged properties.
|
||||
/// </summary>
|
||||
public IEnumerable<TaggedProperty> TaggedProperties { get; private set; }
|
||||
public IEnumerable<TaggedProperty> TaggedProperties { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ namespace Umbraco.Core.Models
|
||||
/// </summary>
|
||||
public class TaggedProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TaggedProperty"/> class.
|
||||
/// </summary>
|
||||
public TaggedProperty(int propertyTypeId, string propertyTypeAlias, IEnumerable<ITag> tags)
|
||||
{
|
||||
PropertyTypeId = propertyTypeId;
|
||||
@@ -15,18 +18,18 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Id of the PropertyType, which this tagged property is based on
|
||||
/// Gets the identifier of the property type.
|
||||
/// </summary>
|
||||
public int PropertyTypeId { get; private set; }
|
||||
public int PropertyTypeId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Alias of the PropertyType, which this tagged property is based on
|
||||
/// Gets the alias of the property type.
|
||||
/// </summary>
|
||||
public string PropertyTypeAlias { get; private set; }
|
||||
public string PropertyTypeAlias { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An enumerable list of Tags for the property
|
||||
/// Gets the tags.
|
||||
/// </summary>
|
||||
public IEnumerable<ITag> Tags { get; private set; }
|
||||
public IEnumerable<ITag> Tags { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,7 +595,6 @@ namespace Umbraco.Core
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to serialize the value to an XmlString using ToXmlString
|
||||
/// </summary>
|
||||
@@ -788,5 +787,7 @@ namespace Umbraco.Core
|
||||
|
||||
return BoolConvertCache[type] = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Dtos
|
||||
{
|
||||
[TableName(Constants.DatabaseSchema.Tables.Tag)]
|
||||
[TableName(TableName)]
|
||||
[PrimaryKey("id")]
|
||||
[ExplicitColumns]
|
||||
internal class TagDto
|
||||
{
|
||||
public const string TableName = Constants.DatabaseSchema.Tables.Tag;
|
||||
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn]
|
||||
public int Id { get; set; }
|
||||
@@ -16,9 +18,15 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
[Length(100)]
|
||||
public string Group { get; set; }
|
||||
|
||||
[Column("languageId")]
|
||||
[ForeignKey(typeof(LanguageDto))]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_LanguageId")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public int? LanguageId { get;set; }
|
||||
|
||||
[Column("tag")]
|
||||
[Length(200)]
|
||||
[Index(IndexTypes.UniqueNonClustered, ForColumns = "group,tag", Name = "IX_cmsTags")]
|
||||
[Index(IndexTypes.UniqueNonClustered, ForColumns = "group,tag,languageId", Name = "IX_cmsTags")]
|
||||
public string Text { get; set; }
|
||||
|
||||
//[Column("key")]
|
||||
|
||||
@@ -3,11 +3,13 @@ using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Dtos
|
||||
{
|
||||
[TableName(Constants.DatabaseSchema.Tables.TagRelationship)]
|
||||
[TableName(TableName)]
|
||||
[PrimaryKey("nodeId", AutoIncrement = false)]
|
||||
[ExplicitColumns]
|
||||
internal class TagRelationshipDto
|
||||
{
|
||||
public const string TableName = Constants.DatabaseSchema.Tables.TagRelationship;
|
||||
|
||||
[Column("nodeId")]
|
||||
[PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsTagRelationship", OnColumns = "nodeId, propertyTypeId, tagId")]
|
||||
[ForeignKey(typeof(ContentDto), Name = "FK_cmsTagRelationship_cmsContent", Column = "nodeId")]
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
public static ITag BuildEntity(TagDto dto)
|
||||
{
|
||||
var entity = new Tag(dto.Id, dto.Group, dto.Text) { NodeCount = dto.NodeCount };
|
||||
var entity = new Tag(dto.Id, dto.Group, dto.Text, dto.LanguageId) { NodeCount = dto.NodeCount };
|
||||
// reset dirty initial properties (U4-1946)
|
||||
entity.ResetDirtyProperties(false);
|
||||
return entity;
|
||||
@@ -20,6 +20,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
Id = entity.Id,
|
||||
Group = entity.Group,
|
||||
Text = entity.Text,
|
||||
LanguageId = entity.LanguageId
|
||||
//Key = entity.Group + "/" + entity.Text // de-normalize
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Umbraco.Core.Persistence.Mappers
|
||||
CacheMap<Tag, TagDto>(src => src.Id, dto => dto.Id);
|
||||
CacheMap<Tag, TagDto>(src => src.Text, dto => dto.Text);
|
||||
CacheMap<Tag, TagDto>(src => src.Group, dto => dto.Group);
|
||||
CacheMap<Tag, TagDto>(src => src.LanguageId, dto => dto.LanguageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Umbraco.Core.Persistence
|
||||
var (s, a) = sql.SqlContext.VisitDto(predicate, alias);
|
||||
return sql.Where(s, a);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Appends a WHERE clause to the Sql statement.
|
||||
/// </summary>
|
||||
@@ -589,11 +589,14 @@ namespace Umbraco.Core.Persistence
|
||||
/// Creates a SELECT COUNT(*) Sql statement.
|
||||
/// </summary>
|
||||
/// <param name="sql">The origin sql.</param>
|
||||
/// <param name="alias">An optional alias.</param>
|
||||
/// <returns>The Sql statement.</returns>
|
||||
public static Sql<ISqlContext> SelectCount(this Sql<ISqlContext> sql)
|
||||
public static Sql<ISqlContext> SelectCount(this Sql<ISqlContext> sql, string alias = null)
|
||||
{
|
||||
if (sql == null) throw new ArgumentNullException(nameof(sql));
|
||||
return sql.Select("COUNT(*)");
|
||||
var text = "COUNT(*)";
|
||||
if (alias != null) text += " AS " + sql.SqlContext.SqlSyntax.GetQuotedColumnName(alias);
|
||||
return sql.Select(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -607,13 +610,29 @@ namespace Umbraco.Core.Persistence
|
||||
/// <para>If <paramref name="fields"/> is empty, all columns are counted.</para>
|
||||
/// </remarks>
|
||||
public static Sql<ISqlContext> SelectCount<TDto>(this Sql<ISqlContext> sql, params Expression<Func<TDto, object>>[] fields)
|
||||
=> sql.SelectCount(null, fields);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SELECT COUNT Sql statement.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to count.</typeparam>
|
||||
/// <param name="sql">The origin sql.</param>
|
||||
/// <param name="alias">An alias.</param>
|
||||
/// <param name="fields">Expressions indicating the columns to count.</param>
|
||||
/// <returns>The Sql statement.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If <paramref name="fields"/> is empty, all columns are counted.</para>
|
||||
/// </remarks>
|
||||
public static Sql<ISqlContext> SelectCount<TDto>(this Sql<ISqlContext> sql, string alias, params Expression<Func<TDto, object>>[] fields)
|
||||
{
|
||||
if (sql == null) throw new ArgumentNullException(nameof(sql));
|
||||
var sqlSyntax = sql.SqlContext.SqlSyntax;
|
||||
var columns = fields.Length == 0
|
||||
? sql.GetColumns<TDto>(withAlias: false)
|
||||
: fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
|
||||
return sql.Select("COUNT (" + string.Join(", ", columns) + ")");
|
||||
var text = "COUNT (" + string.Join(", ", columns) + ")";
|
||||
if (alias != null) text += " AS " + sql.SqlContext.SqlSyntax.GetQuotedColumnName(alias);
|
||||
return sql.Select(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -643,6 +662,26 @@ namespace Umbraco.Core.Persistence
|
||||
return sql.Select(sql.GetColumns(columnExpressions: fields));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SELECT DISTINCT Sql statement.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to select.</typeparam>
|
||||
/// <param name="sql">The origin sql.</param>
|
||||
/// <param name="fields">Expressions indicating the columns to select.</param>
|
||||
/// <returns>The Sql statement.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If <paramref name="fields"/> is empty, all columns are selected.</para>
|
||||
/// </remarks>
|
||||
public static Sql<ISqlContext> SelectDistinct<TDto>(this Sql<ISqlContext> sql, params Expression<Func<TDto, object>>[] fields)
|
||||
{
|
||||
if (sql == null) throw new ArgumentNullException(nameof(sql));
|
||||
var columns = sql.GetColumns(columnExpressions: fields);
|
||||
sql.Append("SELECT DISTINCT " + string.Join(", ", columns));
|
||||
return sql;
|
||||
}
|
||||
|
||||
//this.Append("SELECT " + string.Join(", ", columns), new object[0]);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SELECT Sql statement.
|
||||
/// </summary>
|
||||
@@ -705,6 +744,56 @@ namespace Umbraco.Core.Persistence
|
||||
return sql.Append(", " + string.Join(", ", sql.GetColumns(tableAlias: tableAlias, columnExpressions: fields)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a COUNT(*) to a SELECT Sql statement.
|
||||
/// </summary>
|
||||
/// <param name="sql">The origin sql.</param>
|
||||
/// <param name="alias">An optional alias.</param>
|
||||
/// <returns>The Sql statement.</returns>
|
||||
public static Sql<ISqlContext> AndSelectCount(this Sql<ISqlContext> sql, string alias = null)
|
||||
{
|
||||
if (sql == null) throw new ArgumentNullException(nameof(sql));
|
||||
var text = ", COUNT(*)";
|
||||
if (alias != null) text += " AS " + sql.SqlContext.SqlSyntax.GetQuotedColumnName(alias);
|
||||
return sql.Append(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a COUNT to a SELECT Sql statement.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to count.</typeparam>
|
||||
/// <param name="sql">The origin sql.</param>
|
||||
/// <param name="fields">Expressions indicating the columns to count.</param>
|
||||
/// <returns>The Sql statement.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If <paramref name="fields"/> is empty, all columns are counted.</para>
|
||||
/// </remarks>
|
||||
public static Sql<ISqlContext> AndSelectCount<TDto>(this Sql<ISqlContext> sql, params Expression<Func<TDto, object>>[] fields)
|
||||
=> sql.AndSelectCount(null, fields);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a COUNT to a SELECT Sql statement.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to count.</typeparam>
|
||||
/// <param name="sql">The origin sql.</param>
|
||||
/// <param name="alias">An alias.</param>
|
||||
/// <param name="fields">Expressions indicating the columns to count.</param>
|
||||
/// <returns>The Sql statement.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If <paramref name="fields"/> is empty, all columns are counted.</para>
|
||||
/// </remarks>
|
||||
public static Sql<ISqlContext> AndSelectCount<TDto>(this Sql<ISqlContext> sql, string alias = null, params Expression<Func<TDto, object>>[] fields)
|
||||
{
|
||||
if (sql == null) throw new ArgumentNullException(nameof(sql));
|
||||
var sqlSyntax = sql.SqlContext.SqlSyntax;
|
||||
var columns = fields.Length == 0
|
||||
? sql.GetColumns<TDto>(withAlias: false)
|
||||
: fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray();
|
||||
var text = ", COUNT (" + string.Join(", ", columns) + ")";
|
||||
if (alias != null) text += " AS " + sql.SqlContext.SqlSyntax.GetQuotedColumnName(alias);
|
||||
return sql.Append(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SELECT Sql statement with a referenced Dto.
|
||||
/// </summary>
|
||||
@@ -1115,12 +1204,37 @@ namespace Umbraco.Core.Persistence
|
||||
return string.IsNullOrWhiteSpace(attr?.Name) ? column.Name : attr.Name;
|
||||
}
|
||||
|
||||
internal static void WriteToConsole(this Sql sql)
|
||||
internal static string ToText(this Sql sql)
|
||||
{
|
||||
Console.WriteLine(sql.SQL);
|
||||
var text = new StringBuilder();
|
||||
sql.ToText(text);
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
internal static void ToText(this Sql sql, StringBuilder text)
|
||||
{
|
||||
ToText(sql.SQL, sql.Arguments, text);
|
||||
}
|
||||
|
||||
internal static void ToText(string sql, object[] arguments, StringBuilder text)
|
||||
{
|
||||
text.AppendLine(sql);
|
||||
|
||||
if (arguments == null || arguments.Length == 0)
|
||||
return;
|
||||
|
||||
text.Append(" --");
|
||||
|
||||
var i = 0;
|
||||
foreach (var arg in sql.Arguments)
|
||||
Console.WriteLine($" @{i++}: {arg}");
|
||||
foreach (var arg in arguments)
|
||||
{
|
||||
text.Append(" @");
|
||||
text.Append(i++);
|
||||
text.Append(":");
|
||||
text.Append(arg);
|
||||
}
|
||||
|
||||
text.AppendLine();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -653,6 +653,23 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
else
|
||||
throw new NotSupportedException("Expression is not a proper lambda.");
|
||||
|
||||
// c# 'x == null' becomes sql 'x IS NULL' which is fine
|
||||
// c# 'x == y' becomes sql 'x = @0' which is fine - unless they are nullable types,
|
||||
// because sql 'x = NULL' is always false and the 'IS NULL' syntax is required,
|
||||
// so for comparing nullable types, we use x.SqlNullableEquals(y, fb) where fb is a fallback
|
||||
// value which will be used when values are null - turning the comparison into
|
||||
// sql 'COALESCE(x,fb) = COALESCE(y,fb)' - of course, fb must be a value outside
|
||||
// of x and y range - and if that is not possible, then a manual comparison need
|
||||
// to be written
|
||||
//TODO support SqlNullableEquals with 0 parameters, using the full syntax below
|
||||
case "SqlNullableEquals":
|
||||
var compareTo = Visit(m.Arguments[1]);
|
||||
var fallback = Visit(m.Arguments[2]);
|
||||
// that would work without a fallback value but is more cumbersome
|
||||
//return Visited ? string.Empty : $"((({compareTo} is null) AND ({visitedMethodObject} is null)) OR (({compareTo} is not null) AND ({visitedMethodObject} = {compareTo})))";
|
||||
// use a fallback value
|
||||
return Visited ? string.Empty : $"(COALESCE({visitedMethodObject},{fallback}) = COALESCE({compareTo},{fallback}))";
|
||||
|
||||
default:
|
||||
|
||||
throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name);
|
||||
|
||||
@@ -9,6 +9,21 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
/// </summary>
|
||||
internal static class SqlExpressionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether two nullable values are equal, substituting a fallback value for nulls.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The nullable type.</typeparam>
|
||||
/// <param name="value">The value to compare.</param>
|
||||
/// <param name="other">The value to compare to.</param>
|
||||
/// <param name="fallbackValue">The value to use when any value is null.</param>
|
||||
/// <remarks>Do not use outside of Sql expressions.</remarks>
|
||||
// see usage in ExpressionVisitorBase
|
||||
public static bool SqlNullableEquals<T>(this T? value, T? other, T fallbackValue)
|
||||
where T : struct
|
||||
{
|
||||
return (value ?? fallbackValue).Equals(other ?? fallbackValue);
|
||||
}
|
||||
|
||||
public static bool SqlIn<T>(this IEnumerable<T> collection, T item)
|
||||
{
|
||||
return collection.Contains(item);
|
||||
|
||||
@@ -19,7 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <para>When <paramref name="replaceTags"/> is false, the tags specified in <paramref name="tags"/> are added to those already assigned.</para>
|
||||
/// <para>When <paramref name="tags"/> is empty and <paramref name="replaceTags"/> is true, all assigned tags are removed.</para>
|
||||
/// </remarks>
|
||||
void Assign(int contentId, int propertyTypeId, IEnumerable<ITag> tags, bool replaceTags);
|
||||
// TODO: replaceTags is used as 'false' in tests exclusively - should get rid of it
|
||||
void Assign(int contentId, int propertyTypeId, IEnumerable<ITag> tags, bool replaceTags = true);
|
||||
|
||||
/// <summary>
|
||||
/// Removes assigned tags from a content property.
|
||||
@@ -46,54 +47,48 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
#region Queries
|
||||
|
||||
/// <summary>
|
||||
/// Gets a tagged entity.
|
||||
/// </summary>
|
||||
TaggedEntity GetTaggedEntityByKey(Guid key);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a tagged entity.
|
||||
/// </summary>
|
||||
TaggedEntity GetTaggedEntityById(int id);
|
||||
|
||||
IEnumerable<TaggedEntity> GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup);
|
||||
|
||||
IEnumerable<TaggedEntity> GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null);
|
||||
/// Gets all entities of a type, tagged with any tag in the specified group.
|
||||
IEnumerable<TaggedEntity> GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string group, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tags for an entity type (content/media/member)
|
||||
/// Gets all entities of a type, tagged with the specified tag.
|
||||
/// </summary>
|
||||
/// <param name="objectType">Entity type</param>
|
||||
/// <param name="group">Optional group</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<ITag> GetTagsForEntityType(TaggableObjectTypes objectType, string group = null);
|
||||
IEnumerable<TaggedEntity> GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tags that exist on the content item - Content/Media/Member
|
||||
/// Gets all tags for an entity type.
|
||||
/// </summary>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="group">Optional group</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<ITag> GetTagsForEntity(int contentId, string group = null);
|
||||
IEnumerable<ITag> GetTagsForEntityType(TaggableObjectTypes objectType, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tags that exist on the content item - Content/Media/Member
|
||||
/// Gets all tags attached to an entity.
|
||||
/// </summary>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="group">Optional group</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<ITag> GetTagsForEntity(Guid contentId, string group = null);
|
||||
IEnumerable<ITag> GetTagsForEntity(int contentId, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tags that exist on the content item for the property specified - Content/Media/Member
|
||||
/// Gets all tags attached to an entity.
|
||||
/// </summary>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="propertyTypeAlias">The property alias to get tags for</param>
|
||||
/// <param name="group">Optional group</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<ITag> GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null);
|
||||
IEnumerable<ITag> GetTagsForEntity(Guid contentId, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tags that exist on the content item for the property specified - Content/Media/Member
|
||||
/// Gets all tags attached to an entity via a property.
|
||||
/// </summary>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="propertyTypeAlias">The property alias to get tags for</param>
|
||||
/// <param name="group">Optional group</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<ITag> GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null);
|
||||
IEnumerable<ITag> GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to an entity via a property.
|
||||
/// </summary>
|
||||
IEnumerable<ITag> GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null, string culture = null);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -217,8 +217,26 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
foreach (var property in entity.Properties)
|
||||
{
|
||||
var tagConfiguration = property.GetTagConfiguration();
|
||||
if (tagConfiguration == null) continue;
|
||||
tagRepo.Assign(entity.Id, property.PropertyTypeId, property.GetTagsValue().Select(x => new Tag { Group = tagConfiguration.Group, Text = x }), true);
|
||||
if (tagConfiguration == null) continue; // not a tags property
|
||||
|
||||
if (property.PropertyType.VariesByCulture())
|
||||
{
|
||||
var tags = new List<ITag>();
|
||||
foreach (var pvalue in property.Values)
|
||||
{
|
||||
var tagsValue = property.GetTagsValue(pvalue.Culture);
|
||||
var languageId = LanguageRepository.GetIdByIsoCode(pvalue.Culture);
|
||||
var cultureTags = tagsValue.Select(x => new Tag { Group = tagConfiguration.Group, Text = x, LanguageId = languageId });
|
||||
tags.AddRange(cultureTags);
|
||||
}
|
||||
tagRepo.Assign(entity.Id, property.PropertyTypeId, tags);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tagsValue = property.GetTagsValue(); // strings
|
||||
var tags = tagsValue.Select(x => new Tag { Group = tagConfiguration.Group, Text = x });
|
||||
tagRepo.Assign(entity.Id, property.PropertyTypeId, tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,16 +559,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
propertyDataDtos.AddRange(propertyDataDtos2);
|
||||
var properties = PropertyFactory.BuildEntities(compositionProperties, propertyDataDtos, temp.PublishedVersionId, LanguageRepository).ToList();
|
||||
|
||||
// deal with tags
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (!tagConfigurations.TryGetValue(property.PropertyType.PropertyEditorAlias, out var tagConfiguration))
|
||||
continue;
|
||||
|
||||
//fixme doesn't take into account variants
|
||||
property.SetTagsValue(property.GetValue(), tagConfiguration);
|
||||
}
|
||||
|
||||
if (result.ContainsKey(temp.VersionId))
|
||||
{
|
||||
if (ContentRepositoryBase.ThrowOnWarning)
|
||||
|
||||
@@ -663,9 +663,11 @@ AND umbracoNode.id <> @id",
|
||||
{
|
||||
case ContentVariation.Culture:
|
||||
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
|
||||
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
|
||||
break;
|
||||
case ContentVariation.Nothing:
|
||||
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
|
||||
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
@@ -757,6 +759,139 @@ AND umbracoNode.id <> @id",
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
private void CopyTagData(int? sourceLanguageId, int? targetLanguageId, IReadOnlyCollection<int> propertyTypeIds, IReadOnlyCollection<int> contentTypeIds = null)
|
||||
{
|
||||
// note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers
|
||||
|
||||
// fixme - should we batch then?
|
||||
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
|
||||
if (whereInArgsCount > 2000)
|
||||
throw new NotSupportedException("Too many property/content types.");
|
||||
|
||||
// delete existing relations (for target language)
|
||||
// do *not* delete existing tags
|
||||
|
||||
var sqlSelectTagsToDelete = Sql()
|
||||
.Select<TagDto>(x => x.Id)
|
||||
.From<TagDto>()
|
||||
.InnerJoin<TagRelationshipDto>().On<TagDto, TagRelationshipDto>((tag, rel) => tag.Id == rel.TagId);
|
||||
|
||||
if (contentTypeIds != null)
|
||||
sqlSelectTagsToDelete
|
||||
.InnerJoin<ContentDto>().On<TagRelationshipDto, ContentDto>((rel, content) => rel.NodeId == content.NodeId)
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
|
||||
|
||||
sqlSelectTagsToDelete
|
||||
.WhereIn<TagRelationshipDto>(x => x.PropertyTypeId, propertyTypeIds)
|
||||
.Where<TagDto>(x => x.LanguageId.SqlNullableEquals(targetLanguageId, -1));
|
||||
|
||||
var sqlDeleteRelations = Sql()
|
||||
.Delete<TagRelationshipDto>()
|
||||
.WhereIn<TagRelationshipDto>(x => x.TagId, sqlSelectTagsToDelete);
|
||||
|
||||
Database.Execute(sqlDeleteRelations);
|
||||
|
||||
// do *not* delete the tags - they could be used by other content types / property types
|
||||
/*
|
||||
var sqlDeleteTag = Sql()
|
||||
.Delete<TagDto>()
|
||||
.WhereIn<TagDto>(x => x.Id, sqlTagToDelete);
|
||||
Database.Execute(sqlDeleteTag);
|
||||
*/
|
||||
|
||||
// copy tags from source language to target language
|
||||
// target tags may exist already, so we have to check for existence here
|
||||
//
|
||||
// select tags to insert: tags pointed to by a relation ship, for proper property/content types,
|
||||
// and of source language, and where we cannot left join to an existing tag with same text,
|
||||
// group and languageId
|
||||
|
||||
var targetLanguageIdS = targetLanguageId.HasValue ? targetLanguageId.ToString() : "NULL";
|
||||
var sqlSelectTagsToInsert = Sql()
|
||||
.SelectDistinct<TagDto>(x => x.Text, x => x.Group)
|
||||
.Append(", " + targetLanguageIdS)
|
||||
.From<TagDto>();
|
||||
|
||||
sqlSelectTagsToInsert
|
||||
.InnerJoin<TagRelationshipDto>().On<TagDto, TagRelationshipDto>((tag, rel) => tag.Id == rel.TagId)
|
||||
.LeftJoin<TagDto>("xtags").On<TagDto, TagDto>((tag, xtag) => tag.Text == xtag.Text && tag.Group == xtag.Group && xtag.LanguageId.SqlNullableEquals(targetLanguageId, -1), aliasRight: "xtags");
|
||||
|
||||
if (contentTypeIds != null)
|
||||
sqlSelectTagsToInsert
|
||||
.InnerJoin<ContentDto>().On<TagRelationshipDto, ContentDto>((rel, content) => rel.NodeId == content.NodeId)
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
|
||||
|
||||
sqlSelectTagsToInsert
|
||||
.WhereIn<TagRelationshipDto>(x => x.PropertyTypeId, propertyTypeIds)
|
||||
.WhereNull<TagDto>(x => x.Id, "xtags") // ie, not exists
|
||||
.Where<TagDto>(x => x.LanguageId.SqlNullableEquals(sourceLanguageId, -1));
|
||||
|
||||
var cols = Sql().Columns<TagDto>(x => x.Text, x => x.Group, x => x.LanguageId);
|
||||
var sqlInsertTags = Sql($"INSERT INTO {TagDto.TableName} ({cols})").Append(sqlSelectTagsToInsert);
|
||||
|
||||
Database.Execute(sqlInsertTags);
|
||||
|
||||
// create relations to new tags
|
||||
// any existing relations have been deleted above, no need to check for existence here
|
||||
//
|
||||
// select node id and property type id from existing relations to tags of source language,
|
||||
// for proper property/content types, and select new tag id from tags, with matching text,
|
||||
// and group, but for the target language
|
||||
|
||||
var sqlSelectRelationsToInsert = Sql()
|
||||
.SelectDistinct<TagRelationshipDto>(x => x.NodeId, x => x.PropertyTypeId)
|
||||
.AndSelect<TagDto>("otag", x => x.Id)
|
||||
.From<TagRelationshipDto>()
|
||||
.InnerJoin<TagDto>().On<TagRelationshipDto, TagDto>((rel, tag) => rel.TagId == tag.Id)
|
||||
.InnerJoin<TagDto>("otag").On<TagDto, TagDto>((tag, otag) => tag.Text == otag.Text && tag.Group == otag.Group && otag.LanguageId.SqlNullableEquals(targetLanguageId, -1), aliasRight: "otag");
|
||||
|
||||
if (contentTypeIds != null)
|
||||
sqlSelectRelationsToInsert
|
||||
.InnerJoin<ContentDto>().On<TagRelationshipDto, ContentDto>((rel, content) => rel.NodeId == content.NodeId)
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
|
||||
|
||||
sqlSelectRelationsToInsert
|
||||
.Where<TagDto>(x => x.LanguageId.SqlNullableEquals(sourceLanguageId, -1))
|
||||
.WhereIn<TagRelationshipDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
var relationColumnsToInsert = Sql().Columns<TagRelationshipDto>(x => x.NodeId, x => x.PropertyTypeId, x => x.TagId);
|
||||
var sqlInsertRelations = Sql($"INSERT INTO {TagRelationshipDto.TableName} ({relationColumnsToInsert})").Append(sqlSelectRelationsToInsert);
|
||||
|
||||
Database.Execute(sqlInsertRelations);
|
||||
|
||||
// delete original relations - *not* the tags - all of them
|
||||
// cannot really "go back" with relations, would have to do it with property values
|
||||
|
||||
sqlSelectTagsToDelete = Sql()
|
||||
.Select<TagDto>(x => x.Id)
|
||||
.From<TagDto>()
|
||||
.InnerJoin<TagRelationshipDto>().On<TagDto, TagRelationshipDto>((tag, rel) => tag.Id == rel.TagId);
|
||||
|
||||
if (contentTypeIds != null)
|
||||
sqlSelectTagsToDelete
|
||||
.InnerJoin<ContentDto>().On<TagRelationshipDto, ContentDto>((rel, content) => rel.NodeId == content.NodeId)
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
|
||||
|
||||
sqlSelectTagsToDelete
|
||||
.WhereIn<TagRelationshipDto>(x => x.PropertyTypeId, propertyTypeIds)
|
||||
.Where<TagDto>(x => !x.LanguageId.SqlNullableEquals(targetLanguageId, -1));
|
||||
|
||||
sqlDeleteRelations = Sql()
|
||||
.Delete<TagRelationshipDto>()
|
||||
.WhereIn<TagRelationshipDto>(x => x.TagId, sqlSelectTagsToDelete);
|
||||
|
||||
Database.Execute(sqlDeleteRelations);
|
||||
|
||||
// no
|
||||
/*
|
||||
var sqlDeleteTag = Sql()
|
||||
.Delete<TagDto>()
|
||||
.WhereIn<TagDto>(x => x.Id, sqlTagToDelete);
|
||||
Database.Execute(sqlDeleteTag);
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies property data from one language to another.
|
||||
/// </summary>
|
||||
@@ -766,6 +901,8 @@ AND umbracoNode.id <> @id",
|
||||
/// <param name="contentTypeIds">The content type identifiers.</param>
|
||||
private void CopyPropertyData(int? sourceLanguageId, int? targetLanguageId, IReadOnlyCollection<int> propertyTypeIds, IReadOnlyCollection<int> contentTypeIds = null)
|
||||
{
|
||||
// note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers
|
||||
//
|
||||
// fixme - should we batch then?
|
||||
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
|
||||
if (whereInArgsCount > 2000)
|
||||
@@ -793,11 +930,7 @@ AND umbracoNode.id <> @id",
|
||||
sqlDelete.WhereIn<PropertyDataDto>(x => x.VersionId, inSql);
|
||||
}
|
||||
|
||||
// NPoco cannot turn the clause into IS NULL with a nullable parameter - deal with it
|
||||
if (targetLanguageId == null)
|
||||
sqlDelete.Where<PropertyDataDto>(x => x.LanguageId == null);
|
||||
else
|
||||
sqlDelete.Where<PropertyDataDto>(x => x.LanguageId == targetLanguageId);
|
||||
sqlDelete.Where<PropertyDataDto>(x => x.LanguageId.SqlNullableEquals(targetLanguageId, -1));
|
||||
|
||||
sqlDelete
|
||||
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
@@ -821,11 +954,7 @@ AND umbracoNode.id <> @id",
|
||||
.InnerJoin<ContentVersionDto>().On<PropertyDataDto, ContentVersionDto>((pdata, cversion) => pdata.VersionId == cversion.Id)
|
||||
.InnerJoin<ContentDto>().On<ContentVersionDto, ContentDto>((cversion, c) => cversion.NodeId == c.NodeId);
|
||||
|
||||
// NPoco cannot turn the clause into IS NULL with a nullable parameter - deal with it
|
||||
if (sourceLanguageId == null)
|
||||
sqlSelectData.Where<PropertyDataDto>(x => x.LanguageId == null);
|
||||
else
|
||||
sqlSelectData.Where<PropertyDataDto>(x => x.LanguageId == sourceLanguageId);
|
||||
sqlSelectData.Where<PropertyDataDto>(x => x.LanguageId.SqlNullableEquals(sourceLanguageId, -1));
|
||||
|
||||
sqlSelectData
|
||||
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
@@ -111,7 +111,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
"DELETE FROM umbracoPropertyData WHERE languageId = @id",
|
||||
"DELETE FROM umbracoContentVersionCultureVariation WHERE languageId = @id",
|
||||
"DELETE FROM umbracoDocumentCultureVariation WHERE languageId = @id",
|
||||
"DELETE FROM umbracoLanguage WHERE id = @id"
|
||||
"DELETE FROM umbracoLanguage WHERE id = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id"
|
||||
};
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Factories;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Scoping;
|
||||
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
@@ -109,74 +110,65 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
#region Assign and Remove Tags
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Assign(int contentId, int propertyTypeId, IEnumerable<ITag> tags, bool replaceTags)
|
||||
// only invoked from ContentRepositoryBase with all cultures + replaceTags being true
|
||||
public void Assign(int contentId, int propertyTypeId, IEnumerable<ITag> tags, bool replaceTags = true)
|
||||
{
|
||||
// to no-duplicates array
|
||||
var tagsA = tags.Distinct(new TagComparer()).ToArray();
|
||||
|
||||
// no tags?
|
||||
if (tagsA.Length == 0)
|
||||
// replacing = clear all
|
||||
if (replaceTags)
|
||||
{
|
||||
// replacing = clear all
|
||||
if (replaceTags)
|
||||
{
|
||||
var sql0 = Sql().Delete<TagRelationshipDto>().Where<TagRelationshipDto>(x => x.NodeId == contentId && x.PropertyTypeId == propertyTypeId);
|
||||
Database.Execute(sql0);
|
||||
}
|
||||
|
||||
// nothing else to do
|
||||
return;
|
||||
var sql0 = Sql().Delete<TagRelationshipDto>().Where<TagRelationshipDto>(x => x.NodeId == contentId && x.PropertyTypeId == propertyTypeId);
|
||||
Database.Execute(sql0);
|
||||
}
|
||||
|
||||
// no tags? nothing else to do
|
||||
if (tagsA.Length == 0)
|
||||
return;
|
||||
|
||||
// tags
|
||||
// using some clever logic (?) to insert tags that don't exist in 1 query
|
||||
// must coalesce languageId because equality of NULLs does not exist
|
||||
|
||||
var tagSetSql = GetTagSet(tagsA);
|
||||
var group = SqlSyntax.GetQuotedColumnName("group");
|
||||
|
||||
// insert tags
|
||||
var sql1 = $@"INSERT INTO cmsTags (tag, {group})
|
||||
SELECT tagSet.tag, tagSet.{group}
|
||||
var sql1 = $@"INSERT INTO cmsTags (tag, {group}, languageId)
|
||||
SELECT tagSet.tag, tagSet.{group}, tagSet.languageId
|
||||
FROM {tagSetSql}
|
||||
LEFT OUTER JOIN cmsTags ON (tagSet.tag = cmsTags.tag AND tagSet.{group} = cmsTags.{group})
|
||||
LEFT OUTER JOIN cmsTags ON (tagSet.tag = cmsTags.tag AND tagSet.{group} = cmsTags.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(cmsTags.languageId, -1))
|
||||
WHERE cmsTags.id IS NULL";
|
||||
|
||||
Database.Execute(sql1);
|
||||
|
||||
// if replacing, remove everything first
|
||||
if (replaceTags)
|
||||
{
|
||||
var sql2 = Sql().Delete<TagRelationshipDto>().Where<TagRelationshipDto>(x => x.NodeId == contentId && x.PropertyTypeId == propertyTypeId);
|
||||
Database.Execute(sql2);
|
||||
}
|
||||
|
||||
// insert relations
|
||||
var sql3 = $@"INSERT INTO cmsTagRelationship (nodeId, propertyTypeId, tagId)
|
||||
var sql2 = $@"INSERT INTO cmsTagRelationship (nodeId, propertyTypeId, tagId)
|
||||
SELECT {contentId}, {propertyTypeId}, tagSet2.Id
|
||||
FROM (
|
||||
SELECT t.Id
|
||||
FROM {tagSetSql}
|
||||
INNER JOIN cmsTags as t ON (tagSet.tag = t.tag AND tagSet.{group} = t.{group})
|
||||
INNER JOIN cmsTags as t ON (tagSet.tag = t.tag AND tagSet.{group} = t.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(t.languageId, -1))
|
||||
) AS tagSet2
|
||||
LEFT OUTER JOIN cmsTagRelationship r ON (tagSet2.id = r.tagId AND r.nodeId = {contentId} AND r.propertyTypeID = {propertyTypeId})
|
||||
WHERE r.tagId IS NULL";
|
||||
|
||||
Database.Execute(sql3);
|
||||
Database.Execute(sql2);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
// only invoked from tests
|
||||
public void Remove(int contentId, int propertyTypeId, IEnumerable<ITag> tags)
|
||||
{
|
||||
var tagSetSql = GetTagSet(tags);
|
||||
var group = SqlSyntax.GetQuotedColumnName("group");
|
||||
|
||||
var deleteSql = string.Concat("DELETE FROM cmsTagRelationship WHERE nodeId = ",
|
||||
contentId,
|
||||
" AND propertyTypeId = ",
|
||||
propertyTypeId,
|
||||
" AND tagId IN ",
|
||||
"(SELECT id FROM cmsTags INNER JOIN ",
|
||||
tagSetSql,
|
||||
" ON (TagSet.Tag = cmsTags.Tag and TagSet." + SqlSyntax.GetQuotedColumnName("group") + @" = cmsTags." + SqlSyntax.GetQuotedColumnName("group") + @"))");
|
||||
var deleteSql = $@"DELETE FROM cmsTagRelationship WHERE nodeId = {contentId} AND propertyTypeId = {propertyTypeId} AND tagId IN (
|
||||
SELECT id FROM cmsTags INNER JOIN {tagSetSql} ON (
|
||||
tagSet.tag = cmsTags.tag AND tagSet.{group} = cmsTags.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(cmsTags.languageId, -1)
|
||||
)
|
||||
)";
|
||||
|
||||
Database.Execute(deleteSql);
|
||||
}
|
||||
@@ -207,13 +199,6 @@ WHERE r.tagId IS NULL";
|
||||
//
|
||||
private string GetTagSet(IEnumerable<ITag> tags)
|
||||
{
|
||||
string EscapeSqlString(string s)
|
||||
{
|
||||
// why were we escaping @ symbols?
|
||||
//return NPocoDatabaseExtensions.EscapeAtSymbols(s.Replace("'", "''"));
|
||||
return s.Replace("'", "''");
|
||||
}
|
||||
|
||||
var sql = new StringBuilder();
|
||||
var group = SqlSyntax.GetQuotedColumnName("group");
|
||||
var first = true;
|
||||
@@ -226,11 +211,17 @@ WHERE r.tagId IS NULL";
|
||||
else sql.Append(" UNION ");
|
||||
|
||||
sql.Append("SELECT N'");
|
||||
sql.Append(EscapeSqlString(tag.Text));
|
||||
sql.Append(SqlSyntax.EscapeString(tag.Text));
|
||||
sql.Append("' AS tag, '");
|
||||
sql.Append(EscapeSqlString(tag.Group));
|
||||
sql.Append(SqlSyntax.EscapeString(tag.Group));
|
||||
sql.Append("' AS ");
|
||||
sql.Append(group);
|
||||
sql.Append(" , ");
|
||||
if (tag.LanguageId.HasValue)
|
||||
sql.Append(tag.LanguageId);
|
||||
else
|
||||
sql.Append("NULL");
|
||||
sql.Append(" AS languageId");
|
||||
}
|
||||
|
||||
sql.Append(") AS tagSet");
|
||||
@@ -244,14 +235,17 @@ WHERE r.tagId IS NULL";
|
||||
public bool Equals(ITag x, ITag y)
|
||||
{
|
||||
return ReferenceEquals(x, y) // takes care of both being null
|
||||
|| x != null && y != null && x.Text == y.Text && x.Group == y.Group;
|
||||
|| x != null && y != null && x.Text == y.Text && x.Group == y.Group && x.LanguageId == y.LanguageId;
|
||||
}
|
||||
|
||||
public int GetHashCode(ITag obj)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (obj.Text.GetHashCode() * 397) ^ obj.Group.GetHashCode();
|
||||
var h = obj.Text.GetHashCode();
|
||||
h = h * 397 ^ obj.Group.GetHashCode();
|
||||
h = h * 397 ^ (obj.LanguageId?.GetHashCode() ?? 0);
|
||||
return h;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,118 +258,126 @@ WHERE r.tagId IS NULL";
|
||||
// consider caching implications
|
||||
// add lookups for parentId or path (ie get content in tag group, that are descendants of x)
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Local
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
||||
private class TaggedEntityDto
|
||||
{
|
||||
public int NodeId { get; set; }
|
||||
public string PropertyTypeAlias { get; set; }
|
||||
public int PropertyTypeId { get; set; }
|
||||
public int TagId { get; set; }
|
||||
public string TagText { get; set; }
|
||||
public string TagGroup { get; set; }
|
||||
public int? TagLanguage { get; set; }
|
||||
}
|
||||
// ReSharper restore UnusedAutoPropertyAccessor.Local
|
||||
|
||||
/// <inheritdoc />
|
||||
public TaggedEntity GetTaggedEntityByKey(Guid key)
|
||||
{
|
||||
var sql = Sql()
|
||||
.Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group"))
|
||||
.From<TagDto>()
|
||||
.InnerJoin<TagRelationshipDto>()
|
||||
.On<TagRelationshipDto, TagDto>(left => left.TagId, right => right.Id)
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<ContentDto, TagRelationshipDto>(left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyTypeDto, TagRelationshipDto>(left => left.Id, right => right.PropertyTypeId)
|
||||
.InnerJoin<NodeDto>()
|
||||
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
|
||||
var sql = GetTaggedEntitiesSql(TaggableObjectTypes.All, "*");
|
||||
|
||||
sql = sql
|
||||
.Where<NodeDto>(dto => dto.UniqueId == key);
|
||||
|
||||
return CreateTaggedEntityCollection(Database.Fetch<dynamic>(sql)).FirstOrDefault();
|
||||
return Map(Database.Fetch<TaggedEntityDto>(sql)).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TaggedEntity GetTaggedEntityById(int id)
|
||||
{
|
||||
var sql = Sql()
|
||||
.Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group"))
|
||||
.From<TagDto>()
|
||||
.InnerJoin<TagRelationshipDto>()
|
||||
.On<TagRelationshipDto, TagDto>(left => left.TagId, right => right.Id)
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<ContentDto, TagRelationshipDto>(left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyTypeDto, TagRelationshipDto>(left => left.Id, right => right.PropertyTypeId)
|
||||
.InnerJoin<NodeDto>()
|
||||
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
|
||||
var sql = GetTaggedEntitiesSql(TaggableObjectTypes.All, "*");
|
||||
|
||||
sql = sql
|
||||
.Where<NodeDto>(dto => dto.NodeId == id);
|
||||
|
||||
return CreateTaggedEntityCollection(Database.Fetch<dynamic>(sql)).FirstOrDefault();
|
||||
return Map(Database.Fetch<TaggedEntityDto>(sql)).FirstOrDefault();
|
||||
}
|
||||
|
||||
public IEnumerable<TaggedEntity> GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaggedEntity> GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string group, string culture = null)
|
||||
{
|
||||
var sql = Sql()
|
||||
.Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group"))
|
||||
.From<TagDto>()
|
||||
.InnerJoin<TagRelationshipDto>()
|
||||
.On<TagRelationshipDto, TagDto>(left => left.TagId, right => right.Id)
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<ContentDto, TagRelationshipDto>(left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyTypeDto, TagRelationshipDto>(left => left.Id, right => right.PropertyTypeId)
|
||||
.InnerJoin<NodeDto>()
|
||||
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
|
||||
.Where<TagDto>(dto => dto.Group == tagGroup);
|
||||
var sql = GetTaggedEntitiesSql(objectType, culture);
|
||||
|
||||
if (objectType != TaggableObjectTypes.All)
|
||||
{
|
||||
var nodeObjectType = GetNodeObjectType(objectType);
|
||||
sql = sql
|
||||
.Where<NodeDto>(dto => dto.NodeObjectType == nodeObjectType);
|
||||
}
|
||||
sql = sql
|
||||
.Where<TagDto>(x => x.Group == group);
|
||||
|
||||
return CreateTaggedEntityCollection(
|
||||
Database.Fetch<dynamic>(sql));
|
||||
return Map(Database.Fetch<TaggedEntityDto>(sql));
|
||||
}
|
||||
|
||||
public IEnumerable<TaggedEntity> GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaggedEntity> GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string group = null, string culture = null)
|
||||
{
|
||||
var sql = Sql()
|
||||
.Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group"))
|
||||
.From<TagDto>()
|
||||
.InnerJoin<TagRelationshipDto>()
|
||||
.On<TagRelationshipDto, TagDto>(left => left.TagId, right => right.Id)
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<ContentDto, TagRelationshipDto>(left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyTypeDto, TagRelationshipDto>(left => left.Id, right => right.PropertyTypeId)
|
||||
.InnerJoin<NodeDto>()
|
||||
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
|
||||
var sql = GetTaggedEntitiesSql(objectType, culture);
|
||||
|
||||
sql = sql
|
||||
.Where<TagDto>(dto => dto.Text == tag);
|
||||
|
||||
if (group.IsNullOrWhiteSpace() == false)
|
||||
sql = sql
|
||||
.Where<TagDto>(dto => dto.Group == group);
|
||||
|
||||
return Map(Database.Fetch<TaggedEntityDto>(sql));
|
||||
}
|
||||
|
||||
private Sql<ISqlContext> GetTaggedEntitiesSql(TaggableObjectTypes objectType, string culture)
|
||||
{
|
||||
var sql = Sql()
|
||||
.Select<TagRelationshipDto>(x => Alias(x.NodeId, "NodeId"))
|
||||
.AndSelect<PropertyTypeDto>(x => Alias(x.Alias, "PropertyTypeAlias"), x => Alias(x.Id, "PropertyTypeId"))
|
||||
.AndSelect<TagDto>(x => Alias(x.Id, "TagId"), x => Alias(x.Text, "TagText"), x => Alias(x.Group, "TagGroup"), x => Alias(x.LanguageId, "TagLanguage"))
|
||||
.From<TagDto>()
|
||||
.InnerJoin<TagRelationshipDto>().On<TagDto, TagRelationshipDto>((tag, rel) => tag.Id == rel.TagId)
|
||||
.InnerJoin<ContentDto>().On<TagRelationshipDto, ContentDto>((rel, content) => rel.NodeId == content.NodeId)
|
||||
.InnerJoin<PropertyTypeDto>().On<TagRelationshipDto, PropertyTypeDto>((rel, prop) => rel.PropertyTypeId == prop.Id)
|
||||
.InnerJoin<NodeDto>().On<ContentDto, NodeDto>((content, node) => content.NodeId == node.NodeId);
|
||||
|
||||
if (culture == null)
|
||||
{
|
||||
sql = sql
|
||||
.Where<TagDto>(dto => dto.LanguageId == null);
|
||||
}
|
||||
else if (culture != "*")
|
||||
{
|
||||
sql = sql
|
||||
.InnerJoin<LanguageDto>().On<TagDto, LanguageDto>((tag, lang) => tag.LanguageId == lang.Id)
|
||||
.Where<LanguageDto>(x => x.IsoCode == culture);
|
||||
}
|
||||
|
||||
if (objectType != TaggableObjectTypes.All)
|
||||
{
|
||||
var nodeObjectType = GetNodeObjectType(objectType);
|
||||
sql = sql
|
||||
.Where<NodeDto>(dto => dto.NodeObjectType == nodeObjectType);
|
||||
sql = sql.Where<NodeDto>(dto => dto.NodeObjectType == nodeObjectType);
|
||||
}
|
||||
|
||||
if (tagGroup.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
sql = sql.Where<TagDto>(dto => dto.Group == tagGroup);
|
||||
}
|
||||
|
||||
return CreateTaggedEntityCollection(
|
||||
Database.Fetch<dynamic>(sql));
|
||||
return sql;
|
||||
}
|
||||
|
||||
private IEnumerable<TaggedEntity> CreateTaggedEntityCollection(IEnumerable<dynamic> dbResult)
|
||||
private static IEnumerable<TaggedEntity> Map(IEnumerable<TaggedEntityDto> dtos)
|
||||
{
|
||||
foreach (var node in dbResult.GroupBy(x => (int)x.nodeId))
|
||||
return dtos.GroupBy(x => x.NodeId).Select(dtosForNode =>
|
||||
{
|
||||
var properties = new List<TaggedProperty>();
|
||||
foreach (var propertyType in node.GroupBy(x => new { id = (int)x.propertyTypeId, alias = (string)x.Alias }))
|
||||
var taggedProperties = dtosForNode.GroupBy(x => x.PropertyTypeId).Select(dtosForProperty =>
|
||||
{
|
||||
var tags = propertyType.Select(x => new Tag((int)x.tagId, (string)x.group, (string)x.tag));
|
||||
properties.Add(new TaggedProperty(propertyType.Key.id, propertyType.Key.alias, tags));
|
||||
}
|
||||
yield return new TaggedEntity(node.Key, properties);
|
||||
}
|
||||
string propertyTypeAlias = null;
|
||||
var tags = dtosForProperty.Select(dto =>
|
||||
{
|
||||
propertyTypeAlias = dto.PropertyTypeAlias;
|
||||
return new Tag(dto.TagId, dto.TagGroup, dto.TagText, dto.TagLanguage);
|
||||
}).ToList();
|
||||
return new TaggedProperty(dtosForProperty.Key, propertyTypeAlias, tags);
|
||||
}).ToList();
|
||||
|
||||
return new TaggedEntity(dtosForNode.Key, taggedProperties);
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<ITag> GetTagsForEntityType(TaggableObjectTypes objectType, string group = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetTagsForEntityType(TaggableObjectTypes objectType, string group = null, string culture = null)
|
||||
{
|
||||
var sql = GetTagsQuerySelect(true);
|
||||
var sql = GetTagsSql(culture, true);
|
||||
|
||||
sql = ApplyRelationshipJoinToTagsQuery(sql);
|
||||
AddTagsSqlWhere(sql, culture);
|
||||
|
||||
if (objectType != TaggableObjectTypes.All)
|
||||
{
|
||||
@@ -384,116 +386,126 @@ WHERE r.tagId IS NULL";
|
||||
.Where<NodeDto>(dto => dto.NodeObjectType == nodeObjectType);
|
||||
}
|
||||
|
||||
sql = ApplyGroupFilterToTagsQuery(sql, group);
|
||||
if (group.IsNullOrWhiteSpace() == false)
|
||||
sql = sql
|
||||
.Where<TagDto>(dto => dto.Group == group);
|
||||
|
||||
sql = ApplyGroupByToTagsQuery(sql);
|
||||
sql = sql
|
||||
.GroupBy<TagDto>(x => x.Id, x => x.Text, x => x.Group, x => x.LanguageId);
|
||||
|
||||
return ExecuteTagsQuery(sql);
|
||||
}
|
||||
|
||||
public IEnumerable<ITag> GetTagsForEntity(int contentId, string group = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetTagsForEntity(int contentId, string group = null, string culture = null)
|
||||
{
|
||||
var sql = GetTagsQuerySelect();
|
||||
var sql = GetTagsSql(culture);
|
||||
|
||||
sql = ApplyRelationshipJoinToTagsQuery(sql);
|
||||
AddTagsSqlWhere(sql, culture);
|
||||
|
||||
sql = sql
|
||||
.Where<NodeDto>(dto => dto.NodeId == contentId);
|
||||
|
||||
sql = ApplyGroupFilterToTagsQuery(sql, group);
|
||||
if (group.IsNullOrWhiteSpace() == false)
|
||||
sql = sql
|
||||
.Where<TagDto>(dto => dto.Group == group);
|
||||
|
||||
return ExecuteTagsQuery(sql);
|
||||
}
|
||||
|
||||
public IEnumerable<ITag> GetTagsForEntity(Guid contentId, string group = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetTagsForEntity(Guid contentId, string group = null, string culture = null)
|
||||
{
|
||||
var sql = GetTagsQuerySelect();
|
||||
var sql = GetTagsSql(culture);
|
||||
|
||||
sql = ApplyRelationshipJoinToTagsQuery(sql);
|
||||
AddTagsSqlWhere(sql, culture);
|
||||
|
||||
sql = sql
|
||||
.Where<NodeDto>(dto => dto.UniqueId == contentId);
|
||||
|
||||
sql = ApplyGroupFilterToTagsQuery(sql, group);
|
||||
if (group.IsNullOrWhiteSpace() == false)
|
||||
sql = sql
|
||||
.Where<TagDto>(dto => dto.Group == group);
|
||||
|
||||
return ExecuteTagsQuery(sql);
|
||||
}
|
||||
|
||||
public IEnumerable<ITag> GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null, string culture = null)
|
||||
{
|
||||
var sql = GetTagsQuerySelect();
|
||||
|
||||
sql = ApplyRelationshipJoinToTagsQuery(sql);
|
||||
var sql = GetTagsSql(culture);
|
||||
|
||||
sql = sql
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyTypeDto, TagRelationshipDto>(left => left.Id, right => right.PropertyTypeId)
|
||||
.Where<NodeDto>(dto => dto.NodeId == contentId)
|
||||
.Where<PropertyTypeDto>(dto => dto.Alias == propertyTypeAlias);
|
||||
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, TagRelationshipDto>((prop, rel) => prop.Id == rel.PropertyTypeId)
|
||||
.Where<NodeDto>(x => x.NodeId == contentId)
|
||||
.Where<PropertyTypeDto>(x => x.Alias == propertyTypeAlias);
|
||||
|
||||
sql = ApplyGroupFilterToTagsQuery(sql, group);
|
||||
AddTagsSqlWhere(sql, culture);
|
||||
|
||||
if (group.IsNullOrWhiteSpace() == false)
|
||||
sql = sql
|
||||
.Where<TagDto>(dto => dto.Group == group);
|
||||
|
||||
return ExecuteTagsQuery(sql);
|
||||
}
|
||||
|
||||
public IEnumerable<ITag> GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null, string culture = null)
|
||||
{
|
||||
var sql = GetTagsQuerySelect();
|
||||
|
||||
sql = ApplyRelationshipJoinToTagsQuery(sql);
|
||||
var sql = GetTagsSql(culture);
|
||||
|
||||
sql = sql
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyTypeDto, TagRelationshipDto>(left => left.Id, right => right.PropertyTypeId)
|
||||
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, TagRelationshipDto>((prop, rel) => prop.Id == rel.PropertyTypeId)
|
||||
.Where<NodeDto>(dto => dto.UniqueId == contentId)
|
||||
.Where<PropertyTypeDto>(dto => dto.Alias == propertyTypeAlias);
|
||||
|
||||
sql = ApplyGroupFilterToTagsQuery(sql, group);
|
||||
AddTagsSqlWhere(sql, culture);
|
||||
|
||||
if (group.IsNullOrWhiteSpace() == false)
|
||||
sql = sql
|
||||
.Where<TagDto>(dto => dto.Group == group);
|
||||
|
||||
return ExecuteTagsQuery(sql);
|
||||
}
|
||||
|
||||
private Sql<ISqlContext> GetTagsQuerySelect(bool withGrouping = false)
|
||||
private Sql<ISqlContext> GetTagsSql(string culture, bool withGrouping = false)
|
||||
{
|
||||
var sql = Sql();
|
||||
var sql = Sql()
|
||||
.Select<TagDto>();
|
||||
|
||||
if (withGrouping)
|
||||
{
|
||||
sql = sql.Select("cmsTags.id, cmsTags.tag, cmsTags." + SqlSyntax.GetQuotedColumnName("group") + @", Count(*) NodeCount");
|
||||
}
|
||||
else
|
||||
{
|
||||
sql = sql.Select("DISTINCT cmsTags.*");
|
||||
}
|
||||
sql = sql
|
||||
.AndSelectCount("NodeCount");
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
private Sql<ISqlContext> ApplyRelationshipJoinToTagsQuery(Sql<ISqlContext> sql)
|
||||
{
|
||||
return sql
|
||||
sql = sql
|
||||
.From<TagDto>()
|
||||
.InnerJoin<TagRelationshipDto>()
|
||||
.On<TagRelationshipDto, TagDto>(left => left.TagId, right => right.Id)
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<ContentDto, TagRelationshipDto>(left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<NodeDto>()
|
||||
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId);
|
||||
}
|
||||
.InnerJoin<TagRelationshipDto>().On<TagRelationshipDto, TagDto>((rel, tag) => tag.Id == rel.TagId)
|
||||
.InnerJoin<ContentDto>().On<ContentDto, TagRelationshipDto>((content, rel) => content.NodeId == rel.NodeId)
|
||||
.InnerJoin<NodeDto>().On<NodeDto, ContentDto>((node, content) => node.NodeId == content.NodeId);
|
||||
|
||||
private Sql<ISqlContext> ApplyGroupFilterToTagsQuery(Sql<ISqlContext> sql, string group)
|
||||
{
|
||||
if (group.IsNullOrWhiteSpace() == false)
|
||||
if (culture != null && culture != "*")
|
||||
{
|
||||
sql = sql.Where<TagDto>(dto => dto.Group == group);
|
||||
sql = sql
|
||||
.InnerJoin<LanguageDto>().On<TagDto, LanguageDto>((tag, lang) => tag.LanguageId == lang.Id);
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
private Sql<ISqlContext> ApplyGroupByToTagsQuery(Sql<ISqlContext> sql)
|
||||
private Sql<ISqlContext> AddTagsSqlWhere(Sql<ISqlContext> sql, string culture)
|
||||
{
|
||||
return sql.GroupBy("cmsTags.id", "cmsTags.tag", "cmsTags." + SqlSyntax.GetQuotedColumnName("group") + @"");
|
||||
if (culture == null)
|
||||
{
|
||||
sql = sql
|
||||
.Where<TagDto>(dto => dto.LanguageId == null);
|
||||
}
|
||||
else if (culture != "*")
|
||||
{
|
||||
sql = sql
|
||||
.Where<LanguageDto>(x => x.IsoCode == culture);
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
private IEnumerable<ITag> ExecuteTagsQuery(Sql sql)
|
||||
|
||||
@@ -30,10 +30,24 @@ namespace Umbraco.Core.Persistence
|
||||
Templates = new SqlTemplates(this);
|
||||
}
|
||||
|
||||
// fixme
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SqlContext"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>Initializes an empty context which must be fully initialized using the
|
||||
/// <see cref="Initialize"/> method; this is done in <see cref="UmbracoDatabaseFactory"/>
|
||||
/// as soon as the factory is fully configured.</remarks>
|
||||
internal SqlContext()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="SqlContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="sqlSyntax">The sql syntax provider.</param>
|
||||
/// <param name="pocoDataFactory">The Poco data factory.</param>
|
||||
/// <param name="databaseType">The database type.</param>
|
||||
/// <param name="mappers">The mappers.</param>
|
||||
/// <remarks>Fully initializes an initially empty context; this is done in <see cref="UmbracoDatabaseFactory"/>
|
||||
/// as soon as the factory is fully configured.</remarks>
|
||||
internal void Initialize(ISqlSyntaxProvider sqlSyntax, DatabaseType databaseType, IPocoDataFactory pocoDataFactory, IMapperCollection mappers = null)
|
||||
{
|
||||
// for tests
|
||||
|
||||
@@ -95,9 +95,10 @@ namespace Umbraco.Core.Persistence
|
||||
return new Sql<ISqlContext>(_sqlContext, isBuilt, _sql, args);
|
||||
}
|
||||
|
||||
internal void WriteToConsole()
|
||||
internal string ToText()
|
||||
{
|
||||
new Sql<ISqlContext>(_sqlContext, _sql, _args.Values.ToArray()).WriteToConsole();
|
||||
var sql = new Sql<ISqlContext>(_sqlContext, _sql, _args.Values.ToArray());
|
||||
return sql.ToText();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -67,6 +67,24 @@ namespace Umbraco.Core.Persistence
|
||||
/// <inheritdoc />
|
||||
public ISqlContext SqlContext { get; }
|
||||
|
||||
#region Temp
|
||||
|
||||
// work around NPoco issue https://github.com/schotime/NPoco/issues/517 while we wait for the fix
|
||||
public override DbCommand CreateCommand(DbConnection connection, CommandType commandType, string sql, params object[] args)
|
||||
{
|
||||
var command = base.CreateCommand(connection, commandType, sql, args);
|
||||
|
||||
if (!DatabaseType.IsSqlCe()) return command;
|
||||
|
||||
foreach (DbParameter parameter in command.Parameters)
|
||||
if (parameter.Value == DBNull.Value)
|
||||
parameter.DbType = DbType.String;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Testing, Debugging and Troubleshooting
|
||||
|
||||
private bool _enableCount;
|
||||
@@ -228,24 +246,13 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
private string CommandToString(string sql, object[] args)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var text = new StringBuilder();
|
||||
#if DEBUG_DATABASES
|
||||
sb.Append(InstanceId);
|
||||
sb.Append(": ");
|
||||
text.Append(InstanceId);
|
||||
text.Append(": ");
|
||||
#endif
|
||||
sb.Append(sql);
|
||||
if (args.Length > 0)
|
||||
sb.Append(" --");
|
||||
var i = 0;
|
||||
foreach (var arg in args)
|
||||
{
|
||||
sb.Append(" @");
|
||||
sb.Append(i++);
|
||||
sb.Append(":");
|
||||
sb.Append(arg);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
NPocoSqlExtensions.ToText(sql, args, text);
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
protected override void OnExecutedCommand(DbCommand cmd)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
/// Determines whether an editor supports tags.
|
||||
/// </summary>
|
||||
public static bool IsTagsEditor(this IDataEditor editor)
|
||||
=> editor?.GetType().GetCustomAttribute<TagsPropertyEditorAttribute>(false) != null;
|
||||
=> editor.GetTagAttribute() != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tags configuration attribute of an editor.
|
||||
|
||||
@@ -237,10 +237,10 @@ namespace Umbraco.Core.Runtime
|
||||
// and only these things - the rest should be composed in runtime components
|
||||
|
||||
// register basic things
|
||||
// logging, runtime state, configuration
|
||||
container.RegisterSingleton<IProfiler, LogProfiler>();
|
||||
container.RegisterSingleton<ProfilingLogger>();
|
||||
container.RegisterSingleton<IRuntimeState, RuntimeState>();
|
||||
|
||||
container.RegisterFrom<ConfigurationCompositionRoot>();
|
||||
|
||||
// register caches
|
||||
@@ -254,8 +254,8 @@ namespace Umbraco.Core.Runtime
|
||||
new IsolatedRuntimeCache(type => new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))));
|
||||
container.RegisterSingleton(f => f.GetInstance<CacheHelper>().RuntimeCache);
|
||||
|
||||
// register the plugin manager
|
||||
container.RegisterSingleton(f => new TypeLoader(f.GetInstance<IRuntimeCacheProvider>(), f.GetInstance<IGlobalSettings>(), f.GetInstance<ProfilingLogger>()));
|
||||
// register the type loader
|
||||
container.RegisterSingleton<TypeLoader>();
|
||||
|
||||
// register syntax providers - required by database factory
|
||||
container.Register<ISqlSyntaxProvider, MySqlSyntaxProvider>("MySqlSyntaxProvider");
|
||||
@@ -389,14 +389,14 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger)
|
||||
{
|
||||
var umbracoPlan = new UmbracoPlan();
|
||||
var stateValueKey = Upgrader.GetStateValueKey(umbracoPlan);
|
||||
var upgrader = new UmbracoUpgrader();
|
||||
var stateValueKey = upgrader.StateValueKey;
|
||||
|
||||
// no scope, no service - just directly accessing the database
|
||||
using (var database = databaseFactory.CreateDatabase())
|
||||
{
|
||||
_state.CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey);
|
||||
_state.FinalMigrationState = umbracoPlan.FinalState;
|
||||
_state.FinalMigrationState = upgrader.Plan.FinalState;
|
||||
}
|
||||
|
||||
logger.Debug<CoreRuntime>("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", _state.FinalMigrationState, _state.CurrentMigrationState ?? "<null>");
|
||||
|
||||
@@ -5,7 +5,7 @@ using Umbraco.Core.Models;
|
||||
namespace Umbraco.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Tag service to query for tags in the tags db table. The tags returned are only relavent for published content & saved media or members
|
||||
/// Tag service to query for tags in the tags db table. The tags returned are only relevant for published content & saved media or members
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If there is unpublished content with tags, those tags will not be contained.
|
||||
@@ -15,135 +15,84 @@ namespace Umbraco.Core.Services
|
||||
/// </remarks>
|
||||
public interface ITagService : IService
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets a tagged entity.
|
||||
/// </summary>
|
||||
TaggedEntity GetTaggedEntityById(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a tagged entity.
|
||||
/// </summary>
|
||||
TaggedEntity GetTaggedEntityByKey(Guid key);
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Content by a specific 'Tag Group'.
|
||||
/// Gets all documents tagged with any tag in the specified group.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Content, not the actual Content item.</remarks>
|
||||
/// <param name="tagGroup">Name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
IEnumerable<TaggedEntity> GetTaggedContentByTagGroup(string tagGroup);
|
||||
IEnumerable<TaggedEntity> GetTaggedContentByTagGroup(string group, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Content by a specific 'Tag' and optional 'Tag Group'.
|
||||
/// Gets all documents tagged with the specified tag.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Content, not the actual Content item.</remarks>
|
||||
/// <param name="tag">Tag</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
IEnumerable<TaggedEntity> GetTaggedContentByTag(string tag, string tagGroup = null);
|
||||
IEnumerable<TaggedEntity> GetTaggedContentByTag(string tag, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Media by a specific 'Tag Group'.
|
||||
/// Gets all media tagged with any tag in the specified group.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Media, not the actual Media item.</remarks>
|
||||
/// <param name="tagGroup">Name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
IEnumerable<TaggedEntity> GetTaggedMediaByTagGroup(string tagGroup);
|
||||
IEnumerable<TaggedEntity> GetTaggedMediaByTagGroup(string group, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Media by a specific 'Tag' and optional 'Tag Group'.
|
||||
/// Gets all media tagged with the specified tag.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Media, not the actual Media item.</remarks>
|
||||
/// <param name="tag">Tag</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
IEnumerable<TaggedEntity> GetTaggedMediaByTag(string tag, string tagGroup = null);
|
||||
IEnumerable<TaggedEntity> GetTaggedMediaByTag(string tag, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Members by a specific 'Tag Group'.
|
||||
/// Gets all members tagged with any tag in the specified group.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Member, not the actual Member item.</remarks>
|
||||
/// <param name="tagGroup">Name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
IEnumerable<TaggedEntity> GetTaggedMembersByTagGroup(string tagGroup);
|
||||
IEnumerable<TaggedEntity> GetTaggedMembersByTagGroup(string group, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Members by a specific 'Tag' and optional 'Tag Group'.
|
||||
/// Gets all members tagged with the specified tag.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Member, not the actual Member item.</remarks>
|
||||
/// <param name="tag">Tag</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
IEnumerable<TaggedEntity> GetTaggedMembersByTag(string tag, string tagGroup = null);
|
||||
IEnumerable<TaggedEntity> GetTaggedMembersByTag(string tag, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets every tag stored in the database
|
||||
/// Gets all tags.
|
||||
/// </summary>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
IEnumerable<ITag> GetAllTags(string tagGroup = null);
|
||||
IEnumerable<ITag> GetAllTags(string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags for content items
|
||||
/// Gets all document tags.
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
IEnumerable<ITag> GetAllContentTags(string tagGroup = null);
|
||||
IEnumerable<ITag> GetAllContentTags(string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags for media items
|
||||
/// Gets all media tags.
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
IEnumerable<ITag> GetAllMediaTags(string tagGroup = null);
|
||||
IEnumerable<ITag> GetAllMediaTags(string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags for member items
|
||||
/// Gets all member tags.
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
IEnumerable<ITag> GetAllMemberTags(string tagGroup = null);
|
||||
IEnumerable<ITag> GetAllMemberTags(string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to a property by entity id
|
||||
/// Gets all tags attached to an entity via a property.
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="propertyTypeAlias">Property type alias</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
IEnumerable<ITag> GetTagsForProperty(int contentId, string propertyTypeAlias, string tagGroup = null);
|
||||
IEnumerable<ITag> GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to an entity (content, media or member) by entity id
|
||||
/// Gets all tags attached to an entity.
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
IEnumerable<ITag> GetTagsForEntity(int contentId, string tagGroup = null);
|
||||
IEnumerable<ITag> GetTagsForEntity(int contentId, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to a property by entity id
|
||||
/// Gets all tags attached to an entity via a property.
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="propertyTypeAlias">Property type alias</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
IEnumerable<ITag> GetTagsForProperty(Guid contentId, string propertyTypeAlias, string tagGroup = null);
|
||||
IEnumerable<ITag> GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to an entity (content, media or member) by entity id
|
||||
/// Gets all tags attached to an entity.
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
IEnumerable<ITag> GetTagsForEntity(Guid contentId, string tagGroup = null);
|
||||
IEnumerable<ITag> GetTagsForEntity(Guid contentId, string group = null, string culture = null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,230 +25,147 @@ namespace Umbraco.Core.Services.Implement
|
||||
_tagRepository = tagRepository;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TaggedEntity GetTaggedEntityById(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntityById(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TaggedEntity GetTaggedEntityByKey(Guid key)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntityByKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Content by a specific 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Content, not the actual Content item.</remarks>
|
||||
/// <param name="tagGroup">Name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedContentByTagGroup(string tagGroup)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaggedEntity> GetTaggedContentByTagGroup(string group, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Content, tagGroup);
|
||||
return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Content, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Content by a specific 'Tag' and optional 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Content, not the actual Content item.</remarks>
|
||||
/// <param name="tag">Tag</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedContentByTag(string tag, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaggedEntity> GetTaggedContentByTag(string tag, string group = null, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Content, tag, tagGroup);
|
||||
return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Content, tag, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Media by a specific 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Media, not the actual Media item.</remarks>
|
||||
/// <param name="tagGroup">Name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedMediaByTagGroup(string tagGroup)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaggedEntity> GetTaggedMediaByTagGroup(string group, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Media, tagGroup);
|
||||
return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Media, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Media by a specific 'Tag' and optional 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Media, not the actual Media item.</remarks>
|
||||
/// <param name="tag">Tag</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedMediaByTag(string tag, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaggedEntity> GetTaggedMediaByTag(string tag, string group = null, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Media, tag, tagGroup);
|
||||
return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Media, tag, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Members by a specific 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Member, not the actual Member item.</remarks>
|
||||
/// <param name="tagGroup">Name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedMembersByTagGroup(string tagGroup)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaggedEntity> GetTaggedMembersByTagGroup(string group, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Member, tagGroup);
|
||||
return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Member, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Members by a specific 'Tag' and optional 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Member, not the actual Member item.</remarks>
|
||||
/// <param name="tag">Tag</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedMembersByTag(string tag, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaggedEntity> GetTaggedMembersByTag(string tag, string group = null, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Member, tag, tagGroup);
|
||||
return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Member, tag, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets every tag stored in the database
|
||||
/// </summary>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetAllTags(string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetAllTags(string group = null, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.All, tagGroup);
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.All, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags for content items
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetAllContentTags(string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetAllContentTags(string group = null, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Content, tagGroup);
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Content, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags for media items
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetAllMediaTags(string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetAllMediaTags(string group = null, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Media, tagGroup);
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Media, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags for member items
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetAllMemberTags(string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetAllMemberTags(string group = null, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Member, tagGroup);
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Member, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to a property by entity id
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="propertyTypeAlias">Property type alias</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetTagsForProperty(int contentId, string propertyTypeAlias, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup);
|
||||
return _tagRepository.GetTagsForProperty(contentId, propertyTypeAlias, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to an entity (content, media or member) by entity id
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetTagsForEntity(int contentId, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetTagsForEntity(int contentId, string group = null, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntity(contentId, tagGroup);
|
||||
return _tagRepository.GetTagsForEntity(contentId, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to a property by entity id
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="propertyTypeAlias">Property type alias</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetTagsForProperty(Guid contentId, string propertyTypeAlias, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup);
|
||||
return _tagRepository.GetTagsForProperty(contentId, propertyTypeAlias, group, culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to an entity (content, media or member) by entity id
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetTagsForEntity(Guid contentId, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetTagsForEntity(Guid contentId, string group = null, string culture = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntity(contentId, tagGroup);
|
||||
return _tagRepository.GetTagsForEntity(contentId, group, culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +366,7 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DropTemplateDesignColumn.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\FixLockTablePrimaryKey.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\LanguageColumns.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\MakeTagsVariant.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\PropertyEditorsMigration.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\RefactorMacroColumns.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\RefactorVariantsModel.cs" />
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Examine;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Lucene.Net.Analysis;
|
||||
using Lucene.Net.Index;
|
||||
@@ -6,6 +8,7 @@ using Lucene.Net.QueryParsers;
|
||||
using Lucene.Net.Search;
|
||||
using Lucene.Net.Store;
|
||||
using Version = Lucene.Net.Util.Version;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
@@ -37,6 +40,32 @@ namespace Umbraco.Examine
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forcibly unlocks all lucene based indexes
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is not thread safe, use with care
|
||||
/// </remarks>
|
||||
internal static void UnlockLuceneIndexes(this IExamineManager examineManager, ILogger logger)
|
||||
{
|
||||
foreach (var luceneIndexer in examineManager.Indexes.OfType<LuceneIndex>())
|
||||
{
|
||||
//We now need to disable waiting for indexing for Examine so that the appdomain is shutdown immediately and doesn't wait for pending
|
||||
//indexing operations. We used to wait for indexing operations to complete but this can cause more problems than that is worth because
|
||||
//that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems.
|
||||
luceneIndexer.WaitForIndexQueueOnShutdown = false;
|
||||
|
||||
//we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that
|
||||
//the indexes are not operational unless MainDom is true
|
||||
var dir = luceneIndexer.GetLuceneDirectory();
|
||||
if (IndexWriter.IsLocked(dir))
|
||||
{
|
||||
logger.Info(typeof(ExamineExtensions), "Forcing index {IndexerName} to be unlocked since it was left in a locked state", luceneIndexer.Name);
|
||||
IndexWriter.Unlock(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the index can be read/opened
|
||||
/// </summary>
|
||||
|
||||
@@ -11,7 +11,13 @@ using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.Benchmarks
|
||||
{
|
||||
[Config(typeof(Config))]
|
||||
// some conclusions
|
||||
// - ActivatorCreateInstance is slow
|
||||
// - it's faster to get+invoke the ctor
|
||||
// - emitting the ctor is unless if invoked only 1
|
||||
|
||||
//[Config(typeof(Config))]
|
||||
[MemoryDiagnoser]
|
||||
public class CtorInvokeBenchmarks
|
||||
{
|
||||
private class Config : ManualConfig
|
||||
@@ -25,7 +31,7 @@ namespace Umbraco.Tests.Benchmarks
|
||||
// see benchmarkdotnet FAQ
|
||||
Add(Job.Default
|
||||
.WithLaunchCount(1) // benchmark process will be launched only once
|
||||
.WithIterationTime(TimeInterval.FromMilliseconds(400))
|
||||
.WithIterationTime(TimeInterval.FromMilliseconds(400))
|
||||
.WithWarmupCount(3)
|
||||
.WithIterationCount(6));
|
||||
}
|
||||
@@ -158,6 +164,28 @@ namespace Umbraco.Tests.Benchmarks
|
||||
var foo = new Foo(_foo);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void EmitCtor()
|
||||
{
|
||||
var ctor = ReflectionUtilities.EmitConstuctor<Func<IFoo, Foo>>();
|
||||
var foo = ctor(_foo);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ActivatorCreateInstance()
|
||||
{
|
||||
var foo = Activator.CreateInstance(typeof(Foo), _foo);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void GetAndInvokeCtor()
|
||||
{
|
||||
var ctorArgTypes = new[] { typeof(IFoo) };
|
||||
var type = typeof(Foo);
|
||||
var ctorInfo = type.GetConstructor(ctorArgTypes);
|
||||
var foo = ctorInfo.Invoke(new object[] { _foo });
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void InvokeCtor()
|
||||
{
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet">
|
||||
<Version>0.11.2</Version>
|
||||
<Version>0.11.3</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Umbraco.Tests.Composing
|
||||
public class TypeLoaderTests
|
||||
{
|
||||
private TypeLoader _typeLoader;
|
||||
|
||||
[SetUp]
|
||||
public void Initialize()
|
||||
{
|
||||
@@ -53,6 +54,12 @@ namespace Umbraco.Tests.Composing
|
||||
public void TearDown()
|
||||
{
|
||||
_typeLoader = null;
|
||||
|
||||
|
||||
// cleanup
|
||||
var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
|
||||
foreach (var d in Directory.GetDirectories(Path.Combine(assDir.FullName, "TypeLoader")))
|
||||
Directory.Delete(d, true);
|
||||
}
|
||||
|
||||
private DirectoryInfo PrepareFolder()
|
||||
|
||||
@@ -5,6 +5,7 @@ using NUnit.Framework;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Migrations;
|
||||
using Umbraco.Core.Migrations.Install;
|
||||
using Umbraco.Core.Migrations.Upgrade;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -34,11 +35,12 @@ namespace Umbraco.Tests.Migrations
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var upgrader = new MigrationTests.TestUpgrader(ScopeProvider, builder, Mock.Of<IKeyValueService>(), new PostMigrationCollection(Enumerable.Empty<IPostMigration>()), logger,
|
||||
new MigrationPlan("test", builder, logger)
|
||||
.Add<CreateTableOfTDtoMigration>(string.Empty, "done"));
|
||||
var upgrader = new Upgrader(
|
||||
new MigrationPlan("test")
|
||||
.From(string.Empty)
|
||||
.To<CreateTableOfTDtoMigration>("done"));
|
||||
|
||||
upgrader.Execute();
|
||||
upgrader.Execute(ScopeProvider, builder, Mock.Of<IKeyValueService>(), logger);
|
||||
|
||||
var helper = new DatabaseSchemaCreator(scope.Database, logger);
|
||||
var exists = helper.TableExists("umbracoUser");
|
||||
@@ -71,12 +73,13 @@ namespace Umbraco.Tests.Migrations
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var upgrader = new MigrationTests.TestUpgrader(ScopeProvider, builder, Mock.Of<IKeyValueService>(), new PostMigrationCollection(Enumerable.Empty<IPostMigration>()), logger,
|
||||
new MigrationPlan("test", builder, logger)
|
||||
.Add<CreateTableOfTDtoMigration>(string.Empty, "a")
|
||||
.Add<DeleteKeysAndIndexesMigration>("a", "done"));
|
||||
var upgrader = new Upgrader(
|
||||
new MigrationPlan("test")
|
||||
.From(string.Empty)
|
||||
.To<CreateTableOfTDtoMigration>("a")
|
||||
.To<DeleteKeysAndIndexesMigration>("done"));
|
||||
|
||||
upgrader.Execute();
|
||||
upgrader.Execute(ScopeProvider, builder, Mock.Of<IKeyValueService>(), logger);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
@@ -106,13 +109,14 @@ namespace Umbraco.Tests.Migrations
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var upgrader = new MigrationTests.TestUpgrader(ScopeProvider, builder, Mock.Of<IKeyValueService>(), new PostMigrationCollection(Enumerable.Empty<IPostMigration>()), logger,
|
||||
new MigrationPlan("test", builder, logger)
|
||||
.Add<CreateTableOfTDtoMigration>(string.Empty, "a")
|
||||
.Add<DeleteKeysAndIndexesMigration>("a", "b")
|
||||
.Add<CreateKeysAndIndexesOfTDtoMigration>("b", "done"));
|
||||
var upgrader = new Upgrader(
|
||||
new MigrationPlan("test")
|
||||
.From(string.Empty)
|
||||
.To<CreateTableOfTDtoMigration>("a")
|
||||
.To<DeleteKeysAndIndexesMigration>("b")
|
||||
.To<CreateKeysAndIndexesOfTDtoMigration>("done"));
|
||||
|
||||
upgrader.Execute();
|
||||
upgrader.Execute(ScopeProvider, builder, Mock.Of<IKeyValueService>(), logger);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
@@ -142,13 +146,14 @@ namespace Umbraco.Tests.Migrations
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var upgrader = new MigrationTests.TestUpgrader(ScopeProvider, builder, Mock.Of<IKeyValueService>(), new PostMigrationCollection(Enumerable.Empty<IPostMigration>()), logger,
|
||||
new MigrationPlan("test", builder, logger)
|
||||
.Add<CreateTableOfTDtoMigration>(string.Empty, "a")
|
||||
.Add<DeleteKeysAndIndexesMigration>("a", "b")
|
||||
.Add<CreateKeysAndIndexesMigration>("b", "done"));
|
||||
var upgrader = new Upgrader(
|
||||
new MigrationPlan("test")
|
||||
.From(string.Empty)
|
||||
.To<CreateTableOfTDtoMigration>("a")
|
||||
.To<DeleteKeysAndIndexesMigration>("b")
|
||||
.To<CreateKeysAndIndexesMigration>("done"));
|
||||
|
||||
upgrader.Execute();
|
||||
upgrader.Execute(ScopeProvider, builder, Mock.Of<IKeyValueService>(), logger);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
@@ -176,12 +181,13 @@ namespace Umbraco.Tests.Migrations
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var upgrader = new MigrationTests.TestUpgrader(ScopeProvider, builder, Mock.Of<IKeyValueService>(), new PostMigrationCollection(Enumerable.Empty<IPostMigration>()), logger,
|
||||
new MigrationPlan("test", builder, logger)
|
||||
.Add<CreateTableOfTDtoMigration>(string.Empty, "a")
|
||||
.Add<CreateColumnMigration>("a", "done"));
|
||||
var upgrader = new Upgrader(
|
||||
new MigrationPlan("test")
|
||||
.From(string.Empty)
|
||||
.To<CreateTableOfTDtoMigration>("a")
|
||||
.To<CreateColumnMigration>("done"));
|
||||
|
||||
upgrader.Execute();
|
||||
upgrader.Execute(ScopeProvider, builder, Mock.Of<IKeyValueService>(), logger);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NPoco;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Migrations;
|
||||
using Umbraco.Core.Migrations.Upgrade;
|
||||
@@ -46,13 +48,10 @@ namespace Umbraco.Tests.Migrations
|
||||
}
|
||||
});
|
||||
|
||||
// fixme - NOT a migration collection builder, just a migration builder
|
||||
// done, remove everywhere else, and delete migrationCollection stuff entirely
|
||||
|
||||
var plan = new MigrationPlan("default", migrationBuilder, logger)
|
||||
var plan = new MigrationPlan("default")
|
||||
.From(string.Empty)
|
||||
.Chain<DeleteRedirectUrlTable>("{4A9A1A8F-0DA1-4BCF-AD06-C19D79152E35}")
|
||||
.Chain<NoopMigration>("VERSION.33");
|
||||
.To<DeleteRedirectUrlTable>("{4A9A1A8F-0DA1-4BCF-AD06-C19D79152E35}")
|
||||
.To<NoopMigration>("VERSION.33");
|
||||
|
||||
var kvs = Mock.Of<IKeyValueService>();
|
||||
Mock.Get(kvs).Setup(x => x.GetValue(It.IsAny<string>())).Returns<string>(k => k == "Umbraco.Tests.MigrationPlan" ? string.Empty : null);
|
||||
@@ -64,7 +63,7 @@ namespace Umbraco.Tests.Migrations
|
||||
var sourceState = kvs.GetValue("Umbraco.Tests.MigrationPlan") ?? string.Empty;
|
||||
|
||||
// execute plan
|
||||
state = plan.Execute(s, sourceState);
|
||||
state = plan.Execute(s, sourceState, migrationBuilder, logger);
|
||||
|
||||
// save new state
|
||||
kvs.SetValue("Umbraco.Tests.MigrationPlan", sourceState, state);
|
||||
@@ -81,63 +80,103 @@ namespace Umbraco.Tests.Migrations
|
||||
[Test]
|
||||
public void CanAddMigrations()
|
||||
{
|
||||
var plan = new MigrationPlan("default", Mock.Of<IMigrationBuilder>(), Mock.Of<ILogger>());
|
||||
plan.Add(string.Empty, "aaa");
|
||||
plan.Add("aaa", "bbb");
|
||||
plan.Add("bbb", "ccc");
|
||||
var plan = new MigrationPlan("default");
|
||||
plan
|
||||
.From(string.Empty)
|
||||
.To("aaa")
|
||||
.To("bbb")
|
||||
.To("ccc");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CannotTransitionToSameState()
|
||||
{
|
||||
var plan = new MigrationPlan("default", Mock.Of<IMigrationBuilder>(), Mock.Of<ILogger>());
|
||||
var plan = new MigrationPlan("default");
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
plan.Add("aaa", "aaa");
|
||||
plan.From("aaa").To("aaa");
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnlyOneTransitionPerState()
|
||||
{
|
||||
var plan = new MigrationPlan("default", Mock.Of<IMigrationBuilder>(), Mock.Of<ILogger>());
|
||||
plan.Add("aaa", "bbb");
|
||||
var plan = new MigrationPlan("default");
|
||||
plan.From("aaa").To("bbb");
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
plan.Add("aaa", "ccc");
|
||||
plan.From("aaa").To("ccc");
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CannotContainTwoMoreHeads()
|
||||
{
|
||||
var plan = new MigrationPlan("default", Mock.Of<IMigrationBuilder>(), Mock.Of<ILogger>());
|
||||
plan.Add(string.Empty, "aaa");
|
||||
plan.Add("aaa", "bbb");
|
||||
plan.Add("ccc", "ddd");
|
||||
var plan = new MigrationPlan("default");
|
||||
plan
|
||||
.From(string.Empty)
|
||||
.To("aaa")
|
||||
.To("bbb")
|
||||
.From("ccc")
|
||||
.To("ddd");
|
||||
Assert.Throws<Exception>(() => plan.Validate());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CannotContainLoops()
|
||||
{
|
||||
var plan = new MigrationPlan("default", Mock.Of<IMigrationBuilder>(), Mock.Of<ILogger>());
|
||||
plan.Add(string.Empty, "aaa");
|
||||
plan.Add("aaa", "bbb");
|
||||
plan.Add("bbb", "ccc");
|
||||
plan.Add("ccc", "aaa");
|
||||
var plan = new MigrationPlan("default");
|
||||
plan
|
||||
.From("aaa")
|
||||
.To("bbb")
|
||||
.To("ccc")
|
||||
.To("aaa");
|
||||
Assert.Throws<Exception>(() => plan.Validate());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ValidateUmbracoPlan()
|
||||
{
|
||||
var plan = new UmbracoPlan(Mock.Of<IMigrationBuilder>(), Mock.Of<ILogger>());
|
||||
var plan = new UmbracoPlan();
|
||||
plan.Validate();
|
||||
Console.WriteLine(plan.FinalState);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(plan.FinalState));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanCopyChain()
|
||||
{
|
||||
var plan = new MigrationPlan("default");
|
||||
plan
|
||||
.From(string.Empty)
|
||||
.To("aaa")
|
||||
.To("bbb")
|
||||
.To("ccc")
|
||||
.To("ddd")
|
||||
.To("eee");
|
||||
|
||||
plan
|
||||
.From("xxx")
|
||||
.To("yyy", "bbb", "ddd")
|
||||
.To("eee");
|
||||
|
||||
WritePlanToConsole(plan);
|
||||
|
||||
plan.Validate();
|
||||
Assert.AreEqual("eee", plan.FollowPath("xxx"));
|
||||
Assert.AreEqual("yyy", plan.FollowPath("xxx", "yyy"));
|
||||
}
|
||||
|
||||
private void WritePlanToConsole(MigrationPlan plan)
|
||||
{
|
||||
var final = plan.Transitions.First(x => x.Value == null).Key;
|
||||
|
||||
Console.WriteLine("plan \"{0}\" to final state \"{1}\":", plan.Name, final);
|
||||
foreach (var (_, transition) in plan.Transitions)
|
||||
if (transition != null)
|
||||
Console.WriteLine(transition);
|
||||
}
|
||||
|
||||
public class DeleteRedirectUrlTable : MigrationBase
|
||||
{
|
||||
public DeleteRedirectUrlTable(IMigrationContext context)
|
||||
|
||||
@@ -16,28 +16,32 @@ namespace Umbraco.Tests.Migrations
|
||||
[TestFixture]
|
||||
public class MigrationTests
|
||||
{
|
||||
public class TestUpgrader : Upgrader
|
||||
public class TestUpgraderWithPostMigrations : Upgrader
|
||||
{
|
||||
private readonly MigrationPlan _plan;
|
||||
private PostMigrationCollection _postMigrations;
|
||||
|
||||
public TestUpgrader(IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, PostMigrationCollection postMigrations, ILogger logger, MigrationPlan plan)
|
||||
: base(scopeProvider, migrationBuilder, keyValueService, postMigrations, logger)
|
||||
public TestUpgraderWithPostMigrations(MigrationPlan plan)
|
||||
: base(plan)
|
||||
{ }
|
||||
|
||||
public void Execute(IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, ILogger logger, PostMigrationCollection postMigrations)
|
||||
{
|
||||
_plan = plan;
|
||||
_postMigrations = postMigrations;
|
||||
Execute(scopeProvider, migrationBuilder, keyValueService, logger);
|
||||
}
|
||||
|
||||
protected override MigrationPlan GetPlan()
|
||||
public override void AfterMigrations(IScope scope, ILogger logger)
|
||||
{
|
||||
return _plan;
|
||||
}
|
||||
// run post-migrations
|
||||
var originVersion = new SemVersion(0);
|
||||
var targetVersion = new SemVersion(0);
|
||||
|
||||
protected override (SemVersion, SemVersion) GetVersions()
|
||||
{
|
||||
return (new SemVersion(0), new SemVersion(0));
|
||||
// run post-migrations
|
||||
foreach (var postMigration in _postMigrations)
|
||||
postMigration.Execute(Name, scope, originVersion, targetVersion, logger);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class TestScopeProvider : IScopeProvider
|
||||
{
|
||||
private readonly IScope _scope;
|
||||
|
||||
@@ -50,10 +50,9 @@ namespace Umbraco.Tests.Migrations
|
||||
var sqlContext = new SqlContext(new SqlCeSyntaxProvider(), DatabaseType.SQLCe, Mock.Of<IPocoDataFactory>());
|
||||
var scopeProvider = new MigrationTests.TestScopeProvider(scope) { SqlContext = sqlContext };
|
||||
|
||||
var u1 = new MigrationTests.TestUpgrader(scopeProvider, builder, Mock.Of<IKeyValueService>(), posts, logger,
|
||||
new MigrationPlan("Test", builder, logger)
|
||||
.Add<NoopMigration>(string.Empty, "done"));
|
||||
u1.Execute();
|
||||
var u1 = new MigrationTests.TestUpgraderWithPostMigrations(
|
||||
new MigrationPlan("Test").From(string.Empty).To("done"));
|
||||
u1.Execute(scopeProvider, builder, Mock.Of<IKeyValueService>(), logger, posts);
|
||||
|
||||
Assert.AreEqual(1, changed1.CountExecuted);
|
||||
}
|
||||
@@ -94,18 +93,16 @@ namespace Umbraco.Tests.Migrations
|
||||
var sqlContext = new SqlContext(new SqlCeSyntaxProvider(), DatabaseType.SQLCe, Mock.Of<IPocoDataFactory>());
|
||||
var scopeProvider = new MigrationTests.TestScopeProvider(scope) { SqlContext = sqlContext };
|
||||
|
||||
var u1 = new MigrationTests.TestUpgrader(scopeProvider, builder, Mock.Of<IKeyValueService>(), posts, logger,
|
||||
new MigrationPlan("Test1", builder, logger)
|
||||
.Add<NoopMigration>(string.Empty, "done"));
|
||||
u1.Execute();
|
||||
var u1 = new MigrationTests.TestUpgraderWithPostMigrations(
|
||||
new MigrationPlan("Test1").From(string.Empty).To("done"));
|
||||
u1.Execute(scopeProvider, builder, Mock.Of<IKeyValueService>(), logger, posts);
|
||||
|
||||
Assert.AreEqual(1, changed1.CountExecuted);
|
||||
Assert.AreEqual(0, changed2.CountExecuted);
|
||||
|
||||
var u2 = new MigrationTests.TestUpgrader(scopeProvider, builder, Mock.Of<IKeyValueService>(), posts, logger,
|
||||
new MigrationPlan("Test2", builder, logger)
|
||||
.Add<NoopMigration>(string.Empty, "done"));
|
||||
u2.Execute();
|
||||
var u2 = new MigrationTests.TestUpgraderWithPostMigrations(
|
||||
new MigrationPlan("Test2").From(string.Empty).To("done"));
|
||||
u2.Execute(scopeProvider, builder, Mock.Of<IKeyValueService>(), logger, posts);
|
||||
|
||||
Assert.AreEqual(1, changed1.CountExecuted);
|
||||
Assert.AreEqual(1, changed2.CountExecuted);
|
||||
|
||||
@@ -403,7 +403,6 @@ namespace Umbraco.Tests.Persistence.NPocoTests
|
||||
.From<Thing1Dto>()
|
||||
.Where<Thing1Dto>(x => x.Id == 1);
|
||||
|
||||
sql.WriteToConsole();
|
||||
var dto = scope.Database.Fetch<Thing1Dto>(sql).FirstOrDefault();
|
||||
Assert.IsNotNull(dto);
|
||||
Assert.AreEqual("one", dto.Name);
|
||||
@@ -415,7 +414,6 @@ namespace Umbraco.Tests.Persistence.NPocoTests
|
||||
//Assert.AreEqual("one", dto.Name);
|
||||
|
||||
var sql3 = new Sql(sql.SQL, 1);
|
||||
sql.WriteToConsole();
|
||||
dto = scope.Database.Fetch<Thing1Dto>(sql3).FirstOrDefault();
|
||||
Assert.IsNotNull(dto);
|
||||
Assert.AreEqual("one", dto.Name);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using NPoco;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
@@ -11,6 +12,80 @@ namespace Umbraco.Tests.Persistence.NPocoTests
|
||||
[TestFixture]
|
||||
public class NPocoSqlExtensionsTests : BaseUsingSqlCeSyntax
|
||||
{
|
||||
[Test]
|
||||
public void WhereTest()
|
||||
{
|
||||
var sql = new Sql<ISqlContext>(SqlContext)
|
||||
.Select("*")
|
||||
.From<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == null);
|
||||
Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE (([umbracoPropertyData].[languageId] is null))", sql.SQL, sql.SQL);
|
||||
|
||||
sql = new Sql<ISqlContext>(SqlContext)
|
||||
.Select("*")
|
||||
.From<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == 123);
|
||||
Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE (([umbracoPropertyData].[languageId] = @0))", sql.SQL, sql.SQL);
|
||||
|
||||
var id = 123;
|
||||
|
||||
sql = new Sql<ISqlContext>(SqlContext)
|
||||
.Select("*")
|
||||
.From<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == id);
|
||||
Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE (([umbracoPropertyData].[languageId] = @0))", sql.SQL, sql.SQL);
|
||||
|
||||
int? nid = 123;
|
||||
|
||||
sql = new Sql<ISqlContext>(SqlContext)
|
||||
.Select("*")
|
||||
.From<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == nid);
|
||||
Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE (([umbracoPropertyData].[languageId] = @0))", sql.SQL, sql.SQL);
|
||||
|
||||
// but the above comparison fails if @0 is null
|
||||
// what we want is something similar to:
|
||||
|
||||
sql = new Sql<ISqlContext>(SqlContext)
|
||||
.Select("*")
|
||||
.From<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => (nid == null && x.LanguageId == null) || (nid != null && x.LanguageId == nid));
|
||||
Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE ((((@0 is null) AND ([umbracoPropertyData].[languageId] is null)) OR ((@1 is not null) AND ([umbracoPropertyData].[languageId] = @2))))", sql.SQL, sql.SQL);
|
||||
|
||||
// new SqlNullableEquals method does it automatically
|
||||
// 'course it would be nicer if '==' could do it
|
||||
// see note in ExpressionVisitorBase for SqlNullableEquals
|
||||
|
||||
//sql = new Sql<ISqlContext>(SqlContext)
|
||||
// .Select("*")
|
||||
// .From<PropertyDataDto>()
|
||||
// .Where<PropertyDataDto>(x => x.LanguageId.SqlNullableEquals(nid));
|
||||
//Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE ((((@0 is null) AND ([umbracoPropertyData].[languageId] is null)) OR ((@0 is not null) AND ([umbracoPropertyData].[languageId] = @0))))", sql.SQL, sql.SQL);
|
||||
|
||||
// but, the expression above fails with SQL CE, 'specified argument for the function is not valid' in 'isnull' function
|
||||
// so... compare with fallback values
|
||||
|
||||
sql = new Sql<ISqlContext>(SqlContext)
|
||||
.Select("*")
|
||||
.From<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId.SqlNullableEquals(nid, -1));
|
||||
Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE ((COALESCE([umbracoPropertyData].[languageId],@0) = COALESCE(@1,@0)))", sql.SQL, sql.SQL);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SqlNullableEqualsTest()
|
||||
{
|
||||
int? a, b;
|
||||
a = b = null;
|
||||
Assert.IsTrue(a.SqlNullableEquals(b, -1));
|
||||
b = 2;
|
||||
Assert.IsFalse(a.SqlNullableEquals(b, -1));
|
||||
a = 2;
|
||||
Assert.IsTrue(a.SqlNullableEquals(b, -1));
|
||||
b = null;
|
||||
Assert.IsFalse(a.SqlNullableEquals(b, -1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhereInValueFieldTest()
|
||||
{
|
||||
@@ -117,7 +192,6 @@ INNER JOIN [dto2] ON [dto1].[id] = [dto2].[dto1id]".NoCrLf(), sql.SQL.NoCrLf());
|
||||
var sql = Sql()
|
||||
.Update<DataTypeDto>(u => u.Set(x => x.EditorAlias, "Umbraco.ColorPicker"))
|
||||
.Where<DataTypeDto>(x => x.EditorAlias == "Umbraco.ColorPickerAlias");
|
||||
sql.WriteToConsole();
|
||||
}
|
||||
|
||||
[TableName("dto1")]
|
||||
|
||||
@@ -27,15 +27,9 @@ namespace Umbraco.Tests.Persistence.NPocoTests
|
||||
.From("zbThing1")
|
||||
.Where("id=@id", new { id = SqlTemplate.Arg("id") })).Sql(new { id = 1 });
|
||||
|
||||
sql.WriteToConsole();
|
||||
|
||||
var sql2 = sqlTemplates.Get("xxx", x => throw new InvalidOperationException("Should be cached.")).Sql(1);
|
||||
|
||||
sql2.WriteToConsole();
|
||||
|
||||
var sql3 = sqlTemplates.Get("xxx", x => throw new InvalidOperationException("Should be cached.")).Sql(new { id = 1 });
|
||||
|
||||
sql3.WriteToConsole();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -75,8 +69,8 @@ namespace Umbraco.Tests.Persistence.NPocoTests
|
||||
Assert.AreEqual(1, sql.Arguments.Length);
|
||||
Assert.AreEqual(123, sql.Arguments[0]);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => template.Sql(new { xvalue = 123 }).WriteToConsole());
|
||||
Assert.Throws<InvalidOperationException>(() => template.Sql(new { value = 123, xvalue = 456 }).WriteToConsole());
|
||||
Assert.Throws<InvalidOperationException>(() => template.Sql(new { xvalue = 123 }));
|
||||
Assert.Throws<InvalidOperationException>(() => template.Sql(new { value = 123, xvalue = 456 }));
|
||||
|
||||
var i = 666;
|
||||
|
||||
@@ -121,8 +115,8 @@ namespace Umbraco.Tests.Persistence.NPocoTests
|
||||
Assert.AreEqual(1, sql.Arguments.Length);
|
||||
Assert.AreEqual(123, sql.Arguments[0]);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => template.Sql(new { j = 123 }).WriteToConsole());
|
||||
Assert.Throws<InvalidOperationException>(() => template.Sql(new { i = 123, j = 456 }).WriteToConsole());
|
||||
Assert.Throws<InvalidOperationException>(() => template.Sql(new { j = 123 }));
|
||||
Assert.Throws<InvalidOperationException>(() => template.Sql(new { i = 123, j = 456 }));
|
||||
|
||||
// now with more arguments
|
||||
|
||||
|
||||
@@ -305,12 +305,10 @@ namespace Umbraco.Tests.Persistence.NPocoTests
|
||||
.From<UserLoginDto>()
|
||||
.Where<UserLoginDto>(x => x.SessionId == sessionId);
|
||||
|
||||
sql.WriteToConsole();
|
||||
Assert.AreEqual("SELECT * FROM [umbracoUserLogin] WHERE (([umbracoUserLogin].[sessionId] = @0))", sql.SQL.NoCrLf());
|
||||
|
||||
sql = sql.ForUpdate();
|
||||
|
||||
sql.WriteToConsole();
|
||||
Assert.AreEqual("SELECT * FROM [umbracoUserLogin] WITH (UPDLOCK) WHERE (([umbracoUserLogin].[sessionId] = @0))", sql.SQL.NoCrLf());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,8 +107,6 @@ namespace Umbraco.Tests.Persistence.Querying
|
||||
var translator = new SqlTranslator<IContent>(sql, query);
|
||||
var result = translator.Translate();
|
||||
|
||||
result.WriteToConsole();
|
||||
|
||||
Assert.AreEqual("-1,1046,1076,1089%", result.Arguments[0]);
|
||||
Assert.AreEqual(1046, result.Arguments[1]);
|
||||
Assert.AreEqual(true, result.Arguments[2]);
|
||||
|
||||
@@ -106,12 +106,11 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.AreEqual("<div>This is some content</div>", propVal2.ToString());
|
||||
|
||||
var propVal3 = publishedMedia.Value("Content");
|
||||
Assert.IsInstanceOf<IHtmlString>(propVal3);
|
||||
Assert.IsInstanceOf<IHtmlString>(propVal3);
|
||||
Assert.AreEqual("<div>This is some content</div>", propVal3.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("No point testing with Examine, should refactor this test.")]
|
||||
public void Ensure_Children_Sorted_With_Examine()
|
||||
{
|
||||
var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockMediaService());
|
||||
@@ -119,7 +118,9 @@ namespace Umbraco.Tests.PublishedContent
|
||||
using (var luceneDir = new RandomIdRamDirectory())
|
||||
using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir,
|
||||
validator: new ContentValueSetValidator(true)))
|
||||
using (indexer.ProcessNonAsync())
|
||||
{
|
||||
rebuilder.RegisterIndex(indexer.Name);
|
||||
rebuilder.Populate(indexer);
|
||||
|
||||
var searcher = indexer.GetSearcher();
|
||||
@@ -139,7 +140,6 @@ namespace Umbraco.Tests.PublishedContent
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("No point testing with Examine, should refactor this test.")]
|
||||
public void Do_Not_Find_In_Recycle_Bin()
|
||||
{
|
||||
var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockMediaService());
|
||||
@@ -147,9 +147,10 @@ namespace Umbraco.Tests.PublishedContent
|
||||
using (var luceneDir = new RandomIdRamDirectory())
|
||||
using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir,
|
||||
//include unpublished content since this uses the 'internal' indexer, it's up to the media cache to filter
|
||||
validator: new ContentValueSetValidator(true)))
|
||||
validator: new ContentValueSetValidator(false)))
|
||||
using (indexer.ProcessNonAsync())
|
||||
{
|
||||
rebuilder.RegisterIndex(indexer.Name);
|
||||
rebuilder.Populate(indexer);
|
||||
|
||||
var searcher = indexer.GetSearcher();
|
||||
@@ -187,7 +188,6 @@ namespace Umbraco.Tests.PublishedContent
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("No point testing with Examine, should refactor this test.")]
|
||||
public void Children_With_Examine()
|
||||
{
|
||||
var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockMediaService());
|
||||
@@ -197,6 +197,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
validator: new ContentValueSetValidator(true)))
|
||||
using (indexer.ProcessNonAsync())
|
||||
{
|
||||
rebuilder.RegisterIndex(indexer.Name);
|
||||
rebuilder.Populate(indexer);
|
||||
|
||||
var searcher = indexer.GetSearcher();
|
||||
@@ -215,7 +216,6 @@ namespace Umbraco.Tests.PublishedContent
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("No point testing with Examine, should refactor this test.")]
|
||||
public void Descendants_With_Examine()
|
||||
{
|
||||
var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockMediaService());
|
||||
@@ -225,6 +225,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
validator: new ContentValueSetValidator(true)))
|
||||
using (indexer.ProcessNonAsync())
|
||||
{
|
||||
rebuilder.RegisterIndex(indexer.Name);
|
||||
rebuilder.Populate(indexer);
|
||||
|
||||
var searcher = indexer.GetSearcher();
|
||||
@@ -243,7 +244,6 @@ namespace Umbraco.Tests.PublishedContent
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("No point testing with Examine, should refactor this test.")]
|
||||
public void DescendantsOrSelf_With_Examine()
|
||||
{
|
||||
var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockMediaService());
|
||||
@@ -253,6 +253,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
validator: new ContentValueSetValidator(true)))
|
||||
using (indexer.ProcessNonAsync())
|
||||
{
|
||||
rebuilder.RegisterIndex(indexer.Name);
|
||||
rebuilder.Populate(indexer);
|
||||
|
||||
var searcher = indexer.GetSearcher();
|
||||
@@ -271,7 +272,6 @@ namespace Umbraco.Tests.PublishedContent
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("No point testing with Examine, should refactor this test.")]
|
||||
public void Ancestors_With_Examine()
|
||||
{
|
||||
var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockMediaService());
|
||||
@@ -282,6 +282,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
validator: new ContentValueSetValidator(true)))
|
||||
using (indexer.ProcessNonAsync())
|
||||
{
|
||||
rebuilder.RegisterIndex(indexer.Name);
|
||||
rebuilder.Populate(indexer);
|
||||
|
||||
var ctx = GetUmbracoContext("/test");
|
||||
@@ -297,7 +298,6 @@ namespace Umbraco.Tests.PublishedContent
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("No point testing with Examine, should refactor this test.")]
|
||||
public void AncestorsOrSelf_With_Examine()
|
||||
{
|
||||
var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockMediaService());
|
||||
@@ -307,6 +307,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
validator: new ContentValueSetValidator(true)))
|
||||
using (indexer.ProcessNonAsync())
|
||||
{
|
||||
rebuilder.RegisterIndex(indexer.Name);
|
||||
rebuilder.Populate(indexer);
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,811 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using LightInject;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Tests.Testing;
|
||||
|
||||
namespace Umbraco.Tests.Services
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest,
|
||||
PublishedRepositoryEvents = true,
|
||||
WithApplication = true,
|
||||
Logger = UmbracoTestOptions.Logger.Console)]
|
||||
public class ContentServiceTagsTests : TestWithSomeContentBase
|
||||
{
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
ContentRepositoryBase.ThrowOnWarning = true;
|
||||
}
|
||||
|
||||
public override void TearDown()
|
||||
{
|
||||
ContentRepositoryBase.ThrowOnWarning = false;
|
||||
base.TearDown();
|
||||
}
|
||||
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
|
||||
// fixme - do it differently
|
||||
Container.Register(factory => factory.GetInstance<ServiceContext>().TextService);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsCanBeInvariant()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
IContent content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "another", "one" });
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
content1 = contentService.GetById(content1.Id);
|
||||
|
||||
var enTags = content1.Properties["tags"].GetTagsValue().ToArray();
|
||||
Assert.AreEqual(4, enTags.Length);
|
||||
Assert.Contains("one", enTags);
|
||||
Assert.AreEqual(-1, enTags.IndexOf("plus"));
|
||||
|
||||
var tagGroups = tagService.GetAllTags().GroupBy(x => x.LanguageId);
|
||||
foreach (var tag in tagService.GetAllTags())
|
||||
Console.WriteLine($"{tag.Group}:{tag.Text} {tag.LanguageId}");
|
||||
Assert.AreEqual(1, tagGroups.Count());
|
||||
var enTagGroup = tagGroups.FirstOrDefault(x => x.Key == null);
|
||||
Assert.IsNotNull(enTagGroup);
|
||||
Assert.AreEqual(4, enTagGroup.Count());
|
||||
Assert.IsTrue(enTagGroup.Any(x => x.Text == "one"));
|
||||
Assert.IsFalse(enTagGroup.Any(x => x.Text == "plus"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsCanBeVariant()
|
||||
{
|
||||
var languageService = ServiceContext.LocalizationService;
|
||||
languageService.Save(new Language("fr-FR")); // en-US is already there
|
||||
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
IContent content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.SetCultureName("name-fr", "fr-FR");
|
||||
content1.SetCultureName("name-en", "en-US");
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" }, culture: "fr-FR");
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "another", "one" }, culture: "en-US");
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
content1 = contentService.GetById(content1.Id);
|
||||
|
||||
var frTags = content1.Properties["tags"].GetTagsValue("fr-FR").ToArray();
|
||||
Assert.AreEqual(5, frTags.Length);
|
||||
Assert.Contains("plus", frTags);
|
||||
Assert.AreEqual(-1, frTags.IndexOf("one"));
|
||||
|
||||
var enTags = content1.Properties["tags"].GetTagsValue("en-US").ToArray();
|
||||
Assert.AreEqual(4, enTags.Length);
|
||||
Assert.Contains("one", enTags);
|
||||
Assert.AreEqual(-1, enTags.IndexOf("plus"));
|
||||
|
||||
var tagGroups = tagService.GetAllTags(culture:"*").GroupBy(x => x.LanguageId);
|
||||
foreach (var tag in tagService.GetAllTags())
|
||||
Console.WriteLine($"{tag.Group}:{tag.Text} {tag.LanguageId}");
|
||||
Assert.AreEqual(2, tagGroups.Count());
|
||||
var frTagGroup = tagGroups.FirstOrDefault(x => x.Key == 2);
|
||||
Assert.IsNotNull(frTagGroup);
|
||||
Assert.AreEqual(5, frTagGroup.Count());
|
||||
Assert.IsTrue(frTagGroup.Any(x => x.Text == "plus"));
|
||||
Assert.IsFalse(frTagGroup.Any(x => x.Text == "one"));
|
||||
var enTagGroup = tagGroups.FirstOrDefault(x => x.Key == 1);
|
||||
Assert.IsNotNull(enTagGroup);
|
||||
Assert.AreEqual(4, enTagGroup.Count());
|
||||
Assert.IsTrue(enTagGroup.Any(x => x.Text == "one"));
|
||||
Assert.IsFalse(enTagGroup.Any(x => x.Text == "plus"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsCanBecomeVariant()
|
||||
{
|
||||
var enId = ServiceContext.LocalizationService.GetLanguageIdByIsoCode("en-US").Value;
|
||||
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
PropertyType propertyType;
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
propertyType = new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
IContent content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "another", "one" });
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
// no changes
|
||||
content1 = contentService.GetById(content1.Id);
|
||||
|
||||
var tags = content1.Properties["tags"].GetTagsValue().ToArray();
|
||||
Assert.AreEqual(4, tags.Length);
|
||||
Assert.Contains("one", tags);
|
||||
Assert.AreEqual(-1, tags.IndexOf("plus"));
|
||||
|
||||
var tagGroups = tagService.GetAllTags().GroupBy(x => x.LanguageId);
|
||||
foreach (var tag in tagService.GetAllTags())
|
||||
Console.WriteLine($"{tag.Group}:{tag.Text} {tag.LanguageId}");
|
||||
Assert.AreEqual(1, tagGroups.Count());
|
||||
var enTagGroup = tagGroups.FirstOrDefault(x => x.Key == null);
|
||||
Assert.IsNotNull(enTagGroup);
|
||||
Assert.AreEqual(4, enTagGroup.Count());
|
||||
Assert.IsTrue(enTagGroup.Any(x => x.Text == "one"));
|
||||
Assert.IsFalse(enTagGroup.Any(x => x.Text == "plus"));
|
||||
|
||||
propertyType.Variations = ContentVariation.Culture;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
// changes
|
||||
content1 = contentService.GetById(content1.Id);
|
||||
|
||||
// property value has been moved from invariant to en-US
|
||||
tags = content1.Properties["tags"].GetTagsValue().ToArray();
|
||||
Assert.IsEmpty(tags);
|
||||
|
||||
tags = content1.Properties["tags"].GetTagsValue("en-US").ToArray();
|
||||
Assert.AreEqual(4, tags.Length);
|
||||
Assert.Contains("one", tags);
|
||||
Assert.AreEqual(-1, tags.IndexOf("plus"));
|
||||
|
||||
// tags have been copied from invariant to en-US
|
||||
tagGroups = tagService.GetAllTags(culture: "*").GroupBy(x => x.LanguageId);
|
||||
foreach (var tag in tagService.GetAllTags("*"))
|
||||
Console.WriteLine($"{tag.Group}:{tag.Text} {tag.LanguageId}");
|
||||
Assert.AreEqual(1, tagGroups.Count());
|
||||
|
||||
enTagGroup = tagGroups.FirstOrDefault(x => x.Key == enId);
|
||||
Assert.IsNotNull(enTagGroup);
|
||||
Assert.AreEqual(4, enTagGroup.Count());
|
||||
Assert.IsTrue(enTagGroup.Any(x => x.Text == "one"));
|
||||
Assert.IsFalse(enTagGroup.Any(x => x.Text == "plus"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsCanBecomeInvariant()
|
||||
{
|
||||
var languageService = ServiceContext.LocalizationService;
|
||||
languageService.Save(new Language("fr-FR")); // en-US is already there
|
||||
|
||||
var enId = ServiceContext.LocalizationService.GetLanguageIdByIsoCode("en-US").Value;
|
||||
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
PropertyType propertyType;
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
propertyType = new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
IContent content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.SetCultureName("name-fr", "fr-FR");
|
||||
content1.SetCultureName("name-en", "en-US");
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" }, culture: "fr-FR");
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "another", "one" }, culture: "en-US");
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
// changes
|
||||
content1 = contentService.GetById(content1.Id);
|
||||
|
||||
// property value has been moved from en-US to invariant, fr-FR tags are gone
|
||||
Assert.IsEmpty(content1.Properties["tags"].GetTagsValue("fr-FR"));
|
||||
Assert.IsEmpty(content1.Properties["tags"].GetTagsValue("en-US"));
|
||||
|
||||
var tags = content1.Properties["tags"].GetTagsValue().ToArray();
|
||||
Assert.AreEqual(4, tags.Length);
|
||||
Assert.Contains("one", tags);
|
||||
Assert.AreEqual(-1, tags.IndexOf("plus"));
|
||||
|
||||
// tags have been copied from en-US to invariant, fr-FR tags are gone
|
||||
var tagGroups = tagService.GetAllTags(culture: "*").GroupBy(x => x.LanguageId);
|
||||
foreach (var tag in tagService.GetAllTags("*"))
|
||||
Console.WriteLine($"{tag.Group}:{tag.Text} {tag.LanguageId}");
|
||||
Assert.AreEqual(1, tagGroups.Count());
|
||||
|
||||
var enTagGroup = tagGroups.FirstOrDefault(x => x.Key == null);
|
||||
Assert.IsNotNull(enTagGroup);
|
||||
Assert.AreEqual(4, enTagGroup.Count());
|
||||
Assert.IsTrue(enTagGroup.Any(x => x.Text == "one"));
|
||||
Assert.IsFalse(enTagGroup.Any(x => x.Text == "plus"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsCanBecomeInvariant2()
|
||||
{
|
||||
var languageService = ServiceContext.LocalizationService;
|
||||
languageService.Save(new Language("fr-FR")); // en-US is already there
|
||||
|
||||
var enId = ServiceContext.LocalizationService.GetLanguageIdByIsoCode("en-US").Value;
|
||||
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
PropertyType propertyType;
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
propertyType = new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
IContent content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.SetCultureName("name-fr", "fr-FR");
|
||||
content1.SetCultureName("name-en", "en-US");
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" }, culture: "fr-FR");
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "another", "one" }, culture: "en-US");
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
IContent content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1);
|
||||
content2.SetCultureName("name-fr", "fr-FR");
|
||||
content2.SetCultureName("name-en", "en-US");
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" }, culture: "fr-FR");
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "another", "one" }, culture: "en-US");
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
//// pretend we already have invariant values
|
||||
//using (var scope = ScopeProvider.CreateScope())
|
||||
//{
|
||||
// scope.Database.Execute("INSERT INTO [cmsTags] ([tag], [group], [languageId]) SELECT DISTINCT [tag], [group], NULL FROM [cmsTags] WHERE [languageId] IS NOT NULL");
|
||||
//}
|
||||
|
||||
// this should work
|
||||
propertyType.Variations = ContentVariation.Nothing;
|
||||
Assert.DoesNotThrow(() => contentTypeService.Save(contentType));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsCanBecomeInvariantByPropertyType()
|
||||
{
|
||||
var languageService = ServiceContext.LocalizationService;
|
||||
languageService.Save(new Language("fr-FR")); // en-US is already there
|
||||
|
||||
var enId = ServiceContext.LocalizationService.GetLanguageIdByIsoCode("en-US").Value;
|
||||
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
PropertyType propertyType;
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
propertyType = new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
IContent content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.SetCultureName("name-fr", "fr-FR");
|
||||
content1.SetCultureName("name-en", "en-US");
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" }, culture: "fr-FR");
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "another", "one" }, culture: "en-US");
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
propertyType.Variations = ContentVariation.Nothing;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
// changes
|
||||
content1 = contentService.GetById(content1.Id);
|
||||
|
||||
// property value has been moved from en-US to invariant, fr-FR tags are gone
|
||||
Assert.IsEmpty(content1.Properties["tags"].GetTagsValue("fr-FR"));
|
||||
Assert.IsEmpty(content1.Properties["tags"].GetTagsValue("en-US"));
|
||||
|
||||
var tags = content1.Properties["tags"].GetTagsValue().ToArray();
|
||||
Assert.AreEqual(4, tags.Length);
|
||||
Assert.Contains("one", tags);
|
||||
Assert.AreEqual(-1, tags.IndexOf("plus"));
|
||||
|
||||
// tags have been copied from en-US to invariant, fr-FR tags are gone
|
||||
var tagGroups = tagService.GetAllTags(culture: "*").GroupBy(x => x.LanguageId);
|
||||
foreach (var tag in tagService.GetAllTags("*"))
|
||||
Console.WriteLine($"{tag.Group}:{tag.Text} {tag.LanguageId}");
|
||||
Assert.AreEqual(1, tagGroups.Count());
|
||||
|
||||
var enTagGroup = tagGroups.FirstOrDefault(x => x.Key == null);
|
||||
Assert.IsNotNull(enTagGroup);
|
||||
Assert.AreEqual(4, enTagGroup.Count());
|
||||
Assert.IsTrue(enTagGroup.Any(x => x.Text == "one"));
|
||||
Assert.IsFalse(enTagGroup.Any(x => x.Text == "plus"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsCanBecomeInvariantByPropertyTypeAndBackToVariant()
|
||||
{
|
||||
var languageService = ServiceContext.LocalizationService;
|
||||
languageService.Save(new Language("fr-FR")); // en-US is already there
|
||||
|
||||
var enId = ServiceContext.LocalizationService.GetLanguageIdByIsoCode("en-US").Value;
|
||||
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
PropertyType propertyType;
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
propertyType = new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
IContent content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.SetCultureName("name-fr", "fr-FR");
|
||||
content1.SetCultureName("name-en", "en-US");
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" }, culture: "fr-FR");
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "another", "one" }, culture: "en-US");
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
propertyType.Variations = ContentVariation.Nothing;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
//fixme: This throws due to index violations
|
||||
propertyType.Variations = ContentVariation.Culture;
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
//TODO: Assert results
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsAreUpdatedWhenContentIsTrashedAndUnTrashed_One()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" });
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1);
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
// verify
|
||||
var tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(5, tags.Count());
|
||||
var allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
|
||||
contentService.MoveToRecycleBin(content1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsAreUpdatedWhenContentIsTrashedAndUnTrashed_All()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "bam" });
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1);
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
// verify
|
||||
var tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(5, tags.Count());
|
||||
var allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
|
||||
contentService.Unpublish(content1);
|
||||
contentService.Unpublish(content2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("https://github.com/umbraco/Umbraco-CMS/issues/3821 (U4-8442), will need to be fixed.")]
|
||||
public void TagsAreUpdatedWhenContentIsTrashedAndUnTrashed_Tree()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" });
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", content1.Id);
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
// verify
|
||||
var tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(5, tags.Count());
|
||||
var allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
|
||||
contentService.MoveToRecycleBin(content1);
|
||||
|
||||
// no more tags
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
tags = tagService.GetTagsForEntity(content2.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
|
||||
// no more tags
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(0, allTags.Count());
|
||||
|
||||
contentService.Move(content1, -1);
|
||||
|
||||
Assert.IsFalse(content1.Published);
|
||||
|
||||
// no more tags
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
tags = tagService.GetTagsForEntity(content2.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
|
||||
// no more tags
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(0, allTags.Count());
|
||||
|
||||
content1.PublishCulture();
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
Assert.IsTrue(content1.Published);
|
||||
|
||||
// tags are back
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(5, tags.Count());
|
||||
|
||||
// fixme tag & tree issue
|
||||
// when we publish, we 'just' publish the top one and not the ones below = fails
|
||||
// what we should do is... NOT clear tags when unpublishing or trashing or...
|
||||
// and just update the tag service to NOT return anything related to trashed or
|
||||
// unpublished entities (since trashed is set on ALL entities in the trashed branch)
|
||||
tags = tagService.GetTagsForEntity(content2.Id); // including that one!
|
||||
Assert.AreEqual(4, tags.Count());
|
||||
|
||||
// tags are back
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsAreUpdatedWhenContentIsUnpublishedAndRePublished()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "bam" });
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1);
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
contentService.Unpublish(content1);
|
||||
contentService.Unpublish(content2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("https://github.com/umbraco/Umbraco-CMS/issues/3821 (U4-8442), will need to be fixed.")]
|
||||
public void TagsAreUpdatedWhenContentIsUnpublishedAndRePublished_Tree()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "bam" });
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", content1);
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
contentService.Unpublish(content1);
|
||||
|
||||
var tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
|
||||
// fixme tag & tree issue
|
||||
// when we (un)publish, we 'just' publish the top one and not the ones below = fails
|
||||
// see similar note above
|
||||
tags = tagService.GetTagsForEntity(content2.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
var allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(0, allTags.Count());
|
||||
|
||||
content1.PublishCulture();
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
tags = tagService.GetTagsForEntity(content2.Id);
|
||||
Assert.AreEqual(4, tags.Count());
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Create_Tag_Data_Bulk_Publish_Operation()
|
||||
{
|
||||
//Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var dataTypeService = ServiceContext.DataTypeService;
|
||||
|
||||
//set configuration
|
||||
var dataType = dataTypeService.GetDataType(1041);
|
||||
dataType.Configuration = new TagConfiguration
|
||||
{
|
||||
Group = "test",
|
||||
StorageType = TagsStorageType.Csv
|
||||
};
|
||||
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy<int>(() => contentType.Id), 0, contentType.Alias) };
|
||||
|
||||
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
|
||||
content.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.Save(content);
|
||||
|
||||
var child1 = MockedContent.CreateSimpleContent(contentType, "child 1 content", content.Id);
|
||||
child1.AssignTags("tags", new[] { "hello1", "world1", "some1" });
|
||||
contentService.Save(child1);
|
||||
|
||||
var child2 = MockedContent.CreateSimpleContent(contentType, "child 2 content", content.Id);
|
||||
child2.AssignTags("tags", new[] { "hello2", "world2" });
|
||||
contentService.Save(child2);
|
||||
|
||||
// Act
|
||||
contentService.SaveAndPublishBranch(content, true);
|
||||
|
||||
// Assert
|
||||
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Assert.AreEqual(4, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = content.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
Assert.AreEqual(3, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = child1.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
Assert.AreEqual(2, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = child2.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Create_Tag_Data_For_Non_Published_Version()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
|
||||
// create content type with a tag property
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(new PropertyType("test", ValueStorageType.Ntext, "tags") { DataTypeId = 1041 });
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
// create a content with tags and publish
|
||||
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
|
||||
content.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// edit tags and save
|
||||
content.AssignTags("tags", new[] { "another", "world" }, merge: true);
|
||||
contentService.Save(content);
|
||||
|
||||
// the (edit) property does contain all tags
|
||||
Assert.AreEqual(5, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count());
|
||||
|
||||
// but the database still contains the initial two tags
|
||||
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Assert.AreEqual(4, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = content.Id, propTypeId = propertyTypeId }));
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Replace_Tag_Data_To_Published_Content()
|
||||
{
|
||||
//Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
|
||||
|
||||
|
||||
// Act
|
||||
content.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(4, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count());
|
||||
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Assert.AreEqual(4, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = content.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Append_Tag_Data_To_Published_Content()
|
||||
{
|
||||
//Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
|
||||
content.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// Act
|
||||
content.AssignTags("tags", new[] { "another", "world" }, merge: true);
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(5, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count());
|
||||
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Assert.AreEqual(5, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = content.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Remove_Tag_Data_To_Published_Content()
|
||||
{
|
||||
//Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
|
||||
content.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// Act
|
||||
content.RemoveTags("tags", new[] { "some", "world" });
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count());
|
||||
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Assert.AreEqual(2, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = content.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -193,63 +193,6 @@ namespace Umbraco.Tests.Services
|
||||
Assert.AreEqual(5, found.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that we don't unpublish all nodes when a node is deleted that has an invalid path of -1
|
||||
/// Note: it is actually the MoveToRecycleBin happening on the initial deletion of a node through the UI
|
||||
/// that causes the issue.
|
||||
/// Regression test: http://issues.umbraco.org/issue/U4-9336
|
||||
/// </summary>
|
||||
[Test]
|
||||
[Ignore("not applicable to v8")]
|
||||
|
||||
// fixme - this test was imported from 7.6 BUT it makes no sense for v8
|
||||
// we should trust the PATH, full stop
|
||||
|
||||
public void Moving_Node_To_Recycle_Bin_With_Invalid_Path()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var root = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1);
|
||||
root.PublishCulture();
|
||||
Assert.IsTrue(contentService.SaveAndPublish(root).Success);
|
||||
var content = contentService.CreateAndSave("Test", -1, "umbTextpage", Constants.Security.SuperUserId);
|
||||
content.PublishCulture();
|
||||
Assert.IsTrue(contentService.SaveAndPublish(content).Success);
|
||||
var hierarchy = CreateContentHierarchy().OrderBy(x => x.Level).ToArray();
|
||||
contentService.Save(hierarchy, Constants.Security.SuperUserId);
|
||||
foreach (var c in hierarchy)
|
||||
{
|
||||
c.PublishCulture();
|
||||
Assert.IsTrue(contentService.SaveAndPublish(c).Success);
|
||||
}
|
||||
|
||||
//now make the data corrupted :/
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.Database.Execute("UPDATE umbracoNode SET path = '-1' WHERE id = @id", new { id = content.Id });
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
//re-get with the corrupt path
|
||||
content = contentService.GetById(content.Id);
|
||||
|
||||
// here we get all descendants by the path of the node being moved to bin, and unpublish all of them.
|
||||
// since the path is invalid, there's logic in here to fix that if it's possible and re-persist the entity.
|
||||
var moveResult = ServiceContext.ContentService.MoveToRecycleBin(content);
|
||||
|
||||
Assert.IsTrue(moveResult.Success);
|
||||
|
||||
//re-get with the fixed/moved path
|
||||
content = contentService.GetById(content.Id);
|
||||
|
||||
Assert.AreEqual("-1,-20," + content.Id, content.Path);
|
||||
|
||||
//re-get
|
||||
hierarchy = contentService.GetByIds(hierarchy.Select(x => x.Id).ToArray()).OrderBy(x => x.Level).ToArray();
|
||||
|
||||
Assert.That(hierarchy.All(c => c.Trashed == false), Is.True);
|
||||
Assert.That(hierarchy.All(c => c.Path.StartsWith("-1,-20") == false), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Perform_Scheduled_Publishing()
|
||||
{
|
||||
@@ -501,511 +444,6 @@ namespace Umbraco.Tests.Services
|
||||
Assert.IsEmpty(res);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsAreUpdatedWhenContentIsTrashedAndUnTrashed_One()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" });
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1);
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
// verify
|
||||
var tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(5, tags.Count());
|
||||
var allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
|
||||
contentService.MoveToRecycleBin(content1);
|
||||
|
||||
// fixme - killing the rest of this test
|
||||
// this is not working consistently even in 7 when unpublishing a branch
|
||||
// in 8, tags never go away - one has to check that the entity is published and not trashed
|
||||
return;
|
||||
|
||||
// no more tags for this entity
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
|
||||
// tags still assigned to content2 are still there
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(4, allTags.Count());
|
||||
|
||||
contentService.Move(content1, -1);
|
||||
|
||||
Assert.IsFalse(content1.Published);
|
||||
|
||||
// no more tags for this entity
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
|
||||
// tags still assigned to content2 are still there
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(4, allTags.Count());
|
||||
|
||||
content1.PublishCulture();
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
Assert.IsTrue(content1.Published);
|
||||
|
||||
// tags are back
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(5, tags.Count());
|
||||
|
||||
// tags are back
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsAreUpdatedWhenContentIsTrashedAndUnTrashed_All()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "bam" });
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1);
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
// verify
|
||||
var tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(5, tags.Count());
|
||||
var allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
|
||||
contentService.Unpublish(content1);
|
||||
contentService.Unpublish(content2);
|
||||
|
||||
// fixme - killing the rest of this test
|
||||
// this is not working consistently even in 7 when unpublishing a branch
|
||||
// in 8, tags never go away - one has to check that the entity is published and not trashed
|
||||
return;
|
||||
|
||||
// no more tags
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
|
||||
// no more tags
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(0, allTags.Count());
|
||||
|
||||
contentService.Move(content1, -1);
|
||||
contentService.Move(content2, -1);
|
||||
|
||||
// no more tags
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
|
||||
// no more tags
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(0, allTags.Count());
|
||||
|
||||
content1.PublishCulture();
|
||||
contentService.SaveAndPublish(content1);
|
||||
content2.PublishCulture();
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
// tags are back
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(5, tags.Count());
|
||||
|
||||
// tags are back
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("U4-8442, will need to be fixed eventually.")]
|
||||
public void TagsAreUpdatedWhenContentIsTrashedAndUnTrashed_Tree()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" });
|
||||
content1.PublishCulture();
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", content1.Id);
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
content2.PublishCulture();
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
// verify
|
||||
var tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(5, tags.Count());
|
||||
var allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
|
||||
contentService.MoveToRecycleBin(content1);
|
||||
|
||||
// no more tags
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
tags = tagService.GetTagsForEntity(content2.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
|
||||
// no more tags
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(0, allTags.Count());
|
||||
|
||||
contentService.Move(content1, -1);
|
||||
|
||||
Assert.IsFalse(content1.Published);
|
||||
|
||||
// no more tags
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
tags = tagService.GetTagsForEntity(content2.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
|
||||
// no more tags
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(0, allTags.Count());
|
||||
|
||||
content1.PublishCulture();
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
Assert.IsTrue(content1.Published);
|
||||
|
||||
// tags are back
|
||||
tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(5, tags.Count());
|
||||
|
||||
// fixme tag & tree issue
|
||||
// when we publish, we 'just' publish the top one and not the ones below = fails
|
||||
// what we should do is... NOT clear tags when unpublishing or trashing or...
|
||||
// and just update the tag service to NOT return anything related to trashed or
|
||||
// unpublished entities (since trashed is set on ALL entities in the trashed branch)
|
||||
tags = tagService.GetTagsForEntity(content2.Id); // including that one!
|
||||
Assert.AreEqual(4, tags.Count());
|
||||
|
||||
// tags are back
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsAreUpdatedWhenContentIsUnpublishedAndRePublished()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "bam" });
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1);
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
contentService.Unpublish(content1);
|
||||
contentService.Unpublish(content2);
|
||||
|
||||
// fixme - killing the rest of this test
|
||||
// this is not working consistently even in 7 when unpublishing a branch
|
||||
// in 8, tags never go away - one has to check that the entity is published and not trashed
|
||||
return;
|
||||
|
||||
var tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
var allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(0, allTags.Count());
|
||||
|
||||
content2.PublishCulture();
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
tags = tagService.GetTagsForEntity(content2.Id);
|
||||
Assert.AreEqual(4, tags.Count());
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(4, allTags.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("U4-8442, will need to be fixed eventually.")]
|
||||
public void TagsAreUpdatedWhenContentIsUnpublishedAndRePublished_Tree()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var tagService = ServiceContext.TagService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
|
||||
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "bam" });
|
||||
content1.PublishCulture();
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", content1);
|
||||
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
content2.PublishCulture();
|
||||
contentService.SaveAndPublish(content2);
|
||||
|
||||
contentService.Unpublish(content1);
|
||||
|
||||
var tags = tagService.GetTagsForEntity(content1.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
|
||||
// fixme tag & tree issue
|
||||
// when we (un)publish, we 'just' publish the top one and not the ones below = fails
|
||||
// see similar note above
|
||||
tags = tagService.GetTagsForEntity(content2.Id);
|
||||
Assert.AreEqual(0, tags.Count());
|
||||
var allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(0, allTags.Count());
|
||||
|
||||
content1.PublishCulture();
|
||||
contentService.SaveAndPublish(content1);
|
||||
|
||||
tags = tagService.GetTagsForEntity(content2.Id);
|
||||
Assert.AreEqual(4, tags.Count());
|
||||
allTags = tagService.GetAllContentTags();
|
||||
Assert.AreEqual(5, allTags.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Create_Tag_Data_Bulk_Publish_Operation()
|
||||
{
|
||||
//Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var dataTypeService = ServiceContext.DataTypeService;
|
||||
|
||||
//set configuration
|
||||
var dataType = dataTypeService.GetDataType(1041);
|
||||
dataType.Configuration = new TagConfiguration
|
||||
{
|
||||
Group = "test",
|
||||
StorageType = TagsStorageType.Csv
|
||||
};
|
||||
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy<int>(() => contentType.Id), 0, contentType.Alias) };
|
||||
|
||||
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
|
||||
content.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.Save(content);
|
||||
|
||||
var child1 = MockedContent.CreateSimpleContent(contentType, "child 1 content", content.Id);
|
||||
child1.AssignTags("tags", new[] { "hello1", "world1", "some1" });
|
||||
contentService.Save(child1);
|
||||
|
||||
var child2 = MockedContent.CreateSimpleContent(contentType, "child 2 content", content.Id);
|
||||
child2.AssignTags("tags", new[] { "hello2", "world2" });
|
||||
contentService.Save(child2);
|
||||
|
||||
// Act
|
||||
contentService.SaveAndPublishBranch(content, true);
|
||||
|
||||
// Assert
|
||||
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Assert.AreEqual(4, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = content.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
Assert.AreEqual(3, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = child1.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
Assert.AreEqual(2, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = child2.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Create_Tag_Data_For_Non_Published_Version()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
|
||||
// create content type with a tag property
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(new PropertyType("test", ValueStorageType.Ntext, "tags") { DataTypeId = 1041 });
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
// create a content with tags and publish
|
||||
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
|
||||
content.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// edit tags and save
|
||||
content.AssignTags("tags", new[] { "another", "world" }, merge: true);
|
||||
contentService.Save(content);
|
||||
|
||||
// the (edit) property does contain all tags
|
||||
Assert.AreEqual(5, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count());
|
||||
|
||||
// but the database still contains the initial two tags
|
||||
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Assert.AreEqual(4, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = content.Id, propTypeId = propertyTypeId }));
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Replace_Tag_Data_To_Published_Content()
|
||||
{
|
||||
//Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
|
||||
|
||||
|
||||
// Act
|
||||
content.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(4, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count());
|
||||
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Assert.AreEqual(4, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = content.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Append_Tag_Data_To_Published_Content()
|
||||
{
|
||||
//Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
|
||||
content.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// Act
|
||||
content.AssignTags("tags", new[] { "another", "world" }, merge: true);
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(5, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count());
|
||||
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Assert.AreEqual(5, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = content.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Remove_Tag_Data_To_Published_Content()
|
||||
{
|
||||
//Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
|
||||
contentType.PropertyGroups.First().PropertyTypes.Add(
|
||||
new PropertyType("test", ValueStorageType.Ntext, "tags")
|
||||
{
|
||||
DataTypeId = 1041
|
||||
});
|
||||
contentTypeService.Save(contentType);
|
||||
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
|
||||
content.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// Act
|
||||
content.RemoveTags("tags", new[] { "some", "world" });
|
||||
contentService.SaveAndPublish(content);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2, content.Properties["tags"].GetValue().ToString().Split(',').Distinct().Count());
|
||||
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
Assert.AreEqual(2, scope.Database.ExecuteScalar<int>(
|
||||
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
|
||||
new { nodeId = content.Id, propTypeId = propertyTypeId }));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Remove_Property_Type()
|
||||
{
|
||||
@@ -1134,7 +572,6 @@ namespace Umbraco.Tests.Services
|
||||
Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(2));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Can_Get_All_Versions_Of_Content()
|
||||
{
|
||||
|
||||
@@ -133,6 +133,7 @@
|
||||
<Compile Include="PublishedContent\SolidPublishedSnapshot.cs" />
|
||||
<Compile Include="PublishedContent\NuCacheTests.cs" />
|
||||
<Compile Include="Services\ContentServicePublishBranchTests.cs" />
|
||||
<Compile Include="Services\ContentServiceTagsTests.cs" />
|
||||
<Compile Include="Services\ContentTypeServiceVariantsTests.cs" />
|
||||
<Compile Include="Testing\Objects\TestDataSource.cs" />
|
||||
<Compile Include="Published\PublishedSnapshotTestObjects.cs" />
|
||||
|
||||
+2801
-2813
File diff suppressed because it is too large
Load Diff
+3
-2
@@ -12,11 +12,12 @@
|
||||
|
||||
//expose the property/methods for other directives to use
|
||||
this.content = $scope.content;
|
||||
|
||||
$scope.activeVariant = _.find(this.content.variants, variant => {
|
||||
this.activeVariant = _.find(this.content.variants, variant => {
|
||||
return variant.active;
|
||||
});
|
||||
|
||||
$scope.activeVariant = this.activeVariant;
|
||||
|
||||
$scope.defaultVariant = _.find(this.content.variants, variant => {
|
||||
return variant.language.isDefault;
|
||||
});
|
||||
|
||||
+2
-2
@@ -95,7 +95,7 @@ angular.module('umbraco.directives')
|
||||
};
|
||||
})
|
||||
|
||||
.directive('onOutsideClick', function ($timeout) {
|
||||
.directive('onOutsideClick', function ($timeout, angularHelper) {
|
||||
return function (scope, element, attrs) {
|
||||
|
||||
var eventBindings = [];
|
||||
@@ -136,7 +136,7 @@ angular.module('umbraco.directives')
|
||||
return;
|
||||
}
|
||||
|
||||
scope.$apply(attrs.onOutsideClick);
|
||||
angularHelper.safeApply(scope, attrs.onOutsideClick);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -274,7 +274,7 @@ angular.module("umbraco.directives")
|
||||
}
|
||||
|
||||
//// INIT /////
|
||||
$image.load(function(){
|
||||
$image.on("load", function(){
|
||||
$timeout(function(){
|
||||
init($image);
|
||||
});
|
||||
|
||||
+24
-24
@@ -27,34 +27,34 @@ angular.module("umbraco.directives")
|
||||
var $image = element.find("img");
|
||||
scope.loaded = false;
|
||||
|
||||
$image.load(function(){
|
||||
$timeout(function(){
|
||||
$image.width("auto");
|
||||
$image.height("auto");
|
||||
$image.on("load", function() {
|
||||
$timeout(function () {
|
||||
$image.width("auto");
|
||||
$image.height("auto");
|
||||
|
||||
scope.image = {};
|
||||
scope.image.width = $image[0].width;
|
||||
scope.image.height = $image[0].height;
|
||||
scope.image = {};
|
||||
scope.image.width = $image[0].width;
|
||||
scope.image.height = $image[0].height;
|
||||
|
||||
//we force a lower thumbnail size to fit the max size
|
||||
//we do not compare to the image dimensions, but the thumbs
|
||||
if(scope.maxSize){
|
||||
var ratioCalculation = cropperHelper.calculateAspectRatioFit(
|
||||
scope.width,
|
||||
scope.height,
|
||||
scope.maxSize,
|
||||
scope.maxSize,
|
||||
false);
|
||||
//we force a lower thumbnail size to fit the max size
|
||||
//we do not compare to the image dimensions, but the thumbs
|
||||
if (scope.maxSize) {
|
||||
var ratioCalculation = cropperHelper.calculateAspectRatioFit(
|
||||
scope.width,
|
||||
scope.height,
|
||||
scope.maxSize,
|
||||
scope.maxSize,
|
||||
false);
|
||||
|
||||
//so if we have a max size, override the thumb sizes
|
||||
scope.width = ratioCalculation.width;
|
||||
scope.height = ratioCalculation.height;
|
||||
}
|
||||
//so if we have a max size, override the thumb sizes
|
||||
scope.width = ratioCalculation.width;
|
||||
scope.height = ratioCalculation.height;
|
||||
}
|
||||
|
||||
setPreviewStyle();
|
||||
scope.loaded = true;
|
||||
});
|
||||
});
|
||||
setPreviewStyle();
|
||||
scope.loaded = true;
|
||||
});
|
||||
})
|
||||
|
||||
/// WATCHERS ////
|
||||
scope.$watchCollection('[crop, center]', function(newValues, oldValues){
|
||||
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbTagsEditor
|
||||
**/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('umbraco.directives')
|
||||
.component('umbTagsEditor', {
|
||||
transclude: true,
|
||||
templateUrl: 'views/components/tags/umb-tags-editor.html',
|
||||
controller: umbTagsEditorController,
|
||||
controllerAs: 'vm',
|
||||
bindings: {
|
||||
value: "<",
|
||||
config: "<",
|
||||
validation: "<",
|
||||
culture: "<?",
|
||||
onValueChanged: "&"
|
||||
}
|
||||
});
|
||||
|
||||
function umbTagsEditorController($rootScope, assetsService, umbRequestHelper, angularHelper, $timeout, $element) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
var typeahead;
|
||||
var tagsHound;
|
||||
|
||||
vm.$onInit = onInit;
|
||||
vm.$onChanges = onChanges;
|
||||
vm.$onDestroy = onDestroy;
|
||||
|
||||
vm.validateMandatory = validateMandatory;
|
||||
vm.addTagOnEnter = addTagOnEnter;
|
||||
vm.addTag = addTag;
|
||||
vm.removeTag = removeTag;
|
||||
vm.showPrompt = showPrompt;
|
||||
vm.hidePrompt = hidePrompt;
|
||||
|
||||
vm.htmlId = "t" + String.CreateGuid();
|
||||
vm.isLoading = true;
|
||||
vm.tagToAdd = "";
|
||||
vm.promptIsVisible = "-1";
|
||||
vm.viewModel = [];
|
||||
|
||||
function onInit() {
|
||||
|
||||
assetsService.loadJs("lib/typeahead.js/typeahead.bundle.min.js").then(function () {
|
||||
|
||||
vm.isLoading = false;
|
||||
|
||||
configureViewModel();
|
||||
|
||||
// Set the visible prompt to -1 to ensure it will not be visible
|
||||
vm.promptIsVisible = "-1";
|
||||
|
||||
tagsHound = new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
//pre-fetch the tags for this category
|
||||
prefetch: {
|
||||
url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", { tagGroup: vm.config.group, culture: vm.culture }),
|
||||
//TTL = 5 minutes
|
||||
ttl: 300000,
|
||||
transform: dataTransform
|
||||
},
|
||||
//dynamically get the tags for this category (they may have changed on the server)
|
||||
remote: {
|
||||
url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", { tagGroup: vm.config.group, culture: vm.culture }),
|
||||
transform: dataTransform
|
||||
}
|
||||
});
|
||||
|
||||
tagsHound.initialize(true);
|
||||
|
||||
//configure the type ahead
|
||||
$timeout(function () {
|
||||
|
||||
var sources = {
|
||||
//see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options
|
||||
// name = the data set name, we'll make this the tag group name
|
||||
name: vm.config.group,
|
||||
display: "value",
|
||||
//source: tagsHound
|
||||
source: function (query, cb) {
|
||||
tagsHound.search(query,
|
||||
function(suggestions) {
|
||||
cb(removeCurrentTagsFromSuggestions(suggestions));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var opts = {
|
||||
//This causes some strangeness as it duplicates the textbox, best leave off for now.
|
||||
hint: false,
|
||||
highlight: true,
|
||||
cacheKey: new Date(), // Force a cache refresh each time the control is initialized
|
||||
minLength: 1
|
||||
};
|
||||
|
||||
typeahead = $element.find('.tags-' + vm.htmlId).typeahead(opts, sources)
|
||||
.bind("typeahead:selected", function (obj, datum, name) {
|
||||
angularHelper.safeApply($rootScope, function () {
|
||||
addTagInternal(datum["value"]);
|
||||
vm.tagToAdd = "";
|
||||
// clear the typed text
|
||||
typeahead.typeahead('val', '');
|
||||
});
|
||||
}).bind("typeahead:autocompleted", function (obj, datum, name) {
|
||||
angularHelper.safeApply($rootScope, function () {
|
||||
addTagInternal(datum["value"]);
|
||||
vm.tagToAdd = "";
|
||||
});
|
||||
|
||||
}).bind("typeahead:opened", function (obj) {
|
||||
console.log("opened ");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function onChanges(changes) {
|
||||
|
||||
// watch for value changes externally
|
||||
if (changes.value) {
|
||||
if (!changes.value.isFirstChange() && changes.value.currentValue !== changes.value.previousValue) {
|
||||
|
||||
configureViewModel();
|
||||
//this is required to re-validate
|
||||
vm.tagEditorForm.tagCount.$setViewValue(vm.viewModel.length);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onDestroy() {
|
||||
if (tagsHound) {
|
||||
tagsHound.clearPrefetchCache();
|
||||
tagsHound.clearRemoteCache();
|
||||
tagsHound = null;
|
||||
}
|
||||
$element.find('.tags-' + vm.htmlId).typeahead('destroy');
|
||||
}
|
||||
|
||||
function configureViewModel() {
|
||||
if (vm.value) {
|
||||
if (angular.isString(vm.value) && vm.value.length > 0) {
|
||||
if (vm.config.storageType === "Json") {
|
||||
//json storage
|
||||
vm.viewModel = JSON.parse(vm.value);
|
||||
updateModelValue(vm.viewModel);
|
||||
}
|
||||
else {
|
||||
//csv storage
|
||||
|
||||
// split the csv string, and remove any duplicate values
|
||||
let tempArray = vm.value.split(',').map(function (v) {
|
||||
return v.trim();
|
||||
});
|
||||
|
||||
vm.viewModel = tempArray.filter(function (v, i, self) {
|
||||
return self.indexOf(v) === i;
|
||||
});
|
||||
|
||||
updateModelValue(vm.viewModel);
|
||||
}
|
||||
}
|
||||
else if (angular.isArray(vm.value)) {
|
||||
vm.viewModel = vm.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateModelValue(val) {
|
||||
if (val) {
|
||||
vm.onValueChanged({ value: val });
|
||||
}
|
||||
else {
|
||||
vm.onValueChanged({ value: [] });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method required by the valPropertyValidator directive (returns true if the property editor has at least one tag selected)
|
||||
*/
|
||||
function validateMandatory() {
|
||||
return {
|
||||
isValid: !vm.validation.mandatory || (vm.viewModel != null && vm.viewModel.length > 0),
|
||||
errorMsg: "Value cannot be empty",
|
||||
errorKey: "required"
|
||||
};
|
||||
}
|
||||
|
||||
function addTagInternal(tagToAdd) {
|
||||
if (tagToAdd != null && tagToAdd.length > 0) {
|
||||
if (vm.viewModel.indexOf(tagToAdd) < 0) {
|
||||
vm.viewModel.push(tagToAdd);
|
||||
updateModelValue(vm.viewModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addTagOnEnter(e) {
|
||||
var code = e.keyCode || e.which;
|
||||
if (code == 13) { //Enter keycode
|
||||
if ($element.find('.tags-' + vm.htmlId).parent().find(".tt-menu .tt-cursor").length === 0) {
|
||||
//this is required, otherwise the html form will attempt to submit.
|
||||
e.preventDefault();
|
||||
addTag();
|
||||
}
|
||||
}
|
||||
}
|
||||
function addTag() {
|
||||
//ensure that we're not pressing the enter key whilst selecting a typeahead value from the drop down
|
||||
//we need to use jquery because typeahead duplicates the text box
|
||||
addTagInternal(vm.tagToAdd);
|
||||
vm.tagToAdd = "";
|
||||
//this clears the value stored in typeahead so it doesn't try to add the text again
|
||||
// https://issues.umbraco.org/issue/U4-4947
|
||||
typeahead.typeahead('val', '');
|
||||
}
|
||||
|
||||
function removeTag(tag) {
|
||||
var i = vm.viewModel.indexOf(tag);
|
||||
|
||||
if (i >= 0) {
|
||||
// Make sure to hide the prompt so it does not stay open because another item gets a new number in the array index
|
||||
vm.promptIsVisible = "-1";
|
||||
|
||||
// Remove the tag from the index
|
||||
vm.viewModel.splice(i, 1);
|
||||
|
||||
updateModelValue(vm.viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
function showPrompt(idx, tag) {
|
||||
|
||||
var i = vm.viewModel.indexOf(tag);
|
||||
|
||||
// Make the prompt visible for the clicked tag only
|
||||
if (i === idx) {
|
||||
vm.promptIsVisible = i;
|
||||
}
|
||||
}
|
||||
|
||||
function hidePrompt() {
|
||||
vm.promptIsVisible = "-1";
|
||||
}
|
||||
|
||||
//helper method to format the data for bloodhound
|
||||
function dataTransform(list) {
|
||||
//transform the result to what bloodhound wants
|
||||
var tagList = _.map(list, function (i) {
|
||||
return { value: i.text };
|
||||
});
|
||||
// remove current tags from the list
|
||||
return $.grep(tagList, function (tag) {
|
||||
return ($.inArray(tag.value, vm.viewModel) === -1);
|
||||
});
|
||||
}
|
||||
|
||||
// helper method to remove current tags
|
||||
function removeCurrentTagsFromSuggestions(suggestions) {
|
||||
return $.grep(suggestions, function (suggestion) {
|
||||
return ($.inArray(suggestion.value, vm.viewModel) === -1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
+4
-1
@@ -36,7 +36,7 @@ function valPropertyValidator(serverValidationManager) {
|
||||
|
||||
// Validation method
|
||||
function validate (viewValue) {
|
||||
// Calls the validition method
|
||||
// Calls the validation method
|
||||
var result = scope.valPropertyValidator();
|
||||
if (!result.errorKey || result.isValid === undefined || !result.errorMsg) {
|
||||
throw "The result object from valPropertyValidator does not contain required properties: isValid, errorKey, errorMsg";
|
||||
@@ -55,6 +55,9 @@ function valPropertyValidator(serverValidationManager) {
|
||||
propCtrl.setPropertyError(result.errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// parsers are expected to return a value
|
||||
return (result.isValid) ? viewValue : undefined;
|
||||
};
|
||||
|
||||
// Parsers are called as soon as the value in the form input is modified
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
color: @gray-8 !important;
|
||||
}
|
||||
|
||||
.tt-dropdown-menu {
|
||||
.tt-menu {
|
||||
width: 422px;
|
||||
margin-top: 12px;
|
||||
padding: 8px 0;
|
||||
@@ -61,4 +61,4 @@
|
||||
|
||||
.tt-suggestion p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<div>
|
||||
<ng-form name="vm.tagEditorForm">
|
||||
<div ng-if="vm.isLoading">
|
||||
<localize key="loading">Loading</localize>...
|
||||
</div>
|
||||
|
||||
<div ng-if="!isLoading">
|
||||
|
||||
<input type="hidden" name="tagCount" ng-model="vm.viewModel.length" val-property-validator="vm.validateMandatory" />
|
||||
|
||||
<span ng-repeat="tag in vm.viewModel track by $index" class="label label-primary tag">
|
||||
<span ng-bind-html="tag"></span>
|
||||
|
||||
<i class="icon-trash" ng-click="vm.showPrompt($index, tag)" localize="title" title="@buttons_deleteTag"></i>
|
||||
|
||||
<umb-confirm-action ng-if="vm.promptIsVisible === $index"
|
||||
direction="left"
|
||||
on-confirm="vm.removeTag(tag)"
|
||||
on-cancel="vm.hidePrompt()">
|
||||
</umb-confirm-action>
|
||||
</span>
|
||||
|
||||
<input type="text"
|
||||
id="{{vm.htmlId}}"
|
||||
class="typeahead tags-{{vm.htmlId}}"
|
||||
ng-model="vm.tagToAdd"
|
||||
ng-keydown="vm.addTagOnEnter($event)"
|
||||
on-blur="vm.addTag()"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_enterTags" />
|
||||
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
@@ -1,216 +1,10 @@
|
||||
angular.module("umbraco")
|
||||
.controller("Umbraco.PropertyEditors.TagsController",
|
||||
function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element) {
|
||||
function ($scope) {
|
||||
|
||||
var $typeahead;
|
||||
|
||||
$scope.isLoading = true;
|
||||
$scope.tagToAdd = "";
|
||||
|
||||
function setModelValue(val) {
|
||||
|
||||
$scope.model.value = val || $scope.model.value;
|
||||
if ($scope.model.value) {
|
||||
if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") {
|
||||
//it is csv
|
||||
if (!$scope.model.value) {
|
||||
$scope.model.value = [];
|
||||
}
|
||||
else {
|
||||
if($scope.model.value.length > 0) {
|
||||
// split the csv string, and remove any duplicate values
|
||||
var tempArray = $scope.model.value.split(',').map(function(v) {
|
||||
return v.trim();
|
||||
});
|
||||
|
||||
$scope.model.value = tempArray.filter(function(v, i, self) {
|
||||
return self.indexOf(v) === i;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$scope.model.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
assetsService.loadJs("lib/typeahead.js/typeahead.bundle.min.js", $scope).then(function () {
|
||||
|
||||
$scope.isLoading = false;
|
||||
|
||||
//load current value
|
||||
setModelValue();
|
||||
|
||||
// Method required by the valPropertyValidator directive (returns true if the property editor has at least one tag selected)
|
||||
$scope.validateMandatory = function () {
|
||||
return {
|
||||
isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value.length > 0),
|
||||
errorMsg: "Value cannot be empty",
|
||||
errorKey: "required"
|
||||
};
|
||||
}
|
||||
|
||||
//Helper method to add a tag on enter or on typeahead select
|
||||
function addTag(tagToAdd) {
|
||||
if (tagToAdd != null && tagToAdd.length > 0) {
|
||||
if ($scope.model.value.indexOf(tagToAdd) < 0) {
|
||||
$scope.model.value.push(tagToAdd);
|
||||
//this is required to re-validate
|
||||
$scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.addTagOnEnter = function (e) {
|
||||
var code = e.keyCode || e.which;
|
||||
if (code == 13) { //Enter keycode
|
||||
if ($element.find('.tags-' + $scope.model.alias).parent().find(".tt-dropdown-menu .tt-cursor").length === 0) {
|
||||
//this is required, otherwise the html form will attempt to submit.
|
||||
e.preventDefault();
|
||||
$scope.addTag();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addTag = function () {
|
||||
//ensure that we're not pressing the enter key whilst selecting a typeahead value from the drop down
|
||||
//we need to use jquery because typeahead duplicates the text box
|
||||
addTag($scope.tagToAdd);
|
||||
$scope.tagToAdd = "";
|
||||
//this clears the value stored in typeahead so it doesn't try to add the text again
|
||||
// https://issues.umbraco.org/issue/U4-4947
|
||||
$typeahead.typeahead('val', '');
|
||||
};
|
||||
|
||||
// Set the visible prompt to -1 to ensure it will not be visible
|
||||
$scope.promptIsVisible = "-1";
|
||||
|
||||
$scope.removeTag = function (tag) {
|
||||
var i = $scope.model.value.indexOf(tag);
|
||||
|
||||
if (i >= 0) {
|
||||
// Make sure to hide the prompt so it does not stay open because another item gets a new number in the array index
|
||||
$scope.promptIsVisible = "-1";
|
||||
|
||||
// Remove the tag from the index
|
||||
$scope.model.value.splice(i, 1);
|
||||
|
||||
//this is required to re-validate
|
||||
$scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.showPrompt = function (idx, tag){
|
||||
|
||||
var i = $scope.model.value.indexOf(tag);
|
||||
|
||||
// Make the prompt visible for the clicked tag only
|
||||
if (i === idx) {
|
||||
$scope.promptIsVisible = i;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.hidePrompt = function(){
|
||||
$scope.promptIsVisible = "-1";
|
||||
}
|
||||
|
||||
//vice versa
|
||||
$scope.model.onValueChanged = function (newVal, oldVal) {
|
||||
//update the display val again if it has changed from the server
|
||||
setModelValue(newVal);
|
||||
};
|
||||
|
||||
//configure the tags data source
|
||||
|
||||
//helper method to format the data for bloodhound
|
||||
function dataTransform(list) {
|
||||
//transform the result to what bloodhound wants
|
||||
var tagList = _.map(list, function (i) {
|
||||
return { value: i.text };
|
||||
});
|
||||
// remove current tags from the list
|
||||
return $.grep(tagList, function (tag) {
|
||||
return ($.inArray(tag.value, $scope.model.value) === -1);
|
||||
});
|
||||
}
|
||||
|
||||
// helper method to remove current tags
|
||||
function removeCurrentTagsFromSuggestions(suggestions) {
|
||||
return $.grep(suggestions, function (suggestion) {
|
||||
return ($.inArray(suggestion.value, $scope.model.value) === -1);
|
||||
});
|
||||
}
|
||||
|
||||
var tagsHound = new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
dupDetector : function(remoteMatch, localMatch) {
|
||||
return (remoteMatch["value"] == localMatch["value"]);
|
||||
},
|
||||
//pre-fetch the tags for this category
|
||||
prefetch: {
|
||||
url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]),
|
||||
//TTL = 5 minutes
|
||||
ttl: 300000,
|
||||
filter: dataTransform
|
||||
},
|
||||
//dynamically get the tags for this category (they may have changed on the server)
|
||||
remote: {
|
||||
url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]),
|
||||
filter: dataTransform
|
||||
}
|
||||
});
|
||||
|
||||
tagsHound.initialize(true);
|
||||
|
||||
//configure the type ahead
|
||||
$timeout(function () {
|
||||
|
||||
$typeahead = $element.find('.tags-' + $scope.model.alias).typeahead(
|
||||
{
|
||||
//This causes some strangeness as it duplicates the textbox, best leave off for now.
|
||||
hint: false,
|
||||
highlight: true,
|
||||
cacheKey: new Date(), // Force a cache refresh each time the control is initialized
|
||||
minLength: 1
|
||||
}, {
|
||||
//see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options
|
||||
// name = the data set name, we'll make this the tag group name
|
||||
name: $scope.model.config.group,
|
||||
displayKey: "value",
|
||||
source: function (query, cb) {
|
||||
tagsHound.get(query, function (suggestions) {
|
||||
cb(removeCurrentTagsFromSuggestions(suggestions));
|
||||
});
|
||||
}
|
||||
}).bind("typeahead:selected", function (obj, datum, name) {
|
||||
angularHelper.safeApply($scope, function () {
|
||||
addTag(datum["value"]);
|
||||
$scope.tagToAdd = "";
|
||||
// clear the typed text
|
||||
$typeahead.typeahead('val', '');
|
||||
});
|
||||
|
||||
}).bind("typeahead:autocompleted", function (obj, datum, name) {
|
||||
angularHelper.safeApply($scope, function () {
|
||||
addTag(datum["value"]);
|
||||
$scope.tagToAdd = "";
|
||||
});
|
||||
|
||||
}).bind("typeahead:opened", function (obj) {
|
||||
//console.log("opened ");
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
tagsHound.clearPrefetchCache();
|
||||
tagsHound.clearRemoteCache();
|
||||
$element.find('.tags-' + $scope.model.alias).typeahead('destroy');
|
||||
tagsHound = null;
|
||||
});
|
||||
|
||||
});
|
||||
$scope.valueChanged = function(value) {
|
||||
$scope.model.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,35 +1,10 @@
|
||||
<div ng-controller="Umbraco.PropertyEditors.TagsController" class="umb-property-editor umb-tags">
|
||||
|
||||
<div ng-if="isLoading">
|
||||
<localize key="loading">Loading</localize>...
|
||||
</div>
|
||||
|
||||
<div ng-if="!isLoading">
|
||||
|
||||
<input type="hidden" name="tagCount" ng-model="model.value.length" val-property-validator="validateMandatory" />
|
||||
|
||||
<span ng-repeat="tag in model.value track by $index" class="label label-primary tag">
|
||||
<span ng-bind-html="tag"></span>
|
||||
|
||||
<i class="icon-trash" ng-click="$parent.showPrompt($index, tag)" localize="title" title="@buttons_deleteTag"></i>
|
||||
|
||||
<umb-confirm-action
|
||||
ng-if="$parent.promptIsVisible === $index"
|
||||
direction="left"
|
||||
on-confirm="$parent.removeTag(tag)"
|
||||
on-cancel="$parent.hidePrompt()">
|
||||
</umb-confirm-action>
|
||||
</span>
|
||||
|
||||
<input type="text"
|
||||
id="{{model.alias}}"
|
||||
class="typeahead tags-{{model.alias}}"
|
||||
ng-model="$parent.tagToAdd"
|
||||
ng-keydown="$parent.addTagOnEnter($event)"
|
||||
ng-blur="$parent.addTag()"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_enterTags" />
|
||||
|
||||
</div>
|
||||
<umb-tags-editor value="model.value"
|
||||
config="model.config"
|
||||
validation="model.validation"
|
||||
on-value-changed="valueChanged(value)"
|
||||
culture="model.culture">
|
||||
</umb-tags-editor>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -254,7 +254,7 @@ namespace Umbraco.Web.Editors
|
||||
},
|
||||
{
|
||||
"tagsDataBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TagsDataController>(
|
||||
controller => controller.GetTags(""))
|
||||
controller => controller.GetTags("", ""))
|
||||
},
|
||||
{
|
||||
"examineMgmtBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ExamineManagementController>(
|
||||
|
||||
@@ -1768,7 +1768,8 @@ namespace Umbraco.Web.Editors
|
||||
contentSave,
|
||||
propertyCollection,
|
||||
(save, property) => Varies(property) ? property.GetValue(variant.Culture) : property.GetValue(), //get prop val
|
||||
(save, property, v) => { if (Varies(property)) property.SetValue(v, variant.Culture); else property.SetValue(v); }); //set prop val
|
||||
(save, property, v) => { if (Varies(property)) property.SetValue(v, variant.Culture); else property.SetValue(v); }, //set prop val
|
||||
variant.Culture);
|
||||
|
||||
variantIndex++;
|
||||
}
|
||||
|
||||
@@ -39,17 +39,12 @@ namespace Umbraco.Web.Editors
|
||||
/// <summary>
|
||||
/// Maps the dto property values to the persisted model
|
||||
/// </summary>
|
||||
/// <typeparam name="TPersisted"></typeparam>
|
||||
/// <typeparam name="TSaved"></typeparam>
|
||||
/// <param name="contentItem"></param>
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="getPropertyValue"></param>
|
||||
/// <param name="savePropertyValue"></param>
|
||||
internal void MapPropertyValuesForPersistence<TPersisted, TSaved>(
|
||||
TSaved contentItem,
|
||||
ContentPropertyCollectionDto dto,
|
||||
Func<TSaved, Property, object> getPropertyValue,
|
||||
Action<TSaved, Property, object> savePropertyValue)
|
||||
Action<TSaved, Property, object> savePropertyValue,
|
||||
string culture)
|
||||
where TPersisted : IContentBase
|
||||
where TSaved : IContentSave<TPersisted>
|
||||
{
|
||||
@@ -70,7 +65,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
// get the property
|
||||
var property = contentItem.PersistedContent.Properties[propertyDto.Alias];
|
||||
|
||||
|
||||
// prepare files, if any matching property and culture
|
||||
var files = contentItem.UploadedFiles
|
||||
.Where(x => x.PropertyAlias == propertyDto.Alias && x.Culture == propertyDto.Culture)
|
||||
@@ -96,8 +91,8 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
var tagConfiguration = ConfigurationEditor.ConfigurationAs<TagConfiguration>(propertyDto.DataType.Configuration);
|
||||
if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = tagAttribute.Delimiter;
|
||||
//fixme how is this supposed to work with variants?
|
||||
property.SetTagsValue(value, tagConfiguration);
|
||||
var tagCulture = property.PropertyType.VariesByCulture() ? culture : null;
|
||||
property.SetTagsValue(value, tagConfiguration, tagCulture);
|
||||
}
|
||||
else
|
||||
savePropertyValue(contentItem, property, value);
|
||||
|
||||
@@ -483,8 +483,9 @@ namespace Umbraco.Web.Editors
|
||||
MapPropertyValuesForPersistence<IMedia, MediaItemSave>(
|
||||
contentItem,
|
||||
contentItem.PropertyCollectionDto,
|
||||
(save, property) => property.GetValue(), //get prop val
|
||||
(save, property, v) => property.SetValue(v)); //set prop val
|
||||
(save, property) => property.GetValue(), //get prop val
|
||||
(save, property, v) => property.SetValue(v), //set prop val
|
||||
null); // media are all invariant
|
||||
|
||||
//We need to manually check the validation results here because:
|
||||
// * We still need to save the entity even if there are validation value errors
|
||||
|
||||
@@ -400,8 +400,9 @@ namespace Umbraco.Web.Editors
|
||||
base.MapPropertyValuesForPersistence<IMember, MemberSave>(
|
||||
contentItem,
|
||||
contentItem.PropertyCollectionDto,
|
||||
(save, property) => property.GetValue(), //get prop val
|
||||
(save, property, v) => property.SetValue(v)); //set prop val
|
||||
(save, property) => property.GetValue(), //get prop val
|
||||
(save, property, v) => property.SetValue(v), //set prop val
|
||||
null); // member are all invariant
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web.Models;
|
||||
|
||||
@@ -8,76 +7,53 @@ namespace Umbraco.Web
|
||||
public interface ITagQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all content that is tagged with the specified tag value and optional tag group
|
||||
/// Gets all documents tagged with the specified tag.
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IPublishedContent> GetContentByTag(string tag, string tagGroup = null);
|
||||
IEnumerable<IPublishedContent> GetContentByTag(string tag, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all content that has been tagged with any tag in the specified group
|
||||
/// Gets all documents tagged with any tag in the specified group.
|
||||
/// </summary>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IPublishedContent> GetContentByTagGroup(string tagGroup);
|
||||
IEnumerable<IPublishedContent> GetContentByTagGroup(string group, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all Media that is tagged with the specified tag value and optional tag group
|
||||
/// Gets all media tagged with the specified tag.
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IPublishedContent> GetMediaByTag(string tag, string tagGroup = null);
|
||||
IEnumerable<IPublishedContent> GetMediaByTag(string tag, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all Media that has been tagged with any tag in the specified group
|
||||
/// Gets all media tagged with any tag in the specified group.
|
||||
/// </summary>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IPublishedContent> GetMediaByTagGroup(string tagGroup);
|
||||
IEnumerable<IPublishedContent> GetMediaByTagGroup(string group, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get every tag stored in the database (with optional group)
|
||||
/// Gets all tags.
|
||||
/// </summary>
|
||||
IEnumerable<TagModel> GetAllTags(string group = null);
|
||||
IEnumerable<TagModel> GetAllTags(string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get all tags for content items (with optional group)
|
||||
/// Gets all document tags.
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<TagModel> GetAllContentTags(string group = null);
|
||||
IEnumerable<TagModel> GetAllContentTags(string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get all tags for media items (with optional group)
|
||||
/// Gets all media tags.
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<TagModel> GetAllMediaTags(string group = null);
|
||||
IEnumerable<TagModel> GetAllMediaTags(string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get all tags for member items (with optional group)
|
||||
/// Gets all member tags.
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<TagModel> GetAllMemberTags(string group = null);
|
||||
IEnumerable<TagModel> GetAllMemberTags(string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tags attached to a property by entity id
|
||||
/// Gets all tags attached to an entity via a property.
|
||||
/// </summary>
|
||||
/// <param name="contentId"></param>
|
||||
/// <param name="propertyTypeAlias"></param>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<TagModel> GetTagsForProperty(int contentId, string propertyTypeAlias, string tagGroup = null);
|
||||
IEnumerable<TagModel> GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null, string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tags attached to an entity (content, media or member) by entity id
|
||||
/// Gets all tags attached to an entity.
|
||||
/// </summary>
|
||||
/// <param name="contentId"></param>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<TagModel> GetTagsForEntity(int contentId, string tagGroup = null);
|
||||
IEnumerable<TagModel> GetTagsForEntity(int contentId, string group = null, string culture = null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
using Semver;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Migrations;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Web.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// This will execute after upgrading to remove any xml cache for media that are currently in the bin
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will execute for specific versions -
|
||||
///
|
||||
/// * If current is less than or equal to 7.0.0
|
||||
/// </remarks>
|
||||
public class ClearMediaXmlCacheForDeletedItemsAfterUpgrade : IPostMigration
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ClearMediaXmlCacheForDeletedItemsAfterUpgrade(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger)
|
||||
{
|
||||
if (name != Constants.System.UmbracoUpgradePlanName) return;
|
||||
|
||||
var target70 = new SemVersion(7 /*, 0, 0*/);
|
||||
|
||||
if (originVersion <= target70)
|
||||
{
|
||||
//This query is structured to work with MySql, SQLCE and SqlServer:
|
||||
// http://issues.umbraco.org/issue/U4-3876
|
||||
|
||||
var syntax = scope.SqlContext.SqlSyntax;
|
||||
|
||||
var sql = @"DELETE FROM cmsContentXml WHERE nodeId IN
|
||||
(SELECT nodeId FROM (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml
|
||||
INNER JOIN umbracoNode ON cmsContentXml.nodeId = umbracoNode.id
|
||||
WHERE nodeObjectType = '" + Constants.ObjectTypes.Media + "' AND " + syntax.GetQuotedColumnName("path") + " LIKE '%-21%') x)";
|
||||
|
||||
var count = scope.Database.Execute(sql);
|
||||
|
||||
_logger.Info<ClearMediaXmlCacheForDeletedItemsAfterUpgrade>("Cleared {Total} items from the media xml cache that were trashed and not meant to be there", count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using System;
|
||||
using NPoco;
|
||||
using Semver;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Migrations;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Web.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the built in list view data types
|
||||
/// </summary>
|
||||
public class EnsureDefaultListViewDataTypesCreated : IPostMigration
|
||||
{
|
||||
public void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger)
|
||||
{
|
||||
if (name != Constants.System.UmbracoUpgradePlanName) return;
|
||||
|
||||
var target720 = new SemVersion(7, 2, 0);
|
||||
|
||||
if (originVersion > target720)
|
||||
return;
|
||||
|
||||
var syntax = scope.SqlContext.SqlSyntax;
|
||||
|
||||
try
|
||||
{
|
||||
//Turn on identity insert if db provider is not mysql
|
||||
if (syntax.SupportsIdentityInsert())
|
||||
scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", syntax.GetQuotedTableName("umbracoNode"))));
|
||||
|
||||
if (scope.Database.Exists<NodeDto>(Constants.DataTypes.DefaultContentListView))
|
||||
{
|
||||
//If this already exists then just exit
|
||||
return;
|
||||
}
|
||||
|
||||
scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
}
|
||||
finally
|
||||
{
|
||||
//Turn off identity insert if db provider is not mysql
|
||||
if (syntax.SupportsIdentityInsert())
|
||||
scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF;", syntax.GetQuotedTableName("umbracoNode"))));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//Turn on identity insert if db provider is not mysql
|
||||
if (syntax.SupportsIdentityInsert())
|
||||
scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", syntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DataType))));
|
||||
|
||||
const string memberListViewConfiguration = "{\"pageSize\":10,\"orderBy\":Name,\"orderDirection\":asc,includeProperties:[{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]}";
|
||||
|
||||
scope.Database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultContentListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar" });
|
||||
scope.Database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultMediaListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar" });
|
||||
scope.Database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultMembersListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", Configuration = memberListViewConfiguration });
|
||||
}
|
||||
finally
|
||||
{
|
||||
//Turn off identity insert if db provider is not mysql
|
||||
if (syntax.SupportsIdentityInsert())
|
||||
scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF;", syntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DataType))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using System.IO;
|
||||
using Semver;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Migrations;
|
||||
using Umbraco.Core.Migrations.Upgrade;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Web.Migrations
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// When upgrading version 7.3 the migration MigrateStylesheetDataToFile will execute but we don't want to overwrite the developers
|
||||
/// files during the migration since other parts of the migration might fail. So once the migration is complete, we'll then copy over the temp
|
||||
/// files that this migration created over top of the developer's files. We'll also create a backup of their files.
|
||||
/// </summary>
|
||||
public sealed class OverwriteStylesheetFilesFromTempFiles : IPostMigration
|
||||
{
|
||||
public void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger)
|
||||
{
|
||||
if (name != Constants.System.UmbracoUpgradePlanName) return;
|
||||
|
||||
var target73 = new SemVersion(7, 3, 0);
|
||||
|
||||
if (originVersion <= target73)
|
||||
{
|
||||
var tempCssFolder = IOHelper.MapPath("~/App_Data/TEMP/CssMigration/");
|
||||
var cssFolder = IOHelper.MapPath("~/css");
|
||||
if (Directory.Exists(tempCssFolder))
|
||||
{
|
||||
var files = Directory.GetFiles(tempCssFolder, "*.css", SearchOption.AllDirectories);
|
||||
foreach (var file in files)
|
||||
{
|
||||
var relativePath = file.TrimStart(tempCssFolder).TrimStart("\\");
|
||||
var cssFilePath = Path.Combine(cssFolder, relativePath);
|
||||
if (File.Exists(cssFilePath))
|
||||
{
|
||||
//backup
|
||||
var targetPath = Path.Combine(tempCssFolder, relativePath.EnsureEndsWith(".bak"));
|
||||
logger.Info<OverwriteStylesheetFilesFromTempFiles>("CSS file is being backed up from {CssFilePath}, to {TargetPath} before being migrated to new format", cssFilePath, targetPath);
|
||||
File.Copy(cssFilePath, targetPath, true);
|
||||
}
|
||||
|
||||
//ensure the sub folder eixts
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cssFilePath));
|
||||
File.Copy(file, cssFilePath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using System;
|
||||
using Semver;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Migrations;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.PublishedCache.XmlPublishedCache;
|
||||
|
||||
namespace Umbraco.Web.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Rebuilds the Xml caches after upgrading.
|
||||
/// This will execute after upgrading to rebuild the xml cache
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This cannot execute as part of a DB migration since it needs access to services and repositories.</para>
|
||||
/// <para>Executes for:
|
||||
/// - Media Xml : if current is less than, or equal to, 7.0.0 (superceeded by the next rule)
|
||||
/// - Media & Content Xml : if current is less than, or equal to, 7.3.0 - because 7.3.0 adds .Key to cached items
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class RebuildXmlCachesAfterUpgrade : IPostMigration
|
||||
{
|
||||
public void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger)
|
||||
{
|
||||
if (name != Constants.System.UmbracoUpgradePlanName) return;
|
||||
|
||||
var v730 = new SemVersion(new Version(7, 3, 0));
|
||||
|
||||
var doMedia = originVersion < v730;
|
||||
var doContent = originVersion < v730;
|
||||
|
||||
if (doMedia)
|
||||
{
|
||||
// fixme - maintain - for backward compatibility?! or replace with...?!
|
||||
//var mediaService = (MediaService) ApplicationContext.Current.Services.MediaService;
|
||||
//mediaService.RebuildXmlStructures();
|
||||
|
||||
var svc = Current.PublishedSnapshotService as PublishedSnapshotService;
|
||||
svc?.RebuildMediaXml();
|
||||
|
||||
// note: not re-indexing medias?
|
||||
}
|
||||
|
||||
if (doContent)
|
||||
{
|
||||
// fixme - maintain - for backward compatibility?! or replace with...?!
|
||||
//var contentService = (ContentService) ApplicationContext.Current.Services.ContentService;
|
||||
//contentService.RebuildXmlStructures();
|
||||
|
||||
var svc = Current.PublishedSnapshotService as PublishedSnapshotService;
|
||||
svc?.RebuildContentAndPreviewXml();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,10 @@ namespace Umbraco.Web.PropertyEditors
|
||||
[PluginController("UmbracoApi")]
|
||||
public class TagsDataController : UmbracoAuthorizedApiController
|
||||
{
|
||||
public IEnumerable<TagModel> GetTags(string tagGroup)
|
||||
public IEnumerable<TagModel> GetTags(string tagGroup, string culture)
|
||||
{
|
||||
return Umbraco.TagQuery.GetAllTags(tagGroup);
|
||||
if (culture == string.Empty) culture = null;
|
||||
return Umbraco.TagQuery.GetAllTags(tagGroup, culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace Umbraco.Web
|
||||
/// </summary>
|
||||
public class PublishedContentQuery : IPublishedContentQuery
|
||||
{
|
||||
private readonly IPublishedContentQuery _query;
|
||||
private readonly IPublishedContentCache _contentCache;
|
||||
private readonly IPublishedMediaCache _mediaCache;
|
||||
|
||||
@@ -37,79 +36,52 @@ namespace Umbraco.Web
|
||||
_mediaCache = mediaCache ?? throw new ArgumentNullException(nameof(mediaCache));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor used to wrap the ITypedPublishedContentQuery object passed in
|
||||
/// </summary>
|
||||
/// <param name="query"></param>
|
||||
public PublishedContentQuery(IPublishedContentQuery query)
|
||||
{
|
||||
_query = query ?? throw new ArgumentNullException(nameof(query));
|
||||
}
|
||||
|
||||
#region Content
|
||||
|
||||
public IPublishedContent Content(int id)
|
||||
{
|
||||
return _query == null
|
||||
? ItemById(id, _contentCache)
|
||||
: _query.Content(id);
|
||||
return ItemById(id, _contentCache);
|
||||
}
|
||||
|
||||
public IPublishedContent Content(Guid id)
|
||||
{
|
||||
return _query == null
|
||||
? ItemById(id, _contentCache)
|
||||
: _query.Content(id);
|
||||
return ItemById(id, _contentCache);
|
||||
}
|
||||
|
||||
public IPublishedContent Content(Udi id)
|
||||
{
|
||||
if (!(id is GuidUdi udi)) return null;
|
||||
return _query == null
|
||||
? ItemById(udi.Guid, _contentCache)
|
||||
: _query.Content(udi.Guid);
|
||||
return ItemById(udi.Guid, _contentCache);
|
||||
}
|
||||
|
||||
public IPublishedContent ContentSingleAtXPath(string xpath, params XPathVariable[] vars)
|
||||
{
|
||||
return _query == null
|
||||
? ItemByXPath(xpath, vars, _contentCache)
|
||||
: _query.ContentSingleAtXPath(xpath, vars);
|
||||
return ItemByXPath(xpath, vars, _contentCache);
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> Content(IEnumerable<int> ids)
|
||||
{
|
||||
return _query == null
|
||||
? ItemsByIds(_contentCache, ids)
|
||||
: _query.Content(ids);
|
||||
return ItemsByIds(_contentCache, ids);
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> Content(IEnumerable<Guid> ids)
|
||||
{
|
||||
return _query == null
|
||||
? ItemsByIds(_contentCache, ids)
|
||||
: _query.Content(ids);
|
||||
return ItemsByIds(_contentCache, ids);
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> ContentAtXPath(string xpath, params XPathVariable[] vars)
|
||||
{
|
||||
return _query == null
|
||||
? ItemsByXPath(xpath, vars, _contentCache)
|
||||
: _query.ContentAtXPath(xpath, vars);
|
||||
return ItemsByXPath(xpath, vars, _contentCache);
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars)
|
||||
{
|
||||
return _query == null
|
||||
? ItemsByXPath(xpath, vars, _contentCache)
|
||||
: _query.ContentAtXPath(xpath, vars);
|
||||
return ItemsByXPath(xpath, vars, _contentCache);
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> ContentAtRoot()
|
||||
{
|
||||
return _query == null
|
||||
? ItemsAtRoot(_contentCache)
|
||||
: _query.ContentAtRoot();
|
||||
return ItemsAtRoot(_contentCache);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -118,45 +90,33 @@ namespace Umbraco.Web
|
||||
|
||||
public IPublishedContent Media(int id)
|
||||
{
|
||||
return _query == null
|
||||
? ItemById(id, _mediaCache)
|
||||
: _query.Media(id);
|
||||
return ItemById(id, _mediaCache);
|
||||
}
|
||||
|
||||
public IPublishedContent Media(Guid id)
|
||||
{
|
||||
return _query == null
|
||||
? ItemById(id, _mediaCache)
|
||||
: _query.Media(id);
|
||||
return ItemById(id, _mediaCache);
|
||||
}
|
||||
|
||||
public IPublishedContent Media(Udi id)
|
||||
{
|
||||
if (!(id is GuidUdi udi)) return null;
|
||||
return _query == null
|
||||
? ItemById(udi.Guid, _mediaCache)
|
||||
: _query.Media(udi.Guid);
|
||||
return ItemById(udi.Guid, _mediaCache);
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> Media(IEnumerable<int> ids)
|
||||
{
|
||||
return _query == null
|
||||
? ItemsByIds(_mediaCache, ids)
|
||||
: _query.Media(ids);
|
||||
return ItemsByIds(_mediaCache, ids);
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> Media(IEnumerable<Guid> ids)
|
||||
{
|
||||
return _query == null
|
||||
? ItemsByIds(_mediaCache, ids)
|
||||
: _query.Media(ids);
|
||||
return ItemsByIds(_mediaCache, ids);
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> MediaAtRoot()
|
||||
{
|
||||
return _query == null
|
||||
? ItemsAtRoot(_mediaCache)
|
||||
: _query.MediaAtRoot();
|
||||
return ItemsAtRoot(_mediaCache);
|
||||
}
|
||||
|
||||
|
||||
@@ -231,8 +191,6 @@ namespace Umbraco.Web
|
||||
{
|
||||
//fixme: inject IExamineManager
|
||||
|
||||
if (_query != null) return _query.Search(skip, take, out totalRecords, term, useWildCards, indexName);
|
||||
|
||||
indexName = string.IsNullOrEmpty(indexName)
|
||||
? Constants.UmbracoIndexes.ExternalIndexName
|
||||
: indexName;
|
||||
@@ -259,8 +217,6 @@ namespace Umbraco.Web
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PublishedSearchResult> Search(int skip, int take, out long totalRecords, ISearchCriteria criteria, ISearcher searcher = null)
|
||||
{
|
||||
if (_query != null) return _query.Search(skip, take, out totalRecords, criteria, searcher);
|
||||
|
||||
//fixme: inject IExamineManager
|
||||
if (searcher == null)
|
||||
{
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Umbraco.Web.Routing
|
||||
/// </remarks>
|
||||
public string GetUrl(IPublishedContent content, bool absolute, string culture = null, Uri current = null)
|
||||
=> GetUrl(content, GetMode(absolute), culture, current);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url of a published content.
|
||||
/// </summary>
|
||||
@@ -196,10 +196,6 @@ namespace Umbraco.Web.Routing
|
||||
if (culture == null)
|
||||
culture = _variationContextAccessor?.VariationContext?.Culture ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
culture = null;
|
||||
}
|
||||
|
||||
if (current == null)
|
||||
current = _umbracoContext.CleanedUmbracoUrl;
|
||||
|
||||
@@ -222,23 +222,7 @@ namespace Umbraco.Web.Search
|
||||
if (_isConfigured) return;
|
||||
|
||||
_isConfigured = true;
|
||||
|
||||
foreach (var luceneIndexer in examineManager.Indexes.OfType<LuceneIndex>())
|
||||
{
|
||||
//We now need to disable waiting for indexing for Examine so that the appdomain is shutdown immediately and doesn't wait for pending
|
||||
//indexing operations. We used to wait for indexing operations to complete but this can cause more problems than that is worth because
|
||||
//that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems.
|
||||
luceneIndexer.WaitForIndexQueueOnShutdown = false;
|
||||
|
||||
//we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that
|
||||
//the indexes are not operational unless MainDom is true
|
||||
var dir = luceneIndexer.GetLuceneDirectory();
|
||||
if (IndexWriter.IsLocked(dir))
|
||||
{
|
||||
logger.Info<ExamineComponent>("Forcing index {IndexerName} to be unlocked since it was left in a locked state", luceneIndexer.Name);
|
||||
IndexWriter.Unlock(dir);
|
||||
}
|
||||
}
|
||||
examineManager.UnlockLuceneIndexes(logger);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-137
@@ -9,195 +9,92 @@ using Umbraco.Web.Models;
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that exposes methods used to query tag data in views
|
||||
/// Implements <see cref="ITagQuery"/>.
|
||||
/// </summary>
|
||||
public class TagQuery : ITagQuery
|
||||
{
|
||||
|
||||
//TODO: This class also acts as a wrapper for ITagQuery due to breaking changes, need to fix in
|
||||
// version 8: http://issues.umbraco.org/issue/U4-6899
|
||||
private readonly ITagQuery _wrappedQuery;
|
||||
|
||||
private readonly ITagService _tagService;
|
||||
private readonly IPublishedContentQuery _contentQuery;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for wrapping ITagQuery, see http://issues.umbraco.org/issue/U4-6899
|
||||
/// Initializes a new instance of the <see cref="TagQuery"/> class.
|
||||
/// </summary>
|
||||
/// <param name="wrappedQuery"></param>
|
||||
internal TagQuery(ITagQuery wrappedQuery)
|
||||
{
|
||||
if (wrappedQuery == null) throw new ArgumentNullException("wrappedQuery");
|
||||
_wrappedQuery = wrappedQuery;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="tagService"></param>
|
||||
/// <param name="contentQuery"></param>
|
||||
public TagQuery(ITagService tagService, IPublishedContentQuery contentQuery)
|
||||
{
|
||||
if (tagService == null) throw new ArgumentNullException("tagService");
|
||||
if (contentQuery == null) throw new ArgumentNullException("contentQuery");
|
||||
_tagService = tagService;
|
||||
_contentQuery = contentQuery;
|
||||
_tagService = tagService ?? throw new ArgumentNullException(nameof(tagService));
|
||||
_contentQuery = contentQuery ?? throw new ArgumentNullException(nameof(contentQuery));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all content that is tagged with the specified tag value and optional tag group
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IPublishedContent> GetContentByTag(string tag, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IPublishedContent> GetContentByTag(string tag, string group = null, string culture = null)
|
||||
{
|
||||
//TODO: http://issues.umbraco.org/issue/U4-6899
|
||||
if (_wrappedQuery != null) return _wrappedQuery.GetContentByTag(tag, tagGroup);
|
||||
|
||||
var ids = _tagService.GetTaggedContentByTag(tag, tagGroup)
|
||||
var ids = _tagService.GetTaggedContentByTag(tag, group, culture)
|
||||
.Select(x => x.EntityId);
|
||||
return _contentQuery.Content(ids)
|
||||
.Where(x => x != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all content that has been tagged with any tag in the specified group
|
||||
/// </summary>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IPublishedContent> GetContentByTagGroup(string tagGroup)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IPublishedContent> GetContentByTagGroup(string group, string culture = null)
|
||||
{
|
||||
//TODO: http://issues.umbraco.org/issue/U4-6899
|
||||
if (_wrappedQuery != null) return _wrappedQuery.GetContentByTagGroup(tagGroup);
|
||||
|
||||
var ids = _tagService.GetTaggedContentByTagGroup(tagGroup)
|
||||
var ids = _tagService.GetTaggedContentByTagGroup(group, culture)
|
||||
.Select(x => x.EntityId);
|
||||
return _contentQuery.Content(ids)
|
||||
.Where(x => x != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all Media that is tagged with the specified tag value and optional tag group
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IPublishedContent> GetMediaByTag(string tag, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IPublishedContent> GetMediaByTag(string tag, string group = null, string culture = null)
|
||||
{
|
||||
//TODO: http://issues.umbraco.org/issue/U4-6899
|
||||
if (_wrappedQuery != null) return _wrappedQuery.GetMediaByTag(tag, tagGroup);
|
||||
|
||||
var ids = _tagService.GetTaggedMediaByTag(tag, tagGroup)
|
||||
var ids = _tagService.GetTaggedMediaByTag(tag, group, culture)
|
||||
.Select(x => x.EntityId);
|
||||
return _contentQuery.Media(ids)
|
||||
.Where(x => x != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all Media that has been tagged with any tag in the specified group
|
||||
/// </summary>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IPublishedContent> GetMediaByTagGroup(string tagGroup)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IPublishedContent> GetMediaByTagGroup(string group, string culture = null)
|
||||
{
|
||||
//TODO: http://issues.umbraco.org/issue/U4-6899
|
||||
if (_wrappedQuery != null) return _wrappedQuery.GetMediaByTagGroup(tagGroup);
|
||||
|
||||
var ids = _tagService.GetTaggedMediaByTagGroup(tagGroup)
|
||||
var ids = _tagService.GetTaggedMediaByTagGroup(group, culture)
|
||||
.Select(x => x.EntityId);
|
||||
return _contentQuery.Media(ids)
|
||||
.Where(x => x != null);
|
||||
}
|
||||
|
||||
//TODO: Should prob implement these, requires a bit of work on the member service to do this,
|
||||
// also not sure if its necessary ?
|
||||
//public IEnumerable<IPublishedContent> GetMembersByTag(string tag, string tagGroup = null)
|
||||
//{
|
||||
//}
|
||||
|
||||
//public IEnumerable<IPublishedContent> GetMembersByTagGroup(string tagGroup)
|
||||
//{
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Get every tag stored in the database (with optional group)
|
||||
/// </summary>
|
||||
public IEnumerable<TagModel> GetAllTags(string group = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TagModel> GetAllTags(string group = null, string culture = null)
|
||||
{
|
||||
//TODO: http://issues.umbraco.org/issue/U4-6899
|
||||
if (_wrappedQuery != null) return _wrappedQuery.GetAllTags(group);
|
||||
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetAllTags(group));
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetAllTags(group, culture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all tags for content items (with optional group)
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<TagModel> GetAllContentTags(string group = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TagModel> GetAllContentTags(string group = null, string culture = null)
|
||||
{
|
||||
//TODO: http://issues.umbraco.org/issue/U4-6899
|
||||
if (_wrappedQuery != null) return _wrappedQuery.GetAllContentTags(group);
|
||||
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetAllContentTags(group));
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetAllContentTags(group, culture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all tags for media items (with optional group)
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<TagModel> GetAllMediaTags(string group = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TagModel> GetAllMediaTags(string group = null, string culture = null)
|
||||
{
|
||||
//TODO: http://issues.umbraco.org/issue/U4-6899
|
||||
if (_wrappedQuery != null) return _wrappedQuery.GetAllMediaTags(group);
|
||||
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetAllMediaTags(group));
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetAllMediaTags(group, culture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all tags for member items (with optional group)
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<TagModel> GetAllMemberTags(string group = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TagModel> GetAllMemberTags(string group = null, string culture = null)
|
||||
{
|
||||
//TODO: http://issues.umbraco.org/issue/U4-6899
|
||||
if (_wrappedQuery != null) return _wrappedQuery.GetAllMemberTags(group);
|
||||
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetAllMemberTags(group));
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetAllMemberTags(group, culture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tags attached to a property by entity id
|
||||
/// </summary>
|
||||
/// <param name="contentId"></param>
|
||||
/// <param name="propertyTypeAlias"></param>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<TagModel> GetTagsForProperty(int contentId, string propertyTypeAlias, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TagModel> GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null, string culture = null)
|
||||
{
|
||||
//TODO: http://issues.umbraco.org/issue/U4-6899
|
||||
if (_wrappedQuery != null) return _wrappedQuery.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup);
|
||||
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup));
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetTagsForProperty(contentId, propertyTypeAlias, group, culture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tags attached to an entity (content, media or member) by entity id
|
||||
/// </summary>
|
||||
/// <param name="contentId"></param>
|
||||
/// <param name="tagGroup"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<TagModel> GetTagsForEntity(int contentId, string tagGroup = null)
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TagModel> GetTagsForEntity(int contentId, string group = null, string culture = null)
|
||||
{
|
||||
//TODO: http://issues.umbraco.org/issue/U4-6899
|
||||
if (_wrappedQuery != null) return _wrappedQuery.GetTagsForEntity(contentId, tagGroup);
|
||||
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetTagsForEntity(contentId, tagGroup));
|
||||
return Mapper.Map<IEnumerable<TagModel>>(_tagService.GetTagsForEntity(contentId, group, culture));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -835,9 +835,6 @@
|
||||
<Compile Include="Security\UmbracoBackOfficeCookieAuthOptions.cs" />
|
||||
<Compile Include="Scheduling\TaskAndFactoryExtensions.cs" />
|
||||
<Compile Include="Migrations\ClearCsrfCookiesAfterUpgrade.cs" />
|
||||
<Compile Include="Migrations\ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs" />
|
||||
<Compile Include="Migrations\EnsureListViewDataTypeIsCreated.cs" />
|
||||
<Compile Include="Migrations\OverwriteStylesheetFilesFromTempFiles.cs" />
|
||||
<Compile Include="Components\NotificationsComponent.cs" />
|
||||
<Compile Include="TagQuery.cs" />
|
||||
<Compile Include="Trees\CoreTreeAttribute.cs" />
|
||||
@@ -1005,7 +1002,6 @@
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\JavaScript\ServerVariablesParser.cs" />
|
||||
<Compile Include="Migrations\RebuildXmlCachesAfterUpgrade.cs" />
|
||||
<Compile Include="Components\PublicAccessComponent.cs" />
|
||||
<Compile Include="UI\Bundles\JsJQueryCore.cs" />
|
||||
<Compile Include="UI\Bundles\JsUmbracoApplicationUI.cs" />
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Umbraco.Web
|
||||
private readonly ServiceContext _services;
|
||||
|
||||
private IUmbracoComponentRenderer _componentRenderer;
|
||||
private PublishedContentQuery _query;
|
||||
private IPublishedContentQuery _query;
|
||||
private MembershipHelper _membershipHelper;
|
||||
private ITagQuery _tag;
|
||||
private ICultureDictionary _cultureDictionary;
|
||||
|
||||
Reference in New Issue
Block a user