From b6c62af0159e74b9c2859b8dd751440bd249661e Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 26 Nov 2015 17:14:22 +0100 Subject: [PATCH 001/413] fixes upgrade script --- .../AddUniqueIdPropertyTypeGroupColumn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs index bb09faaa7f..8a50f6fa3d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs @@ -69,7 +69,7 @@ ON cmsContentType.nodeId = umbracoNode.id")) } // set the Unique Id to the one we've generated - Update.Table("cmsPropertyTypeGroup").Set(new { uniqueID = guid }).Where(new { id = data.ptId }); + Update.Table("cmsPropertyTypeGroup").Set(new { uniqueID = guid }).Where(new { id = data.grId }); } } } From 5772bcc7f253052c64a673aa5aa2f4089f51a2cc Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 30 Nov 2015 16:39:27 +0100 Subject: [PATCH 002/413] Implement GetXxxFileStream for Deploy --- .../Interfaces/ITemplateRepository.cs | 8 ++++++++ .../Repositories/TemplateRepository.cs | 19 +++++++++++++++++++ src/Umbraco.Core/Services/FileService.cs | 8 ++++++++ src/Umbraco.Core/Services/IFileService.cs | 8 ++++++++ src/Umbraco.Core/Services/IMediaService.cs | 8 ++++++++ src/Umbraco.Core/Services/MediaService.cs | 8 ++++++++ 6 files changed, 59 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs index 14957ac49f..ad6939f847 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories @@ -54,5 +55,12 @@ namespace Umbraco.Core.Persistence.Repositories /// to validate /// True if Script is valid, otherwise false bool ValidateTemplate(ITemplate template); + + /// + /// Gets the content of a template as a stream. + /// + /// The filesystem path to the template. + /// The content of the template. + Stream GetFileStream(string path); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 0a0dbe37dd..7213dfc92a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -475,6 +475,25 @@ namespace Umbraco.Core.Persistence.Repositories } } + public Stream GetFileStream(string filename) + { + var ext = Path.GetExtension(filename); + IFileSystem fs; + switch (ext) + { + case ".cshtml": + case ".vbhtml": + fs = _viewsFileSystem; + break; + case ".master": + fs = _masterpagesFileSystem; + break; + default: + throw new Exception("Unsupported extension " + ext + "."); + } + return fs.OpenFile(filename); + } + #region Implementation of ITemplateRepository public ITemplate Get(string alias) diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index c3d6c3f2de..17ba21802e 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -515,6 +515,14 @@ namespace Umbraco.Core.Services } } + public Stream GetTemplateFileStream(string path) + { + using (var repository = _repositoryFactory.CreateTemplateRepository(_dataUowProvider.GetUnitOfWork())) + { + return repository.GetFileStream(path); + } + } + #endregion #region Partial Views diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index 18318de1db..45aa8d03dc 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using Umbraco.Core.Models; namespace Umbraco.Core.Services @@ -224,5 +225,12 @@ namespace Umbraco.Core.Services /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. /// RenderingEngine DetermineTemplateRenderingEngine(ITemplate template); + + /// + /// Gets the content of a template as a stream. + /// + /// The filesystem path to the template. + /// The content of the template. + Stream GetTemplateFileStream(string path); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index d32acdd6b1..863c5728b3 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.IO; using Umbraco.Core.Models; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -369,5 +370,12 @@ namespace Umbraco.Core.Services /// Optional id of the user creating the media item /// IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0); + + /// + /// Gets the content of a media as a stream. + /// + /// The filesystem path to the media. + /// The content of the media. + Stream GetMediaFileStream(string path); } } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 3bc97c60bc..f7a88b7ac6 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; +using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading; @@ -9,6 +10,7 @@ using System.Xml.Linq; using Umbraco.Core.Auditing; using Umbraco.Core.Configuration; using Umbraco.Core.Events; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; @@ -1248,6 +1250,12 @@ namespace Umbraco.Core.Services } } + public Stream GetMediaFileStream(string path) + { + var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + return fs.OpenFile(path); + } + #region Event Handlers /// From e68dda81d2f002d6affd97e944f927a996821b10 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 8 Dec 2015 12:53:11 +0100 Subject: [PATCH 003/413] Try to make sense of files and medias for Deploy --- src/Umbraco.Core/IO/FileSystemExtensions.cs | 16 +- src/Umbraco.Core/IO/FileSystemWrapper.cs | 4 +- src/Umbraco.Core/IO/IFileSystem.cs | 3 +- src/Umbraco.Core/IO/MediaFileSystem.cs | 31 +- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 168 +++++-- src/Umbraco.Core/IO/ResizedImage.cs | 20 - src/Umbraco.Core/IO/UmbracoMediaFile.cs | 98 ++--- src/Umbraco.Core/Media/ImageHelper.cs | 410 +++++++++++------- src/Umbraco.Core/Media/MediaHelper.cs | 286 ++++++++++++ .../Media/MediaSubfolderCounter.cs | 58 --- .../Media/UploadAutoFillProperties.cs | 200 +++++++++ src/Umbraco.Core/Models/ContentExtensions.cs | 164 +++---- src/Umbraco.Core/Models/File.cs | 15 + src/Umbraco.Core/Models/IFile.cs | 8 + .../Interfaces/ITemplateRepository.cs | 11 +- .../Repositories/TemplateRepository.cs | 96 ++-- src/Umbraco.Core/Services/FileService.cs | 12 +- src/Umbraco.Core/Services/IFileService.cs | 11 +- src/Umbraco.Core/Services/IMediaService.cs | 25 +- src/Umbraco.Core/Services/MediaService.cs | 24 +- src/Umbraco.Core/Umbraco.Core.csproj | 4 +- .../Editors/ContentControllerBase.cs | 6 + src/Umbraco.Web/Editors/ImagesController.cs | 77 +--- src/Umbraco.Web/MediaPropertyExtensions.cs | 106 ----- .../FileUploadPropertyEditor.cs | 259 +++++------ .../FileUploadPropertyValueEditor.cs | 222 ++++------ .../ImageCropperPropertyEditor.cs | 401 ++++++++--------- .../ImageCropperPropertyValueEditor.cs | 212 ++++----- src/Umbraco.Web/Umbraco.Web.csproj | 1 - .../businesslogic/datatype/FileHandlerData.cs | 2 +- 30 files changed, 1698 insertions(+), 1252 deletions(-) delete mode 100644 src/Umbraco.Core/IO/ResizedImage.cs create mode 100644 src/Umbraco.Core/Media/MediaHelper.cs delete mode 100644 src/Umbraco.Core/Media/MediaSubfolderCounter.cs create mode 100644 src/Umbraco.Core/Media/UploadAutoFillProperties.cs delete mode 100644 src/Umbraco.Web/MediaPropertyExtensions.cs diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index 64dcfc25a0..b71a4cf0c7 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -39,13 +39,19 @@ namespace Umbraco.Core.IO public static long GetSize(this IFileSystem fs, string path) { + // no idea why GetSize is not part of IFileSystem, but + // for physical file system we have way better & faster ways + // to get the size, than to read the entire thing in memory! + var physical = fs as PhysicalFileSystem; + if (physical != null) + return physical.GetSize(path); + + // other filesystems... bah... using (var file = fs.OpenFile(path)) + using (var sr = new StreamReader(file)) { - using (var sr = new StreamReader(file)) - { - var str = sr.ReadToEnd(); - return str.Length; - } + var str = sr.ReadToEnd(); + return str.Length; } } diff --git a/src/Umbraco.Core/IO/FileSystemWrapper.cs b/src/Umbraco.Core/IO/FileSystemWrapper.cs index ba2ad8f48b..676626d069 100644 --- a/src/Umbraco.Core/IO/FileSystemWrapper.cs +++ b/src/Umbraco.Core/IO/FileSystemWrapper.cs @@ -48,9 +48,9 @@ namespace Umbraco.Core.IO _wrapped.AddFile(path, stream); } - public void AddFile(string path, Stream stream, bool overrideIfExists) + public void AddFile(string path, Stream stream, bool overrideExisting) { - _wrapped.AddFile(path, stream, overrideIfExists); + _wrapped.AddFile(path, stream, overrideExisting); } public IEnumerable GetFiles(string path) diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index 3b38432c5c..b4e173b256 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.IO void AddFile(string path, Stream stream); - void AddFile(string path, Stream stream, bool overrideIfExists); + void AddFile(string path, Stream stream, bool overrideExisting); IEnumerable GetFiles(string path); @@ -31,7 +31,6 @@ namespace Umbraco.Core.IO bool FileExists(string path); - string GetRelativePath(string fullPathOrUrl); string GetFullPath(string path); diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index b35264d752..face234970 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -25,24 +26,31 @@ namespace Umbraco.Core.IO _contentConfig = contentConfig; } - public string GetRelativePath(int propertyId, string fileName) + [Obsolete("This low-level method should NOT exist.")] + public string GetRelativePath(int propertyId, string fileName) { - var seperator = _contentConfig.UploadAllowDirectories + var sep = _contentConfig.UploadAllowDirectories ? Path.DirectorySeparatorChar : '-'; - return propertyId.ToString(CultureInfo.InvariantCulture) + seperator + fileName; + return propertyId.ToString(CultureInfo.InvariantCulture) + sep + fileName; } + [Obsolete("This low-level method should NOT exist.")] public string GetRelativePath(string subfolder, string fileName) { - var seperator = _contentConfig.UploadAllowDirectories + var sep = _contentConfig.UploadAllowDirectories ? Path.DirectorySeparatorChar : '-'; - return subfolder + seperator + fileName; + return subfolder + sep + fileName; } + // what's below is weird + // we are not deleting custom thumbnails + // MediaFileSystem is not just IFileSystem + // etc + public IEnumerable GetThumbnails(string path) { var parentDirectory = Path.GetDirectoryName(path); @@ -68,5 +76,16 @@ namespace Umbraco.Core.IO GetThumbnails(path) .ForEach(DeleteFile); } + + public void CopyThumbnails(string sourcePath, string targetPath) + { + var targetPathBase = Path.GetDirectoryName(targetPath) ?? ""; + foreach (var sourceThumbPath in GetThumbnails(sourcePath)) + { + var sourceThumbFilename = Path.GetFileName(sourceThumbPath) ?? ""; + var targetThumbPath = Path.Combine(targetPathBase, sourceThumbFilename); + this.CopyFile(sourceThumbPath, targetThumbPath); + } + } } } diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 47daff932d..7456fb29a4 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -12,6 +12,9 @@ namespace Umbraco.Core.IO // eg "c:" or "c:\path\to\site" or "\\server\path" private readonly string _rootPath; + // _rootPath, with separators replaced by forward-slashes. + private readonly string _rootPathFwd; + // the ??? url, using url separator chars, NOT ending with a separator // eg "" (?) or "/Scripts" or ??? private readonly string _rootUrl; @@ -25,6 +28,7 @@ namespace Umbraco.Core.IO _rootPath = IOHelper.MapPath(virtualRoot); _rootPath = EnsureDirectorySeparatorChar(_rootPath); _rootPath = _rootPath.TrimEnd(Path.DirectorySeparatorChar); + _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); _rootUrl = IOHelper.ResolveUrl(virtualRoot); _rootUrl = EnsureUrlSeparatorChar(_rootUrl); @@ -48,17 +52,22 @@ namespace Umbraco.Core.IO //var localRoot = AppDomain.CurrentDomain.BaseDirectory; var localRoot = IOHelper.GetRootDirectorySafe(); if (Path.IsPathRooted(rootPath) == false) - { rootPath = Path.Combine(localRoot, rootPath); - } rootPath = EnsureDirectorySeparatorChar(rootPath); - rootUrl = EnsureUrlSeparatorChar(rootUrl); - _rootPath = rootPath.TrimEnd(Path.DirectorySeparatorChar); + _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); + + rootUrl = EnsureUrlSeparatorChar(rootUrl); _rootUrl = rootUrl.TrimEnd('/'); } + /// + /// Gets directories in a directory. + /// + /// The filesystem-relative path to the directory. + /// The filesystem-relative path to the directories in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. public IEnumerable GetDirectories(string path) { var fullPath = GetFullPath(path); @@ -80,11 +89,20 @@ namespace Umbraco.Core.IO return Enumerable.Empty(); } + /// + /// Deletes a directory. + /// + /// The filesystem-relative path of the directory. public void DeleteDirectory(string path) { DeleteDirectory(path, false); } + /// + /// Deletes a directory. + /// + /// The filesystem-relative path of the directory. + /// A value indicating whether to recursively delete sub-directories. public void DeleteDirectory(string path, bool recursive) { var fullPath = GetFullPath(path); @@ -101,38 +119,71 @@ namespace Umbraco.Core.IO } } + /// + /// Gets a value indicating whether a directory exists. + /// + /// The filesystem-relative path of the directory. + /// A value indicating whether a directory exists. public bool DirectoryExists(string path) { var fullPath = GetFullPath(path); return Directory.Exists(fullPath); } + /// + /// Saves a file. + /// + /// The filesystem-relative path of the file. + /// A stream containing the file data. + /// Overrides the existing file, if any. public void AddFile(string path, Stream stream) { AddFile(path, stream, true); } - public void AddFile(string path, Stream stream, bool overrideIfExists) + /// + /// Saves a file. + /// + /// The filesystem-relative path of the file. + /// A stream containing the file data. + /// A value indicating whether to override the existing file, if any. + /// If a file exists and is false, an exception is thrown. + public void AddFile(string path, Stream stream, bool overrideExisting) { var fullPath = GetFullPath(path); var exists = File.Exists(fullPath); - if (exists && overrideIfExists == false) + if (exists && overrideExisting == false) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); // ensure it exists + var directory = Path.GetDirectoryName(fullPath); + if (directory == null) throw new InvalidOperationException("Could not get directory."); + Directory.CreateDirectory(directory); // ensure it exists - if (stream.CanSeek) + if (stream.CanSeek) // fixme - what else? stream.Seek(0, 0); - using (var destination = (Stream)File.Create(fullPath)) + using (var destination = (Stream) File.Create(fullPath)) stream.CopyTo(destination); } + /// + /// Gets files in a directory. + /// + /// The filesystem-relative path of the directory. + /// The filesystem-relative path to the files in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. public IEnumerable GetFiles(string path) { return GetFiles(path, "*.*"); } + /// + /// Gets files in a directory. + /// + /// The filesystem-relative path of the directory. + /// A filter. + /// The filesystem-relative path to the matching files in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. public IEnumerable GetFiles(string path, string filter) { var fullPath = GetFullPath(path); @@ -154,12 +205,21 @@ namespace Umbraco.Core.IO return Enumerable.Empty(); } + /// + /// Opens a file. + /// + /// The filesystem-relative path to the file. + /// public Stream OpenFile(string path) { var fullPath = GetFullPath(path); return File.OpenRead(fullPath); } + /// + /// Deletes a file. + /// + /// The filesystem-relative path to the file. public void DeleteFile(string path) { var fullPath = GetFullPath(path); @@ -176,23 +236,25 @@ namespace Umbraco.Core.IO } } + /// + /// Gets a value indicating whether a file exists. + /// + /// The filesystem-relative path to the file. + /// A value indicating whether the file exists. public bool FileExists(string path) { var fullpath = GetFullPath(path); return File.Exists(fullpath); } - // beware, many things depend on how the GetRelative/AbsolutePath methods work! - /// - /// Gets the relative path. + /// Gets the filesystem-relative path of a full path or of an url. /// /// The full path or url. /// The path, relative to this filesystem's root. /// /// The relative path is relative to this filesystem's root, not starting with any - /// directory separator. If input was recognized as a url (path), then output uses url (path) separator - /// chars. + /// directory separator. All separators are forward-slashes. /// public string GetRelativePath(string fullPathOrUrl) { @@ -203,25 +265,22 @@ namespace Umbraco.Core.IO return path.Substring(_rootUrl.Length) // strip it .TrimStart('/'); // it's relative - // test path - path = EnsureDirectorySeparatorChar(fullPathOrUrl); + if (IOHelper.PathStartsWith(path, _rootPathFwd, '/')) // if it starts with the root url... + return path.Substring(_rootPathFwd.Length) // strip it + .TrimStart('/'); // it's relative - if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) // if it starts with the root path - return path.Substring(_rootPath.Length) // strip it - .TrimStart(Path.DirectorySeparatorChar); // it's relative - - // unchanged - including separators - return fullPathOrUrl; + // unchanged - what else? + return path; } /// /// Gets the full path. /// - /// The full or relative path. + /// The full or filesystem-relative path. /// The full path. /// /// On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this - /// filesystem's root) path. All separators are converted to Path.DirectorySeparatorChar. + /// filesystem's root) path. All separators are Path.DirectorySeparatorChar. /// public string GetFullPath(string path) { @@ -229,49 +288,82 @@ namespace Umbraco.Core.IO var opath = path; path = EnsureDirectorySeparatorChar(path); + // fixme - this part should go! // not sure what we are doing here - so if input starts with a (back) slash, // we assume it's not a FS relative path and we try to convert it... but it // really makes little sense? if (path.StartsWith(Path.DirectorySeparatorChar.ToString())) path = GetRelativePath(path); - // if already a full path, return - if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) - return path; + // if not already rooted, combine with the root path + if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar) == false) + path = Path.Combine(_rootPath, path); - // else combine and sanitize, ie GetFullPath will take care of any relative + // sanitize - GetFullPath will take care of any relative // segments in path, eg '../../foo.tmp' - it may throw a SecurityException // if the combined path reaches illegal parts of the filesystem - var fpath = Path.Combine(_rootPath, path); - fpath = Path.GetFullPath(fpath); + path = Path.GetFullPath(path); // at that point, path is within legal parts of the filesystem, ie we have // permissions to reach that path, but it may nevertheless be outside of // our root path, due to relative segments, so better check - if (IOHelper.PathStartsWith(fpath, _rootPath, Path.DirectorySeparatorChar)) - return fpath; + if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) + return path; + // nothing prevents us to reach the file, security-wise, yet it is outside + // this filesystem's root - throw throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root."); } + /// + /// Gets the url. + /// + /// The filesystem-relative path. + /// The url. + /// All separators are forward-slashes. public string GetUrl(string path) { path = EnsureUrlSeparatorChar(path).Trim('/'); return _rootUrl + "/" + path; } + /// + /// Gets the last-modified date of a directory or file. + /// + /// The filesystem-relative path to the directory or the file. + /// The last modified date of the directory or the file. public DateTimeOffset GetLastModified(string path) { - return DirectoryExists(path) - ? new DirectoryInfo(GetFullPath(path)).LastWriteTimeUtc - : new FileInfo(GetFullPath(path)).LastWriteTimeUtc; + var fullpath = GetFullPath(path); + return DirectoryExists(fullpath) + ? new DirectoryInfo(fullpath).LastWriteTimeUtc + : new FileInfo(fullpath).LastWriteTimeUtc; } + /// + /// Gets the created date of a directory or file. + /// + /// The filesystem-relative path to the directory or the file. + /// The created date of the directory or the file. public DateTimeOffset GetCreated(string path) { - return DirectoryExists(path) - ? Directory.GetCreationTimeUtc(GetFullPath(path)) - : File.GetCreationTimeUtc(GetFullPath(path)); + var fullpath = GetFullPath(path); + return DirectoryExists(fullpath) + ? Directory.GetCreationTimeUtc(fullpath) + : File.GetCreationTimeUtc(fullpath); + } + + /// + /// Gets the size of a file. + /// + /// The filesystem-relative path to the file. + /// The file of the size, in bytes. + /// If the file does not exist, returns -1. + public long GetSize(string path) + { + var fullPath = GetFullPath(path); + var file = new FileInfo(fullPath); + return file.Exists ? file.Length : -1; } #region Helper Methods diff --git a/src/Umbraco.Core/IO/ResizedImage.cs b/src/Umbraco.Core/IO/ResizedImage.cs deleted file mode 100644 index 6586699ecf..0000000000 --- a/src/Umbraco.Core/IO/ResizedImage.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Umbraco.Core.IO -{ - internal class ResizedImage - { - public ResizedImage() - { - } - - public ResizedImage(int width, int height, string fileName) - { - Width = width; - Height = height; - FileName = fileName; - } - - public int Width { get; set; } - public int Height { get; set; } - public string FileName { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/IO/UmbracoMediaFile.cs b/src/Umbraco.Core/IO/UmbracoMediaFile.cs index b8fe310f54..17162bcfa8 100644 --- a/src/Umbraco.Core/IO/UmbracoMediaFile.cs +++ b/src/Umbraco.Core/IO/UmbracoMediaFile.cs @@ -1,11 +1,6 @@ using System; -using System.Collections.Generic; using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; using System.IO; -using System.Linq; -using System.Threading.Tasks; using System.Web; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; @@ -87,14 +82,13 @@ namespace Umbraco.Core.IO { Filename = _fs.GetFileName(Path); Extension = _fs.GetExtension(Path) != null - ? _fs.GetExtension(Path).Substring(1).ToLowerInvariant() + ? _fs.GetExtension(Path).TrimStart('.').ToLowerInvariant() : ""; Url = _fs.GetUrl(Path); + Exists = _fs.FileExists(Path); if (Exists == false) - { LogHelper.Warn("The media file doesn't exist: " + Path); - } } public bool Exists { get; private set; } @@ -117,27 +111,15 @@ namespace Umbraco.Core.IO { get { - if (_length == null) - { - if (Exists) - { - _length = _fs.GetSize(Path); - } - else - { - _length = -1; - } - } + if (_length != null) return _length.Value; + _length = Exists ? _fs.GetSize(Path) : -1; return _length.Value; } } public bool SupportsResizing { - get - { - return UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(Extension); - } + get { return ImageHelper.IsImageFile(Extension); } } public string GetFriendlyName() @@ -147,67 +129,49 @@ namespace Umbraco.Core.IO public Size GetDimensions() { - if (_size == null) - { - if (_fs.FileExists(Path)) - { - EnsureFileSupportsResizing(); + if (_size != null) return _size.Value; - using (var fs = _fs.OpenFile(Path)) - { - _size = ImageHelper.GetDimensions(fs); - } - } - else + if (_fs.FileExists(Path)) + { + EnsureFileSupportsResizing(); + + using (var fs = _fs.OpenFile(Path)) { - _size = new Size(-1, -1); + _size = ImageHelper.GetDimensions(fs); } } + else + { + _size = new Size(-1, -1); + } + return _size.Value; } public string Resize(int width, int height) { - if (Exists) - { - EnsureFileSupportsResizing(); + if (Exists == false) return string.Empty; - var fileNameThumb = DoResize(width, height, -1, string.Empty); - - return _fs.GetUrl(fileNameThumb); - } - return string.Empty; + EnsureFileSupportsResizing(); + var filepath = Resize(width, height, -1, string.Empty); + return _fs.GetUrl(filepath); } - public string Resize(int maxWidthHeight, string fileNameAddition) + public string Resize(int maxWidthHeight, string filenameAddition) { - if (Exists) - { - EnsureFileSupportsResizing(); + if (Exists == false) return string.Empty; - var fileNameThumb = DoResize(-1, -1, maxWidthHeight, fileNameAddition); - - return _fs.GetUrl(fileNameThumb); - } - return string.Empty; + EnsureFileSupportsResizing(); + var filepath = Resize(-1, -1, maxWidthHeight, filenameAddition); + return _fs.GetUrl(filepath); } - private string DoResize(int width, int height, int maxWidthHeight, string fileNameAddition) + private string Resize(int width, int height, int maxWidthHeight, string sizeName) { - using (var fs = _fs.OpenFile(Path)) + using (var filestream = _fs.OpenFile(Path)) + using (var image = Image.FromStream(filestream)) { - using (var image = Image.FromStream(fs)) - { - var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition) - ? string.Format("{0}_UMBRACOSYSTHUMBNAIL.jpg", Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal))) - : string.Format("{0}_{1}.jpg", Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition); - - var thumbnail = maxWidthHeight == -1 - ? ImageHelper.GenerateThumbnail(image, width, height, fileNameThumb, Extension, _fs) - : ImageHelper.GenerateThumbnail(image, maxWidthHeight, fileNameThumb, Extension, _fs); - - return thumbnail.FileName; - } + return ImageHelper.GenerateResized(_fs, image, Path, sizeName, maxWidthHeight, width, height).Filepath; } } @@ -216,7 +180,5 @@ namespace Umbraco.Core.IO if (SupportsResizing == false) throw new InvalidOperationException(string.Format("The file {0} is not an image, so can't get dimensions", Filename)); } - - } } diff --git a/src/Umbraco.Core/Media/ImageHelper.cs b/src/Umbraco.Core/Media/ImageHelper.cs index 5842bb67bb..2b62c37645 100644 --- a/src/Umbraco.Core/Media/ImageHelper.cs +++ b/src/Umbraco.Core/Media/ImageHelper.cs @@ -3,36 +3,52 @@ using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; -using System.Globalization; using System.IO; using System.Linq; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Media.Exif; +using Umbraco.Core.Models; namespace Umbraco.Core.Media { /// - /// A helper class used for imaging + /// Provides helper methods for managing images. /// internal static class ImageHelper { + private static readonly Dictionary DefaultSizes = new Dictionary + { + { 100, "thumb" }, + { 500, "big-thumb" } + }; + /// - /// Gets the dimensions of an image based on a stream + /// Gets a value indicating whether the file extension corresponds to an image. /// - /// - /// - /// - /// First try with EXIF, this is because it is insanely faster and doesn't use any memory to read exif data than to load in the entire - /// image via GDI. Otherwise loading an image into GDI consumes a crazy amount of memory on large images. - /// - /// Of course EXIF data might not exist in every file and can only exist in JPGs - /// - public static Size GetDimensions(Stream imageStream) + /// The file extension. + /// A value indicating whether the file extension corresponds to an image. + public static bool IsImageFile(string extension) + { + if (extension == null) return false; + extension = extension.TrimStart('.'); + return UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(extension); + } + + /// + /// Gets the dimensions of an image. + /// + /// A stream containing the image bytes. + /// The dimension of the image. + /// First try with EXIF as it is faster and does not load the entire image + /// in memory. Fallback to GDI which means loading the image in memory and thus + /// use potentially large amounts of memory. + public static Size GetDimensions(Stream stream) { //Try to load with exif try { - var jpgInfo = ImageFile.FromStream(imageStream); + var jpgInfo = ImageFile.FromStream(stream); if (jpgInfo.Format != ImageFileFormat.Unknown && jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension) @@ -52,16 +68,20 @@ namespace Umbraco.Core.Media } //we have no choice but to try to read in via GDI - using (var image = Image.FromStream(imageStream)) + using (var image = Image.FromStream(stream)) { var fileWidth = image.Width; var fileHeight = image.Height; return new Size(fileWidth, fileHeight); - } - + } } + /// + /// Gets the MIME type of an image. + /// + /// The image. + /// The MIME type of the image. public static string GetMimeType(this Image image) { var format = image.RawFormat; @@ -69,172 +89,226 @@ namespace Umbraco.Core.Media return codec.MimeType; } - /// - /// Creates the thumbnails if the image is larger than all of the specified ones. - /// - /// - /// - /// - /// - /// - /// - internal static IEnumerable GenerateMediaThumbnails( + #region GenerateThumbnails + + public static IEnumerable GenerateThumbnails( + IFileSystem fs, + Image image, + string filepath, + string preValue) + { + if (string.IsNullOrWhiteSpace(preValue)) + return GenerateThumbnails(fs, image, filepath); + + var additionalSizes = new List(); + var sep = preValue.Contains(",") ? "," : ";"; + var values = preValue.Split(new[] { sep }, StringSplitOptions.RemoveEmptyEntries); + foreach (var value in values) + { + int size; + if (int.TryParse(value, out size)) + additionalSizes.Add(size); + } + + return GenerateThumbnails(fs, image, filepath, additionalSizes); + } + + public static IEnumerable GenerateThumbnails( + IFileSystem fs, + Image image, + string filepath, + IEnumerable additionalSizes = null) + { + var w = image.Width; + var h = image.Height; + + var sizes = additionalSizes == null ? DefaultSizes.Keys : DefaultSizes.Keys.Concat(additionalSizes); + + // start with default sizes, + // add additional sizes, + // filter out duplicates, + // filter out those that would be larger that the original image + // and create the thumbnail + return sizes + .Distinct() + .Where(x => w >= x && h >= x) + .Select(x => GenerateResized(fs, image, filepath, DefaultSizes.ContainsKey(x) ? DefaultSizes[x] : "", x)) + .ToList(); // now + } + + public static IEnumerable GenerateThumbnails( IFileSystem fs, - string fileName, - string extension, - Image originalImage, - IEnumerable additionalThumbSizes) + Stream filestream, + string filepath, + PropertyType propertyType) { - - var result = new List(); - - var allSizesDictionary = new Dictionary {{100,"thumb"}, {500,"big-thumb"}}; - - //combine the static dictionary with the additional sizes with only unique values - var allSizes = allSizesDictionary.Select(kv => kv.Key) - .Union(additionalThumbSizes.Where(x => x > 0).Distinct()); - - var sizesDictionary = allSizes.ToDictionary(s => s, s => allSizesDictionary.ContainsKey(s) ? allSizesDictionary[s]: ""); - - foreach (var s in sizesDictionary) + // get the original image from the original stream + if (filestream.CanSeek) filestream.Seek(0, 0); // fixme - what if we cannot seek? + using (var image = Image.FromStream(filestream)) { - var size = s.Key; - var name = s.Value; - if (originalImage.Width >= size && originalImage.Height >= size) + return GenerateThumbnails(fs, image, filepath, propertyType); + } + } + + public static IEnumerable GenerateThumbnails( + IFileSystem fs, + Image image, + string filepath, + PropertyType propertyType) + { + // if the editor is an upload field, check for additional thumbnail sizes + // that can be defined in the prevalue for the property data type. otherwise, + // just use the default sizes. + var sizes = propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias + ? ApplicationContext.Current.Services.DataTypeService + .GetPreValuesByDataTypeId(propertyType.DataTypeDefinitionId) + .FirstOrDefault() + : string.Empty; + + return GenerateThumbnails(fs, image, filepath, sizes); + } + + #endregion + + #region GenerateResized - Generate at resized filepath derived from origin filepath + + public static ResizedImage GenerateResized(IFileSystem fs, Image originImage, string originFilepath, string sizeName, int maxWidthHeight) + { + return GenerateResized(fs, originImage, originFilepath, sizeName, maxWidthHeight, -1, -1); + } + + public static ResizedImage GenerateResized(IFileSystem fs, Image originImage, string originFilepath, string sizeName, int fixedWidth, int fixedHeight) + { + return GenerateResized(fs, originImage, originFilepath, sizeName, -1, fixedWidth, fixedHeight); + } + + public static ResizedImage GenerateResized(IFileSystem fs, Image originImage, string originFilepath, string sizeName, int maxWidthHeight, int fixedWidth, int fixedHeight) + { + if (string.IsNullOrWhiteSpace(sizeName)) + sizeName = "UMBRACOSYSTHUMBNAIL"; + var extension = Path.GetExtension(originFilepath) ?? string.Empty; + var filebase = originFilepath.TrimEnd(extension); + var resizedFilepath = filebase + "_" + sizeName + ".jpg"; + + return GenerateResizedAt(fs, originImage, resizedFilepath, maxWidthHeight, fixedWidth, fixedHeight); + } + + #endregion + + #region GenerateResizedAt - Generate at specified resized filepath + + public static ResizedImage GenerateResizedAt(IFileSystem fs, Image originImage, string resizedFilepath, int maxWidthHeight) + { + return GenerateResizedAt(fs, originImage, resizedFilepath, maxWidthHeight, -1, -1); + } + + public static ResizedImage GenerateResizedAt(IFileSystem fs, Image originImage, int fixedWidth, int fixedHeight, string resizedFilepath) + { + return GenerateResizedAt(fs, originImage, resizedFilepath, -1, fixedWidth, fixedHeight); + } + + public static ResizedImage GenerateResizedAt(IFileSystem fs, Image originImage, string resizedFilepath, int maxWidthHeight, int fixedWidth, int fixedHeight) + { + // target dimensions + int width, height; + + // if maxWidthHeight then get ratio + if (maxWidthHeight > 0) + { + var fx = (float) originImage.Size.Width / maxWidthHeight; + var fy = (float) originImage.Size.Height / maxWidthHeight; + var f = Math.Max(fx, fy); // fit in thumbnail size + width = (int) Math.Round(originImage.Size.Width / f); + height = (int) Math.Round(originImage.Size.Height / f); + if (width == 0) width = 1; + if (height == 0) height = 1; + } + else if (fixedWidth > 0 && fixedHeight > 0) + { + width = fixedWidth; + height = fixedHeight; + } + else + { + width = height = 1; + } + + // create new image with best quality settings + using (var bitmap = new Bitmap(width, height)) + using (var graphics = Graphics.FromImage(bitmap)) + { + // if the image size is rather large we cannot use the best quality interpolation mode + // because we'll get out of mem exceptions. So we detect how big the image is and use + // the mid quality interpolation mode when the image size exceeds our max limit. + graphics.InterpolationMode = originImage.Width > 5000 || originImage.Height > 5000 + ? InterpolationMode.Bilinear // mid quality + : InterpolationMode.HighQualityBicubic; // best quality + + // everything else is best-quality + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + + // copy the old image to the new and resize + var rect = new Rectangle(0, 0, width, height); + graphics.DrawImage(originImage, rect, 0, 0, originImage.Width, originImage.Height, GraphicsUnit.Pixel); + + // copy metadata + // fixme - er... no? + + // get an encoder - based upon the file type + var extension = (Path.GetExtension(resizedFilepath) ?? "").TrimStart('.').ToLowerInvariant(); + var encoders = ImageCodecInfo.GetImageEncoders(); + var encoder = extension == "png" || extension == "gif" + ? encoders.Single(t => t.MimeType.Equals("image/png")) + : encoders.Single(t => t.MimeType.Equals("image/jpeg")); + + // set compresion ratio to 90% + var encoderParams = new EncoderParameters(); + encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 90L); + + // save the new image + using (var stream = new MemoryStream()) { - result.Add(Resize(fs, fileName, extension, size, name, originalImage)); + bitmap.Save(stream, encoder, encoderParams); + stream.Seek(0, 0); + if (resizedFilepath.Contains("UMBRACOSYSTHUMBNAIL")) + { + var filepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToInvariantString()); + fs.AddFile(filepath, stream); + // TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385 + stream.Seek(0, 0); + resizedFilepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", width + "x" + height); + } + + fs.AddFile(resizedFilepath, stream); } + + return new ResizedImage(resizedFilepath, width, height); } - - return result; } - /// - /// Performs an image resize - /// - /// - /// - /// - /// - /// - /// - /// - private static ResizedImage Resize(IFileSystem fileSystem, string path, string extension, int maxWidthHeight, string fileNameAddition, Image originalImage) + #endregion + + #region Inner classes + + public class ResizedImage { - var fileNameThumb = String.IsNullOrEmpty(fileNameAddition) - ? string.Format("{0}_UMBRACOSYSTHUMBNAIL.jpg", path.Substring(0, path.LastIndexOf("."))) - : string.Format("{0}_{1}.jpg", path.Substring(0, path.LastIndexOf(".")), fileNameAddition); + public ResizedImage() + { } - var thumb = GenerateThumbnail( - originalImage, - maxWidthHeight, - fileNameThumb, - extension, - fileSystem); - - return thumb; - } - - internal static ResizedImage GenerateThumbnail(Image image, int maxWidthHeight, string thumbnailFileName, string extension, IFileSystem fs) - { - return GenerateThumbnail(image, maxWidthHeight, -1, -1, thumbnailFileName, extension, fs); - } - - internal static ResizedImage GenerateThumbnail(Image image, int fixedWidth, int fixedHeight, string thumbnailFileName, string extension, IFileSystem fs) - { - return GenerateThumbnail(image, -1, fixedWidth, fixedHeight, thumbnailFileName, extension, fs); - } - - private static ResizedImage GenerateThumbnail(Image image, int maxWidthHeight, int fixedWidth, int fixedHeight, string thumbnailFileName, string extension, IFileSystem fs) - { - // Generate thumbnail - float f = 1; - if (maxWidthHeight >= 0) + public ResizedImage(string filepath, int width, int height) { - var fx = (float)image.Size.Width / maxWidthHeight; - var fy = (float)image.Size.Height / maxWidthHeight; - - // must fit in thumbnail size - f = Math.Max(fx, fy); + Filepath = filepath; + Width = width; + Height = height; } - //depending on if we are doing fixed width resizing or not. - fixedWidth = (maxWidthHeight > 0) ? image.Width : fixedWidth; - fixedHeight = (maxWidthHeight > 0) ? image.Height : fixedHeight; - - var widthTh = (int)Math.Round(fixedWidth / f); - var heightTh = (int)Math.Round(fixedHeight / f); - - // fixes for empty width or height - if (widthTh == 0) - widthTh = 1; - if (heightTh == 0) - heightTh = 1; - - // Create new image with best quality settings - using (var bp = new Bitmap(widthTh, heightTh)) - { - using (var g = Graphics.FromImage(bp)) - { - //if the image size is rather large we cannot use the best quality interpolation mode - // because we'll get out of mem exceptions. So we'll detect how big the image is and use - // the mid quality interpolation mode when the image size exceeds our max limit. - - if (image.Width > 5000 || image.Height > 5000) - { - //use mid quality - g.InterpolationMode = InterpolationMode.Bilinear; - } - else - { - //use best quality - g.InterpolationMode = InterpolationMode.HighQualityBicubic; - } - - - g.SmoothingMode = SmoothingMode.HighQuality; - g.PixelOffsetMode = PixelOffsetMode.HighQuality; - g.CompositingQuality = CompositingQuality.HighQuality; - - // Copy the old image to the new and resized - var rect = new Rectangle(0, 0, widthTh, heightTh); - g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel); - - // Copy metadata - var imageEncoders = ImageCodecInfo.GetImageEncoders(); - - var codec = extension.ToLower() == "png" || extension.ToLower() == "gif" - ? imageEncoders.Single(t => t.MimeType.Equals("image/png")) - : imageEncoders.Single(t => t.MimeType.Equals("image/jpeg")); - - // Set compresion ratio to 90% - var ep = new EncoderParameters(); - ep.Param[0] = new EncoderParameter(Encoder.Quality, 90L); - - // Save the new image using the dimensions of the image - var predictableThumbnailName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToString(CultureInfo.InvariantCulture)); - using (var ms = new MemoryStream()) - { - bp.Save(ms, codec, ep); - ms.Seek(0, 0); - - fs.AddFile(predictableThumbnailName, ms); - } - - // TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385 - var newFileName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", string.Format("{0}x{1}", widthTh, heightTh)); - using (var ms = new MemoryStream()) - { - bp.Save(ms, codec, ep); - ms.Seek(0, 0); - - fs.AddFile(newFileName, ms); - } - - return new ResizedImage(widthTh, heightTh, newFileName); - } - } + public string Filepath { get; set; } + public int Width { get; set; } + public int Height { get; set; } } + #endregion } } diff --git a/src/Umbraco.Core/Media/MediaHelper.cs b/src/Umbraco.Core/Media/MediaHelper.cs new file mode 100644 index 0000000000..55bbd80d3b --- /dev/null +++ b/src/Umbraco.Core/Media/MediaHelper.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Media +{ + /// + /// Provides helper methods for managing medias. + /// + /// Medias can be anything that can be uploaded via an upload + /// property, including but not limited to, images. See ImageHelper for + /// image-specific methods. + internal static class MediaHelper + { + private static long _folderCounter; + private static bool _folderCounterInitialized; + private static readonly object FolderCounterLock = new object(); + + // fixme - should be a config option of some sort! + //public static bool UseTheNewMediaPathScheme { get; set; } + public const bool UseTheNewMediaPathScheme = false; + + public static MediaFileSystem FileSystem { get { return FileSystemProviderManager.Current.GetFileSystemProvider(); } } + + #region Media Path + + /// + /// Gets the file path of a media file. + /// + /// The file name. + /// The unique identifier of the content/media owning the file. + /// The unique identifier of the property type owning the file. + /// The filesystem-relative path to the media file. + public static string GetMediaPath(string filename, Guid cuid, Guid puid) + { + filename = Path.GetFileName(filename); + if (filename == null) throw new ArgumentException("Cannot become a safe filename.", "filename"); + filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); + + string folder; + if (UseTheNewMediaPathScheme == false) + { + // old scheme: filepath is "/" OR "-" + // default media filesystem maps to "~/media/" + folder = GetNextFolder(); + } + else + { + // new scheme: path is "-/" OR "--" + // default media filesystem maps to "~/media/" + // fixme - this assumes that the keys exists and won't change (even when creating a new content) + // fixme - this is going to create looooong filepaths, any chance we can shorten them? + folder = cuid.ToString("N") + "-" + puid.ToString("N"); + } + + var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories + ? Path.Combine(folder, filename) + : folder + "-" + filename; + + return filepath; + } + + /// + /// Gets the file path of a media file. + /// + /// The file name. + /// A previous file path. + /// The unique identifier of the content/media owning the file. + /// The unique identifier of the property type owning the file. + /// The filesystem-relative path to the media file. + /// In the old, legacy, number-based scheme, we try to re-use the media folder + /// specified by . Else, we create a new one. + public static string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid) + { + if (UseTheNewMediaPathScheme || string.IsNullOrWhiteSpace(prevpath)) + return GetMediaPath(filename, cuid, puid); + + filename = Path.GetFileName(filename); + if (filename == null) throw new ArgumentException("Cannot become a safe filename.", "filename"); + filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); + + // old scheme, with a previous path + // prevpath should be "/" OR "-" + // and we want to reuse the "" part, so try to find it + + var sep = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories ? "/" : "-"; + var pos = prevpath.IndexOf(sep, StringComparison.Ordinal); + var s = pos > 0 ? prevpath.Substring(0, pos) : null; + int ignored; + + var folder = (pos > 0 && int.TryParse(s, out ignored)) ? s : GetNextFolder(); + + // ReSharper disable once AssignNullToNotNullAttribute + var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories + ? Path.Combine(folder, filename) + : folder + "-" + filename; + + return filepath; + } + + /// + /// Gets the next media folder in the original number-based scheme. + /// + /// + internal static string GetNextFolder() + { + lock (FolderCounterLock) + { + if (_folderCounterInitialized == false) + { + // fixme - seed was not respected in MediaSubfolderCounter? + _folderCounter = 1000; // seed + var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + var directories = fs.GetDirectories(""); + foreach (var directory in directories) + { + long folderNumber; + if (long.TryParse(directory, out folderNumber) && folderNumber > _folderCounter) + _folderCounter = folderNumber; + } + + _folderCounterInitialized = true; + } + } + + return Interlocked.Increment(ref _folderCounter).ToString(CultureInfo.InvariantCulture); + } + + #endregion + + /// + /// Stores a media file. + /// + /// The content item owning the media file. + /// The property type owning the media file. + /// The media file name. + /// A stream containing the media bytes. + /// An optional filesystem-relative filepath to the previous media file. + /// The filesystem-relative filepath to the media file. + /// + /// The file is considered "owned" by the content/propertyType. + /// If an is provided then that file (and thumbnails) is deleted + /// before the new file is saved, and depending on the media path scheme, the folder + /// may be reused for the new file. + /// + public static string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath) + { + if (content == null) throw new ArgumentNullException("content"); + if (propertyType == null) throw new ArgumentNullException("propertyType"); + if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException("Null or empty.", "filename"); + if (filestream == null) throw new ArgumentNullException("filestream"); + + // clear the old file, if any + var fs = FileSystem; + if (string.IsNullOrWhiteSpace(oldpath)) + fs.DeleteFile(oldpath, true); + + // sanity check - fixme - every entity should be created with a proper Guid + if (content.Key == Guid.Empty) content.Key = Guid.NewGuid(); + + // get the filepath, store the data + // use oldpath as "prevpath" to try and reuse the folder, in original number-based scheme + var filepath = GetMediaPath(filename, oldpath, content.Key, propertyType.Key); + fs.AddFile(filepath, filestream); + return filepath; + } + + /// + /// Clears a media file. + /// + /// The filesystem-relative path to the media file. + public static void DeleteFile(string filepath) + { + FileSystem.DeleteFile(filepath, true); + } + + /// + /// Copies a media file. + /// + /// The content item owning the copy of the media file. + /// The property type owning the copy of the media file. + /// The filesystem-relative path to the source media file. + /// The filesystem-relative path to the copy of the media file. + public static string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath) + { + if (content == null) throw new ArgumentNullException("content"); + if (propertyType == null) throw new ArgumentNullException("propertyType"); + if (string.IsNullOrWhiteSpace(sourcepath)) throw new ArgumentException("Null or empty.", "sourcepath"); + + // ensure we have a file to copy + var fs = FileSystem; + if (fs.FileExists(sourcepath) == false) return null; + + // sanity check - fixme - every entity should be created with a proper Guid + if (content.Key == Guid.Empty) content.Key = Guid.NewGuid(); + + // get the filepath + var filename = Path.GetFileName(sourcepath); + var filepath = GetMediaPath(filename, content.Key, propertyType.Key); + fs.CopyFile(sourcepath, filepath); + fs.CopyThumbnails(sourcepath, filepath); + return filepath; + } + + /// + /// Gets or creates a property for a content item. + /// + /// The content item. + /// The property type alias. + /// The property. + private static Property GetProperty(IContentBase content, string propertyTypeAlias) + { + var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (property != null) return property; + + var propertyType = content.GetContentType().CompositionPropertyTypes + .FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (propertyType == null) + throw new Exception("No property type exists with alias " + propertyTypeAlias + "."); + + property = new Property(propertyType); + content.Properties.Add(property); + return property; + } + + public static void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream) + { + var property = GetProperty(content, propertyTypeAlias); + var svalue = property.Value as string; + var oldpath = svalue == null ? null : FileSystem.GetRelativePath(svalue); + var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath); + property.Value = FileSystem.GetUrl(filepath); + SetUploadFile(content, property, null, filepath, filestream); + } + + public static void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath) + { + var property = GetProperty(content, propertyTypeAlias); + var svalue = property.Value as string; + var oldpath = svalue == null ? null : FileSystem.GetRelativePath(svalue); // FIXME DELETE? + if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath) + FileSystem.DeleteFile(oldpath); + property.Value = FileSystem.GetUrl(filepath); + var fs = FileSystem; + using (var filestream = fs.OpenFile(filepath)) + { + SetUploadFile(content, property, fs, filepath, filestream); + } + } + + // sets a file for the FileUpload property editor + // ie generates thumbnails and populates autofill properties + private static void SetUploadFile(IContentBase content, Property property, IFileSystem fs, string filepath, Stream filestream) + { + // check if file is an image (and supports resizing and thumbnails etc) + var extension = Path.GetExtension(filepath); + var isImage = ImageHelper.IsImageFile(extension); + + // specific stuff for images (thumbnails etc) + if (isImage) + { + using (var image = Image.FromStream(filestream)) + { + // use one image for all + ImageHelper.GenerateThumbnails(fs, image, filepath, property.PropertyType); + UploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, image); + } + } + else + { + // will use filepath for extension, and filestream for length + UploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream); + } + } + } +} diff --git a/src/Umbraco.Core/Media/MediaSubfolderCounter.cs b/src/Umbraco.Core/Media/MediaSubfolderCounter.cs deleted file mode 100644 index 6f0142cacf..0000000000 --- a/src/Umbraco.Core/Media/MediaSubfolderCounter.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using Umbraco.Core.IO; - -namespace Umbraco.Core.Media -{ - /// - /// Internal singleton to handle the numbering of subfolders within the Media-folder. - /// When this class is initiated it will look for numbered subfolders and select the highest number, - /// which will be the start point for the naming of the next subfolders. If no subfolders exists - /// then the starting point will be 1000, ie. /media/1000/koala.jpg - /// - internal class MediaSubfolderCounter - { - #region Singleton - - private long _numberedFolder = 1000;//Default starting point - private static readonly ReaderWriterLockSlim ClearLock = new ReaderWriterLockSlim(); - private static readonly Lazy Lazy = new Lazy(() => new MediaSubfolderCounter()); - - public static MediaSubfolderCounter Current { get { return Lazy.Value; } } - - private MediaSubfolderCounter() - { - var folders = new List(); - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - var directories = fs.GetDirectories(""); - foreach (var directory in directories) - { - long dirNum; - if (long.TryParse(directory, out dirNum)) - { - folders.Add(dirNum); - } - } - var last = folders.OrderBy(x => x).LastOrDefault(); - if(last != default(long)) - _numberedFolder = last; - } - - #endregion - - /// - /// Returns an increment of the numbered media subfolders. - /// - /// A value - public long Increment() - { - using (new ReadLock(ClearLock)) - { - _numberedFolder = _numberedFolder + 1; - return _numberedFolder; - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs new file mode 100644 index 0000000000..c7157c4eb6 --- /dev/null +++ b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs @@ -0,0 +1,200 @@ +using System; +using System.Drawing; +using System.IO; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Media +{ + /// + /// Provides extension methods to manage auto-fill properties for upload fields. + /// + internal static class UploadAutoFillProperties + { + /// + /// Gets the auto-fill configuration for a specified property alias. + /// + /// The property type alias. + /// The auto-fill configuration for the specified property alias, or null. + public static IImagingAutoFillUploadField GetConfig(string propertyTypeAlias) + { + var autoFillConfigs = UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties; + return autoFillConfigs == null ? null : autoFillConfigs.FirstOrDefault(x => x.Alias == propertyTypeAlias); + } + + /// + /// Resets the auto-fill properties of a content item, for a specified property alias. + /// + /// The content item. + /// The property type alias. + public static void Reset(IContentBase content, string propertyTypeAlias) + { + if (content == null) throw new ArgumentNullException("content"); + if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias"); + + // get the config, no config = nothing to do + var autoFillConfig = GetConfig(propertyTypeAlias); + if (autoFillConfig == null) return; // nothing + + // reset + Reset(content, autoFillConfig); + } + + /// + /// Resets the auto-fill properties of a content item, for a specified auto-fill configuration. + /// + /// The content item. + /// The auto-fill configuration. + public static void Reset(IContentBase content, IImagingAutoFillUploadField autoFillConfig) + { + if (content == null) throw new ArgumentNullException("content"); + if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig"); + + ResetProperties(content, autoFillConfig); + } + + /// + /// Populates the auto-fill properties of a content item. + /// + /// The content item. + /// The property type alias. + /// The filesystem-relative filepath, or null to clear properties. + public static void Populate(IContentBase content, string propertyTypeAlias, string filepath) + { + if (content == null) throw new ArgumentNullException("content"); + if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias"); + + // no property = nothing to do + if (content.Properties.Contains(propertyTypeAlias) == false) return; + + // get the config, no config = nothing to do + var autoFillConfig = GetConfig(propertyTypeAlias); + if (autoFillConfig == null) return; // nothing + + // populate + Populate(content, autoFillConfig, filepath); + } + + /// + /// Populates the auto-fill properties of a content item. + /// + /// The content item. + /// The property type alias. + /// The filesystem-relative filepath, or null to clear properties. + /// The stream containing the file data. + /// The file data as an image object. + public static void Populate(IContentBase content, string propertyTypeAlias, string filepath, Stream filestream, Image image = null) + { + if (content == null) throw new ArgumentNullException("content"); + if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias"); + + // no property = nothing to do + if (content.Properties.Contains(propertyTypeAlias) == false) return; + + // get the config, no config = nothing to do + var autoFillConfig = GetConfig(propertyTypeAlias); + if (autoFillConfig == null) return; // nothing + + // populate + Populate(content, autoFillConfig, filepath, filestream, image); + } + + /// + /// Populates the auto-fill properties of a content item, for a specified auto-fill configuration. + /// + /// The content item. + /// The auto-fill configuration. + /// The filesystem path to the uploaded file. + /// The parameter is the path relative to the filesystem. + public static void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath) + { + if (content == null) throw new ArgumentNullException("content"); + if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig"); + + // no file = reset, file = auto-fill + if (filepath.IsNullOrWhiteSpace()) + { + ResetProperties(content, autoFillConfig); + } + else + { + var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + using (var filestream = fs.OpenFile(filepath)) + { + var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); + var size = ImageHelper.IsImageFile(extension) ? (Size?) ImageHelper.GetDimensions(filestream) : null; + SetProperties(content, autoFillConfig, size, filestream.Length, extension); + } + } + } + + /// + /// Populates the auto-fill properties of a content item. + /// + /// The content item. + /// + /// The filesystem-relative filepath, or null to clear properties. + /// The stream containing the file data. + /// The file data as an image object. + public static void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath, Stream filestream, Image image = null) + { + if (content == null) throw new ArgumentNullException("content"); + if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig"); + + // no file = reset, file = auto-fill + if (filepath.IsNullOrWhiteSpace() || filestream == null) + { + ResetProperties(content, autoFillConfig); + } + else + { + var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); + Size? size; + if (image == null) + size = ImageHelper.IsImageFile(extension) ? (Size?) ImageHelper.GetDimensions(filestream) : null; + else + size = new Size(image.Width, image.Height); + SetProperties(content, autoFillConfig, size, filestream.Length, extension); + } + } + + private static void SetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig, Size? size, long length, string extension) + { + if (content == null) throw new ArgumentNullException("content"); + if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig"); + + if (content.Properties.Contains(autoFillConfig.WidthFieldAlias)) + content.Properties[autoFillConfig.WidthFieldAlias].Value = size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty; + + if (content.Properties.Contains(autoFillConfig.HeightFieldAlias)) + content.Properties[autoFillConfig.HeightFieldAlias].Value = size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty; + + if (content.Properties.Contains(autoFillConfig.LengthFieldAlias)) + content.Properties[autoFillConfig.LengthFieldAlias].Value = length; + + if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) + content.Properties[autoFillConfig.ExtensionFieldAlias].Value = extension; +} + + private static void ResetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig) + { + if (content == null) throw new ArgumentNullException("content"); + if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig"); + + if (content.Properties.Contains(autoFillConfig.WidthFieldAlias)) + content.Properties[autoFillConfig.WidthFieldAlias].Value = string.Empty; + + if (content.Properties.Contains(autoFillConfig.HeightFieldAlias)) + content.Properties[autoFillConfig.HeightFieldAlias].Value = string.Empty; + + if (content.Properties.Contains(autoFillConfig.LengthFieldAlias)) + content.Properties[autoFillConfig.LengthFieldAlias].Value = string.Empty; + + if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) + content.Properties[autoFillConfig.ExtensionFieldAlias].Value = string.Empty; + } + } +} diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index e91996e32a..863f6b197a 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -1,25 +1,15 @@ using System; using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; -using System.Globalization; using System.IO; using System.Linq; using System.Web; -using System.Xml; using System.Xml.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Media; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Strings; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Services; namespace Umbraco.Core.Models @@ -329,7 +319,7 @@ namespace Umbraco.Core.Models { if (property.Value is string) { - var value = (string)property.Value; + var value = (string) property.Value; property.Value = value.ToValidXmlString(); } } @@ -444,6 +434,19 @@ namespace Umbraco.Core.Models } } + public static IContentTypeComposition GetContentType(this IContentBase contentBase) + { + if (contentBase == null) throw new ArgumentNullException("contentBase"); + + var content = contentBase as IContent; + if (content != null) return content.ContentType; + var media = contentBase as IMedia; + if (media != null) return media.ContentType; + var member = contentBase as IMember; + if (member != null) return member.ContentType; + throw new NotSupportedException("Unsupported IContentBase implementation: " + contentBase.GetType().FullName + "."); + } + #region SetValue for setting file contents /// @@ -454,20 +457,24 @@ namespace Umbraco.Core.Models /// The containing the file that will be uploaded public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value) { - // Ensure we get the filename without the path in IE in intranet mode + // ensure we get the filename without the path in IE in intranet mode // http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie - var fileName = value.FileName; - if (fileName.LastIndexOf(@"\") > 0) - fileName = fileName.Substring(fileName.LastIndexOf(@"\") + 1); + var filename = value.FileName; + var pos = filename.LastIndexOf(@"\", StringComparison.InvariantCulture); + if (pos > 0) + filename = filename.Substring(pos + 1); - var name = - IOHelper.SafeFileName( - fileName.Substring(fileName.LastIndexOf(IOHelper.DirSepChar) + 1, - fileName.Length - fileName.LastIndexOf(IOHelper.DirSepChar) - 1) - .ToLower()); + // strip any directory info + pos = filename.LastIndexOf(IOHelper.DirSepChar); + if (pos > 0) + filename = filename.Substring(pos + 1); - if (string.IsNullOrEmpty(name) == false) - SetFileOnContent(content, propertyTypeAlias, name, value.InputStream); + // get a safe filename - should this be done by MediaHelper? + filename = IOHelper.SafeFileName(filename); + if (string.IsNullOrWhiteSpace(filename)) return; + filename = filename.ToLower(); // fixme - er... why? + + MediaHelper.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream); } /// @@ -494,104 +501,33 @@ namespace Umbraco.Core.Models } /// - /// Sets and uploads the file from a as the property value + /// Sets a content item property value with a file coming from a stream. /// - /// to add property value to - /// Alias of the property to save the value on - /// Name of the file - /// to save to disk - public static void SetValue(this IContentBase content, string propertyTypeAlias, string fileName, Stream fileStream) + /// The content item. + /// The property type alias. + /// Name of the file + /// The stream containing the file data. + /// This really is for FileUpload fields only, and should be obsoleted. For anything else, + /// you need to store the file by yourself using and then figure out + /// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself. + public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream) { - var name = IOHelper.SafeFileName(fileName); + if (filename == null || filestream == null) return; - if (string.IsNullOrEmpty(name) == false && fileStream != null) - SetFileOnContent(content, propertyTypeAlias, name, fileStream); + // get a safe filename - should this be done by MediaHelper? + filename = IOHelper.SafeFileName(filename); + if (string.IsNullOrWhiteSpace(filename)) return; + filename = filename.ToLower(); // fixme - er... why? + + MediaHelper.SetUploadFile(content, propertyTypeAlias, filename, filestream); } - private static void SetFileOnContent(IContentBase content, string propertyTypeAlias, string filename, Stream fileStream) + public static string StoreFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string oldpath) { - var property = content.Properties.FirstOrDefault(x => x.Alias == propertyTypeAlias); - if (property == null) - return; - - //TODO: ALl of this naming logic needs to be put into the ImageHelper and then we need to change FileUploadPropertyValueEditor to do the same! - - var numberedFolder = MediaSubfolderCounter.Current.Increment(); - var fileName = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories - ? Path.Combine(numberedFolder.ToString(CultureInfo.InvariantCulture), filename) - : numberedFolder + "-" + filename; - - var extension = Path.GetExtension(filename).Substring(1).ToLowerInvariant(); - - //the file size is the length of the stream in bytes - var fileSize = fileStream.Length; - - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - fs.AddFile(fileName, fileStream); - - //Check if file supports resizing and create thumbnails - var supportsResizing = UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(extension); - - //the config section used to auto-fill properties - IImagingAutoFillUploadField uploadFieldConfigNode = null; - - //Check for auto fill of additional properties - if (UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties != null) - { - uploadFieldConfigNode = UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties - .FirstOrDefault(x => x.Alias == propertyTypeAlias); - - } - - if (supportsResizing) - { - //get the original image from the original stream - if (fileStream.CanSeek) fileStream.Seek(0, 0); - using (var originalImage = Image.FromStream(fileStream)) - { - var additionalSizes = new List(); - - //Look up Prevalues for this upload datatype - if it is an upload datatype - get additional configured sizes - if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias) - { - //Get Prevalues by the DataType's Id: property.PropertyType.DataTypeId - var values = ApplicationContext.Current.Services.DataTypeService.GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId); - var thumbnailSizes = values.FirstOrDefault(); - //Additional thumbnails configured as prevalues on the DataType - if (thumbnailSizes != null) - { - foreach (var thumb in thumbnailSizes.Split(new[] { ";", "," }, StringSplitOptions.RemoveEmptyEntries)) - { - int thumbSize; - if (thumb != "" && int.TryParse(thumb, out thumbSize)) - { - additionalSizes.Add(thumbSize); - } - } - } - } - - ImageHelper.GenerateMediaThumbnails(fs, fileName, extension, originalImage, additionalSizes); - - //while the image is still open, we'll check if we need to auto-populate the image properties - if (uploadFieldConfigNode != null) - { - content.SetValue(uploadFieldConfigNode.WidthFieldAlias, originalImage.Width.ToString(CultureInfo.InvariantCulture)); - content.SetValue(uploadFieldConfigNode.HeightFieldAlias, originalImage.Height.ToString(CultureInfo.InvariantCulture)); - } - - } - } - - //if auto-fill is true, then fill the remaining, non-image properties - if (uploadFieldConfigNode != null) - { - content.SetValue(uploadFieldConfigNode.LengthFieldAlias, fileSize.ToString(CultureInfo.InvariantCulture)); - content.SetValue(uploadFieldConfigNode.ExtensionFieldAlias, extension); - } - - //Set the value of the property to that of the uploaded file's url - property.Value = fs.GetUrl(fileName); + var propertyType = content.GetContentType() + .CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (propertyType == null) throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + "."); + return MediaHelper.StoreFile(content, propertyType, filename, filestream, oldpath); } #endregion diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 8ead6da5f8..da093f792a 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -25,6 +25,21 @@ namespace Umbraco.Core.Models private string _content; internal Func GetFileContent { get; set; } + // whether to use whatever already exists on filesystem + internal bool _useExistingContent; + + /// + /// Indicates that the file should use whatever content already + /// exists on the filesystem which manages the file, bypassing content + /// management entirely. + /// + /// Only for the next save. Is resetted when the file is saved. + public void UseExistingContent() + { + _useExistingContent = true; + _content = null; // force content to be loaded + } + protected File(string path, Func getFileContent = null) { _path = SanitizePath(path); diff --git a/src/Umbraco.Core/Models/IFile.cs b/src/Umbraco.Core/Models/IFile.cs index de900c50ec..0d1cf24cdd 100644 --- a/src/Umbraco.Core/Models/IFile.cs +++ b/src/Umbraco.Core/Models/IFile.cs @@ -39,6 +39,14 @@ namespace Umbraco.Core.Models /// string Content { get; set; } + /// + /// Indicates that the file should use whatever content already + /// exists on the filesystem which manages the file, bypassing content + /// management entirely. + /// + /// Only for the next save. Is resetted when the file is saved. + void UseExistingContent(); + /// /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs index ad6939f847..147e0b40f8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs @@ -59,8 +59,15 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Gets the content of a template as a stream. /// - /// The filesystem path to the template. + /// The filesystem path to the template. /// The content of the template. - Stream GetFileStream(string path); + Stream GetFileStream(string filepath); + + /// + /// Sets the content of a template. + /// + /// The filesystem path to the template. + /// The content of the template. + void SetFile(string filepath, Stream content); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index a2f7c841fc..da18a16598 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -202,29 +202,7 @@ namespace Umbraco.Core.Persistence.Repositories template.Path = nodeDto.Path; //now do the file work - - if (DetermineTemplateRenderingEngine(entity) == RenderingEngine.Mvc) - { - var result = _viewHelper.CreateView(template, true); - if (result != entity.Content) - { - entity.Content = result; - //re-persist it... though we don't really care about the templates in the db do we??!! - dto.Design = result; - Database.Update(dto); - } - } - else - { - var result = _masterPageHelper.CreateMasterPage(template, this, true); - if (result != entity.Content) - { - entity.Content = result; - //re-persist it... though we don't really care about the templates in the db do we??!! - dto.Design = result; - Database.Update(dto); - } - } + SaveFile(template, dto); template.ResetDirtyProperties(); @@ -274,29 +252,7 @@ namespace Umbraco.Core.Persistence.Repositories template.IsMasterTemplate = axisDefs.Any(x => x.ParentId == dto.NodeId); //now do the file work - - if (DetermineTemplateRenderingEngine(entity) == RenderingEngine.Mvc) - { - var result = _viewHelper.UpdateViewFile(entity, originalAlias); - if (result != entity.Content) - { - entity.Content = result; - //re-persist it... though we don't really care about the templates in the db do we??!! - dto.Design = result; - Database.Update(dto); - } - } - else - { - var result = _masterPageHelper.UpdateMasterPageFile(entity, originalAlias, this); - if (result != entity.Content) - { - entity.Content = result; - //re-persist it... though we don't really care about the templates in the db do we??!! - dto.Design = result; - Database.Update(dto); - } - } + SaveFile((Template) entity, dto, originalAlias); entity.ResetDirtyProperties(); @@ -305,6 +261,38 @@ namespace Umbraco.Core.Persistence.Repositories template.GetFileContent = file => GetFileContent((Template) file, false); } + private void SaveFile(Template template, TemplateDto dto, string originalAlias = null) + { + string content; + + if (template._useExistingContent) + { + content = _viewHelper.GetFileContents(template); // BUT the template does not exist yet?! + template._useExistingContent = false; // reset + } + else + { + if (DetermineTemplateRenderingEngine(template) == RenderingEngine.Mvc) + { + content = originalAlias == null + ? _viewHelper.CreateView(template, true) + : _viewHelper.UpdateViewFile(template, originalAlias); + } + else + { + content = originalAlias == null + ? _masterPageHelper.CreateMasterPage(template, this, true) + : _masterPageHelper.UpdateMasterPageFile(template, originalAlias, this); + } + } + + template.Content = content; + + if (dto.Design == content) return; + dto.Design = content; + Database.Update(dto); // though... we don't care about the db value really??!! + } + protected override void PersistDeletedItem(ITemplate entity) { var deletes = GetDeleteClauses().ToArray(); @@ -475,9 +463,19 @@ namespace Umbraco.Core.Persistence.Repositories } } - public Stream GetFileStream(string filename) + public Stream GetFileStream(string filepath) { - var ext = Path.GetExtension(filename); + return GetFileSystem(filepath).OpenFile(filepath); + } + + public void SetFile(string filepath, Stream content) + { + GetFileSystem(filepath).AddFile(filepath, content, true); + } + + private IFileSystem GetFileSystem(string filepath) + { + var ext = Path.GetExtension(filepath); IFileSystem fs; switch (ext) { @@ -491,7 +489,7 @@ namespace Umbraco.Core.Persistence.Repositories default: throw new Exception("Unsupported extension " + ext + "."); } - return fs.OpenFile(filename); + return fs; } #region Implementation of ITemplateRepository diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 17ba21802e..5c2aaff930 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -515,11 +515,19 @@ namespace Umbraco.Core.Services } } - public Stream GetTemplateFileStream(string path) + public Stream GetTemplateFileStream(string filepath) { using (var repository = _repositoryFactory.CreateTemplateRepository(_dataUowProvider.GetUnitOfWork())) { - return repository.GetFileStream(path); + return repository.GetFileStream(filepath); + } + } + + public void SetTemplateFile(string filepath, Stream content) + { + using (var repository = _repositoryFactory.CreateTemplateRepository(_dataUowProvider.GetUnitOfWork())) + { + repository.SetFile(filepath, content); } } diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index 45aa8d03dc..ef608850c0 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -229,8 +229,15 @@ namespace Umbraco.Core.Services /// /// Gets the content of a template as a stream. /// - /// The filesystem path to the template. + /// The filesystem path to the template. /// The content of the template. - Stream GetTemplateFileStream(string path); + Stream GetTemplateFileStream(string filepath); + + /// + /// Sets the content of a template. + /// + /// The filesystem path to the template. + /// The content of the template. + void SetTemplateFile(string filepath, Stream content); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 863c5728b3..40f8c86ced 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -374,8 +374,29 @@ namespace Umbraco.Core.Services /// /// Gets the content of a media as a stream. /// - /// The filesystem path to the media. + /// The filesystem path to the media. /// The content of the media. - Stream GetMediaFileStream(string path); + Stream GetMediaFileStream(string filepath); + + /// + /// Sets the content of a media. + /// + /// The filesystem path to the media. + /// The content of the media. + void SetMediaFile(string filepath, Stream content); + + /// + /// Deletes a media file and all thumbnails. + /// + /// The filesystem path to the media. + void DeleteMediaFile(string filepath); + + /// + /// Generates thumbnails. + /// + /// The filesystem-relative path to the original image. + /// The property type. + /// This should be obsoleted, we should not generate thumbnails. + void GenerateThumbnails(string filepath, PropertyType propertyType); } } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 431eb52c8d..19bf01f991 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Media; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; @@ -1258,10 +1259,27 @@ namespace Umbraco.Core.Services } } - public Stream GetMediaFileStream(string path) + public Stream GetMediaFileStream(string filepath) { - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - return fs.OpenFile(path); + return MediaHelper.FileSystem.OpenFile(filepath); + } + + public void SetMediaFile(string filepath, Stream stream) + { + MediaHelper.FileSystem.AddFile(filepath, stream, true); + } + + public void DeleteMediaFile(string filepath) + { + MediaHelper.FileSystem.DeleteFile(filepath, true); + } + + public void GenerateThumbnails(string filepath, PropertyType propertyType) + { + using (var filestream = MediaHelper.FileSystem.OpenFile(filepath)) + { + ImageHelper.GenerateThumbnails(MediaHelper.FileSystem, filestream, filepath, propertyType); + } } #region Event Handlers diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 997f57b0ca..776a090884 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -356,6 +356,8 @@ + + @@ -482,7 +484,6 @@ - @@ -531,7 +532,6 @@ - diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index bf9f2056b3..ec9763b678 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -100,6 +100,12 @@ namespace Umbraco.Web.Editors if (files.Any()) { d.Add("files", files); + // add extra things needed to figure out where to put the files + // fixme - every entity should have a Guid when created - would that be breaking? + if (contentItem.PersistedContent.Key == Guid.Empty) + contentItem.PersistedContent.Key = Guid.NewGuid(); + d.Add("cuid", contentItem.PersistedContent.Key); + d.Add("puid", dboProperty.PropertyType.Key); } var data = new ContentPropertyData(p.Value, p.PreValues, d); diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web/Editors/ImagesController.cs index 39960317b1..ce55d16e95 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web/Editors/ImagesController.cs @@ -33,17 +33,13 @@ namespace Umbraco.Web.Editors { var media = Services.MediaService.GetById(mediaId); if (media == null) - { return Request.CreateResponse(HttpStatusCode.NotFound); - } + var imageProp = media.Properties[Constants.Conventions.Media.File]; if (imageProp == null) - { return Request.CreateResponse(HttpStatusCode.NotFound); - } var imagePath = imageProp.Value.ToString(); - return GetBigThumbnail(imagePath); } @@ -57,10 +53,9 @@ namespace Umbraco.Web.Editors /// public HttpResponseMessage GetBigThumbnail(string originalImagePath) { - if (string.IsNullOrWhiteSpace(originalImagePath)) - return Request.CreateResponse(HttpStatusCode.OK); - - return GetResized(originalImagePath, 500, "big-thumb"); + return string.IsNullOrWhiteSpace(originalImagePath) + ? Request.CreateResponse(HttpStatusCode.OK) + : GetResized(originalImagePath, 500, "big-thumb"); } /// @@ -76,17 +71,13 @@ namespace Umbraco.Web.Editors { var media = Services.MediaService.GetById(mediaId); if (media == null) - { return new HttpResponseMessage(HttpStatusCode.NotFound); - } + var imageProp = media.Properties[Constants.Conventions.Media.File]; if (imageProp == null) - { return new HttpResponseMessage(HttpStatusCode.NotFound); - } var imagePath = imageProp.Value.ToString(); - return GetResized(imagePath, width); } @@ -111,63 +102,43 @@ namespace Umbraco.Web.Editors /// /// /// - /// + /// /// - private HttpResponseMessage GetResized(string imagePath, int width, string suffix) + private HttpResponseMessage GetResized(string imagePath, int width, string sizeName) { - var mediaFileSystem = FileSystemProviderManager.Current.GetFileSystemProvider(); + var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); var ext = Path.GetExtension(imagePath); - //we need to check if it is an image by extension - if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(ext.TrimStart('.')) == false) - { + // we need to check if it is an image by extension + if (ImageHelper.IsImageFile(ext) == false) return Request.CreateResponse(HttpStatusCode.NotFound); - } - var thumbFilePath = imagePath.TrimEnd(ext) + "_" + suffix + ".jpg"; - var fullOrgPath = mediaFileSystem.GetFullPath(mediaFileSystem.GetRelativePath(imagePath)); - var fullNewPath = mediaFileSystem.GetFullPath(mediaFileSystem.GetRelativePath(thumbFilePath)); - var thumbIsNew = mediaFileSystem.FileExists(fullNewPath) == false; - if (thumbIsNew) + var resizedPath = imagePath.TrimEnd(ext) + "_" + sizeName + ".jpg"; + var generate = fs.FileExists(resizedPath) == false; + if (generate) { - //we need to generate it - if (mediaFileSystem.FileExists(fullOrgPath) == false) - { + // we need to generate it - if we have a source + if (fs.FileExists(imagePath) == false) return Request.CreateResponse(HttpStatusCode.NotFound); - } - using (var fileStream = mediaFileSystem.OpenFile(fullOrgPath)) + using (var fileStream = fs.OpenFile(imagePath)) + using (var originalImage = Image.FromStream(fileStream)) { - if (fileStream.CanSeek) fileStream.Seek(0, 0); - using (var originalImage = Image.FromStream(fileStream)) - { - //If it is bigger, then do the resize - if (originalImage.Width >= width && originalImage.Height >= width) - { - ImageHelper.GenerateThumbnail( - originalImage, - width, - fullNewPath, - "jpg", - mediaFileSystem); - } - else - { - //just return the original image - fullNewPath = fullOrgPath; - } - - } + // if original image is bigger than requested size, then resize, else return original image + if (originalImage.Width >= width && originalImage.Height >= width) + ImageHelper.GenerateResizedAt(fs, originalImage, resizedPath, width); + else + resizedPath = imagePath; } } var result = Request.CreateResponse(HttpStatusCode.OK); //NOTE: That we are not closing this stream as the framework will do that for us, if we try it will // fail. See http://stackoverflow.com/questions/9541351/returning-binary-file-from-controller-in-asp-net-web-api - var stream = mediaFileSystem.OpenFile(fullNewPath); + var stream = fs.OpenFile(resizedPath); if (stream.CanSeek) stream.Seek(0, 0); result.Content = new StreamContent(stream); - result.Headers.Date = mediaFileSystem.GetLastModified(imagePath); + result.Headers.Date = fs.GetLastModified(imagePath); result.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg"); return result; } diff --git a/src/Umbraco.Web/MediaPropertyExtensions.cs b/src/Umbraco.Web/MediaPropertyExtensions.cs deleted file mode 100644 index cc6d018174..0000000000 --- a/src/Umbraco.Web/MediaPropertyExtensions.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; -using Umbraco.Core.Models; - -namespace Umbraco.Web -{ - internal static class MediaPropertyExtensions - { - - internal static void AutoPopulateFileMetaDataProperties(this IContentBase model, string propertyAlias, string relativefilePath = null) - { - var mediaFileSystem = FileSystemProviderManager.Current.GetFileSystemProvider(); - var uploadFieldConfigNode = - UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties - .FirstOrDefault(x => x.Alias == propertyAlias); - - if (uploadFieldConfigNode != null && model.Properties.Contains(propertyAlias)) - { - if (relativefilePath == null) - relativefilePath = model.GetValue(propertyAlias); - - //now we need to check if there is a path - if (!string.IsNullOrEmpty(relativefilePath)) - { - var fullPath = mediaFileSystem.GetFullPath(mediaFileSystem.GetRelativePath(relativefilePath)); - var umbracoFile = new UmbracoMediaFile(fullPath); - FillProperties(uploadFieldConfigNode, model, umbracoFile); - } - else - { - //for now I'm just resetting this - ResetProperties(uploadFieldConfigNode, model); - } - } - } - - internal static void ResetFileMetaDataProperties(this IContentBase content, IImagingAutoFillUploadField uploadFieldConfigNode) - { - if (uploadFieldConfigNode == null) throw new ArgumentNullException("uploadFieldConfigNode"); - ResetProperties(uploadFieldConfigNode, content); - } - - private static void ResetProperties(IImagingAutoFillUploadField uploadFieldConfigNode, IContentBase content) - { - if (content.Properties.Contains(uploadFieldConfigNode.WidthFieldAlias)) - content.Properties[uploadFieldConfigNode.WidthFieldAlias].Value = string.Empty; - - if (content.Properties.Contains(uploadFieldConfigNode.HeightFieldAlias)) - content.Properties[uploadFieldConfigNode.HeightFieldAlias].Value = string.Empty; - - if (content.Properties.Contains(uploadFieldConfigNode.LengthFieldAlias)) - content.Properties[uploadFieldConfigNode.LengthFieldAlias].Value = string.Empty; - - if (content.Properties.Contains(uploadFieldConfigNode.ExtensionFieldAlias)) - content.Properties[uploadFieldConfigNode.ExtensionFieldAlias].Value = string.Empty; - } - - - internal static void PopulateFileMetaDataProperties(this IContentBase content, IImagingAutoFillUploadField uploadFieldConfigNode, string relativeFilePath) - { - if (uploadFieldConfigNode == null) throw new ArgumentNullException("uploadFieldConfigNode"); - if (relativeFilePath.IsNullOrWhiteSpace() == false) - { - var mediaFileSystem = FileSystemProviderManager.Current.GetFileSystemProvider(); - var fullPath = mediaFileSystem.GetFullPath(mediaFileSystem.GetRelativePath(relativeFilePath)); - var umbracoFile = new UmbracoMediaFile(fullPath); - FillProperties(uploadFieldConfigNode, content, umbracoFile); - } - else - { - //for now I'm just resetting this since we cant detect a file - ResetProperties(uploadFieldConfigNode, content); - } - } - - private static void FillProperties(IImagingAutoFillUploadField uploadFieldConfigNode, IContentBase content, UmbracoMediaFile um) - { - if (uploadFieldConfigNode == null) throw new ArgumentNullException("uploadFieldConfigNode"); - if (content == null) throw new ArgumentNullException("content"); - if (um == null) throw new ArgumentNullException("um"); - var size = um.SupportsResizing ? (Size?)um.GetDimensions() : null; - - if (content.Properties.Contains(uploadFieldConfigNode.WidthFieldAlias)) - content.Properties[uploadFieldConfigNode.WidthFieldAlias].Value = size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty; - - if (content.Properties.Contains(uploadFieldConfigNode.HeightFieldAlias)) - content.Properties[uploadFieldConfigNode.HeightFieldAlias].Value = size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty; - - if (content.Properties.Contains(uploadFieldConfigNode.LengthFieldAlias)) - content.Properties[uploadFieldConfigNode.LengthFieldAlias].Value = um.Length; - - if (content.Properties.Contains(uploadFieldConfigNode.ExtensionFieldAlias)) - content.Properties[uploadFieldConfigNode.ExtensionFieldAlias].Value = um.Extension; - } - - - } -} diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index 72beb09e69..bf1086513a 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -2,18 +2,11 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Drawing; -using System.Globalization; +using System.IO; using System.Linq; -using System.Text.RegularExpressions; -using System.Xml; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using umbraco.cms.businesslogic.Files; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; +using Umbraco.Core.Media; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; @@ -23,35 +16,55 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.UploadFieldAlias, "File upload", "fileupload", Icon = "icon-download-alt", Group = "media")] public class FileUploadPropertyEditor : PropertyEditor { - /// - /// We're going to bind to the MediaService Saving event so that we can populate the umbracoFile size, type, etc... label fields - /// if we find any attached to the current media item. - /// - /// - /// I think this kind of logic belongs on this property editor, I guess it could exist elsewhere but it all has to do with the upload field. - /// + // The FileUploadPropertyEditor properties own files and as such must manage these files, + // so we are binding to events in order to make sure that + // - files are deleted when the owning content/media is + // - files are copied when the owning content is + // - populate the auto-fill properties when the owning content/media is saved + // + // NOTE: + // although some code fragments seem to want to support uploading multiple files, + // this is NOT a feature of the FileUploadPropertyEditor and is NOT supported + // + // auto-fill properties are recalculated EVERYTIME the content/media is saved, + // even if the property has NOT been modified (it could be the same filename but + // a different file) - this is accepted (auto-fill props should die) + // + // FIXME + // for some weird backward compatibility reasons, + // - media copy is not supported + // - auto-fill properties are not supported for content items + // - auto-fill runs on MediaService.Created which makes no sense (no properties yet) + static FileUploadPropertyEditor() { + MediaService.Created += MediaServiceCreated; // see above - makes no sense MediaService.Saving += MediaServiceSaving; - MediaService.Created += MediaServiceCreating; - ContentService.Copied += ContentServiceCopied; + //MediaService.Copied += MediaServiceCopied; // see above - missing - MediaService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(ServiceDeleted(args.DeletedEntities.Cast())); - MediaService.EmptiedRecycleBin += (sender, args) => - args.Files.AddRange(ServiceEmptiedRecycleBin(args.AllPropertyData)); - ContentService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.EmptiedRecycleBin += (sender, args) => - args.Files.AddRange(ServiceEmptiedRecycleBin(args.AllPropertyData)); - MemberService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.Copied += ContentServiceCopied; + //ContentService.Saving += ContentServiceSaving; // see above - missing + + MediaService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( + GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); + + MediaService.EmptiedRecycleBin += (sender, args) => args.Files.AddRange( + GetFilesToDelete(args.AllPropertyData.SelectMany(x => x.Value))); + + ContentService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( + GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); + + ContentService.EmptiedRecycleBin += (sender, args) => args.Files.AddRange( + GetFilesToDelete(args.AllPropertyData.SelectMany(x => x.Value))); + + MemberService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( + GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); } /// - /// Creates our custom value editor + /// Creates the corresponding property value editor. /// - /// + /// The corresponding property value editor. protected override PropertyValueEditor CreateValueEditor() { var baseEditor = base.CreateValueEditor(); @@ -59,123 +72,121 @@ namespace Umbraco.Web.PropertyEditors return new FileUploadPropertyValueEditor(baseEditor); } + /// + /// Creates the corresponding preValue editor. + /// + /// The corresponding preValue editor. protected override PreValueEditor CreatePreValueEditor() { return new FileUploadPreValueEditor(); } /// - /// Ensures any files associated are removed + /// Gets a value indicating whether a property is an upload field. /// - /// - static IEnumerable ServiceEmptiedRecycleBin(Dictionary> allPropertyData) + /// The property. + /// A value indicating whether to check that the property has a non-empty value. + /// A value indicating whether a property is an upload field, and (optionaly) has a non-empty value. + private static bool IsUploadField(Property property, bool ensureValue) { - var list = new List(); - //Get all values for any image croppers found - foreach (var uploadVal in allPropertyData - .SelectMany(x => x.Value) - .Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias) - .Select(x => x.Value) - .WhereNotNull()) - { - if (uploadVal.ToString().IsNullOrWhiteSpace() == false) - { - list.Add(uploadVal.ToString()); - } - } - return list; + if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias) + return false; + if (ensureValue == false) + return true; + return property.Value is string && string.IsNullOrWhiteSpace((string) property.Value) == false; } /// - /// Ensures any files associated are removed + /// Gets the files that need to be deleted when entities are deleted. /// - /// - static IEnumerable ServiceDeleted(IEnumerable deletedEntities) + /// The properties that were deleted. + static IEnumerable GetFilesToDelete(IEnumerable properties) { - var list = new List(); - foreach (var property in deletedEntities.SelectMany(deletedEntity => deletedEntity - .Properties - .Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias - && x.Value != null - && string.IsNullOrEmpty(x.Value.ToString()) == false))) - { - if (property.Value != null && property.Value.ToString().IsNullOrWhiteSpace() == false) - { - list.Add(property.Value.ToString()); - } - } - return list; + var fs = MediaHelper.FileSystem; + + return properties + .Where(x => IsUploadField(x, true)) + .Select(x => fs.GetRelativePath((string) x.Value)) + .ToList(); } /// - /// After the content is copied we need to check if there are files that also need to be copied + /// After a content has been copied, also copy uploaded files. /// - /// - /// - static void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs e) + /// The event sender. + /// The event arguments. + static void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs args) { - if (e.Original.Properties.Any(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)) + // get the upload field properties with a value + var properties = args.Original.Properties.Where(x => IsUploadField(x, true)); + + // copy files + var isUpdated = false; + var fs = MediaHelper.FileSystem; + foreach (var property in properties) { - bool isUpdated = false; - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - - //Loop through properties to check if the content contains media that should be deleted - foreach (var property in e.Original.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias - && x.Value != null - && string.IsNullOrEmpty(x.Value.ToString()) == false)) - { - if (fs.FileExists(fs.GetRelativePath(property.Value.ToString()))) - { - var currentPath = fs.GetRelativePath(property.Value.ToString()); - var propertyId = e.Copy.Properties.First(x => x.Alias == property.Alias).Id; - var newPath = fs.GetRelativePath(propertyId, System.IO.Path.GetFileName(currentPath)); - - fs.CopyFile(currentPath, newPath); - e.Copy.SetValue(property.Alias, fs.GetUrl(newPath)); - - //Copy thumbnails - foreach (var thumbPath in fs.GetThumbnails(currentPath)) - { - var newThumbPath = fs.GetRelativePath(propertyId, System.IO.Path.GetFileName(thumbPath)); - fs.CopyFile(thumbPath, newThumbPath); - } - isUpdated = true; - } - } - - if (isUpdated) - { - //need to re-save the copy with the updated path value - sender.Save(e.Copy); - } + var sourcePath = fs.GetRelativePath((string) property.Value); + var copyPath = MediaHelper.CopyFile(args.Copy, property.PropertyType, sourcePath); + args.Copy.SetValue(property.Alias, fs.GetUrl(copyPath)); + isUpdated = true; } + + // if updated, re-save the copy with the updated value + if (isUpdated) + sender.Save(args.Copy); } - static void MediaServiceCreating(IMediaService sender, Core.Events.NewEventArgs e) + /// + /// After a media has been created, auto-fill the properties. + /// + /// The event sender. + /// The event arguments. + static void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs args) { - AutoFillProperties(e.Entity); + AutoFillProperties(args.Entity); } - static void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs e) + /// + /// After a media has been saved, auto-fill the properties. + /// + /// The event sender. + /// The event arguments. + static void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs args) { - foreach (var m in e.SavedEntities) + foreach (var entity in args.SavedEntities) + AutoFillProperties(entity); + } + + /// + /// After a content item has been saved, auto-fill the properties. + /// + /// The event sender. + /// The event arguments. + static void ContentServiceSaving(IContentService sender, Core.Events.SaveEventArgs args) + { + foreach (var entity in args.SavedEntities) + AutoFillProperties(entity); + } + + /// + /// Auto-fill properties (or clear). + /// + /// The content. + static void AutoFillProperties(IContentBase content) + { + var properties = content.Properties.Where(x => IsUploadField(x, false)); + var fs = MediaHelper.FileSystem; + + foreach (var property in properties) { - AutoFillProperties(m); - } - } + var autoFillConfig = UploadAutoFillProperties.GetConfig(property.Alias); + if (autoFillConfig == null) continue; - static void AutoFillProperties(IContentBase model) - { - foreach (var p in model.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)) - { - var uploadFieldConfigNode = - UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties - .FirstOrDefault(x => x.Alias == p.Alias); - - if (uploadFieldConfigNode != null) - { - model.PopulateFileMetaDataProperties(uploadFieldConfigNode, p.Value == null ? string.Empty : p.Value.ToString()); - } + var svalue = property.Value as string; + if (string.IsNullOrWhiteSpace(svalue)) + UploadAutoFillProperties.Reset(content, autoFillConfig); + else + UploadAutoFillProperties.Populate(content, autoFillConfig, fs.GetRelativePath(svalue)); } } @@ -185,7 +196,6 @@ namespace Umbraco.Web.PropertyEditors internal class FileUploadPreValueEditor : ValueListPreValueEditor { public FileUploadPreValueEditor() - : base() { var field = Fields.First(); field.Description = "Enter a max width/height for each thumbnail"; @@ -210,14 +220,12 @@ namespace Umbraco.Web.PropertyEditors { //there should only be one val var delimited = dictionary.First().Value.Value.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - for (var index = 0; index < delimited.Length; index++) - { - result.Add(new PreValue(index, delimited[index])); - } + var i = 0; + result.AddRange(delimited.Select(x => new PreValue(i++, x))); } //the items list will be a dictionary of it's id -> value we need to use the id for persistence for backwards compatibility - return new Dictionary { { "items", result.ToDictionary(x => x.Id, x => PreValueAsDictionary(x)) } }; + return new Dictionary { { "items", result.ToDictionary(x => x.Id, PreValueAsDictionary) } }; } private IDictionary PreValueAsDictionary(PreValue preValue) @@ -274,6 +282,5 @@ namespace Umbraco.Web.PropertyEditors } } } - } } diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs index e4ff4da1d6..f07928591e 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -20,159 +20,125 @@ using Umbraco.Core; namespace Umbraco.Web.PropertyEditors { /// - /// The editor for the file upload property editor + /// The value editor for the file upload property editor. /// internal class FileUploadPropertyValueEditor : PropertyValueEditorWrapper { - public FileUploadPropertyValueEditor(PropertyValueEditor wrapped) : base(wrapped) - { - } + public FileUploadPropertyValueEditor(PropertyValueEditor wrapped) + : base(wrapped) + { } /// - /// Overrides the deserialize value so that we can save the file accordingly + /// Converts the value received from the editor into the value can be stored in the database. /// - /// - /// This is value passed in from the editor. We normally don't care what the editorValue.Value is set to because - /// we are more interested in the files collection associated with it, however we do care about the value if we - /// are clearing files. By default the editorValue.Value will just be set to the name of the file (but again, we - /// just ignore this and deal with the file collection in editorValue.AdditionalData.ContainsKey("files") ) - /// - /// - /// The current value persisted for this property. This will allow us to determine if we want to create a new - /// file path or use the existing file path. - /// - /// + /// The value received from the editor. + /// The current value of the property + /// The converted value. + /// + /// The is used to re-use the folder, if possible. + /// The is value passed in from the editor. We normally don't care what + /// the editorValue.Value is set to because we are more interested in the files collection associated with it, + /// however we do care about the value if we are clearing files. By default the editorValue.Value will just + /// be set to the name of the file - but again, we just ignore this and deal with the file collection in + /// editorValue.AdditionalData.ContainsKey("files") + /// We only process ONE file. We understand that the current value may contain more than one file, + /// and that more than one file may be uploaded, so we take care of them all, but we only store ONE file. + /// Other places (FileUploadPropertyEditor...) do NOT deal with multiple files, and our logic for reusing + /// folders would NOT work, etc. + /// public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) { - if (currentValue == null) - { - currentValue = string.Empty; - } + currentValue = currentValue ?? string.Empty; - //if the value is the same then just return the current value so we don't re-process everything - if (string.IsNullOrEmpty(currentValue.ToString()) == false && editorValue.Value == currentValue.ToString()) - { + // at that point, + // currentValue is either empty or "/media/path/to/img.jpg" + // editorValue.Value is { "clearFiles": true } or { "selectedFiles": "img1.jpg,img2.jpg" } + // comparing them makes little sense + + // check the editorValue value to see whether we need to clear files + var editorJsonValue = editorValue.Value as JObject; + var clears = editorJsonValue != null && editorJsonValue["clearFiles"] != null && editorJsonValue["clearFiles"].Value(); + var uploads = editorValue.AdditionalData.ContainsKey("files") && editorValue.AdditionalData["files"] is IEnumerable; + + // nothing = no changes, return what we have already (leave existing files intact) + if (clears == false && uploads == false) return currentValue; - } - //check the editorValue value to see if we need to clear the files or not. - var clear = false; - var json = editorValue.Value as JObject; - if (json != null && json["clearFiles"] != null && json["clearFiles"].Value()) + // get the current file paths + var fs = MediaHelper.FileSystem; + var currentPaths = currentValue.ToString() + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => fs.GetRelativePath(x)) // get the fs-relative path + .ToArray(); + + // if clearing, remove these files and return + if (clears) { - clear = json["clearFiles"].Value(); + foreach (var pathToRemove in currentPaths) + fs.DeleteFile(pathToRemove, true); + return string.Empty; // no more files } + + // ensure we have the required guids + if (editorValue.AdditionalData.ContainsKey("cuid") == false // for the content item + || editorValue.AdditionalData.ContainsKey("puid") == false) // and the property type + throw new Exception("Missing cuid/puid additional data."); + var cuido = editorValue.AdditionalData["cuid"]; + var puido = editorValue.AdditionalData["puid"]; + if ((cuido is Guid) == false || (puido is Guid) == false) + throw new Exception("Invalid cuid/puid additional data."); + var cuid = (Guid) cuido; + var puid = (Guid) puido; + if (cuid == Guid.Empty || puid == Guid.Empty) + throw new Exception("Invalid cuid/puid additional data."); - var currentPersistedValues = new string[] {}; - if (string.IsNullOrEmpty(currentValue.ToString()) == false) + // process the files + var files = ((IEnumerable) editorValue.AdditionalData["files"]).ToArray(); + + var newPaths = new List(); + const int maxLength = 1; // we only process ONE file + for (var i = 0; i < maxLength /*files.Length*/; i++) { - currentPersistedValues = currentValue.ToString().Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); - } + var file = files[i]; - var newValue = new List(); + // skip invalid files + if (UploadFileTypeValidator.ValidateFileExtension(file.FileName) == false) + continue; - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + // get the filepath + // in case we are using the old path scheme, try to re-use numbers (bah...) + var reuse = i < currentPaths.Length ? currentPaths[i] : null; // this would be WRONG with many files + var filepath = MediaHelper.GetMediaPath(file.FileName, reuse, cuid, puid); // fs-relative path - if (clear) - { - //Remove any files that are saved for this item - foreach (var toRemove in currentPersistedValues) + using (var filestream = File.OpenRead(file.TempFilePath)) { - fs.DeleteFile(fs.GetRelativePath(toRemove), true); - } - return ""; - } - - //check for any files - if (editorValue.AdditionalData.ContainsKey("files")) - { - var files = editorValue.AdditionalData["files"] as IEnumerable; - if (files != null) - { - //now we just need to move the files to where they should be - var filesAsArray = files.ToArray(); - //a list of all of the newly saved files so we can compare with the current saved files and remove the old ones - var savedFilePaths = new List(); - for (var i = 0; i < filesAsArray.Length; i++) + fs.AddFile(filepath, filestream, true); // must overwrite! + + var ext = fs.GetExtension(filepath); + if (ImageHelper.IsImageFile(ext)) { - var file = filesAsArray[i]; - - //don't continue if this is not allowed! - if (UploadFileTypeValidator.ValidateFileExtension(file.FileName) == false) - { - continue; - } - - //TODO: ALl of this naming logic needs to be put into the ImageHelper and then we need to change ContentExtensions to do the same! - - var currentPersistedFile = currentPersistedValues.Length >= (i + 1) - ? currentPersistedValues[i] - : ""; - - var name = IOHelper.SafeFileName(file.FileName.Substring(file.FileName.LastIndexOf(IOHelper.DirSepChar) + 1, file.FileName.Length - file.FileName.LastIndexOf(IOHelper.DirSepChar) - 1).ToLower()); - - var subfolder = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories - ? currentPersistedFile.Replace(fs.GetUrl("/"), "").Split('/')[0] - : currentPersistedFile.Substring(currentPersistedFile.LastIndexOf("/", StringComparison.Ordinal) + 1).Split('-')[0]; - - int subfolderId; - var numberedFolder = int.TryParse(subfolder, out subfolderId) - ? subfolderId.ToString(CultureInfo.InvariantCulture) - : MediaSubfolderCounter.Current.Increment().ToString(CultureInfo.InvariantCulture); - - var fileName = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories - ? Path.Combine(numberedFolder, name) - : numberedFolder + "-" + name; - - using (var fileStream = File.OpenRead(file.TempFilePath)) - { - var umbracoFile = UmbracoMediaFile.Save(fileStream, fileName); - - if (umbracoFile.SupportsResizing) - { - var additionalSizes = new List(); - //get the pre-vals value - var thumbs = editorValue.PreValues.FormatAsDictionary(); - if (thumbs.Any()) - { - var thumbnailSizes = thumbs.First().Value.Value; - // additional thumbnails configured as prevalues on the DataType - foreach (var thumb in thumbnailSizes.Split(new[] { ";", "," }, StringSplitOptions.RemoveEmptyEntries)) - { - int thumbSize; - if (thumb == "" || int.TryParse(thumb, out thumbSize) == false) continue; - additionalSizes.Add(thumbSize); - } - } - - using (var image = Image.FromStream(fileStream)) - { - ImageHelper.GenerateMediaThumbnails(fs, fileName, umbracoFile.Extension, image, additionalSizes); - } - - } - newValue.Add(umbracoFile.Url); - //add to the saved paths - savedFilePaths.Add(umbracoFile.Url); - } - //now remove the temp file - File.Delete(file.TempFilePath); + var preValues = editorValue.PreValues.FormatAsDictionary(); + var sizes = preValues.Any() ? preValues.First().Value.Value : string.Empty; + using (var image = Image.FromStream(filestream)) + ImageHelper.GenerateThumbnails(fs, image, filepath, sizes); } - - //Remove any files that are no longer saved for this item - foreach (var toRemove in currentPersistedValues.Except(savedFilePaths)) - { - fs.DeleteFile(fs.GetRelativePath(toRemove), true); - } - - return string.Join(",", newValue); + // all related properties (auto-fill) are managed by FileUploadPropertyEditor + // when the content is saved (through event handlers) + + newPaths.Add(filepath); } } - //if we've made it here, we had no files to save and we were not clearing anything so just persist the same value we had before - return currentValue; + // remove all temp files + foreach (var file in files) + File.Delete(file.TempFilePath); + + // remove files that are not there anymore + foreach (var pathToRemove in currentPaths.Except(newPaths)) + fs.DeleteFile(pathToRemove, true); + + return string.Join(",", newPaths.Select(x => fs.GetUrl(x))); } - } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index 9858217a5b..a913d2cb44 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -2,13 +2,11 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Media; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; @@ -18,48 +16,62 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.ImageCropperAlias, "Image Cropper", "imagecropper", ValueType = "JSON", HideLabel = false, Group="media", Icon="icon-crop")] public class ImageCropperPropertyEditor : PropertyEditor { + // The ImageCropperPropertyEditor properties own files and as such must manage these files, + // so we are binding to events in order to make sure that + // - files are deleted when the owning content/media is + // - files are copied when the owning content is (NOTE: not supporting media copy here!) + // - populate the auto-fill properties when files are changing + // - populate the auto-fill properties when the owning content/media is saved + // + // NOTE: + // uploading multiple files is NOT a feature of the ImageCropperPropertyEditor + // + // auto-fill properties are recalculated EVERYTIME the content/media is saved, + // even if the property has NOT been modified (it could be the same filename but + // a different file) - this is accepted (auto-fill props should die) + // + // FIXME + // for some weird backward compatibility reasons, + // - media copy is not supported + // - auto-fill properties are not supported for content items + // - auto-fill runs on MediaService.Created which makes no sense (no properties yet) - /// - /// We're going to bind to the MediaService Saving event so that we can populate the umbracoFile size, type, etc... label fields - /// if we find any attached to the current media item. - /// - /// - /// I think this kind of logic belongs on this property editor, I guess it could exist elsewhere but it all has to do with the cropper. - /// static ImageCropperPropertyEditor() { + MediaService.Created += MediaServiceCreated; // see above - makes no sense MediaService.Saving += MediaServiceSaving; - MediaService.Created += MediaServiceCreated; - ContentService.Copied += ContentServiceCopied; + //MediaService.Copied += MediaServiceCopied; // see above - missing - MediaService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(ServiceDeleted(args.DeletedEntities.Cast())); - MediaService.EmptiedRecycleBin += (sender, args) => - args.Files.AddRange(ServiceEmptiedRecycleBin(args.AllPropertyData)); - ContentService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.EmptiedRecycleBin += (sender, args) => - args.Files.AddRange(ServiceEmptiedRecycleBin(args.AllPropertyData)); - MemberService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.Copied += ContentServiceCopied; + //ContentService.Saving += ContentServiceSaving; // see above - missing + + MediaService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( + GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); + + MediaService.EmptiedRecycleBin += (sender, args) => args.Files.AddRange( + GetFilesToDelete(args.AllPropertyData.SelectMany(x => x.Value))); + + ContentService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( + GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); + + ContentService.EmptiedRecycleBin += (sender, args) => args.Files.AddRange( + GetFilesToDelete(args.AllPropertyData.SelectMany(x => x.Value))); + + MemberService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( + GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); + } + + // preValues + private IDictionary _internalPreValues; + public override IDictionary DefaultPreValues + { + get { return _internalPreValues; } + set { _internalPreValues = value; } } /// - /// Creates our custom value editor + /// Initializes a new instance of the class. /// - /// - protected override PropertyValueEditor CreateValueEditor() - { - var baseEditor = base.CreateValueEditor(); - return new ImageCropperPropertyValueEditor(baseEditor); - } - - protected override PreValueEditor CreatePreValueEditor() - { - return new ImageCropperPreValueEditor(); - } - - public ImageCropperPropertyEditor() { _internalPreValues = new Dictionary @@ -70,194 +82,191 @@ namespace Umbraco.Web.PropertyEditors } /// - /// Ensures any files associated are removed + /// Creates the corresponding property value editor. /// - /// - static IEnumerable ServiceEmptiedRecycleBin(Dictionary> allPropertyData) + /// The corresponding property value editor. + protected override PropertyValueEditor CreateValueEditor() { - var list = new List(); - //Get all values for any image croppers found - foreach (var cropperVal in allPropertyData - .SelectMany(x => x.Value) - .Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias) - .Select(x => x.Value) - .WhereNotNull()) - { - JObject json; - try - { - json = JsonConvert.DeserializeObject(cropperVal.ToString()); - } - catch (Exception ex) - { - LogHelper.Error("An error occurred parsing the value stored in the image cropper value: " + cropperVal, ex); - continue; - } - - if (json["src"] != null && json["src"].ToString().IsNullOrWhiteSpace() == false) - { - list.Add(json["src"].ToString()); - } - } - return list; + var baseEditor = base.CreateValueEditor(); + return new ImageCropperPropertyValueEditor(baseEditor); } /// - /// Ensures any files associated are removed + /// Creates the corresponding preValue editor. /// - /// - static IEnumerable ServiceDeleted(IEnumerable deletedEntities) + /// The corresponding preValue editor. + protected override PreValueEditor CreatePreValueEditor() { - var list = new List(); - foreach (var property in deletedEntities.SelectMany(deletedEntity => deletedEntity - .Properties - .Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias - && x.Value != null - && string.IsNullOrEmpty(x.Value.ToString()) == false))) - { - JObject json; - try - { - json = JsonConvert.DeserializeObject(property.Value.ToString()); - } - catch (Exception ex) - { - LogHelper.Error("An error occurred parsing the value stored in the image cropper value: " + property.Value, ex); - continue; - } - - if (json["src"] != null && json["src"].ToString().IsNullOrWhiteSpace() == false) - { - list.Add(json["src"].ToString()); - } - } - return list; + return new ImageCropperPreValueEditor(); } /// - /// After the content is copied we need to check if there are files that also need to be copied + /// Gets a value indicating whether a property is an image cropper field. /// - /// - /// - static void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs e) + /// The property. + /// A value indicating whether to check that the property has a non-empty value. + /// A value indicating whether a property is an image cropper field, and (optionaly) has a non-empty value. + private static bool IsCropperField(Property property, bool ensureValue) { - if (e.Original.Properties.Any(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias)) + if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias) + return false; + if (ensureValue == false) + return true; + return property.Value is string && string.IsNullOrWhiteSpace((string)property.Value) == false; + } + + /// + /// Parses the property value into a json object. + /// + /// The property value. + /// A value indicating whether to log the error. + /// The json object corresponding to the property value. + /// In case of an error, optionaly logs the error and returns null. + private static JObject GetJObject(string value, bool writeLog) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + + try { - bool isUpdated = false; - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - - //Loop through properties to check if the content contains media that should be deleted - foreach (var property in e.Original.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias - && x.Value != null - && string.IsNullOrEmpty(x.Value.ToString()) == false)) - { - JObject json; - try - { - json = JsonConvert.DeserializeObject(property.Value.ToString()); - } - catch (Exception ex) - { - LogHelper.Error("An error occurred parsing the value stored in the image cropper value: " + property.Value.ToString(), ex); - continue; - } - - if (json["src"] != null && json["src"].ToString().IsNullOrWhiteSpace() == false) - { - if (fs.FileExists(fs.GetRelativePath(json["src"].ToString()))) - { - var currentPath = fs.GetRelativePath(json["src"].ToString()); - var propertyId = e.Copy.Properties.First(x => x.Alias == property.Alias).Id; - var newPath = fs.GetRelativePath(propertyId, System.IO.Path.GetFileName(currentPath)); - - fs.CopyFile(currentPath, newPath); - json["src"] = fs.GetUrl(newPath); - e.Copy.SetValue(property.Alias, json.ToString()); - - //Copy thumbnails - foreach (var thumbPath in fs.GetThumbnails(currentPath)) - { - var newThumbPath = fs.GetRelativePath(propertyId, System.IO.Path.GetFileName(thumbPath)); - fs.CopyFile(thumbPath, newThumbPath); - } - isUpdated = true; - } - } - - - } - - if (isUpdated) - { - //need to re-save the copy with the updated path value - sender.Save(e.Copy); - } + return JsonConvert.DeserializeObject(value); + } + catch (Exception ex) + { + if (writeLog) + LogHelper.Error("Could not parse image cropper value \"" + value + "\"", ex); + return null; } } - static void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs e) + /// + /// Gets the files that need to be deleted when entities are deleted. + /// + /// The properties that were deleted. + static IEnumerable GetFilesToDelete(IEnumerable properties) { - AutoFillProperties(e.Entity); + var fs = MediaHelper.FileSystem; + + return properties.Where(x => IsCropperField(x, true)).Select(x => + { + var jo = GetJObject((string) x.Value, true); + if (jo == null || jo["src"] == null) return null; + var src = jo["src"].Value(); + return string.IsNullOrWhiteSpace(src) ? null : fs.GetRelativePath(src); + }).WhereNotNull(); } - static void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs e) + /// + /// After a content has been copied, also copy uploaded files. + /// + /// The event sender. + /// The event arguments. + static void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs args) { - foreach (var m in e.SavedEntities) + // get the image cropper field properties with a value + var properties = args.Original.Properties.Where(x => IsCropperField(x, true)); + + // copy files + var isUpdated = false; + var fs = MediaHelper.FileSystem; + foreach (var property in properties) { - AutoFillProperties(m); + var jo = GetJObject((string) property.Value, true); + if (jo == null || jo["src"] == null) continue; + + var src = jo["src"].Value(); + if (string.IsNullOrWhiteSpace(src)) continue; + + var sourcePath = fs.GetRelativePath(src); + var copyPath = MediaHelper.CopyFile(args.Copy, property.PropertyType, sourcePath); + jo["src"] = fs.GetUrl(copyPath); + args.Copy.SetValue(property.Alias, jo.ToString()); + isUpdated = true; } + + // if updated, re-save the copy with the updated value + if (isUpdated) + sender.Save(args.Copy); } - static void AutoFillProperties(IContentBase model) + /// + /// After a media has been created, auto-fill the properties. + /// + /// The event sender. + /// The event arguments. + static void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs args) { - foreach (var p in model.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias)) - { - var uploadFieldConfigNode = - UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties - .FirstOrDefault(x => x.Alias == p.Alias); + AutoFillProperties(args.Entity); + } - if (uploadFieldConfigNode != null) + /// + /// After a media has been saved, auto-fill the properties. + /// + /// The event sender. + /// The event arguments. + static void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs args) + { + foreach (var entity in args.SavedEntities) + AutoFillProperties(entity); + } + + /// + /// After a content item has been saved, auto-fill the properties. + /// + /// The event sender. + /// The event arguments. + static void ContentServiceSaving(IContentService sender, Core.Events.SaveEventArgs args) + { + foreach (var entity in args.SavedEntities) + AutoFillProperties(entity); + } + + /// + /// Auto-fill properties (or clear). + /// + /// The content. + static void AutoFillProperties(IContentBase content) + { + var properties = content.Properties.Where(x => IsCropperField(x, false)); + var fs = MediaHelper.FileSystem; + + foreach (var property in properties) + { + var autoFillConfig = UploadAutoFillProperties.GetConfig(property.Alias); + if (autoFillConfig == null) continue; + + var svalue = property.Value as string; + if (string.IsNullOrWhiteSpace(svalue)) { - if (p.Value != null) - { - JObject json = null; - try - { - json = JObject.Parse((string)p.Value); - } - catch (JsonException) - { - //note: we are swallowing this exception because in some cases a normal string/non json value will be passed in which will just be the - // file path like /media/23454/hello.jpg - // This will happen everytime an image is uploaded via the folder browser and we don't really want to pollute the log since it's not actually - // a problem and we take care of this below. - // see: http://issues.umbraco.org/issue/U4-4756 - } - if (json != null && json["src"] != null) - { - model.PopulateFileMetaDataProperties(uploadFieldConfigNode, json["src"].Value()); - } - else if (p.Value is string) - { - var src = p.Value == null ? string.Empty : p.Value.ToString(); - var config = ApplicationContext.Current.Services.DataTypeService.GetPreValuesByDataTypeId(p.PropertyType.DataTypeDefinitionId).FirstOrDefault(); - var crops = string.IsNullOrEmpty(config) == false ? config : "[]"; - p.Value = "{src: '" + p.Value + "', crops: " + crops + "}"; - //Only provide the source path, not the whole JSON value - model.PopulateFileMetaDataProperties(uploadFieldConfigNode, src); - } - } - else - model.ResetFileMetaDataProperties(uploadFieldConfigNode); + UploadAutoFillProperties.Reset(content, autoFillConfig); + continue; } - } - } - private IDictionary _internalPreValues; - public override IDictionary DefaultPreValues - { - get { return _internalPreValues; } - set { _internalPreValues = value; } + var jo = GetJObject(svalue, false); + string src; + if (jo == null) + { + // so we have a non-empty string value that cannot be parsed into a json object + // see http://issues.umbraco.org/issue/U4-4756 + // it can happen when an image is uploaded via the folder browser, in which case + // the property value will be the file source eg '/media/23454/hello.jpg' and we + // are fixing that anomaly here - does not make any sense at all but... bah... + var config = ApplicationContext.Current.Services.DataTypeService + .GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId).FirstOrDefault(); + var crops = string.IsNullOrWhiteSpace(config) ? "[]" : config; + src = svalue; + property.Value = "{src: '" + svalue + "', crops: " + crops + "}"; + } + else + { + src = jo["src"] == null ? null : jo["src"].Value(); + } + + if (src == null) + UploadAutoFillProperties.Reset(content, autoFillConfig); + else + UploadAutoFillProperties.Populate(content, autoFillConfig, fs.GetRelativePath(src)); + } } internal class ImageCropperPreValueEditor : PreValueEditor diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index 7c84ff7026..aa46bd9843 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using System.Drawing; using System.Globalization; using System.IO; using System.Linq; @@ -16,145 +17,160 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Models.ContentEditing; +using File = System.IO.File; namespace Umbraco.Web.PropertyEditors { + /// + /// The value editor for the image cropper property editor. + /// internal class ImageCropperPropertyValueEditor : PropertyValueEditorWrapper { public ImageCropperPropertyValueEditor(PropertyValueEditor wrapped) : base(wrapped) - { - - } - + { } /// - /// Overrides the deserialize value so that we can save the file accordingly + /// Converts the value received from the editor into the value can be stored in the database. /// - /// + /// The value received from the editor. + /// The current value of the property + /// The converted value. + /// + /// The is used to re-use the folder, if possible. + /// FIXME this is ?! /// This is value passed in from the editor. We normally don't care what the editorValue.Value is set to because /// we are more interested in the files collection associated with it, however we do care about the value if we /// are clearing files. By default the editorValue.Value will just be set to the name of the file (but again, we /// just ignore this and deal with the file collection in editorValue.AdditionalData.ContainsKey("files") ) - /// - /// - /// The current value persisted for this property. This will allow us to determine if we want to create a new - /// file path or use the existing file path. - /// - /// + /// + /// public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) { + var fs = MediaHelper.FileSystem; - - string oldFile = string.Empty; - string newFile = string.Empty; - JObject newJson = null; - JObject oldJson = null; - - //get the old src path - if (currentValue != null && string.IsNullOrEmpty(currentValue.ToString()) == false) + // get the current path + var currentPath = string.Empty; + try { - try - { - oldJson = JObject.Parse(currentValue.ToString()); - } - catch (Exception ex) - { - //for some reason the value is invalid so continue as if there was no value there - LogHelper.WarnWithException("Could not parse current db value to a JObject", ex); - } - - if (oldJson != null && oldJson["src"] != null) - { - oldFile = oldJson["src"].Value(); - } + var svalue = currentValue as string; + var currentJson = string.IsNullOrWhiteSpace(svalue) ? null : JObject.Parse(svalue); + if (currentJson != null && currentJson["src"] != null) + currentPath = currentJson["src"].Value(); } + catch (Exception ex) + { + // for some reason the value is invalid so continue as if there was no value there + LogHelper.WarnWithException("Could not parse current db value to a JObject.", ex); + } + if (string.IsNullOrWhiteSpace(currentPath) == false) + currentPath = fs.GetRelativePath(currentPath); - //get the new src path + // get the new json and path + JObject editorJson = null; + var editorFile = string.Empty; if (editorValue.Value != null) { - newJson = editorValue.Value as JObject; - if (newJson != null && newJson["src"] != null) - { - newFile = newJson["src"].Value(); - } + editorJson = editorValue.Value as JObject; + if (editorJson != null && editorJson["src"] != null) + editorFile = editorJson["src"].Value(); } - //compare old and new src path - //if not alike, that means we have a new file, or delete the current one... - if (string.IsNullOrEmpty(newFile) || editorValue.AdditionalData.ContainsKey("files")) + // ensure we have the required guids + if (editorValue.AdditionalData.ContainsKey("cuid") == false // for the content item + || editorValue.AdditionalData.ContainsKey("puid") == false) // and the property type + throw new Exception("Missing cuid/puid additional data."); + var cuido = editorValue.AdditionalData["cuid"]; + var puido = editorValue.AdditionalData["puid"]; + if ((cuido is Guid) == false || (puido is Guid) == false) + throw new Exception("Invalid cuid/puid additional data."); + var cuid = (Guid)cuido; + var puid = (Guid)puido; + if (cuid == Guid.Empty || puid == Guid.Empty) + throw new Exception("Invalid cuid/puid additional data."); + + // editorFile is empty whenever a new file is being uploaded + // or when the file is cleared (in which case editorJson is null) + // else editorFile contains the unchanged value + + var uploads = editorValue.AdditionalData.ContainsKey("files") && editorValue.AdditionalData["files"] is IEnumerable; + var files = uploads ? ((IEnumerable)editorValue.AdditionalData["files"]).ToArray() : new ContentItemFile[0]; + var file = uploads ? files.FirstOrDefault() : null; + + if (file == null) // not uploading a file { - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - - //if we have an existing file, delete it - if (string.IsNullOrEmpty(oldFile) == false) - fs.DeleteFile(fs.GetRelativePath(oldFile), true); - else - oldFile = string.Empty; - - //if we have a new file, add it to the media folder and set .src - - if (editorValue.AdditionalData.ContainsKey("files")) + // if editorFile is empty then either there was nothing to begin with, + // or it has been cleared and we need to remove the file - else the + // value is unchanged. + if (string.IsNullOrWhiteSpace(editorFile) && string.IsNullOrWhiteSpace(currentPath) == false) { - var files = editorValue.AdditionalData["files"] as IEnumerable; - if (files != null && files.Any()) - { - var file = files.First(); - - if (UploadFileTypeValidator.ValidateFileExtension(file.FileName)) - { - //create name and folder number - var name = IOHelper.SafeFileName(file.FileName.Substring(file.FileName.LastIndexOf(IOHelper.DirSepChar) + 1, file.FileName.Length - file.FileName.LastIndexOf(IOHelper.DirSepChar) - 1).ToLower()); - - //try to reuse the folder number from the current file - var subfolder = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories - ? oldFile.Replace(fs.GetUrl("/"), "").Split('/')[0] - : oldFile.Substring(oldFile.LastIndexOf("/", StringComparison.Ordinal) + 1).Split('-')[0]; - - //if we dont find one, create a new one - int subfolderId; - var numberedFolder = int.TryParse(subfolder, out subfolderId) - ? subfolderId.ToString(CultureInfo.InvariantCulture) - : MediaSubfolderCounter.Current.Increment().ToString(CultureInfo.InvariantCulture); - - //set a file name or full path - var fileName = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories - ? Path.Combine(numberedFolder, name) - : numberedFolder + "-" + name; - - //save file and assign to the json - using (var fileStream = System.IO.File.OpenRead(file.TempFilePath)) - { - var umbracoFile = UmbracoMediaFile.Save(fileStream, fileName); - newJson["src"] = umbracoFile.Url; - - return newJson.ToString(); - } - } - } + fs.DeleteFile(currentPath, true); + return null; // clear } + + return editorJson == null ? null : editorJson.ToString(); // unchanged } - //incase we submit nothing back - if (editorValue.Value == null) + // process the file + var filepath = editorJson == null ? null : ProcessFile(editorValue, file, fs, currentPath, cuid, puid); + + // remove all temp files + foreach (var f in files) + File.Delete(f.TempFilePath); + + // remove current file if replaced + if (currentPath != filepath && string.IsNullOrWhiteSpace(currentPath) == false) + fs.DeleteFile(currentPath, true); + + // update json and return + if (editorJson == null) return null; + editorJson["src"] = filepath == null ? string.Empty : fs.GetUrl(filepath); + return editorJson.ToString(); + } + + private string ProcessFile(ContentPropertyData editorValue, ContentItemFile file, IFileSystem fs, string currentPath, Guid cuid, Guid puid) + { + // process the file + // no file, invalid file, reject change + if (UploadFileTypeValidator.ValidateFileExtension(file.FileName) == false) return null; - return editorValue.Value.ToString(); + // get the filepath + // in case we are using the old path scheme, try to re-use numbers (bah...) + var filepath = MediaHelper.GetMediaPath(file.FileName, currentPath, cuid, puid); // fs-relative path + + using (var filestream = File.OpenRead(file.TempFilePath)) + { + fs.AddFile(filepath, filestream, true); // must overwrite! + + var ext = fs.GetExtension(filepath); + if (ImageHelper.IsImageFile(ext)) + { + var preValues = editorValue.PreValues.FormatAsDictionary(); + var sizes = preValues.Any() ? preValues.First().Value.Value : string.Empty; + using (var image = Image.FromStream(filestream)) + ImageHelper.GenerateThumbnails(fs, image, filepath, sizes); + } + + // all related properties (auto-fill) are managed by ImageCropperPropertyEditor + // when the content is saved (through event handlers) + } + + return filepath; } - - public override string ConvertDbToString(Property property, PropertyType propertyType, Core.Services.IDataTypeService dataTypeService) { - if(property.Value == null || string.IsNullOrEmpty(property.Value.ToString())) + if (property.Value == null || string.IsNullOrEmpty(property.Value.ToString())) return null; - //if we dont have a json structure, we will get it from the property type + // if we dont have a json structure, we will get it from the property type var val = property.Value.ToString(); if (val.DetectIsJson()) return val; + // more magic here ;-( var config = dataTypeService.GetPreValuesByDataTypeId(propertyType.DataTypeDefinitionId).FirstOrDefault(); - var crops = !string.IsNullOrEmpty(config) ? config : "[]"; + var crops = string.IsNullOrEmpty(config) ? "[]" : config; var newVal = "{src: '" + val + "', crops: " + crops + "}"; return newVal; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 68c7f5c592..d037d79f8c 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -456,7 +456,6 @@ - diff --git a/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs b/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs index 12f151876d..b80b2892a4 100644 --- a/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs +++ b/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs @@ -73,7 +73,7 @@ namespace umbraco.cms.businesslogic.datatype int subfolderId; var numberedFolder = int.TryParse(subfolder, out subfolderId) ? subfolderId.ToString(CultureInfo.InvariantCulture) - : MediaSubfolderCounter.Current.Increment().ToString(CultureInfo.InvariantCulture); + : MediaHelper.GetNextFolder(); var fileName = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories ? Path.Combine(numberedFolder, name) From 02fd789f206870382368ed7baab577b3bbcaad89 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 16 Dec 2015 17:16:44 +0100 Subject: [PATCH 004/413] Sanitize UniqueId --- src/Umbraco.Core/Media/MediaHelper.cs | 6 ----- src/Umbraco.Core/Models/Content.cs | 12 --------- src/Umbraco.Core/Models/ContentType.cs | 13 ---------- src/Umbraco.Core/Models/ContentTypeBase.cs | 5 ---- .../Models/ContentTypeCompositionBase.cs | 3 --- src/Umbraco.Core/Models/DataTypeDefinition.cs | 8 ------ src/Umbraco.Core/Models/DictionaryItem.cs | 11 -------- src/Umbraco.Core/Models/EntityBase/Entity.cs | 6 +++-- src/Umbraco.Core/Models/Media.cs | 12 --------- src/Umbraco.Core/Models/MediaType.cs | 21 ---------------- src/Umbraco.Core/Models/Member.cs | 5 +--- src/Umbraco.Core/Models/MemberGroup.cs | 12 --------- src/Umbraco.Core/Models/MemberType.cs | 21 ---------------- src/Umbraco.Core/Models/PublicAccessEntry.cs | 12 --------- src/Umbraco.Core/Models/PublicAccessRule.cs | 12 --------- src/Umbraco.Core/Models/Rdbms/NodeDto.cs | 6 ----- .../Models/Rdbms/PropertyTypeDto.cs | 6 ----- .../Models/Rdbms/PropertyTypeGroupDto.cs | 6 ----- src/Umbraco.Core/Models/Template.cs | 12 --------- .../Factories/PropertyGroupFactory.cs | 8 ++---- .../Repositories/ContentTypeBaseRepository.cs | 1 + .../Repositories/EntityContainerRepository.cs | 7 ++---- .../Services/Importing/PackageImportTests.cs | 25 +++++++++++++++++++ .../Editors/ContentControllerBase.cs | 3 --- .../controls/ContentTypeControlNew.ascx.cs | 3 +-- 25 files changed, 36 insertions(+), 200 deletions(-) diff --git a/src/Umbraco.Core/Media/MediaHelper.cs b/src/Umbraco.Core/Media/MediaHelper.cs index 55bbd80d3b..5367455a43 100644 --- a/src/Umbraco.Core/Media/MediaHelper.cs +++ b/src/Umbraco.Core/Media/MediaHelper.cs @@ -165,9 +165,6 @@ namespace Umbraco.Core.Media if (string.IsNullOrWhiteSpace(oldpath)) fs.DeleteFile(oldpath, true); - // sanity check - fixme - every entity should be created with a proper Guid - if (content.Key == Guid.Empty) content.Key = Guid.NewGuid(); - // get the filepath, store the data // use oldpath as "prevpath" to try and reuse the folder, in original number-based scheme var filepath = GetMediaPath(filename, oldpath, content.Key, propertyType.Key); @@ -201,9 +198,6 @@ namespace Umbraco.Core.Media var fs = FileSystem; if (fs.FileExists(sourcepath) == false) return null; - // sanity check - fixme - every entity should be created with a proper Guid - if (content.Key == Guid.Empty) content.Key = Guid.NewGuid(); - // get the filepath var filename = Path.GetFileName(sourcepath); var filepath = GetMediaPath(filename, content.Key, propertyType.Key); diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 2238260611..b8e96c2793 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -340,18 +340,6 @@ namespace Umbraco.Core.Models } } - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() - { - base.AddingEntity(); - - if(Key == Guid.Empty) - Key = Guid.NewGuid(); - } - /// /// Method to call when Entity is being updated /// diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 355d724fbe..b6021e6538 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -137,19 +137,6 @@ namespace Umbraco.Core.Models return result; } - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() - { - base.AddingEntity(); - - if (Key == Guid.Empty) - Key = Guid.NewGuid(); - } - - /// /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset /// diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index a83defb7b5..7218a2421d 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -452,11 +452,6 @@ namespace Umbraco.Core.Models /// Returns True if PropertyType was added, otherwise False public bool AddPropertyType(PropertyType propertyType) { - if (propertyType.HasIdentity == false) - { - propertyType.Key = Guid.NewGuid(); - } - if (PropertyTypeExists(propertyType.Alias) == false) { _propertyTypes.Add(propertyType); diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 5ac21885d7..cba0ed07b7 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -210,9 +210,6 @@ namespace Umbraco.Core.Models /// Returns True if PropertyType was added, otherwise False public override bool AddPropertyType(PropertyType propertyType, string propertyGroupName) { - if (propertyType.HasIdentity == false) - propertyType.Key = Guid.NewGuid(); - // ensure no duplicate alias - over all composition properties if (PropertyTypeExists(propertyType.Alias)) return false; diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index a2ca15e46a..4c12d6fbef 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -265,13 +265,5 @@ namespace Umbraco.Core.Models { get { return _additionalData; } } - - internal override void AddingEntity() - { - base.AddingEntity(); - - if(Key == default(Guid)) - Key = Guid.NewGuid(); - } } } diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 7e59726c80..367e897c35 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -87,16 +87,5 @@ namespace Umbraco.Core.Models enumerable => enumerable.GetHashCode())); } } - - /// - /// Method to call before inserting a new entity in the db - /// - internal override void AddingEntity() - { - base.AddingEntity(); - - Key = Guid.NewGuid(); - } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index eeacb771a9..c4838dfd0a 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -62,9 +62,9 @@ namespace Umbraco.Core.Models.EntityBase { get { + // if an entity does NOT have a UniqueId yet, assign one now if (_key == Guid.Empty) - return _id.ToGuid(); - + _key = Guid.NewGuid(); return _key; } set @@ -136,6 +136,7 @@ namespace Umbraco.Core.Models.EntityBase { _hasIdentity = false; _id = default(int); + _key = Guid.Empty; } /// @@ -242,6 +243,7 @@ namespace Umbraco.Core.Models.EntityBase { //Memberwise clone on Entity will work since it doesn't have any deep elements // for any sub class this will work for standard properties as well that aren't complex object's themselves. + var ignored = this.Key; // ensure that 'this' has a key, before cloning var clone = (Entity)MemberwiseClone(); //ensure the clone has it's own dictionaries clone.ResetChangeTrackingCollections(); diff --git a/src/Umbraco.Core/Models/Media.cs b/src/Umbraco.Core/Models/Media.cs index 4f922d28cf..a7e794a400 100644 --- a/src/Umbraco.Core/Models/Media.cs +++ b/src/Umbraco.Core/Models/Media.cs @@ -118,17 +118,5 @@ namespace Umbraco.Core.Models //The Media Recycle Bin Id is -21 so we correct that here ParentId = parentId == -20 ? -21 : parentId; } - - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() - { - base.AddingEntity(); - - if (Key == Guid.Empty) - Key = Guid.NewGuid(); - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/MediaType.cs b/src/Umbraco.Core/Models/MediaType.cs index 052e231136..c8e2915afd 100644 --- a/src/Umbraco.Core/Models/MediaType.cs +++ b/src/Umbraco.Core/Models/MediaType.cs @@ -38,26 +38,5 @@ namespace Umbraco.Core.Models : base(parent, alias) { } - - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() - { - base.AddingEntity(); - - if (Key == Guid.Empty) - Key = Guid.NewGuid(); - } - - /// - /// Method to call when Entity is being updated - /// - /// Modified Date is set and a new Version guid is set - internal override void UpdatingEntity() - { - base.UpdatingEntity(); - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 7788fadf75..70c21e4307 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -509,11 +509,8 @@ namespace Umbraco.Core.Models { base.AddingEntity(); - if (Key == Guid.Empty) - { - Key = Guid.NewGuid(); + if (ProviderUserKey == null) ProviderUserKey = Key; - } } /// diff --git a/src/Umbraco.Core/Models/MemberGroup.cs b/src/Umbraco.Core/Models/MemberGroup.cs index e52448a11d..ff7e05be9e 100644 --- a/src/Umbraco.Core/Models/MemberGroup.cs +++ b/src/Umbraco.Core/Models/MemberGroup.cs @@ -60,17 +60,5 @@ namespace Umbraco.Core.Models } public IDictionary AdditionalData { get; private set; } - - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() - { - base.AddingEntity(); - - if (Key == Guid.Empty) - Key = Guid.NewGuid(); - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 74a879be81..9000a33b54 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -131,26 +131,5 @@ namespace Umbraco.Core.Models MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); } } - - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() - { - base.AddingEntity(); - - if (Key == Guid.Empty) - Key = Guid.NewGuid(); - } - - /// - /// Method to call when Entity is being updated - /// - /// Modified Date is set and a new Version guid is set - internal override void UpdatingEntity() - { - base.UpdatingEntity(); - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index 27d1cd2121..9e12d6ab57 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -108,18 +108,6 @@ namespace Umbraco.Core.Models } } - /// - /// Method to call on entity saved when first added - /// - internal override void AddingEntity() - { - if (Key == default(Guid)) - { - Key = Guid.NewGuid(); - } - base.AddingEntity(); - } - [DataMember] public int LoginNodeId { diff --git a/src/Umbraco.Core/Models/PublicAccessRule.cs b/src/Umbraco.Core/Models/PublicAccessRule.cs index 484652e8bd..c785d028d0 100644 --- a/src/Umbraco.Core/Models/PublicAccessRule.cs +++ b/src/Umbraco.Core/Models/PublicAccessRule.cs @@ -28,18 +28,6 @@ namespace Umbraco.Core.Models public Guid AccessEntryId { get; internal set; } - /// - /// Method to call on entity saved when first added - /// - internal override void AddingEntity() - { - if (Key == default(Guid)) - { - Key = Guid.NewGuid(); - } - base.AddingEntity(); - } - public string RuleValue { get { return _ruleValue; } diff --git a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs index 7003c58e77..c5fac092df 100644 --- a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs @@ -10,12 +10,6 @@ namespace Umbraco.Core.Models.Rdbms [ExplicitColumns] internal class NodeDto { - public NodeDto() - { - //By default, always generate a new guid - UniqueId = Guid.NewGuid(); - } - public const int NodeIdSeed = 1050; [Column("id")] diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs index 74a6d34289..2be4b24157 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs @@ -10,12 +10,6 @@ namespace Umbraco.Core.Models.Rdbms [ExplicitColumns] internal class PropertyTypeDto { - public PropertyTypeDto() - { - //by default always create a new guid - UniqueId = Guid.NewGuid(); - } - [Column("id")] [PrimaryKeyColumn(IdentitySeed = 50)] public int Id { get; set; } diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs index 42abd9ed49..68de420a97 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs @@ -11,12 +11,6 @@ namespace Umbraco.Core.Models.Rdbms [ExplicitColumns] internal class PropertyTypeGroupDto { - public PropertyTypeGroupDto() - { - //by default always create a new guid - UniqueId = Guid.NewGuid(); - } - [Column("id")] [PrimaryKeyColumn(IdentitySeed = 12)] public int Id { get; set; } diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 4aca88f286..1c8b44b674 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -118,18 +118,6 @@ namespace Umbraco.Core.Models return ApplicationContext.Current.Services.FileService.DetermineTemplateRenderingEngine(this); } - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() - { - base.AddingEntity(); - - if (Key == Guid.Empty) - Key = Guid.NewGuid(); - } - public void SetMasterTemplate(ITemplate masterTemplate) { if (masterTemplate == null) diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index 5b2cad3415..2dfb996bb3 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -99,9 +99,7 @@ namespace Umbraco.Core.Persistence.Factories ContentTypeNodeId = _contentTypeId, SortOrder = propertyGroup.SortOrder, Text = propertyGroup.Name, - UniqueId = propertyGroup.Key == Guid.Empty - ? Guid.NewGuid() - : propertyGroup.Key + UniqueId = propertyGroup.Key }; if (propertyGroup.HasIdentity) @@ -124,9 +122,7 @@ namespace Umbraco.Core.Persistence.Factories Name = propertyType.Name, SortOrder = propertyType.SortOrder, ValidationRegExp = propertyType.ValidationRegExp, - UniqueId = propertyType.Key == Guid.Empty - ? Guid.NewGuid() - : propertyType.Key + UniqueId = propertyType.Key }; if (tabId != default(int)) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 5ed0f62124..21748e6ffd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -466,6 +466,7 @@ AND umbracoNode.id <> @id", propType.DataTypeDefinitionId = dto.DataTypeId; propType.Description = dto.Description; propType.Id = dto.Id; + propType.Key = dto.UniqueId; propType.Name = dto.Name; propType.Mandatory = dto.Mandatory; propType.SortOrder = dto.SortOrder; diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index 6fd5045e04..fcdd2bd3d2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -174,12 +174,10 @@ namespace Umbraco.Core.Persistence.Repositories Path = path, SortOrder = 0, Text = entity.Name, - UserId = entity.CreatorId + UserId = entity.CreatorId, + UniqueId = entity.Key }; - if (entity.Key != default(Guid)) - nodeDto.UniqueId = entity.Key; - // insert, get the id, update the path with the id var id = Convert.ToInt32(Database.Insert(nodeDto)); nodeDto.Path = nodeDto.Path + "," + nodeDto.NodeId; @@ -187,7 +185,6 @@ namespace Umbraco.Core.Persistence.Repositories // refresh the entity entity.Id = id; - entity.Key = nodeDto.UniqueId; entity.Path = nodeDto.Path; entity.Level = nodeDto.Level; entity.SortOrder = 0; diff --git a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs index 215f24e375..04f785c82d 100644 --- a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs +++ b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs @@ -74,6 +74,31 @@ namespace Umbraco.Tests.Services.Importing Assert.That(uBlogsyLanding.CompositionPropertyGroups.Count(), Is.EqualTo(5)); } + [Test] + public void PackagingService_Can_Import_Inherited_ContentTypes_And_Verify_PropertyTypes_UniqueIds() + { + // Arrange + var strXml = ImportResources.InheritedDocTypes_Package; + var xml = XElement.Parse(strXml); + var dataTypeElement = xml.Descendants("DataTypes").First(); + var templateElement = xml.Descendants("Templates").First(); + var docTypeElement = xml.Descendants("DocumentTypes").First(); + var packagingService = ServiceContext.PackagingService; + + // Act + var dataTypes = packagingService.ImportDataTypeDefinitions(dataTypeElement); + var templates = packagingService.ImportTemplates(templateElement); + var contentTypes = packagingService.ImportContentTypes(docTypeElement); + + // Assert + var mRBasePage = contentTypes.First(x => x.Alias == "MRBasePage"); + foreach (var propertyType in mRBasePage.PropertyTypes) + { + var propertyTypeDto = this.DatabaseContext.Database.First("WHERE id = @id", new { id = propertyType.Id }); + Assert.AreEqual(propertyTypeDto.UniqueId, propertyType.Key); + } + } + [Test] public void PackagingService_Can_Import_Inherited_ContentTypes_And_Verify_PropertyGroups_And_PropertyTypes() { diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index ec9763b678..758356a8f8 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -101,9 +101,6 @@ namespace Umbraco.Web.Editors { d.Add("files", files); // add extra things needed to figure out where to put the files - // fixme - every entity should have a Guid when created - would that be breaking? - if (contentItem.PersistedContent.Key == Guid.Empty) - contentItem.PersistedContent.Key = Guid.NewGuid(); d.Add("cuid", contentItem.PersistedContent.Key); d.Add("puid", dboProperty.PropertyType.Key); } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index 24694ad706..83163b3b93 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -1081,8 +1081,7 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); Name = gpData.Name.Trim(), Mandatory = gpData.Mandatory, ValidationRegExp = gpData.Validation, - Description = gpData.Description, - Key = Guid.NewGuid() + Description = gpData.Description }; //gpData.Tab == 0 Generic Properties / No Group if (gpData.Tab == 0) From a05c71ec859342fe2b45ce474bad97c3ad21d69a Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 11 Dec 2015 17:22:47 +0100 Subject: [PATCH 005/413] Fix files and medias upload --- .../Media/UploadAutoFillProperties.cs | 20 ++++++++++++++----- src/Umbraco.Tests/Models/MediaXmlTest.cs | 11 ++++++++++ .../Querying/ContentTypeSqlMappingTests.cs | 8 ++++---- .../FileUploadPropertyEditor.cs | 2 +- .../ImageCropperPropertyEditor.cs | 2 +- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs index c7157c4eb6..553c994599 100644 --- a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs +++ b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs @@ -5,6 +5,7 @@ using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Core.Models; namespace Umbraco.Core.Media @@ -121,12 +122,21 @@ namespace Umbraco.Core.Media } else { - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - using (var filestream = fs.OpenFile(filepath)) + // if anything goes wrong, just reset the properties + try { - var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); - var size = ImageHelper.IsImageFile(extension) ? (Size?) ImageHelper.GetDimensions(filestream) : null; - SetProperties(content, autoFillConfig, size, filestream.Length, extension); + using (var filestream = MediaHelper.FileSystem.OpenFile(filepath)) + { + var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); + var size = ImageHelper.IsImageFile(extension) ? (Size?)ImageHelper.GetDimensions(filestream) : null; + SetProperties(content, autoFillConfig, size, filestream.Length, extension); + } + } + catch (Exception ex) + { + LogHelper.Error(typeof(UploadAutoFillProperties), "Could not populate upload auto-fill properties for file \"" + + filepath + "\".", ex); + ResetProperties(content, autoFillConfig); } } } diff --git a/src/Umbraco.Tests/Models/MediaXmlTest.cs b/src/Umbraco.Tests/Models/MediaXmlTest.cs index a5db651115..bd711d2191 100644 --- a/src/Umbraco.Tests/Models/MediaXmlTest.cs +++ b/src/Umbraco.Tests/Models/MediaXmlTest.cs @@ -9,6 +9,7 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using umbraco.editorControls.tinyMCE3; using umbraco.interfaces; +using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Models { @@ -44,9 +45,19 @@ namespace Umbraco.Tests.Models var mediaType = MockedContentTypes.CreateImageMediaType("image2"); ServiceContext.ContentTypeService.Save(mediaType); + // reference, so static ctor runs, so event handlers register + // and then, this will reset the width, height... because the file does not exist, of course ;-( + var ignored = new FileUploadPropertyEditor(); + var media = MockedMedia.CreateMediaImage(mediaType, -1); ServiceContext.MediaService.Save(media, 0); + // so we have to force-reset these values because the property editor has cleared them + media.SetValue(Constants.Conventions.Media.Width, "200"); + media.SetValue(Constants.Conventions.Media.Height, "200"); + media.SetValue(Constants.Conventions.Media.Bytes, "100"); + media.SetValue(Constants.Conventions.Media.Extension, "png"); + var nodeName = media.ContentType.Alias.ToSafeAliasWithForcingCheck(); var urlName = media.GetUrlSegment(); diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs index 916b6d3333..caf872a995 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs @@ -151,10 +151,10 @@ namespace Umbraco.Tests.Persistence.Querying DatabaseContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsContentType")))); DatabaseContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsPropertyTypeGroup")))); - DatabaseContext.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77776, ContentTypeNodeId = 99999, Text = "Group1", SortOrder = 1 }); - DatabaseContext.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77777, ContentTypeNodeId = 99999, Text = "Group2", SortOrder = 2 }); - DatabaseContext.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77778, ContentTypeNodeId = 99999, Text = "Group3", SortOrder = 3 }); - DatabaseContext.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77779, ContentTypeNodeId = 99999, Text = "Group4", SortOrder = 4 }); + DatabaseContext.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77776, UniqueId = 77776.ToGuid(), ContentTypeNodeId = 99999, Text = "Group1", SortOrder = 1 }); + DatabaseContext.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77777, UniqueId = 77777.ToGuid(), ContentTypeNodeId = 99999, Text = "Group2", SortOrder = 2 }); + DatabaseContext.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77778, UniqueId = 77778.ToGuid(), ContentTypeNodeId = 99999, Text = "Group3", SortOrder = 3 }); + DatabaseContext.Database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 77779, UniqueId = 77779.ToGuid(), ContentTypeNodeId = 99999, Text = "Group4", SortOrder = 4 }); DatabaseContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsPropertyTypeGroup")))); DatabaseContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsPropertyType")))); diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index bf1086513a..74882fd0e4 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -89,7 +89,7 @@ namespace Umbraco.Web.PropertyEditors /// A value indicating whether a property is an upload field, and (optionaly) has a non-empty value. private static bool IsUploadField(Property property, bool ensureValue) { - if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias) + if (property.PropertyType.PropertyEditorAlias != Constants.PropertyEditors.UploadFieldAlias) return false; if (ensureValue == false) return true; diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index a913d2cb44..bfbe578875 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -108,7 +108,7 @@ namespace Umbraco.Web.PropertyEditors /// A value indicating whether a property is an image cropper field, and (optionaly) has a non-empty value. private static bool IsCropperField(Property property, bool ensureValue) { - if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias) + if (property.PropertyType.PropertyEditorAlias != Constants.PropertyEditors.ImageCropperAlias) return false; if (ensureValue == false) return true; From 14adc2a3b36137b6f425bcdac0eec75efb5c52ac Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 26 Jan 2016 14:18:30 +0100 Subject: [PATCH 006/413] deploy-30 refactoring --- src/Umbraco.Core/IO/FileSystemExtensions.cs | 5 ++ src/Umbraco.Core/IO/FileSystemWrapper.cs | 2 + src/Umbraco.Core/IO/IFileSystem.cs | 4 +- src/Umbraco.Core/IO/MediaFileSystem.cs | 10 ++-- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 50 +++++++++---------- src/Umbraco.Core/IO/UmbracoMediaFile.cs | 8 +-- src/Umbraco.Core/Media/ImageHelper.cs | 41 +++++++++++++++ .../Repositories/TemplateRepository.cs | 34 ++++++------- src/Umbraco.Web/Editors/ImagesController.cs | 4 +- 9 files changed, 104 insertions(+), 54 deletions(-) diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index b71a4cf0c7..baa8c1d6d4 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -39,6 +39,11 @@ namespace Umbraco.Core.IO public static long GetSize(this IFileSystem fs, string path) { + // unwrap, eg MediaFileSystem is wrapping an IFileSystem + FileSystemWrapper w; + while ((w = fs as FileSystemWrapper) != null) + fs = w.Wrapped; + // no idea why GetSize is not part of IFileSystem, but // for physical file system we have way better & faster ways // to get the size, than to read the entire thing in memory! diff --git a/src/Umbraco.Core/IO/FileSystemWrapper.cs b/src/Umbraco.Core/IO/FileSystemWrapper.cs index 676626d069..db4ab115f6 100644 --- a/src/Umbraco.Core/IO/FileSystemWrapper.cs +++ b/src/Umbraco.Core/IO/FileSystemWrapper.cs @@ -23,6 +23,8 @@ namespace Umbraco.Core.IO _wrapped = wrapped; } + internal IFileSystem Wrapped { get { return _wrapped; } } + public IEnumerable GetDirectories(string path) { return _wrapped.GetDirectories(path); diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index b4e173b256..3e1c1e3527 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -16,10 +16,10 @@ namespace Umbraco.Core.IO void DeleteDirectory(string path, bool recursive); bool DirectoryExists(string path); - + void AddFile(string path, Stream stream); - void AddFile(string path, Stream stream, bool overrideExisting); + void AddFile(string path, Stream stream, bool overrideIfExists); IEnumerable GetFiles(string path); diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index face234970..b6ef6f7bf5 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -51,6 +51,7 @@ namespace Umbraco.Core.IO // MediaFileSystem is not just IFileSystem // etc + [Obsolete("", true)] public IEnumerable GetThumbnails(string path) { var parentDirectory = Path.GetDirectoryName(path); @@ -61,7 +62,8 @@ namespace Umbraco.Core.IO .ToList(); } - public void DeleteFile(string path, bool deleteThumbnails) + [Obsolete("", true)] + public void DeleteFile(string path, bool deleteThumbnails) { DeleteFile(path); @@ -71,13 +73,15 @@ namespace Umbraco.Core.IO DeleteThumbnails(path); } - public void DeleteThumbnails(string path) + [Obsolete("", true)] + public void DeleteThumbnails(string path) { GetThumbnails(path) .ForEach(DeleteFile); } - public void CopyThumbnails(string sourcePath, string targetPath) + [Obsolete("", true)] + public void CopyThumbnails(string sourcePath, string targetPath) { var targetPathBase = Path.GetDirectoryName(targetPath) ?? ""; foreach (var sourceThumbPath in GetThumbnails(sourcePath)) diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 7456fb29a4..6b07ff21f8 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -12,27 +12,26 @@ namespace Umbraco.Core.IO // eg "c:" or "c:\path\to\site" or "\\server\path" private readonly string _rootPath; - // _rootPath, with separators replaced by forward-slashes. + // _rootPath, but with separators replaced by forward-slashes + // eg "c:" or "c:/path/to/site" or "//server/path" + // (is used in GetRelativePath) private readonly string _rootPathFwd; - // the ??? url, using url separator chars, NOT ending with a separator - // eg "" (?) or "/Scripts" or ??? + // the relative url, using url separator chars, NOT ending with a separator + // eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path private readonly string _rootUrl; + // virtualRoot should be "~/path/to/root" eg "~/Views" + // the "~/" is mandatory. public PhysicalFileSystem(string virtualRoot) { if (virtualRoot == null) throw new ArgumentNullException("virtualRoot"); if (virtualRoot.StartsWith("~/") == false) throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'"); - _rootPath = IOHelper.MapPath(virtualRoot); - _rootPath = EnsureDirectorySeparatorChar(_rootPath); - _rootPath = _rootPath.TrimEnd(Path.DirectorySeparatorChar); + _rootPath = EnsureDirectorySeparatorChar(IOHelper.MapPath(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar); _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); - - _rootUrl = IOHelper.ResolveUrl(virtualRoot); - _rootUrl = EnsureUrlSeparatorChar(_rootUrl); - _rootUrl = _rootUrl.TrimEnd('/'); + _rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd('/'); } public PhysicalFileSystem(string rootPath, string rootUrl) @@ -47,19 +46,16 @@ namespace Umbraco.Core.IO throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'"); // rootPath should be... rooted, as in, it's a root path! - // but the test suite App.config cannot really "root" anything so we'll have to do it here - - //var localRoot = AppDomain.CurrentDomain.BaseDirectory; - var localRoot = IOHelper.GetRootDirectorySafe(); if (Path.IsPathRooted(rootPath) == false) + { + // but the test suite App.config cannot really "root" anything so we have to do it here + var localRoot = IOHelper.GetRootDirectorySafe(); rootPath = Path.Combine(localRoot, rootPath); + } - rootPath = EnsureDirectorySeparatorChar(rootPath); - _rootPath = rootPath.TrimEnd(Path.DirectorySeparatorChar); + _rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar); _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); - - rootUrl = EnsureUrlSeparatorChar(rootUrl); - _rootUrl = rootUrl.TrimEnd('/'); + _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd('/'); } /// @@ -152,7 +148,7 @@ namespace Umbraco.Core.IO { var fullPath = GetFullPath(path); var exists = File.Exists(fullPath); - if (exists && overrideExisting == false) + if (exists && overrideExisting == false) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); var directory = Path.GetDirectoryName(fullPath); @@ -261,13 +257,15 @@ namespace Umbraco.Core.IO // test url var path = fullPathOrUrl.Replace('\\', '/'); // ensure url separator char - if (IOHelper.PathStartsWith(path, _rootUrl, '/')) // if it starts with the root url... - return path.Substring(_rootUrl.Length) // strip it - .TrimStart('/'); // it's relative + // if it starts with the root url, strip it and trim the starting slash to make it relative + // eg "/Media/1234/img.jpg" => "1234/img.jpg" + if (IOHelper.PathStartsWith(path, _rootUrl, '/')) + return path.Substring(_rootUrl.Length).TrimStart('/'); - if (IOHelper.PathStartsWith(path, _rootPathFwd, '/')) // if it starts with the root url... - return path.Substring(_rootPathFwd.Length) // strip it - .TrimStart('/'); // it's relative + // if it starts with the root path, strip it and trim the starting slash to make it relative + // eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg" + if (IOHelper.PathStartsWith(path, _rootPathFwd, '/')) + return path.Substring(_rootPathFwd.Length).TrimStart('/'); // unchanged - what else? return path; diff --git a/src/Umbraco.Core/IO/UmbracoMediaFile.cs b/src/Umbraco.Core/IO/UmbracoMediaFile.cs index 17162bcfa8..27699baef4 100644 --- a/src/Umbraco.Core/IO/UmbracoMediaFile.cs +++ b/src/Umbraco.Core/IO/UmbracoMediaFile.cs @@ -111,7 +111,7 @@ namespace Umbraco.Core.IO { get { - if (_length != null) return _length.Value; + if (_length.HasValue) return _length.Value; _length = Exists ? _fs.GetSize(Path) : -1; return _length.Value; } @@ -129,7 +129,7 @@ namespace Umbraco.Core.IO public Size GetDimensions() { - if (_size != null) return _size.Value; + if (_size.HasValue) return _size.Value; if (_fs.FileExists(Path)) { @@ -157,12 +157,12 @@ namespace Umbraco.Core.IO return _fs.GetUrl(filepath); } - public string Resize(int maxWidthHeight, string filenameAddition) + public string Resize(int maxWidthHeight, string fileNameAddition) { if (Exists == false) return string.Empty; EnsureFileSupportsResizing(); - var filepath = Resize(-1, -1, maxWidthHeight, filenameAddition); + var filepath = Resize(-1, -1, maxWidthHeight, fileNameAddition); return _fs.GetUrl(filepath); } diff --git a/src/Umbraco.Core/Media/ImageHelper.cs b/src/Umbraco.Core/Media/ImageHelper.cs index 281a229d49..a9775321e8 100644 --- a/src/Umbraco.Core/Media/ImageHelper.cs +++ b/src/Umbraco.Core/Media/ImageHelper.cs @@ -89,6 +89,47 @@ namespace Umbraco.Core.Media return codec.MimeType; } + #region Manage thumbnails + + public static IEnumerable GetThumbnails(IFileSystem fs, string path) + { + var parentDirectory = Path.GetDirectoryName(path); + var extension = Path.GetExtension(path); + + return fs.GetFiles(parentDirectory) + .Where(x => x.StartsWith(path.TrimEnd(extension) + "_thumb") || x.StartsWith(path.TrimEnd(extension) + "_big-thumb")) + .ToList(); + } + + public static void DeleteFile(IFileSystem fs, string path, bool deleteThumbnails) + { + fs.DeleteFile(path); + + if (deleteThumbnails == false) + return; + + DeleteThumbnails(fs, path); + } + + public static void DeleteThumbnails(IFileSystem fs, string path) + { + GetThumbnails(fs, path) + .ForEach(fs.DeleteFile); + } + + public static void CopyThumbnails(IFileSystem fs, string sourcePath, string targetPath) + { + var targetPathBase = Path.GetDirectoryName(targetPath) ?? ""; + foreach (var sourceThumbPath in GetThumbnails(fs, sourcePath)) + { + var sourceThumbFilename = Path.GetFileName(sourceThumbPath) ?? ""; + var targetThumbPath = Path.Combine(targetPathBase, sourceThumbFilename); + fs.CopyFile(sourceThumbPath, targetThumbPath); + } + } + + #endregion + #region GenerateThumbnails public static IEnumerable GenerateThumbnails( diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 7addacdd87..cf5c883dc0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Persistence.Repositories _viewsFileSystem = viewFileSystem; _templateConfig = templateConfig; _viewHelper = new ViewHelper(_viewsFileSystem); - _masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); + _masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); } @@ -80,7 +80,7 @@ namespace Umbraco.Core.Persistence.Repositories if (dtos.Count == 0) return Enumerable.Empty(); - //look up the simple template definitions that have a master template assigned, this is used + //look up the simple template definitions that have a master template assigned, this is used // later to populate the template item's properties var childIds = (ids.Any() ? GetAxisDefinitions(dtos.ToArray()) @@ -90,7 +90,7 @@ namespace Umbraco.Core.Persistence.Repositories ParentId = x.NodeDto.ParentId, Name = x.Alias })).ToArray(); - + return dtos.Select(d => MapFromDto(d, childIds)); } @@ -104,7 +104,7 @@ namespace Umbraco.Core.Persistence.Repositories if (dtos.Count == 0) return Enumerable.Empty(); - //look up the simple template definitions that have a master template assigned, this is used + //look up the simple template definitions that have a master template assigned, this is used // later to populate the template item's properties var childIds = GetAxisDefinitions(dtos.ToArray()).ToArray(); @@ -311,14 +311,14 @@ namespace Umbraco.Core.Persistence.Repositories { var masterpageName = string.Concat(entity.Alias, ".master"); _masterpagesFileSystem.DeleteFile(masterpageName); - } + } } #endregion private IEnumerable GetAxisDefinitions(params TemplateDto[] templates) { - //look up the simple template definitions that have a master template assigned, this is used + //look up the simple template definitions that have a master template assigned, this is used // later to populate the template item's properties var childIdsSql = new Sql() .Select("nodeId,alias,parentID") @@ -327,7 +327,7 @@ namespace Umbraco.Core.Persistence.Repositories .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId) //lookup axis's .Where("umbracoNode." + SqlSyntax.GetQuotedColumnName("id") + " IN (@parentIds) OR umbracoNode.parentID IN (@childIds)", - new {parentIds = templates.Select(x => x.NodeDto.ParentId), childIds = templates.Select(x => x.NodeId)}); + new {parentIds = templates.Select(x => x.NodeDto.ParentId), childIds = templates.Select(x => x.NodeId)}); var childIds = Database.Fetch(childIdsSql) .Select(x => new UmbracoEntity @@ -542,7 +542,7 @@ namespace Umbraco.Core.Persistence.Repositories //return the list - it will be naturally ordered by level return descendants; } - + public IEnumerable GetDescendants(string alias) { var all = base.GetAll().ToArray(); @@ -586,7 +586,7 @@ namespace Umbraco.Core.Persistence.Repositories /// [Obsolete("Use GetDescendants instead")] public TemplateNode GetTemplateNode(string alias) - { + { //first get all template objects var allTemplates = base.GetAll().ToArray(); @@ -595,7 +595,7 @@ namespace Umbraco.Core.Persistence.Repositories { return null; } - + var top = selfTemplate; while (top.MasterTemplateAlias.IsNullOrWhiteSpace() == false) { @@ -646,16 +646,16 @@ namespace Umbraco.Core.Persistence.Repositories } /// - /// This checks what the default rendering engine is set in config but then also ensures that there isn't already - /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate + /// This checks what the default rendering engine is set in config but then also ensures that there isn't already + /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate /// rendering engine to use. - /// + /// /// /// /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx - /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml - /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. - /// This is mostly related to installing packages since packages install file templates to the file system and then create the + /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml + /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. + /// This is mostly related to installing packages since packages install file templates to the file system and then create the /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. /// public RenderingEngine DetermineTemplateRenderingEngine(ITemplate template) @@ -763,7 +763,7 @@ namespace Umbraco.Core.Persistence.Repositories /// private void EnsureValidAlias(ITemplate template) { - //ensure unique alias + //ensure unique alias template.Alias = template.Alias.ToCleanString(CleanStringType.UnderscoreAlias); if (template.Alias.Length > 100) diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web/Editors/ImagesController.cs index b732dbd79e..eae620e745 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web/Editors/ImagesController.cs @@ -112,8 +112,8 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.NotFound); //redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file - var response = Request.CreateResponse( HttpStatusCode.Found ); - var imageLastModified = fs.GetLastModified( imagePath ); + var response = Request.CreateResponse(HttpStatusCode.Found); + var imageLastModified = fs.GetLastModified(imagePath); response.Headers.Location = new Uri( string.Format( "{0}?rnd={1}&width={2}", imagePath, string.Format( "{0:yyyyMMddHHmmss}", imageLastModified ), width ), UriKind.Relative ); return response; } From f5ceab82f1d4a19352aef73ad838ff9dbc15155e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pjengaard?= Date: Fri, 12 Feb 2016 14:00:45 +0100 Subject: [PATCH 007/413] Update da.xml Added translation for contentPublishedFailedAwaitingRelease --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 446796dd88..841faab68f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -837,6 +837,7 @@ Mange hilsner fra Umbraco robotten Annulleret Handlingen blev annulleret af et 3. part tilføjelsesprogram Udgivelsen blev standset af et 3. parts modul + Udgivelsen kunne ikke udgives da publiceringsdato er sat Property type eksisterer allerede Egenskabstype oprettet DataType: %1%]]> From c10534a064d8fd2806f3c913ef0f2a1635665dad Mon Sep 17 00:00:00 2001 From: Alexander Bryukhov Date: Thu, 14 Apr 2016 23:31:24 +0600 Subject: [PATCH 008/413] Update UI language ru.xml Missing keys & some typos found --- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index fbf0e0adf2..2fd54be962 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -215,8 +215,10 @@ ДА, удалить - была перемещена внутрь + перемещены внутрь + скопированы внутрь Выбрать папку для перемещения + Выбрать папку для копирования в структуре дерева Все типы документов @@ -328,7 +330,7 @@ Провайдеры аутентификации Подробное сообщение об ошибке Трассировка стека - Inner Exception + Внутренняя ошибка Связать Разорвать связь @@ -725,6 +727,7 @@ Нажмите, чтобы загрузить + Невозможна загрузка этого файла, этот тип файлов не разрешен для загрузки Перетащите файлы сюда... Ссылка на файл или нажмите сюда, чтобы выбрать файлы @@ -1219,6 +1222,6 @@ Валидация числового значения Валидация по формату Url ...или указать свои правила валидации - Обязательно к заполению + Обязательно к заполнению From 378e8f5cd93f3fd5919b0dcd01e66862c45c4a22 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 15 Apr 2016 13:25:11 +0200 Subject: [PATCH 009/413] deploy-30 - fixes --- src/Umbraco.Core/IO/MediaFileSystem.cs | 45 ++++---------- src/Umbraco.Core/Media/ImageHelper.cs | 2 + src/Umbraco.Core/Media/MediaHelper.cs | 13 ++-- .../Media/UploadAutoFillProperties.cs | 2 +- src/Umbraco.Core/Models/ContentExtensions.cs | 60 ++++++++++++------- src/Umbraco.Core/Models/File.cs | 15 ----- src/Umbraco.Core/Models/IFile.cs | 8 --- src/Umbraco.Core/Models/Template.cs | 2 +- src/Umbraco.Core/Models/TemplateOnDisk.cs | 52 ++++++++++++++++ .../Interfaces/ITemplateRepository.cs | 4 +- .../Repositories/TemplateRepository.cs | 13 ++-- .../Repositories/VersionableRepositoryBase.cs | 3 +- src/Umbraco.Core/Services/FileService.cs | 12 ++-- src/Umbraco.Core/Services/IFileService.cs | 4 +- src/Umbraco.Core/Services/IMediaService.cs | 4 +- src/Umbraco.Core/Services/MediaService.cs | 6 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../FileUploadPropertyValueEditor.cs | 4 +- .../ImageCropperPropertyValueEditor.cs | 4 +- .../umbraco/channels/UmbracoMetaWeblogAPI.cs | 2 +- src/umbraco.cms/businesslogic/Content.cs | 3 +- .../media/UmbracoFileMediaFactory.cs | 2 +- .../media/UmbracoMediaFactory.cs | 14 ++++- 23 files changed, 161 insertions(+), 114 deletions(-) create mode 100644 src/Umbraco.Core/Models/TemplateOnDisk.cs diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index b6ef6f7bf5..9dd0a8fda1 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Media; namespace Umbraco.Core.IO { @@ -26,6 +27,8 @@ namespace Umbraco.Core.IO _contentConfig = contentConfig; } + // none of the methods below are used in Core anymore + [Obsolete("This low-level method should NOT exist.")] public string GetRelativePath(int propertyId, string fileName) { @@ -36,7 +39,7 @@ namespace Umbraco.Core.IO return propertyId.ToString(CultureInfo.InvariantCulture) + sep + fileName; } - [Obsolete("This low-level method should NOT exist.")] + [Obsolete("This low-level method should NOT exist.", false)] public string GetRelativePath(string subfolder, string fileName) { var sep = _contentConfig.UploadAllowDirectories @@ -46,50 +49,28 @@ namespace Umbraco.Core.IO return subfolder + sep + fileName; } - // what's below is weird - // we are not deleting custom thumbnails - // MediaFileSystem is not just IFileSystem - // etc - - [Obsolete("", true)] + [Obsolete("Use ImageHelper.GetThumbnails instead.", false)] public IEnumerable GetThumbnails(string path) - { - var parentDirectory = Path.GetDirectoryName(path); - var extension = Path.GetExtension(path); - - return GetFiles(parentDirectory) - .Where(x => x.StartsWith(path.TrimEnd(extension) + "_thumb") || x.StartsWith(path.TrimEnd(extension) + "_big-thumb")) - .ToList(); + { + return ImageHelper.GetThumbnails(this, path); } - [Obsolete("", true)] + [Obsolete("Use ImageHelper.DeleteFile instead.", false)] public void DeleteFile(string path, bool deleteThumbnails) { - DeleteFile(path); - - if (deleteThumbnails == false) - return; - - DeleteThumbnails(path); + ImageHelper.DeleteFile(this, path, deleteThumbnails); } - [Obsolete("", true)] + [Obsolete("Use ImageHelper.DeleteThumbnails instead.", false)] public void DeleteThumbnails(string path) { - GetThumbnails(path) - .ForEach(DeleteFile); + ImageHelper.DeleteThumbnails(this, path); } - [Obsolete("", true)] + [Obsolete("Use ImageHelper.CopyThumbnails instead.", false)] public void CopyThumbnails(string sourcePath, string targetPath) { - var targetPathBase = Path.GetDirectoryName(targetPath) ?? ""; - foreach (var sourceThumbPath in GetThumbnails(sourcePath)) - { - var sourceThumbFilename = Path.GetFileName(sourceThumbPath) ?? ""; - var targetThumbPath = Path.Combine(targetPathBase, sourceThumbFilename); - this.CopyFile(sourceThumbPath, targetThumbPath); - } + ImageHelper.CopyThumbnails(this, sourcePath, targetPath); } } } diff --git a/src/Umbraco.Core/Media/ImageHelper.cs b/src/Umbraco.Core/Media/ImageHelper.cs index a9775321e8..61d786a1fa 100644 --- a/src/Umbraco.Core/Media/ImageHelper.cs +++ b/src/Umbraco.Core/Media/ImageHelper.cs @@ -91,6 +91,8 @@ namespace Umbraco.Core.Media #region Manage thumbnails + // note: this does not find 'custom' thumbnails? + // will find _thumb and _big-thumb but NOT _custom? public static IEnumerable GetThumbnails(IFileSystem fs, string path) { var parentDirectory = Path.GetDirectoryName(path); diff --git a/src/Umbraco.Core/Media/MediaHelper.cs b/src/Umbraco.Core/Media/MediaHelper.cs index 5367455a43..4b7133cce0 100644 --- a/src/Umbraco.Core/Media/MediaHelper.cs +++ b/src/Umbraco.Core/Media/MediaHelper.cs @@ -41,6 +41,7 @@ namespace Umbraco.Core.Media /// The unique identifier of the content/media owning the file. /// The unique identifier of the property type owning the file. /// The filesystem-relative path to the media file. + /// With the old media path scheme, this CREATES a new media path each time it is invoked. public static string GetMediaPath(string filename, Guid cuid, Guid puid) { filename = Path.GetFileName(filename); @@ -79,7 +80,7 @@ namespace Umbraco.Core.Media /// The unique identifier of the property type owning the file. /// The filesystem-relative path to the media file. /// In the old, legacy, number-based scheme, we try to re-use the media folder - /// specified by . Else, we create a new one. + /// specified by . Else, we CREATE a new one. Each time we are invoked. public static string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid) { if (UseTheNewMediaPathScheme || string.IsNullOrWhiteSpace(prevpath)) @@ -112,14 +113,14 @@ namespace Umbraco.Core.Media /// Gets the next media folder in the original number-based scheme. /// /// + /// Should be private, is internal for legacy FileHandlerData which is obsolete. internal static string GetNextFolder() { lock (FolderCounterLock) { if (_folderCounterInitialized == false) { - // fixme - seed was not respected in MediaSubfolderCounter? - _folderCounter = 1000; // seed + _folderCounter = 1000; // seed - was not respected in MediaSubfolderCounter? var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); var directories = fs.GetDirectories(""); foreach (var directory in directories) @@ -163,7 +164,7 @@ namespace Umbraco.Core.Media // clear the old file, if any var fs = FileSystem; if (string.IsNullOrWhiteSpace(oldpath)) - fs.DeleteFile(oldpath, true); + ImageHelper.DeleteFile(fs, oldpath, true); // get the filepath, store the data // use oldpath as "prevpath" to try and reuse the folder, in original number-based scheme @@ -178,7 +179,7 @@ namespace Umbraco.Core.Media /// The filesystem-relative path to the media file. public static void DeleteFile(string filepath) { - FileSystem.DeleteFile(filepath, true); + ImageHelper.DeleteFile(FileSystem, filepath, true); } /// @@ -202,7 +203,7 @@ namespace Umbraco.Core.Media var filename = Path.GetFileName(sourcepath); var filepath = GetMediaPath(filename, content.Key, propertyType.Key); fs.CopyFile(sourcepath, filepath); - fs.CopyThumbnails(sourcepath, filepath); + ImageHelper.CopyThumbnails(fs, sourcepath, filepath); return filepath; } diff --git a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs index 553c994599..625bdc4e47 100644 --- a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs +++ b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs @@ -11,7 +11,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Media { /// - /// Provides extension methods to manage auto-fill properties for upload fields. + /// Provides methods to manage auto-fill properties for upload fields. /// internal static class UploadAutoFillProperties { diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index 863f6b197a..e54653ea29 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -450,11 +450,11 @@ namespace Umbraco.Core.Models #region SetValue for setting file contents /// - /// Sets and uploads the file from a HttpPostedFileBase object as the property value + /// Stores and sets an uploaded HttpPostedFileBase as a property value. /// - /// to add property value to - /// Alias of the property to save the value on - /// The containing the file that will be uploaded + /// A content item. + /// The property alias. + /// The uploaded . public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value) { // ensure we get the filename without the path in IE in intranet mode @@ -478,37 +478,37 @@ namespace Umbraco.Core.Models } /// - /// Sets and uploads the file from a HttpPostedFile object as the property value + /// Stores and sets an uploaded HttpPostedFile as a property value. /// - /// to add property value to - /// Alias of the property to save the value on - /// The containing the file that will be uploaded + /// A content item. + /// The property alias. + /// The uploaded . public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFile value) { - SetValue(content, propertyTypeAlias, (HttpPostedFileBase)new HttpPostedFileWrapper(value)); + SetValue(content, propertyTypeAlias, (HttpPostedFileBase) new HttpPostedFileWrapper(value)); } /// - /// Sets and uploads the file from a HttpPostedFileWrapper object as the property value + /// Stores and sets an uploaded HttpPostedFileWrapper as a property value. /// - /// to add property value to - /// Alias of the property to save the value on - /// The containing the file that will be uploaded + /// A content item. + /// The property alias. + /// The uploaded . [Obsolete("There is no reason for this overload since HttpPostedFileWrapper inherits from HttpPostedFileBase")] public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileWrapper value) { - SetValue(content, propertyTypeAlias, (HttpPostedFileBase)value); + SetValue(content, propertyTypeAlias, (HttpPostedFileBase) value); } /// - /// Sets a content item property value with a file coming from a stream. + /// Stores and sets a file as a property value. /// - /// The content item. - /// The property type alias. - /// Name of the file - /// The stream containing the file data. + /// A content item. + /// The property alias. + /// The name of the file. + /// A stream containing the file data. /// This really is for FileUpload fields only, and should be obsoleted. For anything else, - /// you need to store the file by yourself using and then figure out + /// you need to store the file by yourself using Store and then figure out /// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself. public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream) { @@ -522,12 +522,28 @@ namespace Umbraco.Core.Models MediaHelper.SetUploadFile(content, propertyTypeAlias, filename, filestream); } - public static string StoreFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string oldpath) + /// + /// Stores a file. + /// + /// A content item. + /// The property alias. + /// The name of the file. + /// A stream containing the file data. + /// The original file path, if any. + /// The path to the file, relative to the media filesystem. + /// + /// Does NOT set the property value, so one should probably store the file and then do + /// something alike: property.Value = MediaHelper.FileSystem.GetUrl(filepath). + /// The original file path is used, in the old media file path scheme, to try and reuse + /// the "folder number" that was assigned to the previous file referenced by the property, + /// if any. + /// + public static string StoreFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string filepath) { var propertyType = content.GetContentType() .CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); if (propertyType == null) throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + "."); - return MediaHelper.StoreFile(content, propertyType, filename, filestream, oldpath); + return MediaHelper.StoreFile(content, propertyType, filename, filestream, filepath); } #endregion diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index da093f792a..8ead6da5f8 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -25,21 +25,6 @@ namespace Umbraco.Core.Models private string _content; internal Func GetFileContent { get; set; } - // whether to use whatever already exists on filesystem - internal bool _useExistingContent; - - /// - /// Indicates that the file should use whatever content already - /// exists on the filesystem which manages the file, bypassing content - /// management entirely. - /// - /// Only for the next save. Is resetted when the file is saved. - public void UseExistingContent() - { - _useExistingContent = true; - _content = null; // force content to be loaded - } - protected File(string path, Func getFileContent = null) { _path = SanitizePath(path); diff --git a/src/Umbraco.Core/Models/IFile.cs b/src/Umbraco.Core/Models/IFile.cs index 0d1cf24cdd..de900c50ec 100644 --- a/src/Umbraco.Core/Models/IFile.cs +++ b/src/Umbraco.Core/Models/IFile.cs @@ -39,14 +39,6 @@ namespace Umbraco.Core.Models /// string Content { get; set; } - /// - /// Indicates that the file should use whatever content already - /// exists on the filesystem which manages the file, bypassing content - /// management entirely. - /// - /// Only for the next save. Is resetted when the file is saved. - void UseExistingContent(); - /// /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) /// diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 1c8b44b674..6a271ad7ab 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -15,7 +15,7 @@ using Umbraco.Core.Strings; namespace Umbraco.Core.Models { /// - /// Represents a Template file + /// Represents a Template file. /// [Serializable] [DataContract(IsReference = true)] diff --git a/src/Umbraco.Core/Models/TemplateOnDisk.cs b/src/Umbraco.Core/Models/TemplateOnDisk.cs new file mode 100644 index 0000000000..7f89b47e34 --- /dev/null +++ b/src/Umbraco.Core/Models/TemplateOnDisk.cs @@ -0,0 +1,52 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Template file that can have its content on disk. + /// + [Serializable] + [DataContract(IsReference = true)] + public class TemplateOnDisk : Template + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the template. + /// The alias of the template. + public TemplateOnDisk(string name, string alias) + : base(name, alias) + { + IsOnDisk = true; + } + + /// + /// Gets or sets a value indicating whether the content is on disk already. + /// + public bool IsOnDisk { get; set; } + + /// + /// Gets or sets the content. + /// + /// + /// Getting the content while the template is "on disk" throws, + /// the template must be saved before its content can be retrieved. + /// Setting the content means it is not "on disk" anymore, and the + /// template becomes (and behaves like) a normal template. + /// + public override string Content + { + get + { + if (IsOnDisk) throw new InvalidOperationException("On-disk template do not have content until saved."); + return base.Content; + } + set + { + base.Content = value; + IsOnDisk = false; + } + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs index 48b0cf66b7..11ac404f5d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs @@ -64,13 +64,13 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The filesystem path to the template. /// The content of the template. - Stream GetFileStream(string filepath); + Stream GetFileContent(string filepath); /// /// Sets the content of a template. /// /// The filesystem path to the template. /// The content of the template. - void SetFile(string filepath, Stream content); + void SetFileContent(string filepath, Stream content); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 0242d9d222..34cbd30df7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -251,13 +251,15 @@ namespace Umbraco.Core.Persistence.Repositories { string content; - if (template._useExistingContent) + var templateOnDisk = template as TemplateOnDisk; + if (templateOnDisk != null && templateOnDisk.IsOnDisk) { - content = _viewHelper.GetFileContents(template); // BUT the template does not exist yet?! - template._useExistingContent = false; // reset + // if "template on disk" load content from disk + content = _viewHelper.GetFileContents(template); } else { + // else, create or write template.Content to disk if (DetermineTemplateRenderingEngine(template) == RenderingEngine.Mvc) { content = originalAlias == null @@ -272,6 +274,7 @@ namespace Umbraco.Core.Persistence.Repositories } } + // once content has been set, "template on disk" are not "on disk" anymore template.Content = content; if (dto.Design == content) return; @@ -449,12 +452,12 @@ namespace Umbraco.Core.Persistence.Repositories } } - public Stream GetFileStream(string filepath) + public Stream GetFileContent(string filepath) { return GetFileSystem(filepath).OpenFile(filepath); } - public void SetFile(string filepath, Stream content) + public void SetFileContent(string filepath, Stream content) { GetFileSystem(filepath).AddFile(filepath, content, true); } diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 56b0b63ad5..6ffa692fa0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -22,6 +22,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Dynamics; using Umbraco.Core.IO; +using Umbraco.Core.Media; namespace Umbraco.Core.Persistence.Repositories { @@ -579,7 +580,7 @@ WHERE EXISTS( } else { - fs.DeleteFile(file, true); + ImageHelper.DeleteFile(fs, file, true); } } catch (Exception e) diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 1dae926c6a..06ab3c5229 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -558,19 +558,19 @@ namespace Umbraco.Core.Services } } - public Stream GetTemplateFileStream(string filepath) + public Stream GetTemplateFileContent(string filepath) { - using (var repository = _repositoryFactory.CreateTemplateRepository(_dataUowProvider.GetUnitOfWork())) + using (var repository = RepositoryFactory.CreateTemplateRepository(UowProvider.GetUnitOfWork())) { - return repository.GetFileStream(filepath); + return repository.GetFileContent(filepath); } } - public void SetTemplateFile(string filepath, Stream content) + public void SetTemplateFileContent(string filepath, Stream content) { - using (var repository = _repositoryFactory.CreateTemplateRepository(_dataUowProvider.GetUnitOfWork())) + using (var repository = RepositoryFactory.CreateTemplateRepository(UowProvider.GetUnitOfWork())) { - repository.SetFile(filepath, content); + repository.SetFileContent(filepath, content); } } diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index 8ea1d9b23b..010dd7067f 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -242,13 +242,13 @@ namespace Umbraco.Core.Services /// /// The filesystem path to the template. /// The content of the template. - Stream GetTemplateFileStream(string filepath); + Stream GetTemplateFileContent(string filepath); /// /// Sets the content of a template. /// /// The filesystem path to the template. /// The content of the template. - void SetTemplateFile(string filepath, Stream content); + void SetTemplateFileContent(string filepath, Stream content); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 83c815c78d..3fcad5f32e 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -377,14 +377,14 @@ namespace Umbraco.Core.Services /// /// The filesystem path to the media. /// The content of the media. - Stream GetMediaFileStream(string filepath); + Stream GetMediaFileContent(string filepath); /// /// Sets the content of a media. /// /// The filesystem path to the media. /// The content of the media. - void SetMediaFile(string filepath, Stream content); + void SetMediaFileContent(string filepath, Stream content); /// /// Deletes a media file and all thumbnails. diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 5240c5c9b3..121b7f508e 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -1259,19 +1259,19 @@ namespace Umbraco.Core.Services } } - public Stream GetMediaFileStream(string filepath) + public Stream GetMediaFileContent(string filepath) { return MediaHelper.FileSystem.OpenFile(filepath); } - public void SetMediaFile(string filepath, Stream stream) + public void SetMediaFileContent(string filepath, Stream stream) { MediaHelper.FileSystem.AddFile(filepath, stream, true); } public void DeleteMediaFile(string filepath) { - MediaHelper.FileSystem.DeleteFile(filepath, true); + ImageHelper.DeleteFile(MediaHelper.FileSystem, filepath, true); } public void GenerateThumbnails(string filepath, PropertyType propertyType) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f329fab881..1ebd25cda5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -403,6 +403,7 @@ + diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs index 857f2860b2..1bba10c7a3 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -75,7 +75,7 @@ namespace Umbraco.Web.PropertyEditors if (clears) { foreach (var pathToRemove in currentPaths) - fs.DeleteFile(pathToRemove, true); + ImageHelper.DeleteFile(fs, pathToRemove, true); return string.Empty; // no more files } @@ -136,7 +136,7 @@ namespace Umbraco.Web.PropertyEditors // remove files that are not there anymore foreach (var pathToRemove in currentPaths.Except(newPaths)) - fs.DeleteFile(pathToRemove, true); + ImageHelper.DeleteFile(fs, pathToRemove, true); return string.Join(",", newPaths.Select(x => fs.GetUrl(x))); diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index cf46037402..f1f26f003b 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -122,7 +122,7 @@ namespace Umbraco.Web.PropertyEditors // value is unchanged. if (string.IsNullOrWhiteSpace(editorFile) && string.IsNullOrWhiteSpace(currentPath) == false) { - fs.DeleteFile(currentPath, true); + ImageHelper.DeleteFile(fs, currentPath, true); return null; // clear } @@ -138,7 +138,7 @@ namespace Umbraco.Web.PropertyEditors // remove current file if replaced if (currentPath != filepath && string.IsNullOrWhiteSpace(currentPath) == false) - fs.DeleteFile(currentPath, true); + ImageHelper.DeleteFile(fs, currentPath, true); // update json and return if (editorJson == null) return null; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/channels/UmbracoMetaWeblogAPI.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/channels/UmbracoMetaWeblogAPI.cs index 6eefec8ab0..b4fa6ed573 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/channels/UmbracoMetaWeblogAPI.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/channels/UmbracoMetaWeblogAPI.cs @@ -450,7 +450,7 @@ namespace umbraco.presentation.channels Property fileObject = m.getProperty(userChannel.MediaTypeFileProperty); var filename = file.name.Replace("/", "_"); - var relativeFilePath = _fs.GetRelativePath(fileObject.Id, filename); + var relativeFilePath = UmbracoMediaFactory.GetRelativePath(fileObject.Id, filename); fileObject.Value = _fs.GetUrl(relativeFilePath); fileUrl.url = fileObject.Value.ToString(); diff --git a/src/umbraco.cms/businesslogic/Content.cs b/src/umbraco.cms/businesslogic/Content.cs index f014ddae93..e7b9d7fe66 100644 --- a/src/umbraco.cms/businesslogic/Content.cs +++ b/src/umbraco.cms/businesslogic/Content.cs @@ -13,6 +13,7 @@ using umbraco.DataLayer; using System.Runtime.CompilerServices; using umbraco.cms.helpers; using umbraco.cms.businesslogic.datatype.controls; +using Umbraco.Core.Media; using File = System.IO.File; using Property = umbraco.cms.businesslogic.property.Property; using PropertyType = umbraco.cms.businesslogic.propertytype.PropertyType; @@ -631,7 +632,7 @@ namespace umbraco.cms.businesslogic } else { - fs.DeleteFile(relativeFilePath, true); + ImageHelper.DeleteFile(fs, relativeFilePath, true); } } } diff --git a/src/umbraco.cms/businesslogic/media/UmbracoFileMediaFactory.cs b/src/umbraco.cms/businesslogic/media/UmbracoFileMediaFactory.cs index 0c265d8b07..dc13f83ea6 100644 --- a/src/umbraco.cms/businesslogic/media/UmbracoFileMediaFactory.cs +++ b/src/umbraco.cms/businesslogic/media/UmbracoFileMediaFactory.cs @@ -29,7 +29,7 @@ namespace umbraco.cms.businesslogic.media var propertyId = media.getProperty(Constants.Conventions.Media.File).Id; // Get paths - var destFilePath = FileSystem.GetRelativePath(propertyId, uploadedFile.FileName); + var destFilePath = GetRelativePath(propertyId, uploadedFile.FileName); var ext = Path.GetExtension(destFilePath).Substring(1); //var absoluteDestPath = HttpContext.Current.Server.MapPath(destPath); diff --git a/src/umbraco.cms/businesslogic/media/UmbracoMediaFactory.cs b/src/umbraco.cms/businesslogic/media/UmbracoMediaFactory.cs index 54d8dfa770..8cdf519a09 100644 --- a/src/umbraco.cms/businesslogic/media/UmbracoMediaFactory.cs +++ b/src/umbraco.cms/businesslogic/media/UmbracoMediaFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading; @@ -99,7 +100,7 @@ namespace umbraco.cms.businesslogic.media if (int.TryParse(subfolder, out subfolderId)) { - var destFilePath = FileSystem.GetRelativePath(subfolderId, fileName); + var destFilePath = GetRelativePath(subfolderId, fileName); var destFileUrl = FileSystem.GetUrl(destFilePath); if (prop.Value.ToString() == destFileUrl) @@ -154,6 +155,17 @@ namespace umbraco.cms.businesslogic.media return friendlyName; } + public static string GetRelativePath(int propertyId, string fileName) + { + var contentConfig = UmbracoConfig.For.UmbracoSettings().Content; + + var sep = contentConfig.UploadAllowDirectories + ? Path.DirectorySeparatorChar + : '-'; + + return propertyId.ToString(CultureInfo.InvariantCulture) + sep + fileName; + } + #endregion } } From 179f36e2a2ffd67a9a60c0a860c180bf561c8707 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 15 Apr 2016 16:36:53 +0200 Subject: [PATCH 010/413] deploy-30 - fixes --- src/Umbraco.Core/Models/TemplateOnDisk.cs | 3 +-- .../Persistence/Repositories/FileRepository.cs | 16 +++++++++++++--- .../Repositories/TemplateRepository.cs | 14 +++++++++++++- src/Umbraco.Core/Services/MediaService.cs | 11 ++++++++++- .../config/ClientDependency.config | 2 +- 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Models/TemplateOnDisk.cs b/src/Umbraco.Core/Models/TemplateOnDisk.cs index 7f89b47e34..a8420adcb6 100644 --- a/src/Umbraco.Core/Models/TemplateOnDisk.cs +++ b/src/Umbraco.Core/Models/TemplateOnDisk.cs @@ -39,8 +39,7 @@ namespace Umbraco.Core.Models { get { - if (IsOnDisk) throw new InvalidOperationException("On-disk template do not have content until saved."); - return base.Content; + return IsOnDisk ? string.Empty : base.Content; } set { diff --git a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs index e1e4c3105a..7ce0d71097 100644 --- a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs @@ -217,10 +217,20 @@ namespace Umbraco.Core.Persistence.Repositories protected string GetFileContent(string filename) { - using (var stream = FileSystem.OpenFile(filename)) - using (var reader = new StreamReader(stream, Encoding.UTF8, true)) + if (FileSystem.FileExists(filename) == false) + return null; + + try { - return reader.ReadToEnd(); + using (var stream = FileSystem.OpenFile(filename)) + using (var reader = new StreamReader(stream, Encoding.UTF8, true)) + { + return reader.ReadToEnd(); + } + } + catch + { + return null; // deal with race conds } } diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 34cbd30df7..5ee83a47a8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -418,6 +418,8 @@ namespace Umbraco.Core.Persistence.Repositories fsname = string.Concat(template.Alias, ".master"); if (_masterpagesFileSystem.FileExists(fsname)) return GetFileContent(template, _masterpagesFileSystem, fsname, init); + + template.VirtualPath = string.Empty; // file not found... return string.Empty; } @@ -454,7 +456,17 @@ namespace Umbraco.Core.Persistence.Repositories public Stream GetFileContent(string filepath) { - return GetFileSystem(filepath).OpenFile(filepath); + var fs = GetFileSystem(filepath); + if (fs.FileExists(filepath) == false) return null; + + try + { + return GetFileSystem(filepath).OpenFile(filepath); + } + catch + { + return null; // deal with race conds + } } public void SetFileContent(string filepath, Stream content) diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 121b7f508e..7cd1c2ab00 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -1261,7 +1261,16 @@ namespace Umbraco.Core.Services public Stream GetMediaFileContent(string filepath) { - return MediaHelper.FileSystem.OpenFile(filepath); + if (MediaHelper.FileSystem.FileExists(filepath) == false) + return null; + try + { + return MediaHelper.FileSystem.OpenFile(filepath); + } + catch + { + return null; // deal with race conds + } } public void SetMediaFileContent(string filepath, Stream stream) diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config index 80d8db2da3..fd5df58715 100644 --- a/src/Umbraco.Web.UI/config/ClientDependency.config +++ b/src/Umbraco.Web.UI/config/ClientDependency.config @@ -10,7 +10,7 @@ NOTES: * Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config * A new version will invalidate both client and server cache and create new persisted files --> - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 02ffaa9ee84ff125a031056874b052b9ad361dde Mon Sep 17 00:00:00 2001 From: Dennis Aaen Date: Tue, 10 May 2016 20:33:03 +0200 Subject: [PATCH 014/413] Fixed issue: U4-8435 so the link is correct in settingsdashboardintro dashboard --- .../src/views/dashboard/settings/settingsdashboardintro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html index fa9849022c..3a45776872 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html @@ -9,6 +9,6 @@
  • Download the Editors Manual for details on working with the Umbraco UI
  • Ask a question in the Community Forum
  • Watch our tutorial videos (some are free, some require a subscription)
  • -
  • Find out about our productivity boosting tools and commercial support
  • +
  • Find out about our productivity boosting tools and commercial support
  • Find out about real-life training and certification opportunities
  • From 5fab1ca207db4d82ac064b22741ad16f74a4809a Mon Sep 17 00:00:00 2001 From: bjarnef Date: Sun, 22 May 2016 13:46:48 +0200 Subject: [PATCH 015/413] Localize move and copy overlay titles opened from listview actions. Prepare localization of bulk messages. --- .../src/views/common/overlays/copy/copy.html | 2 +- .../listview/listview.controller.js | 95 ++++++++++++++++--- src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml | 28 ++++++ src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 59 +++++++++++- src/Umbraco.Web.UI/umbraco/config/lang/de.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 28 ++++++ .../umbraco/config/lang/en_us.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/es.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/fr.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/he.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/it.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/ja.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/ko.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/pl.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/pt.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/sv.xml | 28 ++++++ src/Umbraco.Web.UI/umbraco/config/lang/zh.xml | 28 ++++++ 20 files changed, 615 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html index adb57b4af0..c0e22c4d27 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html @@ -31,8 +31,8 @@
    - diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 518c27d110..2ea325b451 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -208,7 +208,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $timeout(function () { $scope.bulkStatus = ""; $scope.actionInProgress = false; - }, 500); + }, 0); if (reload === true) { $scope.reloadView($scope.contentId); @@ -220,7 +220,9 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie } } else if (successMsg) { - notificationsService.success("Done", successMsg); + localizationService.localize("bulk_done").then(function (v) { + notificationsService.success(v, successMsg); + }); } } @@ -368,28 +370,67 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.delete = function () { applySelected( function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, - function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, + function (count, total) { + return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); + //var key = (total === 1 ? "bulk_deletedItemOfItem" : "bulk_deletedItemOfItems"); + //localizationService.localize(key, [count, total]).then(function (value) { + // return value; + //}); + }, + function (total) { + return "Deleted " + total + " item" + (total > 1 ? "s" : ""); + //var key = (total === 1 ? "bulk_deletedItem" : "bulk_deletedItems"); + //localizationService.localize(key, [total]).then(function (value) { + // return value; + //}); + }, + //localizationService.localize("defaultdialogs_confirmdelete") + "?" "Sure you want to delete?"); }; - $scope.publish = function () { + $scope.publish = function () { applySelected( function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, - function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); + function (count, total) { + return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); + //var key = (total === 1 ? "bulk_publishedItemOfItem" : "bulk_publishedItemOfItems"); + //localizationService.localize(key, [count, total]).then(function (value) { + // console.log(key, value); + // return value; + //}); + }, + function (total) { + return "Published " + total + " item" + (total > 1 ? "s" : ""); + //var key = (total === 1 ? "bulk_publishedItem" : "bulk_publishedItems"); + //localizationService.localize(key, [total]).then(function (value) { + // console.log(key, value); + // return value; + //}); + }); }; $scope.unpublish = function () { applySelected( function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, - function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); + function (count, total) { + return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); + //var key = (total === 1 ? "bulk_unpublishedItemOfItem" : "bulk_unpublishedItemOfItems"); + //localizationService.localize(key, [count, total]).then(function (value) { + // return value; + //}); + }, + function (total) { + return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); + //var key = (total === 1 ? "bulk_unpublishedItem" : "bulk_unpublishedItems"); + //localizationService.localize(key, [total]).then(function (value) { + // return value; + //}); + }); }; $scope.move = function () { $scope.moveDialog = {}; - $scope.moveDialog.title = "Move"; + $scope.moveDialog.title = localizationService.localize("general_move"); $scope.moveDialog.section = $scope.entityType; $scope.moveDialog.currentNode = $scope.contentId; $scope.moveDialog.view = "move"; @@ -416,13 +457,25 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie applySelected( function (selected, index) { return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }); }, - function (count, total) { return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Moved " + total + " item" + (total > 1 ? "s" : ""); }); + function (count, total) { + return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); + //var key = (total === 1 ? "bulk_movedItemOfItem" : "bulk_movedItemOfItems"); + //localizationService.localize(key, [count, total]).then(function (value) { + // return value; + //}); + }, + function (total) { + return "Moved " + total + " item" + (total > 1 ? "s" : ""); + //var key = (total === 1 ? "bulk_movedItem" : "bulk_movedItems"); + //localizationService.localize(key, [total]).then(function (value) { + // return value; + //}); + }); } $scope.copy = function () { $scope.copyDialog = {}; - $scope.copyDialog.title = "Copy"; + $scope.copyDialog.title = localizationService.localize("general_copy"); $scope.copyDialog.section = $scope.entityType; $scope.copyDialog.currentNode = $scope.contentId; $scope.copyDialog.view = "copy"; @@ -447,8 +500,20 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie function performCopy(target, relateToOriginal) { applySelected( function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, - function (count, total) { return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); + function (count, total) { + return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); + //var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems"); + //localizationService.localize(key, [count, total]).then(function (value) { + // return value; + //}); + }, + function (total) { + return "Copied " + total + " item" + (total > 1 ? "s" : ""); + //var key = (total === 1 ? "bulk_copiedItem" : "bulk_copiedItems"); + //localizationService.localize(key, [total]).then(function (value) { + // return value; + //}); + }); } function getCustomPropertyValue(alias, properties) { diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index 1ae28daafe..50d5c0d911 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -185,6 +185,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Název Spravovat názvy hostitelů diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 275267e98c..f3701c706a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -191,6 +191,34 @@ Discard changes You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items Navn på lokal link diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 0280a46e05..d68e21daf7 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -198,6 +198,34 @@ Du har ikke-gemte ændringer Er du sikker på du vil navigere væk fra denne side? - du har ikke-gemte ændringer + + Færdig + + Slettede %0% element + Slettede %0% elementer + Slettede %0% ud af %1% element + Slettede %0% ud af %1% elementer + + Udgav %0% element + Udgav %0% elementer + Udgav %0% ud af %1% element + Udgav %0% ud af %1% elementer + + Fjernede %0% element fra udgivelse + Fjernede %0% elementer fra udgivelse + Fjernede %0% ud af %1% element fra udgivelse + Fjernede %0% ud af %1% elementer fra udgivelse + + Flyttede %0% element + Flyttede %0% elementer + Flyttede %0% ud af %1% element + Flyttede %0% ud af %1% elementer + + Kopierede %0% element + Kopierede %0% elementer + Kopierede %0% ud af %1% element + Kopierede %0% ud af %1% elementer + Navn på lokalt link Rediger domæner @@ -242,7 +270,36 @@ Vælg et placeholder id fra listen herunder. Du kan kun vælge id'er fra den nuværende masterskabelon.]]> Klik på billedet for at se den fulde størrelse Vælg - Se Cache Item + Se cache element + Opret mappe... + + Relatér til original + + Link til side + + Åbner det linket dokument i et nyt vindue eller fane + Åbner det linket dokument i fuld visning af vinduet + Åbner det linket dokument i "parent frame" + + Link til medie + + Vælg medie + Vælg ikon + Vælg item + Vælg link + Vælg makro + Vælg indhold + Vælg medlem + Vælg medlemsgruppe + + Der er ingen parametre for denne makro + + Link dit + Fjern link fra dit + + konto + + Vælg editor Rediger de forskellige sprogversioner for ordbogselementet '%0%' herunder. Du tilføjer flere sprog under 'sprog' i menuen til venstre diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index 3b4c1ee5ab..d7f2c320b9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -192,6 +192,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Name Hostnamen verwalten diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 84dedbb364..2353929a4c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -205,6 +205,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Name Manage hostnames diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index ec41299b25..528830d86a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -207,6 +207,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Name Manage hostnames diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml index 80e39fe219..09af60593f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml @@ -191,6 +191,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Nombre Administrar dominios diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml index 59021956a5..4affcf02f0 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml @@ -187,6 +187,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Name Gérer les noms d'hôtes diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml index aa9c2112e2..02d8311c14 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml @@ -137,6 +137,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + שם ניהול שם מתחם diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml index 9c0682eba0..8be7261a8d 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml @@ -133,6 +133,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Nome Gestione alias Hostnames diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index 6acfd997e1..beced6eb79 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -200,6 +200,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + 名前 ドメインの割り当て diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml index c7178e312b..b72503e987 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml @@ -131,6 +131,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + 이름 호스트네임 관리 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index 7d1a4cc2d3..2955ce716a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -194,6 +194,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Naam Beheer domeinnamen diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml index c2c8b4d073..97dcf9ef38 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml @@ -131,6 +131,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Nazwa Zarządzaj nazwami hostów diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml index 016f7c2a19..e67a4c4b15 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml @@ -131,6 +131,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Nome Gerenciar hostnames diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index e162dbaead..84b87b872f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -260,6 +260,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Название Управление доменами diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index ea1f0c9ef0..10e882f260 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -187,6 +187,34 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + Namn Hantera domännamn diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index 372bc480cc..b8c933c101 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -171,6 +171,34 @@ Discard changes You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes + + + Done + + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items 锚点名称 From e2aee08959874ed1647caf3585e321d86a2b7b64 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Sun, 22 May 2016 13:52:02 +0200 Subject: [PATCH 016/413] Change back to original timeout value. --- .../src/views/propertyeditors/listview/listview.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 2ea325b451..5f3810723a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -208,7 +208,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $timeout(function () { $scope.bulkStatus = ""; $scope.actionInProgress = false; - }, 0); + }, 500); if (reload === true) { $scope.reloadView($scope.contentId); From e39f818389d306852755336c3f42d5ed2f19449a Mon Sep 17 00:00:00 2001 From: bjarnef Date: Sun, 22 May 2016 13:53:43 +0200 Subject: [PATCH 017/413] Fix indent --- src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index f3701c706a..602f66ddf4 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -192,7 +192,7 @@ You have unsaved changes Are you sure you want to navigate away from this page? - you have unsaved changes - + Done Deleted %0% item From 45ff11b884573c2925cb246c2d4ad37fdd543a2a Mon Sep 17 00:00:00 2001 From: bjarnef Date: Sun, 22 May 2016 14:26:37 +0200 Subject: [PATCH 018/413] Change "siten" to "sitet" --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index d68e21daf7..4cb2794976 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -28,7 +28,7 @@ Udgiv Fortryd udgivelse Genindlæs elementer - Genudgiv hele siten + Genudgiv hele sitet Gendan Rettigheder Fortryd ændringer @@ -262,7 +262,7 @@ Fjern makro Obligatorisk Sitet er genindekseret - Siten er nu genudgivet + Sitet er nu genudgivet Websitets cache vil blive genopfrisket. Alt udgivet indhold vil blive opdateret, mens upubliceret indhold vil forblive upubliceret. Antal kolonner Antal rækker From 06077820287171923ed3ab46befa6b66692d44d5 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Sun, 22 May 2016 21:08:21 +0200 Subject: [PATCH 019/413] Added localization for the new password reset stuff. --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 4cb2794976..33e9e4a4d2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -429,7 +429,7 @@ Layout Henter Låst - Login + Log ind Log af Log ud Makro @@ -606,7 +606,6 @@ Forny for at gemme dine ændringer - Så er det søndag! Smil, det er mandag! Hurra, det er tirsdag! @@ -614,13 +613,21 @@ Glædelig torsdag! Endelig fredag! Glædelig lørdag - - + Log ind nedenfor indtast brugernavn og kodeord Din session er udløbet - - © 2001 - %0%
    umbraco.com

    ]]>
    + Glemt adgangskode? + En e-mail vil blive sendt til den angivne adresse med et link til at nulstille din adgangskode + En e-mail med instruktioner for nulstilling af adgangskoden vil blive sendt til den angivne adresse, hvis det matcher vores optegnelser + Tilbage til login formular + Angiv en ny adgangskode + Din adgangskode er blevet opdateret + Det link, du har klikket på, er ugyldigt eller udløbet + Umbraco: Nulstil adgangskode + + Dit brugernavn til at logge på Umbraco backoffice er: %0%

    Klik her for at nulstille din adgangskode eller kopier/indsæt denne URL i din browser:

    %1%

    ]]> +
    Skrivebord @@ -637,7 +644,7 @@ Intet element valgt, vælg et element i listen ovenfor før der klikkes 'fortsæt' Det nuværende element kan ikke lægges under denne pga. sin type Det nuværende element kan ikke ligge under en af dens undersider - Dette element må ikke findes på rodniveau + Dette element må ikke findes på rodniveau Denne handling er ikke tilladt fordi du ikke har de fornødne rettigheder på et eller flere af under-dokumenterne Relater det kopierede element til originalen From 695c69af7fbbbe49f51611ab90b136cc272567a2 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Sun, 22 May 2016 21:11:51 +0200 Subject: [PATCH 020/413] Updated indent --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 33e9e4a4d2..d4b70939ae 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -644,9 +644,9 @@ Intet element valgt, vælg et element i listen ovenfor før der klikkes 'fortsæt' Det nuværende element kan ikke lægges under denne pga. sin type Det nuværende element kan ikke ligge under en af dens undersider - Dette element må ikke findes på rodniveau + Dette element må ikke findes på rodniveau Denne handling er ikke tilladt fordi du ikke har de fornødne rettigheder på et eller flere af under-dokumenterne - Relater det kopierede element til originalen + Relater det kopierede element til originalen Rediger dine notificeringer for %0% From 1fc17e6f46db1c6f4114195e2558ceeb591e8dfa Mon Sep 17 00:00:00 2001 From: Jeroen Breuer Date: Tue, 24 May 2016 17:04:39 +0200 Subject: [PATCH 021/413] Fix for U4-8510 --- .../ValueConverters/MultipleTextStringValueConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs index 39bcf85b12..a3b12a6688 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs @@ -28,7 +28,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // // - var sourceString = source.ToString(); + var sourceString = source != null ? source.ToString() : null; if (string.IsNullOrWhiteSpace(sourceString)) return Enumerable.Empty(); //SD: I have no idea why this logic is here, I'm pretty sure we've never saved the multiple txt string From b168923d5f8880c64e0b08663ccd64fcc28b6a98 Mon Sep 17 00:00:00 2001 From: Darren Ferguson Date: Sun, 29 May 2016 15:46:42 +0100 Subject: [PATCH 022/413] http://issues.umbraco.org/issue/U4-7032 : Include a password checker for Active Directory - as ability to authenticate via AD was removed in 7.3 --- ...eDirectoryBackOfficeUserPasswordChecker.cs | 25 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 ++ 2 files changed, 27 insertions(+) create mode 100644 src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs diff --git a/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs b/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs new file mode 100644 index 0000000000..aef4f3dc76 --- /dev/null +++ b/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs @@ -0,0 +1,25 @@ +using System.Configuration; +using System.DirectoryServices.AccountManagement; +using System.Threading.Tasks; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Security +{ + public class ActiveDirectoryBackOfficeUserPasswordChecker : IBackOfficeUserPasswordChecker + { + public Task CheckPasswordAsync(BackOfficeIdentityUser user, string password) + { + bool isValid; + using (var pc = new PrincipalContext(ContextType.Domain, ConfigurationManager.AppSettings["ActiveDirectoryDomain"])) + { + isValid = pc.ValidateCredentials(user.UserName, password); + } + + var result = isValid + ? BackOfficeUserPasswordCheckerResult.ValidCredentials + : BackOfficeUserPasswordCheckerResult.InvalidCredentials; + + return Task.FromResult(result); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 141622687e..72bf9ddd42 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -110,6 +110,7 @@ False ..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll + @@ -485,6 +486,7 @@ + From def690b8c368f784094e5f4c98b75a28f639eb47 Mon Sep 17 00:00:00 2001 From: Darren Ferguson Date: Mon, 30 May 2016 11:15:03 +0100 Subject: [PATCH 023/413] Fix U4-8532 - No built in Active Directory authentication in Umbraco 7.3+ --- .../ActiveDirectoryBackOfficeUserPasswordChecker.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs b/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs index aef4f3dc76..819fa87a56 100644 --- a/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs @@ -7,10 +7,16 @@ namespace Umbraco.Core.Security { public class ActiveDirectoryBackOfficeUserPasswordChecker : IBackOfficeUserPasswordChecker { + public virtual string ActiveDirectoryDomain { + get { + return ConfigurationManager.AppSettings["ActiveDirectoryDomain"]; + } + } + public Task CheckPasswordAsync(BackOfficeIdentityUser user, string password) { bool isValid; - using (var pc = new PrincipalContext(ContextType.Domain, ConfigurationManager.AppSettings["ActiveDirectoryDomain"])) + using (var pc = new PrincipalContext(ContextType.Domain, ActiveDirectoryDomain)) { isValid = pc.ValidateCredentials(user.UserName, password); } From 3f5729767d362c86011865845cebea28f1ea5c92 Mon Sep 17 00:00:00 2001 From: Oliver Philpott Date: Thu, 9 Jun 2016 10:08:05 +0100 Subject: [PATCH 024/413] U4-8509 - Added woff2 mime type --- src/Umbraco.Web.UI/web.Template.config | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index dd66460408..1d170be7b1 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -317,6 +317,8 @@ + + From 952ff74899377b85d5767b670fc35abb64c329b4 Mon Sep 17 00:00:00 2001 From: Oliver Philpott Date: Thu, 9 Jun 2016 10:26:03 +0100 Subject: [PATCH 025/413] Fixed formatting for woff2 mimetype --- src/Umbraco.Web.UI/web.Template.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 1d170be7b1..ffc8889625 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -317,8 +317,8 @@ - - + + From a099c40d9322d81ef0914b25c5c638094e357ea1 Mon Sep 17 00:00:00 2001 From: Claus Date: Sun, 12 Jun 2016 20:39:53 +0200 Subject: [PATCH 026/413] U4-8584 Starterkit thumbnails are not really thumbnails adding a parameter to the image url to avoid loading a massive image when its only rendered in a thumbnail size. --- src/Umbraco.Web.UI/umbraco/Install/Legacy/loadStarterKits.ascx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/Install/Legacy/loadStarterKits.ascx b/src/Umbraco.Web.UI/umbraco/Install/Legacy/loadStarterKits.ascx index aadd444a42..f509ab96f6 100644 --- a/src/Umbraco.Web.UI/umbraco/Install/Legacy/loadStarterKits.ascx +++ b/src/Umbraco.Web.UI/umbraco/Install/Legacy/loadStarterKits.ascx @@ -42,7 +42,7 @@
  • ">
    - <%# ((Package)Container.DataItem).Text %> + <%# ((Package)Container.DataItem).Text %>

    <%# ((Package)Container.DataItem).Text %>

    <%# ((Package)Container.DataItem).Description %> From 2e9e8f23e96f0b785e5d0af48592c71ef71de2c9 Mon Sep 17 00:00:00 2001 From: Claus Date: Sun, 12 Jun 2016 20:40:39 +0200 Subject: [PATCH 027/413] adjustments for paddings. --- src/Umbraco.Web.UI/umbraco/Install/Legacy/loadStarterKits.ascx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/Install/Legacy/loadStarterKits.ascx b/src/Umbraco.Web.UI/umbraco/Install/Legacy/loadStarterKits.ascx index f509ab96f6..490ff5b3ba 100644 --- a/src/Umbraco.Web.UI/umbraco/Install/Legacy/loadStarterKits.ascx +++ b/src/Umbraco.Web.UI/umbraco/Install/Legacy/loadStarterKits.ascx @@ -42,7 +42,7 @@
  • ">
    - <%# ((Package)Container.DataItem).Text %> + <%# ((Package)Container.DataItem).Text %>

    <%# ((Package)Container.DataItem).Text %>

    <%# ((Package)Container.DataItem).Description %> From f247e1b66f3a119743fc6789bb853b6c2f7d5ff5 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Mon, 20 Jun 2016 19:54:46 +0200 Subject: [PATCH 028/413] Document getByQuery --- .../src/common/resources/entity.resource.js | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index a9296acc37..914b601249 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -109,16 +109,6 @@ function entityResource($q, $http, umbRequestHelper) { [{ id: id}, {type: type }])), 'Failed to retrieve entity data for id ' + id); }, - - getByQuery: function (query, nodeContextId, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByQuery", - [{query: query},{ nodeContextId: nodeContextId}, {type: type }])), - 'Failed to retrieve entity data for query ' + query); - }, /** * @ngdoc method @@ -168,7 +158,41 @@ function entityResource($q, $http, umbRequestHelper) { /** * @ngdoc method - * @name umbraco.resources.entityResource#getEntityById + * @name umbraco.resources.entityResource#getByQuery + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity from a given xpath + * + * ##usage + *
    +         * //get content by xpath
    +         * entityResource.getByQuery("$current", -1, "Document")
    +         *    .then(function(ent) {
    +         *        var myDoc = ent; 
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {string} query xpath to use in query + * @param {Int} nodeContextId id id to start from + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getByQuery: function (query, nodeContextId, type) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByQuery", + [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), + 'Failed to retrieve entity data for query ' + query); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getAll * @methodOf umbraco.resources.entityResource * * @description @@ -260,7 +284,7 @@ function entityResource($q, $http, umbRequestHelper) { /** * @ngdoc method - * @name umbraco.resources.entityResource#searchMedia + * @name umbraco.resources.entityResource#search * @methodOf umbraco.resources.entityResource * * @description From 944898d6e5fe9c736aec65ae334b5a1f54159648 Mon Sep 17 00:00:00 2001 From: Wincent Date: Tue, 21 Jun 2016 14:18:55 +0200 Subject: [PATCH 029/413] http://issues.umbraco.org/issue/U4-8530 Adds Preview property to ContentItemDisplay --- src/Umbraco.Web.UI.Client/src/views/content/edit.html | 2 +- .../Models/ContentEditing/ContentItemDisplay.cs | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/content/edit.html index be79f797a0..c07dad7555 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/edit.html @@ -58,7 +58,7 @@ { + public ContentItemDisplay() + { + Preview = true; + } + [DataMember(Name = "publishDate")] public DateTime? PublishDate { get; set; } @@ -34,7 +39,10 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "urls")] public string[] Urls { get; set; } - + + [DataMember( Name = "preview" )] + public bool Preview { get; set; } + /// /// The allowed 'actions' based on the user's permissions - Create, Update, Publish, Send to publish /// From 8d55a55a80ee79d4e628b736e0374d300658a7a3 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Thu, 23 Jun 2016 17:45:01 +0200 Subject: [PATCH 030/413] Added a tooltip with icon alias on mouseover --- .../src/views/components/editor/umb-editor-header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html index 46c666b8cc..ed96587cb0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html @@ -6,7 +6,7 @@
    -
    +
    From 889ef47fd2bb8d0767bbabfcd49ee21db2744acd Mon Sep 17 00:00:00 2001 From: bjarnef Date: Fri, 24 Jun 2016 00:44:45 +0200 Subject: [PATCH 031/413] Update localization of listview bulk actions --- .../listview/listview.controller.js | 95 ++++++++----------- 1 file changed, 41 insertions(+), 54 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 5f3810723a..e68cc71010 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -371,41 +371,34 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie applySelected( function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, function (count, total) { - return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); - //var key = (total === 1 ? "bulk_deletedItemOfItem" : "bulk_deletedItemOfItems"); - //localizationService.localize(key, [count, total]).then(function (value) { - // return value; - //}); + var key = (total === 1 ? "bulk_deletedItemOfItem" : "bulk_deletedItemOfItems"); + localizationService.localize(key, [count, total]).then(function (value) { + return value; + }); }, function (total) { - return "Deleted " + total + " item" + (total > 1 ? "s" : ""); - //var key = (total === 1 ? "bulk_deletedItem" : "bulk_deletedItems"); - //localizationService.localize(key, [total]).then(function (value) { - // return value; - //}); + var key = (total === 1 ? "bulk_deletedItem" : "bulk_deletedItems"); + localizationService.localize(key, [total]).then(function (value) { + return value; + }); }, - //localizationService.localize("defaultdialogs_confirmdelete") + "?" - "Sure you want to delete?"); + localizationService.localize("defaultdialogs_confirmdelete") + "?"); }; $scope.publish = function () { applySelected( function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, function (count, total) { - return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); - //var key = (total === 1 ? "bulk_publishedItemOfItem" : "bulk_publishedItemOfItems"); - //localizationService.localize(key, [count, total]).then(function (value) { - // console.log(key, value); - // return value; - //}); + var key = (total === 1 ? "bulk_publishedItemOfItem" : "bulk_publishedItemOfItems"); + localizationService.localize(key, [count, total]).then(function (value) { + return value; + }); }, function (total) { - return "Published " + total + " item" + (total > 1 ? "s" : ""); - //var key = (total === 1 ? "bulk_publishedItem" : "bulk_publishedItems"); - //localizationService.localize(key, [total]).then(function (value) { - // console.log(key, value); - // return value; - //}); + var key = (total === 1 ? "bulk_publishedItem" : "bulk_publishedItems"); + localizationService.localize(key, [total]).then(function (value) { + return value; + }); }); }; @@ -413,18 +406,16 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie applySelected( function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, function (count, total) { - return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); - //var key = (total === 1 ? "bulk_unpublishedItemOfItem" : "bulk_unpublishedItemOfItems"); - //localizationService.localize(key, [count, total]).then(function (value) { - // return value; - //}); + var key = (total === 1 ? "bulk_unpublishedItemOfItem" : "bulk_unpublishedItemOfItems"); + localizationService.localize(key, [count, total]).then(function (value) { + return value; + }); }, function (total) { - return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); - //var key = (total === 1 ? "bulk_unpublishedItem" : "bulk_unpublishedItems"); - //localizationService.localize(key, [total]).then(function (value) { - // return value; - //}); + var key = (total === 1 ? "bulk_unpublishedItem" : "bulk_unpublishedItems"); + localizationService.localize(key, [total]).then(function (value) { + return value; + }); }); }; @@ -458,18 +449,16 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie applySelected( function (selected, index) { return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }); }, function (count, total) { - return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); - //var key = (total === 1 ? "bulk_movedItemOfItem" : "bulk_movedItemOfItems"); - //localizationService.localize(key, [count, total]).then(function (value) { - // return value; - //}); + var key = (total === 1 ? "bulk_movedItemOfItem" : "bulk_movedItemOfItems"); + localizationService.localize(key, [count, total]).then(function (value) { + return value; + }); }, function (total) { - return "Moved " + total + " item" + (total > 1 ? "s" : ""); - //var key = (total === 1 ? "bulk_movedItem" : "bulk_movedItems"); - //localizationService.localize(key, [total]).then(function (value) { - // return value; - //}); + var key = (total === 1 ? "bulk_movedItem" : "bulk_movedItems"); + localizationService.localize(key, [total]).then(function (value) { + return value; + }); }); } @@ -501,18 +490,16 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie applySelected( function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, function (count, total) { - return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); - //var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems"); - //localizationService.localize(key, [count, total]).then(function (value) { - // return value; - //}); + var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems"); + localizationService.localize(key, [count, total]).then(function (value) { + return value; + }); }, function (total) { - return "Copied " + total + " item" + (total > 1 ? "s" : ""); - //var key = (total === 1 ? "bulk_copiedItem" : "bulk_copiedItems"); - //localizationService.localize(key, [total]).then(function (value) { - // return value; - //}); + var key = (total === 1 ? "bulk_copiedItem" : "bulk_copiedItems"); + localizationService.localize(key, [total]).then(function (value) { + return value; + }); }); } From 29e45534c51f61439606eeaa9188f97fd49bb80b Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Sat, 25 Jun 2016 10:54:10 +0100 Subject: [PATCH 032/413] Add an overload to GetDictionaryValue to provide a fallback - U4-8657 --- src/Umbraco.Web/UmbracoHelper.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 6d5e33b239..098e56f0dd 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -375,7 +375,21 @@ namespace Umbraco.Web { return CultureDictionary[key]; } - + /// + /// Returns the dictionary value for the key specified, and if empty returns the specified default fall back value + /// + /// key of dictionary item + /// fall back text if dictionary item is empty - Name altText to match Umbraco.Field + /// + public string GetDictionaryValue(string key, string altText) + { + var dictionaryValue = GetDictionaryValue(key); + if (String.IsNullOrWhiteSpace(dictionaryValue)) + { + dictionaryValue = altText; + } + return dictionaryValue; + } /// /// Returns the ICultureDictionary for access to dictionary items /// From fdd1a89fb35e78abe4202f6c9db25f751199d0c0 Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Tue, 28 Jun 2016 13:11:19 +0200 Subject: [PATCH 033/413] Added utilities font weight --- src/Umbraco.Web.UI.Client/src/less/belle.less | 3 +++ .../src/less/utilities/_font-weight.less | 10 ++++++++++ .../src/views/dashboard/developer/healthcheck.html | 6 +++--- 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/utilities/_font-weight.less diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 8700bb8f36..e1e10c33b1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -60,6 +60,9 @@ @import "application/shadows.less"; @import "application/animations.less"; +// Utilities +@import "utilities/_font-weight.less"; + // Belle styles @import "buttons.less"; @import "forms.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_font-weight.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_font-weight.less new file mode 100644 index 0000000000..111ed4b2ef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_font-weight.less @@ -0,0 +1,10 @@ +/* + FONT WEIGHT +*/ + + + +.light { font-weight: 300; } +.normal { font-weight: 500; } +.semi-bold { font-weight: 600; } +.bold { font-weight: 700; } diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html index 2c60fa6868..382ccbb0bb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html @@ -1,6 +1,6 @@
    -

    Health Check

    -

    The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button.
    +

    Health Check

    +

    The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button.
    You can add your own health checks, have a look at the documentation for more information about custom health checks.

    @@ -86,7 +86,7 @@
    - Set new value: + Set new value: From 4b084426522b257a859aad9bbf678cbdc8058b47 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Fri, 1 Jul 2016 00:04:09 +0200 Subject: [PATCH 034/413] Fix localization --- .../listview/listview.controller.js | 78 ++++++++----------- 1 file changed, 31 insertions(+), 47 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index e68cc71010..3ed91ee90a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -368,38 +368,34 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie } $scope.delete = function () { - applySelected( - function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, - function (count, total) { - var key = (total === 1 ? "bulk_deletedItemOfItem" : "bulk_deletedItemOfItems"); - localizationService.localize(key, [count, total]).then(function (value) { - return value; - }); - }, - function (total) { - var key = (total === 1 ? "bulk_deletedItem" : "bulk_deletedItems"); - localizationService.localize(key, [total]).then(function (value) { - return value; - }); - }, - localizationService.localize("defaultdialogs_confirmdelete") + "?"); + var confirmDeleteText = ''; + localizationService.localize("defaultdialogs_confirmdelete").then(function (value) { + confirmDeleteText = value; + applySelected( + function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, + function (count, total) { + var key = (total === 1 ? "bulk_deletedItemOfItem" : "bulk_deletedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function (total) { + var key = (total === 1 ? "bulk_deletedItem" : "bulk_deletedItems"); + return localizationService.localize(key, [total]); + }, + confirmDeleteText + "?"); + }); }; - $scope.publish = function () { - applySelected( - function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, - function (count, total) { - var key = (total === 1 ? "bulk_publishedItemOfItem" : "bulk_publishedItemOfItems"); - localizationService.localize(key, [count, total]).then(function (value) { - return value; + $scope.publish = function () { + applySelected( + function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, + function (count, total) { + var key = (total === 1 ? "bulk_publishedItemOfItem" : "bulk_publishedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function (total) { + var key = (total === 1 ? "bulk_publishedItem" : "bulk_publishedItems"); + return localizationService.localize(key, [total]); }); - }, - function (total) { - var key = (total === 1 ? "bulk_publishedItem" : "bulk_publishedItems"); - localizationService.localize(key, [total]).then(function (value) { - return value; - }); - }); }; $scope.unpublish = function () { @@ -407,15 +403,11 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, function (count, total) { var key = (total === 1 ? "bulk_unpublishedItemOfItem" : "bulk_unpublishedItemOfItems"); - localizationService.localize(key, [count, total]).then(function (value) { - return value; - }); + return localizationService.localize(key, [count, total]); }, function (total) { var key = (total === 1 ? "bulk_unpublishedItem" : "bulk_unpublishedItems"); - localizationService.localize(key, [total]).then(function (value) { - return value; - }); + return localizationService.localize(key, [total]); }); }; @@ -450,15 +442,11 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie function (selected, index) { return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }); }, function (count, total) { var key = (total === 1 ? "bulk_movedItemOfItem" : "bulk_movedItemOfItems"); - localizationService.localize(key, [count, total]).then(function (value) { - return value; - }); + return localizationService.localize(key, [count, total]); }, function (total) { var key = (total === 1 ? "bulk_movedItem" : "bulk_movedItems"); - localizationService.localize(key, [total]).then(function (value) { - return value; - }); + return localizationService.localize(key, [total]); }); } @@ -491,15 +479,11 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, function (count, total) { var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems"); - localizationService.localize(key, [count, total]).then(function (value) { - return value; - }); + return localizationService.localize(key, [count, total]); }, function (total) { var key = (total === 1 ? "bulk_copiedItem" : "bulk_copiedItems"); - localizationService.localize(key, [total]).then(function (value) { - return value; - }); + return localizationService.localize(key, [total]); }); } From b84f17ce5e00f6e1dfe88e8a620d24dc03a3388e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Kottal?= Date: Fri, 1 Jul 2016 21:32:09 +0200 Subject: [PATCH 035/413] U4-8579: Replace linebreaks Uses Umbraco Helper to replace linebreaks for html in model.value --- .../Views/Partials/Grid/Editors/TextString.cshtml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml index 0cac4eb1ff..a031c658a9 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml @@ -5,7 +5,9 @@ { string markup = Model.editor.config.markup.ToString(); - markup = markup.Replace("#value#", Model.value.ToString()); + var UmbracoHelper = new UmbracoHelper(UmbracoContext.Current); + + markup = markup.Replace("#value#", UmbracoHelper.ReplaceLineBreaksForHtml(Model.value.ToString())); markup = markup.Replace("#style#", Model.editor.config.style.ToString()); From 826b9e9cc30e29846b88dd4047b1edb91accf363 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Mon, 4 Jul 2016 13:09:07 +0200 Subject: [PATCH 036/413] Added editor alias to the list of available editors --- .../src/views/propertyeditors/grid/dialogs/rowconfig.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html index b1343a960b..e54ffead62 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html @@ -75,6 +75,7 @@ checklist-model="currentCell.allowed" checklist-value="editor.alias"> {{editor.name}} + ({{editor.alias}})
  • From b9b3de0b0106fc3cb26da79c3c79d7e93686453b Mon Sep 17 00:00:00 2001 From: bjarnef Date: Thu, 7 Jul 2016 23:08:58 +0200 Subject: [PATCH 037/413] Only add class "-light" to grid items in content. --- .../views/components/umb-content-grid.html | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html index 499594253e..8ac49e4452 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html @@ -5,23 +5,23 @@ ng-repeat="item in content" ng-class="{'-selected': item.selected}" ng-click="clickItem(item, $event, $index)"> - +
    - +
    -
    {{ item.name }}
    +
    {{ item.name }}
    -
      -
    • -
      {{ property.header }}:
      -
      {{ item[property.alias] }}
      -
    • -
    +
      +
    • +
      {{ property.header }}:
      +
      {{ item[property.alias] }}
      +
    • +
    @@ -33,4 +33,4 @@ There are no items to show -
    + \ No newline at end of file From e7f0eda504b96a5dba556fd2f9e2d8972a084838 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Fri, 8 Jul 2016 12:45:17 +0200 Subject: [PATCH 038/413] Might as well have constants for the external searcher (and indexers) --- src/Umbraco.Core/Constants-Examine.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Umbraco.Core/Constants-Examine.cs b/src/Umbraco.Core/Constants-Examine.cs index 4ff6115749..6a362543cc 100644 --- a/src/Umbraco.Core/Constants-Examine.cs +++ b/src/Umbraco.Core/Constants-Examine.cs @@ -10,6 +10,20 @@ namespace Umbraco.Core { public static class Examine { + /// + /// The alias of the internal member indexer + /// + public const string InternalMemberIndexer = "InternalMemberIndexer"; + + /// + /// The alias of the internal content indexer + /// + public const string InternalIndexer = "InternalIndexer"; + + /// + /// The alias of the external content indexer + /// + public const string ExternalIndexer = "ExternalIndexer"; /// /// The alias of the internal member searcher /// @@ -19,6 +33,11 @@ namespace Umbraco.Core /// The alias of the internal content searcher ///
    public const string InternalSearcher = "InternalSearcher"; + + /// + /// The alias of the external content searcher + /// + public const string ExternalSearcher = "ExternalSearcher"; } } } From a40f87eb3699670eee3181e3a1373b462e1a2ec4 Mon Sep 17 00:00:00 2001 From: Jeremy Pyne Date: Thu, 14 Jul 2016 17:01:51 -0400 Subject: [PATCH 039/413] Fixed an issues with user generated PetaPoco's would not work if they included schema's. Fixed an issue where strong typed helper methods would not generate valid SQL for column names if the poco didn't explicitly set a [Column(Name="")] attribute. The PetaPoco format allows for this though and will default to the property name. Again this would cause issues for developers trying to use the DatabaseContext.Database class with their own Poco's. Both the above causes would happen for example if using the PetaPoco T4 templates to automatically generate Poco's. --- .../Persistence/PetaPocoSqlExtensions.cs | 16 +++++++++++----- .../SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs | 6 +++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs index 3cbd70803d..72fb03c498 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs @@ -51,7 +51,8 @@ namespace Umbraco.Core.Persistence public static Sql OrderBy(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlSyntax) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; + var columnAttribute = column.FirstAttribute(); + var columnName = columnAttribute == null || string.IsNullOrEmpty(columnAttribute.Name) ? column.Name : columnAttribute.Name; var type = typeof(TColumn); var tableNameAttribute = type.FirstAttribute(); @@ -74,7 +75,8 @@ namespace Umbraco.Core.Persistence public static Sql OrderByDescending(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlSyntax) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; + var columnAttribute = column.FirstAttribute(); + var columnName = columnAttribute == null || string.IsNullOrEmpty(columnAttribute.Name) ? column.Name : columnAttribute.Name; var type = typeof(TColumn); var tableNameAttribute = type.FirstAttribute(); @@ -96,7 +98,8 @@ namespace Umbraco.Core.Persistence public static Sql GroupBy(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlProvider) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; + var columnAttribute = column.FirstAttribute(); + var columnName = columnAttribute == null || string.IsNullOrEmpty(columnAttribute.Name) ? column.Name : columnAttribute.Name; return sql.GroupBy(sqlProvider.GetQuotedColumnName(columnName)); } @@ -178,8 +181,11 @@ namespace Umbraco.Core.Persistence var left = ExpressionHelper.FindProperty(leftMember) as PropertyInfo; var right = ExpressionHelper.FindProperty(rightMember) as PropertyInfo; - var leftColumnName = left.FirstAttribute().Name; - var rightColumnName = right.FirstAttribute().Name; + + var leftColumnAttribute = left.FirstAttribute(); + var leftColumnName = leftColumnAttribute == null || string.IsNullOrEmpty(leftColumnAttribute.Name) ? left.Name : leftColumnAttribute.Name; + var rightColumnAttribute = right.FirstAttribute(); + var rightColumnName = rightColumnAttribute == null || string.IsNullOrEmpty(rightColumnAttribute.Name) ? right.Name : rightColumnAttribute.Name; string onClause = string.Format("{0}.{1} = {2}.{3}", sqlSyntax.GetQuotedTableName(leftTableName), diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs index a6d1d690ba..2b8afcd3e8 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs @@ -29,7 +29,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax public override string GetQuotedTableName(string tableName) { - return string.Format("[{0}]", tableName); + if (tableName.Contains(".")) { + var tableNameParts = tableName.Split(new char[] { '.' }, 2); + return string.Format("[{0}].[{1}]", tableNameParts[0], tableNameParts[1]); + } else + return string.Format("[{0}]", tableName); } public override string GetQuotedColumnName(string columnName) From 2e24923cc400ec63df4dbb8afa5f21518d8cc34d Mon Sep 17 00:00:00 2001 From: Jeremy Pyne Date: Mon, 18 Jul 2016 09:00:23 -0400 Subject: [PATCH 040/413] Update PetaPoco.cs Sql() class should reset build cache when new Append operations are called. If not then stepping through with the debugger or doing internal logging can cause invalid SQL and unexpected results. --- src/Umbraco.Core/Persistence/PetaPoco.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index 88f90639d1..32f9024d49 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -2417,6 +2417,7 @@ namespace Umbraco.Core.Persistence else _rhs = sql; + _sqlFinal = null; return this; } From 4786a8037c442aa3a63575ffb520dbbac3b3e45a Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 18 Jul 2016 19:26:49 +0200 Subject: [PATCH 041/413] U4-6247 - fix PublicAccess repo --- .../Repositories/PublicAccessRepository.cs | 9 +++---- .../umbraco/dialogs/protectPage.aspx.cs | 25 +++++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs index 22fad9d99b..37200b2172 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -17,8 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories { public PublicAccessRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - } + { } private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; protected override IRepositoryCachePolicyFactory CachePolicyFactory @@ -46,6 +45,8 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoAccess.id IN (@ids)", new { ids = ids }); } + sql.OrderBy(x => x.NodeId, SqlSyntax); + var factory = new PublicAccessEntryFactory(); var dtos = Database.Fetch(new AccessRulesRelator().Map, sql); return dtos.Select(factory.BuildEntity); @@ -69,7 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories .From(SqlSyntax) .LeftJoin(SqlSyntax) .On(SqlSyntax, left => left.Id, right => right.AccessId); - + return sql; } @@ -162,7 +163,5 @@ namespace Umbraco.Core.Persistence.Repositories { return entity.Key; } - - } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs index a082b847cf..04fb9e6bf9 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs @@ -133,19 +133,21 @@ namespace umbraco.presentation.umbraco.dialogs _memberGroups.ID = "Membergroups"; _memberGroups.Width = 175; var selectedGroups = ""; - var roles = Roles.GetAllRoles().OrderBy(x => x); - if (roles.Any()) + // get roles from the membership provider + var roles = Roles.GetAllRoles().OrderBy(x => x).ToArray(); + + if (roles.Length > 0) { - foreach (string role in roles) + foreach (var role in roles) { - ListItem li = new ListItem(role, role); - if (IsPostBack == false) - { - if (Access.IsProtectedByMembershipRole(int.Parse(helper.Request("nodeid")), role)) - selectedGroups += role + ","; - } - _memberGroups.Items.Add(li); + var listItem = new ListItem(role, role); + _memberGroups.Items.Add(listItem); + if (IsPostBack) continue; + + // first time, initialize selected roles + if (Access.IsProtectedByMembershipRole(documentId, role)) + selectedGroups += role + ","; } } else @@ -153,6 +155,7 @@ namespace umbraco.presentation.umbraco.dialogs p_noGroupsFound.Visible = true; rb_advanced.Enabled = false; } + _memberGroups.Value = selectedGroups; groupsSelector.Controls.Add(_memberGroups); @@ -183,7 +186,7 @@ namespace umbraco.presentation.umbraco.dialogs SimpleLoginNameValidator.IsValid = true; var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - + int pageId = int.Parse(helper.Request("nodeId")); if (e.CommandName == "simple") From 576539c774dba808143a03bf244b9b466f95f44d Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 19 Jul 2016 10:46:18 +0200 Subject: [PATCH 042/413] U4-8663 - fix deleting properties in doctype editor --- .../Repositories/ContentTypeBaseRepository.cs | 190 ++++++++++-------- 1 file changed, 107 insertions(+), 83 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index dc85ad3a33..d86f77168e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -251,8 +251,8 @@ AND umbracoNode.id <> @id", var nodeDto = dto.NodeDto; Database.Update(nodeDto); + // look up ContentType entry to get PrimaryKey for updating the DTO // fixme - why? we are UPDATING so we should ALREADY have a PK! - //Look up ContentType entry to get PrimaryKey for updating the DTO var dtoPk = Database.First("WHERE nodeId = @Id", new { Id = entity.Id }); dto.PrimaryKey = dtoPk.PrimaryKey; Database.Update(dto); @@ -262,31 +262,30 @@ AND umbracoNode.id <> @id", foreach (var composition in entity.ContentTypeComposition) Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id }); - //Removing a ContentType from a composition (U4-1690) - //1. Find content based on the current ContentType: entity.Id - //2. Find all PropertyTypes on the ContentType that was removed - tracked id (key) - //3. Remove properties based on property types from the removed content type where the content ids correspond to those found in step one + // removing a ContentType from a composition (U4-1690) + // 1. Find content based on the current ContentType: entity.Id + // 2. Find all PropertyTypes on the ContentType that was removed - tracked id (key) + // 3. Remove properties based on property types from the removed content type where the content ids correspond to those found in step one var compositionBase = entity as ContentTypeCompositionBase; if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null && compositionBase.RemovedContentTypeKeyTracker.Any()) { - //Find Content based on the current ContentType + // find Content based on the current ContentType var sql = new Sql(); sql.Select("*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) + .From(SqlSyntax) + .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) .Where(x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document)) .Where(x => x.ContentTypeId == entity.Id); - var contentDtos = Database.Fetch(sql); - //Loop through all tracked keys, which corresponds to the ContentTypes that has been removed from the composition + + // loop through all tracked keys, which corresponds to the ContentTypes that has been removed from the composition foreach (var key in compositionBase.RemovedContentTypeKeyTracker) { - //Find PropertyTypes for the removed ContentType + // find PropertyTypes for the removed ContentType var propertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = key }); - //Loop through the Content that is based on the current ContentType in order to remove the Properties that are - //based on the PropertyTypes that belong to the removed ContentType. + // loop through the Content that is based on the current ContentType in order to remove the Properties that are + // based on the PropertyTypes that belong to the removed ContentType. foreach (var contentDto in contentDtos) { foreach (var propertyType in propertyTypes) @@ -294,51 +293,47 @@ AND umbracoNode.id <> @id", var nodeId = contentDto.NodeId; var propertyTypeId = propertyType.Id; var propertySql = new Sql().Select("cmsPropertyData.id") - .From() - .InnerJoin() - .On( - left => left.PropertyTypeId, right => right.Id) - .Where(x => x.NodeId == nodeId) - .Where(x => x.Id == propertyTypeId); + .From(SqlSyntax) + .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) + .Where(x => x.NodeId == nodeId) + .Where(x => x.Id == propertyTypeId); - //Finally delete the properties that match our criteria for removing a ContentType from the composition + // finally delete the properties that match our criteria for removing a ContentType from the composition Database.Delete(new Sql("WHERE id IN (" + propertySql.SQL + ")", propertySql.Arguments)); } } } } - //Delete the allowed content type entries before adding the updated collection - Database.Delete("WHERE Id = @Id", new { Id = entity.Id }); - //Insert collection of allowed content types + // delete the allowed content type entries before re-inserting the collectino of allowed content types + Database.Delete("WHERE Id = @Id", new { entity.Id }); foreach (var allowedContentType in entity.AllowedContentTypes) { Database.Insert(new ContentTypeAllowedContentTypeDto - { - Id = entity.Id, - AllowedId = allowedContentType.Id.Value, - SortOrder = allowedContentType.SortOrder - }); + { + Id = entity.Id, + AllowedId = allowedContentType.Id.Value, + SortOrder = allowedContentType.SortOrder + }); } + // FIXME below, manage the property types - if (((ICanBeDirty)entity).IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) + // delete ??? fixme wtf is this?! + // by excepting entries from db with entries from collections + if (entity.IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) { - //Delete PropertyTypes by excepting entries from db with entries from collections var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = entity.Id }); var dbPropertyTypeAlias = dbPropertyTypes.Select(x => x.Id); var entityPropertyTypes = entity.PropertyTypes.Where(x => x.HasIdentity).Select(x => x.Id); var items = dbPropertyTypeAlias.Except(entityPropertyTypes); foreach (var item in items) - { - //Before a PropertyType can be deleted, all Properties based on that PropertyType should be deleted. - Database.Delete("WHERE propertyTypeId = @Id", new { Id = item }); - Database.Delete("WHERE propertytypeid = @Id", new { Id = item }); - Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId", - new { Id = entity.Id, PropertyTypeId = item }); - } + DeletePropertyType(entity.Id, item); } + // delete tabs + // by excepting entries from db with entries from collections + List orphanPropertyTypeIds = null; if (entity.IsPropertyDirty("PropertyGroups") || entity.PropertyGroups.Any(x => x.IsDirty())) { // todo @@ -357,68 +352,97 @@ AND umbracoNode.id <> @id", // (all gone) // delete tabs that do not exist anymore - // get the tabs that are currently existing (in the db) - // get the tabs that we want, now - // and derive the tabs that we want to delete + // get the tabs that are currently existing (in the db), get the tabs that we want, + // now, and derive the tabs that we want to delete var existingPropertyGroups = Database.Fetch("WHERE contentTypeNodeId = @id", new { id = entity.Id }) .Select(x => x.Id) .ToList(); var newPropertyGroups = entity.PropertyGroups.Select(x => x.Id).ToList(); - var tabsToDelete = existingPropertyGroups + var groupsToDelete = existingPropertyGroups .Except(newPropertyGroups) .ToArray(); - // move properties to generic properties, and delete the tabs - if (tabsToDelete.Length > 0) + // delete the tabs + if (groupsToDelete.Length > 0) { - Database.Update("SET propertyTypeGroupId=NULL WHERE propertyTypeGroupId IN (@ids)", new { ids = tabsToDelete }); - Database.Delete("WHERE id IN (@ids)", new { ids = tabsToDelete }); + // if the tab contains properties, take care of them + // - move them to 'generic properties' so they remain consistent + // - keep track of them, later on we'll figure out what to do with them + // see http://issues.umbraco.org/issue/U4-8663 + orphanPropertyTypeIds = Database.Fetch("WHERE propertyTypeGroupId IN (@ids)", new { ids = groupsToDelete }) + .Select(x => x.Id).ToList(); + Database.Update("SET propertyTypeGroupId=NULL WHERE propertyTypeGroupId IN (@ids)", new { ids = groupsToDelete }); + + // now we can delete the tabs + Database.Delete("WHERE id IN (@ids)", new { ids = groupsToDelete }); } } + var propertyGroupFactory = new PropertyGroupFactory(entity.Id); - //Run through all groups to insert or update entries + // insert or update groups, assign properties foreach (var propertyGroup in entity.PropertyGroups) { - var tabDto = propertyGroupFactory.BuildGroupDto(propertyGroup); - int groupPrimaryKey = propertyGroup.HasIdentity - ? Database.Update(tabDto) - : Convert.ToInt32(Database.Insert(tabDto)); + // insert or update group + var groupDto = propertyGroupFactory.BuildGroupDto(propertyGroup); + var groupId = propertyGroup.HasIdentity + ? Database.Update(groupDto) + : Convert.ToInt32(Database.Insert(groupDto)); if (propertyGroup.HasIdentity == false) - propertyGroup.Id = groupPrimaryKey; //Set Id on new PropertyGroup + propertyGroup.Id = groupId; + else + groupId = propertyGroup.Id; - //Ensure that the PropertyGroup's Id is set on the PropertyTypes within a group - //unless the PropertyGroupId has already been changed. + // assign properties to the group + // (all of them, even those that have .IsPropertyDirty("PropertyGroupId") == true, + // because it should have been set to this group anyways and better be safe) foreach (var propertyType in propertyGroup.PropertyTypes) - { - if (propertyType.IsPropertyDirty("PropertyGroupId") == false) - { - var tempGroup = propertyGroup; - propertyType.PropertyGroupId = new Lazy(() => tempGroup.Id); - } - } + propertyType.PropertyGroupId = new Lazy(() => groupId); } - //Run through all PropertyTypes to insert or update entries + // insert or update properties + // all of them, no-group and in groups foreach (var propertyType in entity.PropertyTypes) { - var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); - //If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias - if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) - { - AssignDataTypeFromPropertyEditor(propertyType); - } + var groupId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); - //validate the alias! + // if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias + if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) + AssignDataTypeFromPropertyEditor(propertyType); + + // validate the alias ValidateAlias(propertyType); - var propertyTypeDto = propertyGroupFactory.BuildPropertyTypeDto(tabId, propertyType); - int typePrimaryKey = propertyType.HasIdentity - ? Database.Update(propertyTypeDto) - : Convert.ToInt32(Database.Insert(propertyTypeDto)); + // insert or update property + var propertyTypeDto = propertyGroupFactory.BuildPropertyTypeDto(groupId, propertyType); + var typeId = propertyType.HasIdentity + ? Database.Update(propertyTypeDto) + : Convert.ToInt32(Database.Insert(propertyTypeDto)); if (propertyType.HasIdentity == false) - propertyType.Id = typePrimaryKey; //Set Id on new PropertyType + propertyType.Id = typeId; + else + typeId = propertyType.Id; + + // not an orphan anymore + if (orphanPropertyTypeIds != null) + orphanPropertyTypeIds.Remove(typeId); } + + // deal with orphan properties: those that were in a deleted tab, + // and have not been re-mapped to another tab or to 'generic properties' + if (orphanPropertyTypeIds != null) + foreach (var id in orphanPropertyTypeIds) + DeletePropertyType(entity.Id, id); + } + + private void DeletePropertyType(int contentTypeId, int propertyTypeId) + { + // first clear dependencies + Database.Delete("WHERE propertyTypeId = @Id", new { Id = propertyTypeId }); + Database.Delete("WHERE propertytypeid = @Id", new { Id = propertyTypeId }); + + // then delete the property type + Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId", new { Id = contentTypeId, PropertyTypeId = propertyTypeId }); } protected IEnumerable GetAllowedContentTypeIds(int id) @@ -649,7 +673,7 @@ AND umbracoNode.id <> @id", var allParentContentTypes = contentTypes.Where(x => allParentIdsAsArray.Contains(x.Id)).ToArray(); foreach (var contentType in contentTypes) - { + { var entityId = contentType.Id; var parentContentTypes = allParentContentTypes.Where(x => @@ -714,10 +738,10 @@ AND umbracoNode.id <> @id", out IDictionary> parentMediaTypeIds) { Mandate.ParameterNotNull(db, "db"); - + var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, @@ -741,7 +765,7 @@ AND umbracoNode.id <> @id", ON ParentTypes.childContentTypeId = cmsContentType.nodeId WHERE (umbracoNode.nodeObjectType = @nodeObjectType) ORDER BY ctId"; - + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.MediaType) }); if (result.Any() == false) @@ -848,16 +872,16 @@ AND umbracoNode.id <> @id", return mediaType; } - internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, + internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, out IDictionary> associatedTemplates, out IDictionary> parentContentTypeIds) { Mandate.ParameterNotNull(db, "db"); - + var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId, cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, @@ -890,7 +914,7 @@ AND umbracoNode.id <> @id", ON ParentTypes.childContentTypeId = cmsContentType.nodeId WHERE (umbracoNode.nodeObjectType = @nodeObjectType) ORDER BY ctId"; - + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType)}); if (result.Any() == false) @@ -911,7 +935,7 @@ AND umbracoNode.id <> @id", { var ct = queue.Dequeue(); - //check for default templates + //check for default templates bool? isDefaultTemplate = Convert.ToBoolean(ct.dtIsDefault); int? templateId = ct.dtTemplateId; if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && isDefaultTemplate.Value && templateId.HasValue) From e2c2cdd632319a3574bccee0ef30e0296c2fd036 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 19 Jul 2016 11:22:23 +0200 Subject: [PATCH 043/413] Use the newest version of ModelsBuilder --- build/NuSpecs/UmbracoCms.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 5ee38e57fe..a815fb0446 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -17,7 +17,7 @@ - + From 2481b77f038b913e2c0c6c9267dac15322b10b7a Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 19 Jul 2016 11:13:49 +0200 Subject: [PATCH 044/413] U4-8729 - reviewing PR --- src/Umbraco.Core/Persistence/PetaPoco.cs | 110 +++++++++--------- .../Persistence/PetaPocoSqlExtensions.cs | 59 +++++----- .../MicrosoftSqlSyntaxProviderBase.cs | 8 +- 3 files changed, 90 insertions(+), 87 deletions(-) diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index 32f9024d49..8a32deb331 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -1,10 +1,10 @@ /* PetaPoco v4.0.3 - A Tiny ORMish thing for your POCO's. * Copyright © 2011 Topten Software. All Rights Reserved. - * + * * Apache License 2.0 - http://www.toptensoftware.com/petapoco/license - * - * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for - * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice + * + * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for + * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice * and Adam Schroder (@schotime) for lots of suggestions, improvements and Oracle support */ @@ -88,7 +88,7 @@ namespace Umbraco.Core.Persistence } // Results from paged request - public class Page + public class Page { public long CurrentPage { get; set; } public long TotalPages { get; set; } @@ -202,7 +202,7 @@ namespace Umbraco.Core.Persistence if (_providerName != null) _factory = DbProviderFactories.GetFactory(_providerName); - + string dbtype = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; if (dbtype.StartsWith("MySql")) _dbType = DBType.MySql; @@ -385,7 +385,7 @@ namespace Umbraco.Core.Persistence return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; } } - + // Helper to handle named parameters from object properties static Regex rxParams = new Regex(@"(? args_dest) @@ -425,8 +425,8 @@ namespace Umbraco.Core.Persistence } // Expand collections to parameter lists - if ((arg_val as System.Collections.IEnumerable) != null && - (arg_val as string) == null && + if ((arg_val as System.Collections.IEnumerable) != null && + (arg_val as string) == null && (arg_val as byte[]) == null) { var sb = new StringBuilder(); @@ -487,10 +487,10 @@ namespace Umbraco.Core.Persistence } else if (t == typeof(string)) { - // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. + // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. //Set before attempting to set Size, or Size will always max out at 4000 if ((item as string).Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter") - p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); + p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); p.Size = (item as string).Length + 1; if(p.Size < 4000) @@ -676,12 +676,12 @@ namespace Umbraco.Core.Persistence public bool ForceDateTimesToUtc { get; set; } // Return a typed list of pocos - public List Fetch(string sql, params object[] args) + public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - public List Fetch(Sql sql) + public List Fetch(Sql sql) { return Fetch(sql.SQL, sql.Arguments); } @@ -726,7 +726,7 @@ namespace Umbraco.Core.Persistence return true; } - public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) + public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) { // Add auto select clause if (EnableAutoSelect) @@ -764,8 +764,8 @@ namespace Umbraco.Core.Persistence } - // Fetch a page - public Page Page(long page, long itemsPerPage, string sql, params object[] args) + // Fetch a page + public Page Page(long page, long itemsPerPage, string sql, params object[] args) { string sqlCount, sqlPage; BuildPageQueries((page-1)*itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage); @@ -791,7 +791,7 @@ namespace Umbraco.Core.Persistence return result; } - public Page Page(long page, long itemsPerPage, Sql sql) + public Page Page(long page, long itemsPerPage, Sql sql) { return Page(page, itemsPerPage, sql.SQL, sql.Arguments); } @@ -820,7 +820,7 @@ namespace Umbraco.Core.Persistence } // Return an enumerable collection of pocos - public IEnumerable Query(string sql, params object[] args) + public IEnumerable Query(string sql, params object[] args) { if (EnableAutoSelect) sql = AddSelectClause(sql); @@ -1033,7 +1033,7 @@ namespace Umbraco.Core.Persistence } private List Delegates { get; set; } private Delegate GetItem(int index) { return Delegates[index]; } - + /// /// Calls the delegate at the specified index and returns its values /// @@ -1068,7 +1068,7 @@ namespace Umbraco.Core.Persistence // Create a multi-poco factory Func CreateMultiPocoFactory(Type[] types, string sql, IDataReader r) - { + { // Call each delegate var dels = new List(); int pos = 0; @@ -1088,7 +1088,7 @@ namespace Umbraco.Core.Persistence static Dictionary AutoMappers = new Dictionary(); static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim(); - // Get (or create) the multi-poco factory for a query + // Get (or create) the multi-poco factory for a query Func GetMultiPocoFactory(Type[] types, string sql, IDataReader r) { // Build a key string (this is crap, should address this at some point) @@ -1113,8 +1113,8 @@ namespace Umbraco.Core.Persistence if (MultiPocoFactories.TryGetValue(key, out oFactory)) { //mpFactory = oFactory; - return (Func)oFactory; - } + return (Func)oFactory; + } } finally { @@ -1130,9 +1130,9 @@ namespace Umbraco.Core.Persistence if (MultiPocoFactories.TryGetValue(key, out oFactory)) { return (Func)oFactory; - } - - // Create the factory + } + + // Create the factory var factory = CreateMultiPocoFactory(types, sql, r); MultiPocoFactories.Add(key, factory); @@ -1207,54 +1207,54 @@ namespace Umbraco.Core.Persistence } } - - public IEnumerable Query(Sql sql) + + public IEnumerable Query(Sql sql) { return Query(sql.SQL, sql.Arguments); } - public bool Exists(object primaryKey) + public bool Exists(object primaryKey) { return FirstOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey) != null; } - public T Single(object primaryKey) + public T Single(object primaryKey) { return Single(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); } - public T SingleOrDefault(object primaryKey) + public T SingleOrDefault(object primaryKey) { return SingleOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); } - public T Single(string sql, params object[] args) + public T Single(string sql, params object[] args) { return Query(sql, args).Single(); } - public T SingleOrDefault(string sql, params object[] args) + public T SingleOrDefault(string sql, params object[] args) { return Query(sql, args).SingleOrDefault(); } - public T First(string sql, params object[] args) + public T First(string sql, params object[] args) { return Query(sql, args).First(); } - public T FirstOrDefault(string sql, params object[] args) + public T FirstOrDefault(string sql, params object[] args) { return Query(sql, args).FirstOrDefault(); } - public T Single(Sql sql) + public T Single(Sql sql) { return Query(sql).Single(); } - public T SingleOrDefault(Sql sql) + public T SingleOrDefault(Sql sql) { return Query(sql).SingleOrDefault(); } - public T First(Sql sql) + public T First(Sql sql) { return Query(sql).First(); } - public T FirstOrDefault(Sql sql) + public T FirstOrDefault(Sql sql) { return Query(sql).FirstOrDefault(); } @@ -1285,7 +1285,7 @@ namespace Umbraco.Core.Persistence return Insert(tableName, primaryKeyName, true, poco); } - // Insert a poco into a table. If the poco has a property with the same name + // Insert a poco into a table. If the poco has a property with the same name // as the primary key the id of the new record is assigned to it. Either way, // the new id is returned. public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco) @@ -1721,7 +1721,7 @@ namespace Umbraco.Core.Persistence { cmd.CommandTimeout = CommandTimeout; } - + // Call hook OnExecutingCommand(cmd); @@ -1779,8 +1779,8 @@ namespace Umbraco.Core.Persistence public class ExpandoColumn : PocoColumn { public override void SetValue(object target, object val) { (target as IDictionary)[ColumnName]=val; } - public override object GetValue(object target) - { + public override object GetValue(object target) + { object val=null; (target as IDictionary).TryGetValue(ColumnName, out val); return val; @@ -1803,7 +1803,7 @@ namespace Umbraco.Core.Persistence } static readonly ObjectCache ObjectCache = new MemoryCache("NPoco"); - + } public class PocoData @@ -1812,7 +1812,7 @@ namespace Umbraco.Core.Persistence internal static bool UseLongKeys = false; //USE ONLY FOR TESTING - default is one hr internal static int SlidingExpirationSeconds = 3600; - + public static PocoData ForObject(object o, string primaryKeyName) { var t = o.GetType(); @@ -1836,7 +1836,7 @@ namespace Umbraco.Core.Persistence #endif return ForType(t); } - + public static PocoData ForType(Type t) { #if !PETAPOCO_NO_DYNAMIC @@ -1856,7 +1856,7 @@ namespace Umbraco.Core.Persistence InnerLock.ExitReadLock(); } - + // Cache it InnerLock.EnterWriteLock(); try @@ -1959,7 +1959,7 @@ namespace Umbraco.Core.Persistence public Delegate GetFactory(string sql, string connString, bool ForceDateTimesToUtc, int firstColumn, int countColumns, IDataReader r) { - //TODO: It would be nice to remove the irrelevant SQL parts - for a mapping operation anything after the SELECT clause isn't required. + //TODO: It would be nice to remove the irrelevant SQL parts - for a mapping operation anything after the SELECT clause isn't required. // This would ensure less duplicate entries that get cached, currently both of these queries would be cached even though they are // returning the same structured data: // SELECT * FROM MyTable ORDER BY MyColumn @@ -1981,7 +1981,7 @@ namespace Umbraco.Core.Persistence combiner.AddInt(countColumns); key = combiner.GetCombinedHashCode(); } - + var objectCache = _managedCache.GetCache(); @@ -2029,7 +2029,7 @@ namespace Umbraco.Core.Persistence il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter? if (converter != null) - il.Emit(OpCodes.Pop); // obj, obj, fieldname, + il.Emit(OpCodes.Pop); // obj, obj, fieldname, il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null if (converter != null) { @@ -2169,7 +2169,7 @@ namespace Umbraco.Core.Persistence // return it var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type)); - + return del; }; @@ -2178,7 +2178,7 @@ namespace Umbraco.Core.Persistence // the line belows returns existing item or adds the new value if it doesn't exist var value = (Lazy)objectCache.AddOrGetExisting(key, newValue, new CacheItemPolicy { - //sliding expiration of 1 hr, if the same key isn't used in this + //sliding expiration of 1 hr, if the same key isn't used in this // timeframe it will be removed from the cache SlidingExpiration = new TimeSpan(0, 0, SlidingExpirationSeconds) }); @@ -2272,7 +2272,7 @@ namespace Umbraco.Core.Persistence public TableInfo TableInfo { get; private set; } public Dictionary Columns { get; private set; } static System.Threading.ReaderWriterLockSlim InnerLock = new System.Threading.ReaderWriterLockSlim(); - + /// /// Returns a report of the current cache being utilized by PetaPoco /// @@ -2286,7 +2286,7 @@ namespace Umbraco.Core.Persistence foreach (var pocoData in m_PocoDatas) { sb.AppendFormat("\t{0}\n", pocoData.Key); - sb.AppendFormat("\t\tTable:{0} - Col count:{1}\n", pocoData.Value.TableInfo.TableName, pocoData.Value.QueryColumns.Length); + sb.AppendFormat("\t\tTable:{0} - Col count:{1}\n", pocoData.Value.TableInfo.TableName, pocoData.Value.QueryColumns.Length); } var cache = managedCache.GetCache(); @@ -2299,7 +2299,7 @@ namespace Umbraco.Core.Persistence totalBytes = Encoding.Unicode.GetByteCount(keys); sb.AppendFormat("\tTotal byte for keys:{0}\n", totalBytes); - + sb.AppendLine("\tAll Poco cache items:"); foreach (var item in cache) diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs index 72fb03c498..cecb0e2982 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs @@ -22,8 +22,7 @@ namespace Umbraco.Core.Persistence public static Sql From(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.From(sqlSyntax.GetQuotedTableName(tableName)); } @@ -51,12 +50,10 @@ namespace Umbraco.Core.Persistence public static Sql OrderBy(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlSyntax) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnAttribute = column.FirstAttribute(); - var columnName = columnAttribute == null || string.IsNullOrEmpty(columnAttribute.Name) ? column.Name : columnAttribute.Name; + var columnName = column.GetColumnName(); var type = typeof(TColumn); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); //need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 var syntax = string.Format("({0}.{1})", @@ -75,12 +72,10 @@ namespace Umbraco.Core.Persistence public static Sql OrderByDescending(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlSyntax) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnAttribute = column.FirstAttribute(); - var columnName = columnAttribute == null || string.IsNullOrEmpty(columnAttribute.Name) ? column.Name : columnAttribute.Name; + var columnName = column.GetColumnName(); var type = typeof(TColumn); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); var syntax = string.Format("{0}.{1} DESC", sqlSyntax.GetQuotedTableName(tableName), @@ -98,8 +93,7 @@ namespace Umbraco.Core.Persistence public static Sql GroupBy(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlProvider) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnAttribute = column.FirstAttribute(); - var columnName = columnAttribute == null || string.IsNullOrEmpty(columnAttribute.Name) ? column.Name : columnAttribute.Name; + var columnName = column.GetColumnName(); return sql.GroupBy(sqlProvider.GetQuotedColumnName(columnName)); } @@ -113,8 +107,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause InnerJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.InnerJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -128,8 +121,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause LeftJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.LeftJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -143,8 +135,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause LeftOuterJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.LeftOuterJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -158,8 +149,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause RightJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.RightJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -176,16 +166,14 @@ namespace Umbraco.Core.Persistence { var leftType = typeof(TLeft); var rightType = typeof(TRight); - var leftTableName = leftType.FirstAttribute().Value; - var rightTableName = rightType.FirstAttribute().Value; + var leftTableName = leftType.GetTableName(); + var rightTableName = rightType.GetTableName(); - var left = ExpressionHelper.FindProperty(leftMember) as PropertyInfo; - var right = ExpressionHelper.FindProperty(rightMember) as PropertyInfo; + var leftColumn = ExpressionHelper.FindProperty(leftMember) as PropertyInfo; + var rightColumn = ExpressionHelper.FindProperty(rightMember) as PropertyInfo; - var leftColumnAttribute = left.FirstAttribute(); - var leftColumnName = leftColumnAttribute == null || string.IsNullOrEmpty(leftColumnAttribute.Name) ? left.Name : leftColumnAttribute.Name; - var rightColumnAttribute = right.FirstAttribute(); - var rightColumnName = rightColumnAttribute == null || string.IsNullOrEmpty(rightColumnAttribute.Name) ? right.Name : rightColumnAttribute.Name; + var leftColumnName = leftColumn.GetColumnName(); + var rightColumnName = rightColumn.GetColumnName(); string onClause = string.Format("{0}.{1} = {2}.{3}", sqlSyntax.GetQuotedTableName(leftTableName), @@ -199,5 +187,20 @@ namespace Umbraco.Core.Persistence { return sql.Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x + " DESC").ToArray()))); } + + private static string GetTableName(this Type type) + { + // todo: returning string.Empty for now + // BUT the code bits that calls this method cannot deal with string.Empty so we + // should either throw, or fix these code bits... + var attr = type.FirstAttribute(); + return attr == null || string.IsNullOrWhiteSpace(attr.Value) ? string.Empty : attr.Value; + } + + private static string GetColumnName(this PropertyInfo column) + { + var attr = column.FirstAttribute(); + return attr == null || string.IsNullOrWhiteSpace(attr.Name) ? column.Name : attr.Name; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs index 2b8afcd3e8..449f5fb3b1 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs @@ -29,11 +29,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax public override string GetQuotedTableName(string tableName) { - if (tableName.Contains(".")) { - var tableNameParts = tableName.Split(new char[] { '.' }, 2); - return string.Format("[{0}].[{1}]", tableNameParts[0], tableNameParts[1]); - } else + if (tableName.Contains(".") == false) return string.Format("[{0}]", tableName); + + var tableNameParts = tableName.Split(new[] { '.' }, 2); + return string.Format("[{0}].[{1}]", tableNameParts[0], tableNameParts[1]); } public override string GetQuotedColumnName(string columnName) From d7e6b0732e7e7c9f5f17035250f9b18bcade0142 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 19 Jul 2016 11:58:02 +0200 Subject: [PATCH 045/413] Add upgrade hints for some of our dependencies --- build/NuSpecs/UmbracoCms.Core.nuspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index e22a57eb0c..f4a8054dac 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -29,6 +29,7 @@ + @@ -36,6 +37,7 @@ + From 05b0c16d186dd69dc662cbe1e9e89929648d4d1a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 19 Jul 2016 12:15:27 +0200 Subject: [PATCH 046/413] Use the newest version of CDF --- build/NuSpecs/UmbracoCms.Core.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index f4a8054dac..0cc09cd331 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -27,7 +27,7 @@ - + From 3c653fbd5f3693271af58867988f162a0e532b27 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 19 Jul 2016 12:40:08 +0200 Subject: [PATCH 047/413] Don't use C#6 syntax in tests --- src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 4ea998b394..28649457dd 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -100,7 +100,10 @@ namespace Umbraco.Tests.TestHelpers return _appContext; } - protected virtual ISqlSyntaxProvider SqlSyntax => GetSyntaxProvider(); + protected virtual ISqlSyntaxProvider SqlSyntax + { + get { return GetSyntaxProvider(); } + } /// /// The database behavior to use for the test/fixture From d687fadac346741a7f179b12753bc154c9ab3f91 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 19 Jul 2016 15:07:38 +0200 Subject: [PATCH 048/413] Performs some null checks in the HttpRequestCacheProvider since if there is no place to cache, it just doesn't do any caching. --- .../Cache/HttpRequestCacheProvider.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs index ca1f4e85a0..4f8e32ca7e 100644 --- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs @@ -9,6 +9,9 @@ namespace Umbraco.Core.Cache /// /// A cache provider that caches items in the HttpContext.Items /// + /// + /// If the Items collection is null, then this provider has no effect + /// internal class HttpRequestCacheProvider : DictionaryCacheProviderBase { // context provider @@ -50,17 +53,24 @@ namespace Umbraco.Core.Cache protected override IEnumerable GetDictionaryEntries() { const string prefix = CacheItemPrefix + "-"; + + if (ContextItems == null) return Enumerable.Empty(); + return ContextItems.Cast() .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix)); } protected override void RemoveEntry(string key) { + if (ContextItems == null) return; + ContextItems.Remove(key); } protected override object GetEntry(string key) { + if (ContextItems == null) return null; + return ContextItems[key]; } @@ -81,6 +91,7 @@ namespace Umbraco.Core.Cache get { + if (ContextItems == null) return new NoopLocker(); return new MonitorLock(ContextItems.SyncRoot); } } @@ -91,6 +102,9 @@ namespace Umbraco.Core.Cache public override object GetCacheItem(string cacheKey, Func getCacheItem) { + //no place to cache so just return the callback result + if (ContextItems == null) return getCacheItem(); + cacheKey = GetCacheKey(cacheKey); Lazy result; @@ -128,5 +142,12 @@ namespace Umbraco.Core.Cache #region Insert #endregion + + private class NoopLocker : DisposableObject + { + protected override void DisposeResources() + { + } + } } } \ No newline at end of file From 6ba761df208318f8f2a8baf0355a37c4f68afb6a Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 19 Jul 2016 15:37:55 +0200 Subject: [PATCH 049/413] Fix null checks in HttpRequestCacheProvider --- .../Cache/HttpRequestCacheProvider.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs index 4f8e32ca7e..792b5982b1 100644 --- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs @@ -37,6 +37,11 @@ namespace Umbraco.Core.Cache get { return _context != null ? _context.Items : HttpContext.Current.Items; } } + private bool HasContextItems + { + get { return (_context != null && _context.Items != null) || HttpContext.Current != null; } + } + // for unit tests public HttpRequestCacheProvider(HttpContextBase context) { @@ -54,7 +59,7 @@ namespace Umbraco.Core.Cache { const string prefix = CacheItemPrefix + "-"; - if (ContextItems == null) return Enumerable.Empty(); + if (HasContextItems == false) return Enumerable.Empty(); return ContextItems.Cast() .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix)); @@ -62,16 +67,14 @@ namespace Umbraco.Core.Cache protected override void RemoveEntry(string key) { - if (ContextItems == null) return; + if (HasContextItems == false) return; ContextItems.Remove(key); } protected override object GetEntry(string key) { - if (ContextItems == null) return null; - - return ContextItems[key]; + return HasContextItems ? ContextItems[key] : null; } #region Lock @@ -91,8 +94,9 @@ namespace Umbraco.Core.Cache get { - if (ContextItems == null) return new NoopLocker(); - return new MonitorLock(ContextItems.SyncRoot); + return HasContextItems + ? (IDisposable) new MonitorLock(ContextItems.SyncRoot) + : new NoopLocker(); } } @@ -103,7 +107,7 @@ namespace Umbraco.Core.Cache public override object GetCacheItem(string cacheKey, Func getCacheItem) { //no place to cache so just return the callback result - if (ContextItems == null) return getCacheItem(); + if (HasContextItems == false) return getCacheItem(); cacheKey = GetCacheKey(cacheKey); @@ -142,12 +146,10 @@ namespace Umbraco.Core.Cache #region Insert #endregion - private class NoopLocker : DisposableObject { protected override void DisposeResources() - { - } + { } } } } \ No newline at end of file From 0603cb3ab13c3466573f2092e1e89ec2649227b2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 19 Jul 2016 13:00:14 +0200 Subject: [PATCH 050/413] U4-8720 - add IPublishedContent Site, Children ext. methods --- src/Umbraco.Web/PublishedContentExtensions.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 578aab2755..95ae867e73 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1746,6 +1746,11 @@ namespace Umbraco.Web return content.Children().Where(predicate); } + public static IEnumerable Children(this IPublishedContent content, params string[] alias) + { + return content.Children(x => alias.InvariantContains(x.DocumentTypeAlias)); + } + /// /// Gets the children of the content, of a given content type. /// @@ -1853,6 +1858,16 @@ namespace Umbraco.Web #endregion + #region Axes: custom + + // todo: in v8, rename this 'Root' + public static IPublishedContent Site(this IPublishedContent content) + { + return content.AncestorOrSelf(1); + } + + #endregion + #region OfTypes // the .OfType() filter is nice when there's only one type From dc476dcb02823c260046907377fb4684612b86f0 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 19 Jul 2016 13:00:43 +0200 Subject: [PATCH 051/413] U4-8720 - get typed content by guid --- .../ITypedPublishedContentQuery.cs | 8 +++++ src/Umbraco.Web/PublishedContentQuery.cs | 33 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/ITypedPublishedContentQuery.cs b/src/Umbraco.Web/ITypedPublishedContentQuery.cs index aefe243a0b..63bb2dabe3 100644 --- a/src/Umbraco.Web/ITypedPublishedContentQuery.cs +++ b/src/Umbraco.Web/ITypedPublishedContentQuery.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Xml.XPath; using Umbraco.Core.Models; @@ -11,14 +12,21 @@ namespace Umbraco.Web public interface ITypedPublishedContentQuery { IPublishedContent TypedContent(int id); + IPublishedContent TypedContent(Guid id); IPublishedContent TypedContentSingleAtXPath(string xpath, params XPathVariable[] vars); IEnumerable TypedContent(IEnumerable ids); + IEnumerable TypedContent(IEnumerable ids); IEnumerable TypedContentAtXPath(string xpath, params XPathVariable[] vars); IEnumerable TypedContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); IEnumerable TypedContentAtRoot(); + // note: we CANNOT implement TypedMedia in v7 without break-changing IPublishedCache, + // since we don't support XPath navigation of the media tree. + IPublishedContent TypedMedia(int id); + //IPublishedContent TypedMedia(Guid id); IEnumerable TypedMedia(IEnumerable ids); + //IEnumerable TypedMedia(IEnumerable ids); IEnumerable TypedMediaAtRoot(); /// diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 16f42b8197..4966310c0f 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Xml.XPath; +using umbraco; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.Dynamics; using Umbraco.Core.Models; using Umbraco.Core.Xml; @@ -56,6 +58,13 @@ namespace Umbraco.Web : _typedContentQuery.TypedContent(id); } + public IPublishedContent TypedContent(Guid id) + { + return _typedContentQuery == null + ? TypedDocumentById(id, _contentCache) + : _typedContentQuery.TypedContent(id); + } + public IPublishedContent TypedContentSingleAtXPath(string xpath, params XPathVariable[] vars) { return _typedContentQuery == null @@ -70,6 +79,13 @@ namespace Umbraco.Web : _typedContentQuery.TypedContent(ids); } + public IEnumerable TypedContent(IEnumerable ids) + { + return _typedContentQuery == null + ? TypedDocumentsByIds(_contentCache, ids) + : _typedContentQuery.TypedContent(ids); + } + public IEnumerable TypedContentAtXPath(string xpath, params XPathVariable[] vars) { return _typedContentQuery == null @@ -150,7 +166,7 @@ namespace Umbraco.Web ? TypedDocumentById(id, _mediaCache) : _typedContentQuery.TypedMedia(id); } - + public IEnumerable TypedMedia(IEnumerable ids) { return _typedContentQuery == null @@ -196,6 +212,15 @@ namespace Umbraco.Web return doc; } + private IPublishedContent TypedDocumentById(Guid id, ContextualPublishedCache cache) + { + // todo: in v8, implement in a more efficient way + var legacyXml = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema; + var xpath = legacyXml ? "//node [@key=$guid]" : "//* [@isDoc and @key=$guid]"; + var doc = cache.GetSingleByXPath(xpath, new XPathVariable("guid", id.ToString())); + return doc; + } + private IPublishedContent TypedDocumentByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) { var doc = cache.GetSingleByXPath(xpath, vars); @@ -214,6 +239,12 @@ namespace Umbraco.Web return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); } + private IEnumerable TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable ids) + { + // todo: in v8, implement in a more efficient way + return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); + } + private IEnumerable TypedDocumentsByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) { var doc = cache.GetByXPath(xpath, vars); From f4a29908192824ebb937f86853513f2181f7781c Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 19 Jul 2016 14:38:44 +0100 Subject: [PATCH 052/413] Replace inline style with CSS class that achieves same result --- .../src/views/propertyeditors/grid/dialogs/rowconfig.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html index e54ffead62..093afd3c73 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html @@ -75,7 +75,7 @@ checklist-model="currentCell.allowed" checklist-value="editor.alias"> {{editor.name}} - ({{editor.alias}}) + ({{editor.alias}}) From a0caab410ec441d173856411291c09ec536c19ec Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 19 Jul 2016 16:13:46 +0200 Subject: [PATCH 053/413] U4-8720 - add dynamic support, UmbracoHelper methods --- .../IDynamicPublishedContentQuery.cs | 10 +- .../ITypedPublishedContentQuery.cs | 2 +- src/Umbraco.Web/PublishedContentQuery.cs | 33 ++- src/Umbraco.Web/UmbracoHelper.cs | 220 +++++++++++++----- 4 files changed, 207 insertions(+), 58 deletions(-) diff --git a/src/Umbraco.Web/IDynamicPublishedContentQuery.cs b/src/Umbraco.Web/IDynamicPublishedContentQuery.cs index 051ba07ff6..7c95fd7e2e 100644 --- a/src/Umbraco.Web/IDynamicPublishedContentQuery.cs +++ b/src/Umbraco.Web/IDynamicPublishedContentQuery.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Xml.XPath; using Umbraco.Core.Xml; @@ -10,15 +11,22 @@ namespace Umbraco.Web public interface IDynamicPublishedContentQuery { dynamic Content(int id); + dynamic Content(Guid id); dynamic ContentSingleAtXPath(string xpath, params XPathVariable[] vars); dynamic ContentSingleAtXPath(XPathExpression xpath, params XPathVariable[] vars); dynamic Content(IEnumerable ids); + dynamic Content(IEnumerable ids); dynamic ContentAtXPath(string xpath, params XPathVariable[] vars); dynamic ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); dynamic ContentAtRoot(); - + + // note: we CANNOT implement Media by Guid in v7 without break-changing IPublishedCache, + // since we don't support XPath navigation of the media tree. + dynamic Media(int id); + //dynamic Media(Guid id); dynamic Media(IEnumerable ids); + //dynamic Media(IEnumerable ids); dynamic MediaAtRoot(); /// diff --git a/src/Umbraco.Web/ITypedPublishedContentQuery.cs b/src/Umbraco.Web/ITypedPublishedContentQuery.cs index 63bb2dabe3..7a2be964f7 100644 --- a/src/Umbraco.Web/ITypedPublishedContentQuery.cs +++ b/src/Umbraco.Web/ITypedPublishedContentQuery.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web IEnumerable TypedContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); IEnumerable TypedContentAtRoot(); - // note: we CANNOT implement TypedMedia in v7 without break-changing IPublishedCache, + // note: we CANNOT implement TypedMedia by Guid in v7 without break-changing IPublishedCache, // since we don't support XPath navigation of the media tree. IPublishedContent TypedMedia(int id); diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 4966310c0f..f0f2461ad8 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -113,7 +113,14 @@ namespace Umbraco.Web ? DocumentById(id, _contentCache, DynamicNull.Null) : _dynamicContentQuery.Content(id); } - + + public dynamic Content(Guid id) + { + return _dynamicContentQuery == null + ? DocumentById(id, _contentCache, DynamicNull.Null) + : _dynamicContentQuery.Content(id); + } + public dynamic ContentSingleAtXPath(string xpath, params XPathVariable[] vars) { return _dynamicContentQuery == null @@ -135,6 +142,13 @@ namespace Umbraco.Web : _dynamicContentQuery.Content(ids); } + public dynamic Content(IEnumerable ids) + { + return _dynamicContentQuery == null + ? DocumentByIds(_contentCache, ids.ToArray()) + : _dynamicContentQuery.Content(ids); + } + public dynamic ContentAtXPath(string xpath, params XPathVariable[] vars) { return _dynamicContentQuery == null @@ -270,6 +284,14 @@ namespace Umbraco.Web : new DynamicPublishedContent(doc).AsDynamic(); } + private dynamic DocumentById(Guid id, ContextualPublishedCache cache, object ifNotFound) + { + var doc = TypedDocumentById(id, cache); + return doc == null + ? ifNotFound + : new DynamicPublishedContent(doc).AsDynamic(); + } + private dynamic DocumentByXPath(string xpath, XPathVariable[] vars, ContextualPublishedCache cache, object ifNotFound) { var doc = cache.GetSingleByXPath(xpath, vars); @@ -295,6 +317,15 @@ namespace Umbraco.Web return new DynamicPublishedContentList(nodes); } + private dynamic DocumentByIds(ContextualPublishedCache cache, IEnumerable ids) + { + var dNull = DynamicNull.Null; + var nodes = ids.Select(eachId => DocumentById(eachId, cache, dNull)) + .Where(x => TypeHelper.IsTypeAssignableFrom(x) == false) + .Cast(); + return new DynamicPublishedContentList(nodes); + } + private dynamic DocumentsByXPath(string xpath, XPathVariable[] vars, ContextualPublishedCache cache) { return new DynamicPublishedContentList( diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 165b81daba..587c821daa 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -15,6 +15,7 @@ using Umbraco.Web.Routing; using Umbraco.Web.Security; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Web.Mvc; using System.Web.Routing; using Umbraco.Core.Cache; @@ -561,21 +562,35 @@ namespace Umbraco.Web #region Content public IPublishedContent TypedContent(object id) - { - int intId; - return ConvertIdObjectToInt(id, out intId) ? ContentQuery.TypedContent(intId) : null; + { + return TypedContentForObject(id); } - public IPublishedContent TypedContent(int id) + private IPublishedContent TypedContentForObject(object id) + { + int intId; + if (ConvertIdObjectToInt(id, out intId)) + return ContentQuery.TypedContent(intId); + Guid guidId; + if (ConvertIdObjectToGuid(id, out guidId)) + return ContentQuery.TypedContent(guidId); + return null; + } + + public IPublishedContent TypedContent(int id) { return ContentQuery.TypedContent(id); } + public IPublishedContent TypedContent(Guid id) + { + return ContentQuery.TypedContent(id); + } + public IPublishedContent TypedContent(string id) { - int intId; - return ConvertIdObjectToInt(id, out intId) ? ContentQuery.TypedContent(intId) : null; - } + return TypedContentForObject(id); + } public IPublishedContent TypedContentSingleAtXPath(string xpath, params XPathVariable[] vars) { @@ -584,9 +599,21 @@ namespace Umbraco.Web public IEnumerable TypedContent(params object[] ids) { - return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); + return TypedContentForObjects(ids); } + private IEnumerable TypedContentForObjects(IEnumerable ids) + { + var idsA = ids.ToArray(); + IEnumerable intIds; + if (ConvertIdsObjectToInts(idsA, out intIds)) + return ContentQuery.TypedContent(intIds); + IEnumerable guidIds; + if (ConvertIdsObjectToGuids(idsA, out guidIds)) + return ContentQuery.TypedContent(guidIds); + return Enumerable.Empty(); + } + /// /// Gets the contents corresponding to the identifiers. /// @@ -598,6 +625,11 @@ namespace Umbraco.Web return ContentQuery.TypedContent(ids); } + public IEnumerable TypedContent(params Guid[] ids) + { + return ContentQuery.TypedContent(ids); + } + /// /// Gets the contents corresponding to the identifiers. /// @@ -605,8 +637,8 @@ namespace Umbraco.Web /// The existing contents corresponding to the identifiers. /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable TypedContent(params string[] ids) - { - return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); + { + return TypedContentForObjects(ids); } /// @@ -616,8 +648,8 @@ namespace Umbraco.Web /// The existing contents corresponding to the identifiers. /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable TypedContent(IEnumerable ids) - { - return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); + { + return TypedContentForObjects(ids); } /// @@ -628,7 +660,7 @@ namespace Umbraco.Web /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable TypedContent(IEnumerable ids) { - return ContentQuery.TypedContent(ConvertIdsObjectToInts(ids)); + return TypedContentForObjects(ids); } /// @@ -659,20 +691,29 @@ namespace Umbraco.Web public dynamic Content(object id) { - int intId; - return ConvertIdObjectToInt(id, out intId) ? ContentQuery.Content(intId) : DynamicNull.Null; + return ContentForObject(id); } - public dynamic Content(int id) + private dynamic ContentForObject(object id) + { + int intId; + if (ConvertIdObjectToInt(id, out intId)) + return ContentQuery.Content(intId); + Guid guidId; + if (ConvertIdObjectToGuid(id, out guidId)) + return ContentQuery.Content(guidId); + return DynamicNull.Null; + } + + public dynamic Content(int id) { return ContentQuery.Content(id); } public dynamic Content(string id) { - int intId; - return ConvertIdObjectToInt(id, out intId) ? ContentQuery.Content(intId) : DynamicNull.Null; - } + return ContentForObject(id); + } public dynamic ContentSingleAtXPath(string xpath, params XPathVariable[] vars) { @@ -691,9 +732,21 @@ namespace Umbraco.Web /// The existing contents corresponding to the identifiers. /// If an identifier does not match an existing content, it will be missing in the returned value. public dynamic Content(params object[] ids) - { - return ContentQuery.Content(ConvertIdsObjectToInts(ids)); - } + { + return ContentForObjects(ids); + } + + private dynamic ContentForObjects(IEnumerable ids) + { + var idsA = ids.ToArray(); + IEnumerable intIds; + if (ConvertIdsObjectToInts(idsA, out intIds)) + return ContentQuery.Content(intIds); + IEnumerable guidIds; + if (ConvertIdsObjectToGuids(idsA, out guidIds)) + return ContentQuery.Content(guidIds); + return Enumerable.Empty(); + } /// /// Gets the contents corresponding to the identifiers. @@ -714,8 +767,8 @@ namespace Umbraco.Web /// If an identifier does not match an existing content, it will be missing in the returned value. public dynamic Content(params string[] ids) { - return ContentQuery.Content(ConvertIdsObjectToInts(ids)); - } + return ContentForObjects(ids); + } /// /// Gets the contents corresponding to the identifiers. @@ -725,8 +778,8 @@ namespace Umbraco.Web /// If an identifier does not match an existing content, it will be missing in the returned value. public dynamic Content(IEnumerable ids) { - return ContentQuery.Content(ConvertIdsObjectToInts(ids)); - } + return ContentForObjects(ids); + } /// /// Gets the contents corresponding to the identifiers. @@ -747,8 +800,8 @@ namespace Umbraco.Web /// If an identifier does not match an existing content, it will be missing in the returned value. public dynamic Content(IEnumerable ids) { - return ContentQuery.Content(ConvertIdsObjectToInts(ids)); - } + return ContentForObjects(ids); + } public dynamic ContentAtXPath(string xpath, params XPathVariable[] vars) { @@ -765,35 +818,68 @@ namespace Umbraco.Web return ContentQuery.ContentAtRoot(); } - private bool ConvertIdObjectToInt(object id, out int intId) + private static bool ConvertIdObjectToInt(object id, out int intId) { var s = id as string; if (s != null) { return int.TryParse(s, out intId); - } - + } if (id is int) { intId = (int) id; return true; } - - throw new InvalidOperationException("The value of parameter 'id' must be either a string or an integer"); + intId = default(int); + return false; } - private IEnumerable ConvertIdsObjectToInts(IEnumerable ids) + private static bool ConvertIdObjectToGuid(object id, out Guid guidId) + { + var s = id as string; + if (s != null) + { + return Guid.TryParse(s, out guidId); + } + if (id is Guid) + { + guidId = (Guid) id; + return true; + } + guidId = default(Guid); + return false; + } + + private static bool ConvertIdsObjectToInts(IEnumerable ids, out IEnumerable intIds) { var list = new List(); + intIds = null; foreach (var id in ids) { int intId; if (ConvertIdObjectToInt(id, out intId)) - { list.Add(intId); - } + else + return false; // if one of them is not an int, fail } - return list; + intIds = list; + return true; + } + + private static bool ConvertIdsObjectToGuids(IEnumerable ids, out IEnumerable guidIds) + { + var list = new List(); + guidIds = null; + foreach (var id in ids) + { + Guid guidId; + if (ConvertIdObjectToGuid(id, out guidId)) + list.Add(guidId); + else + return false; // if one of them is not a guid, fail + } + guidIds = list; + return true; } #endregion @@ -834,9 +920,21 @@ namespace Umbraco.Web /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable TypedMedia(params object[] ids) - { - return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); - } + { + return TypedMediaForObjects(ids); + } + + private IEnumerable TypedMediaForObjects(IEnumerable ids) + { + var idsA = ids.ToArray(); + IEnumerable intIds; + if (ConvertIdsObjectToInts(idsA, out intIds)) + return ContentQuery.TypedMedia(intIds); + //IEnumerable guidIds; + //if (ConvertIdsObjectToGuids(idsA, out guidIds)) + // return ContentQuery.TypedMedia(guidIds); + return Enumerable.Empty(); + } /// /// Gets the medias corresponding to the identifiers. @@ -857,8 +955,8 @@ namespace Umbraco.Web /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable TypedMedia(params string[] ids) { - return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); - } + return TypedMediaForObjects(ids); + } /// /// Gets the medias corresponding to the identifiers. @@ -868,8 +966,8 @@ namespace Umbraco.Web /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable TypedMedia(IEnumerable ids) { - return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); - } + return TypedMediaForObjects(ids); + } /// /// Gets the medias corresponding to the identifiers. @@ -890,8 +988,8 @@ namespace Umbraco.Web /// If an identifier does not match an existing media, it will be missing in the returned value. public IEnumerable TypedMedia(IEnumerable ids) { - return ContentQuery.TypedMedia(ConvertIdsObjectToInts(ids)); - } + return TypedMediaForObjects(ids); + } public IEnumerable TypedMediaAtRoot() { @@ -922,9 +1020,21 @@ namespace Umbraco.Web /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public dynamic Media(params object[] ids) - { - return ContentQuery.Media(ConvertIdsObjectToInts(ids)); - } + { + return MediaForObjects(ids); + } + + private dynamic MediaForObjects(IEnumerable ids) + { + var idsA = ids.ToArray(); + IEnumerable intIds; + if (ConvertIdsObjectToInts(idsA, out intIds)) + return ContentQuery.Media(intIds); + //IEnumerable guidIds; + //if (ConvertIdsObjectToGuids(idsA, out guidIds)) + // return ContentQuery.Media(guidIds); + return Enumerable.Empty(); + } /// /// Gets the medias corresponding to the identifiers. @@ -944,8 +1054,8 @@ namespace Umbraco.Web /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. public dynamic Media(params string[] ids) - { - return ContentQuery.Media(ConvertIdsObjectToInts(ids)); + { + return MediaForObjects(ids); } /// @@ -956,8 +1066,8 @@ namespace Umbraco.Web /// If an identifier does not match an existing media, it will be missing in the returned value. public dynamic Media(IEnumerable ids) { - return ContentQuery.Media(ConvertIdsObjectToInts(ids)); - } + return MediaForObjects(ids); + } /// /// Gets the medias corresponding to the identifiers. @@ -978,8 +1088,8 @@ namespace Umbraco.Web /// If an identifier does not match an existing media, it will be missing in the returned value. public dynamic Media(IEnumerable ids) { - return ContentQuery.Media(ConvertIdsObjectToInts(ids)); - } + return MediaForObjects(ids); + } public dynamic MediaAtRoot() { From 2eaa2b3166e4c7eb1bf4a75b500e23b928bf8af6 Mon Sep 17 00:00:00 2001 From: Lukas Vorlicek Date: Tue, 19 Jul 2016 20:00:16 +0200 Subject: [PATCH 054/413] U4-7475 Feature UA-1736 (element+class styles) for TinyMCE in grid credits to @SorenA https://github.com/umbraco/Umbraco-CMS/pull/630 --- .../directives/components/grid/grid.rte.directive.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index 0692a10e90..263d6be1cb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -74,6 +74,14 @@ angular.module("umbraco.directives") // since only one element can have one id. r.inline = "span"; r.attributes = { id: rule.selector.substring(1) }; + }else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) { + var split = rule.selector.split("."); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); + }else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) { + var split = rule.selector.split("#"); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); }else { r.block = rule.selector; } From 0fe24a45fffd604c50776f124e88537c83af4ef9 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Thu, 21 Jul 2016 00:47:28 +0200 Subject: [PATCH 055/413] Fix position of user and help dialog position and optimization in mobile layout. --- .../src/less/application/grid.less | 37 ++++++--------- .../src/less/components/overlays.less | 11 ++++- .../src/less/sections.less | 47 ++++++++++++------- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index 02f56e4046..e909d0376d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -118,6 +118,12 @@ body { } } +@media (max-width: 500px) { + #search-form .form-search { + width: ~"calc(100% - 80px)" + } +} + #navigation { left: 80px; top: 0; @@ -217,19 +223,17 @@ body { padding-left:20px } - - @media (max-width: 767px) { // make leftcolumn smaller on tablets #leftcolumn { - width: 60px; + width: 61px; } #applications ul.sections { - width: 60px; + width: 61px; } ul.sections.sections-tray { - width: 60px; + width: 61px; } #applications-tray { left: 60px; @@ -241,35 +245,24 @@ body { left: 30px; } #umbracoMainPageBody .umb-modal-left.fade.in { - margin-left: 60px; + margin-left: 61px; } } - - @media (max-width: 500px) { // make leftcolumn smaller on mobiles #leftcolumn { - width: 40px; + width: 41px; } #applications ul.sections { - width: 40px; - } - ul.sections li [class^="icon-"]:before { - font-size: 25px!important; - } - #applications ul.sections li.avatar a img { - width: 25px; - } - ul.sections a span { - display:none !important; + width: 41px; } #applications ul.sections-tray { - width: 40px; + width: 41px; } ul.sections.sections-tray { - width: 40px; + width: 41px; } #applications-tray { left: 40px; @@ -281,7 +274,7 @@ body { left: 20px; } #umbracoMainPageBody .umb-modal-left.fade.in { - margin-left: 40px; + margin-left: 41px; width: 85%!important; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index 0af0555b89..bcdd1ba572 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -133,7 +133,7 @@ bottom: 0; border: none; box-shadow: 0 0 20px rgba(0,0,0,0.19), 0 0 6px rgba(0,0,0,0.23); - margin-left: 80px; + margin-left: 81px; } .umb-overlay.umb-overlay-left .umb-overlay-header { @@ -148,7 +148,14 @@ @media (max-width: 767px) { .umb-overlay.umb-overlay-left { - margin-left: 60px; + margin-left: 61px; + } +} + +@media (max-width: 500px) { + .umb-overlay.umb-overlay-left { + margin-left: 41px; + width: auto; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index c89be83a10..26abf4404f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -18,8 +18,8 @@ ul.sections li { transition: all .3s linear; } -ul.sections li [class^="icon-"]:before, -ul.sections li [class*=" icon-"]:before, +ul.sections li [class^="icon-"], +ul.sections li [class*=" icon-"], ul.sections li img.icon-section { font-size: 30px; line-height: 20px; /* set line-height to ensure all icons use same line-height */ @@ -31,8 +31,8 @@ ul.sections li img.icon-section { transition: all .3s linear; } -ul.sections:hover li [class^="icon-"]:before, -ul.sections:hover li [class*=" icon-"]:before, +ul.sections:hover li [class^="icon-"], +ul.sections:hover li [class*=" icon-"], ul.sections:hover li img.icon-section { opacity: 1 } @@ -111,32 +111,43 @@ ul.sections li.help { bottom: 0; left: 0; display: block; - width: 100%; + width: ~"calc(100% - 5px)"; //subtract 4px orange border + 1px border-right for sections } ul.sections li.help a { border-bottom: none; } +@media (max-width: 500px) { + ul.sections li [class^="icon-"], + ul.sections li [class*=" icon-"] { + font-size: 25px; + } + ul.sections li:not(.avatar) a { + padding-top: 12px; + padding-bottom: 6px; + + .icon, .icon-section { + display: inline-block; + padding-left: 2px; + } + } + ul.sections a span { + display:none; + } +} + // Section slide-out tray for additional apps // ------------------------- -li.expand a, li.expand{border: none !Important;} +li.expand a, li.expand{border: none !important;} li.expand { + > a > i.icon { + transition: all .3s linear; + } &.open > a > i.icon { - -webkit-transition: all 1s !important; - -o-transition: all 1s !important; - -moz-transition: all 1s !important; - transition: all 1s !important; - - &:before { - -ms-transform: rotate(180deg) !important; /* IE 9 */ - -webkit-transform: rotate(180deg) !important; /* Chrome, Safari, Opera */ - -o-transform: rotate(180deg) !important; - -moz-transform: rotate(180deg) !important; - transform: rotate(180deg) !important; - } + transform: rotate(180deg); } } From ed392cd252dcf7826026380331134d0dbd3520d7 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Thu, 21 Jul 2016 02:31:40 +0200 Subject: [PATCH 056/413] Fix issue where subsequent submit of reset password form cleared error message --- .../src/views/common/dialogs/login.controller.js | 9 +++++++-- .../src/views/common/dialogs/login.html | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 6 +----- src/Umbraco.Web.UI/umbraco/config/lang/de.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/sv.xml | 1 + 8 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index c38c55fcfb..aa037b7431 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -79,7 +79,7 @@ } $scope.loginSubmit = function (login, password) { - + //if the login and password are not empty we need to automatically // validate them - this is because if there are validation errors on the server // then the user has to change both username & password to resubmit which isn't ideal, @@ -121,13 +121,18 @@ $scope.requestPasswordResetSubmit = function (email) { - $scope.errorMsg = ""; + if (email && email.length > 0) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + $scope.showEmailResetConfirmation = false; if ($scope.requestPasswordResetForm.$invalid) { return; } + $scope.errorMsg = ""; + authResource.performRequestPasswordReset(email) .then(function () { //remove the email entered diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 12f25f42a5..5933848c36 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -25,7 +25,7 @@ id="{{login.authType}}" name="provider" value="{{login.authType}}" title="Log in using your {{login.caption}} account"> - Sign in with {{login.caption}} + Sign in with {{login.caption}} @@ -33,7 +33,7 @@

    -
    Or
    +
    or
    diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 275267e98c..d02161b550 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -502,6 +502,7 @@ Endelig fredag! Gledelig lørdag Logg på nedenfor + Logg på med Din sesjon er utløpt © 2001 - %0%
    umbraco.com

    ]]>
    diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index f5820e43c5..2cce55d8a4 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -551,7 +551,6 @@ Forny for at gemme dine ændringer - Så er det søndag! Smil, det er mandag! Hurra, det er tirsdag! @@ -559,12 +558,9 @@ Glædelig torsdag! Endelig fredag! Glædelig lørdag - - indtast brugernavn og kodeord + Log ind med Din session er udløbet - - © 2001 - %0%
    umbraco.com

    ]]>
    diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index 3b4c1ee5ab..8041fd1a29 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -537,6 +537,7 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Frohen freundlichen Freitag Wunderbaren sonnigen Samstag Hier anmelden: + Anmelden mit Sitzung abgelaufen <p style="text-align:right;">&copy; 2001 - %0% <br /><a href="http://umbraco.com" style="text-decoration: none" target="_blank">umbraco.org</a></p> diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 8012081a1c..423bc61afa 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -672,6 +672,7 @@ To manage your website, simply open the Umbraco back office and start adding con Happy funky Friday Happy Caturday Log in below + Sign in with Session timed out © 2001 - %0%
    Umbraco.com

    ]]>
    Forgotten password? diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 8416efa423..4ebc399905 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -670,6 +670,7 @@ To manage your website, simply open the Umbraco back office and start adding con Happy funky Friday Happy Caturday Log in below + Sign in with Session timed out © 2001 - %0%
    Umbraco.com

    ]]>
    Forgotten password? diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index ea1f0c9ef0..6e25f06853 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -492,6 +492,7 @@ Happy friendly Friday Happy shiny Saturday Logga in nedan + Logga in med Sessionen har nått sin maxgräns From 3f2ebbda594eec99376e40c6aa67e639e26816e5 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 21 Jul 2016 11:24:08 +0200 Subject: [PATCH 057/413] U4-8757 DB error installing 7.5.0 beta2 with SQL server - first escape characters, THEN wrap the password in single quotes #U4-8757 Fixed --- src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs index fbd0b5025c..4854919efb 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs @@ -61,8 +61,8 @@ namespace Umbraco.Web.Install.InstallSteps } else { - var password = string.Format("'{0}'", database.Password); - password = password.Replace("&", "&").Replace(">", ">").Replace("<", "<").Replace("\"", """).Replace("'", "''"); + var password = database.Password.Replace("&", "&").Replace(">", ">").Replace("<", "<").Replace("\"", """).Replace("'", "''"); + password = string.Format("'{0}'", password); dbContext.ConfigureDatabaseConnection( database.Server, database.DatabaseName, database.Login, password, From a4fec9dd0fab1b209080a9889c659ad82dda63b0 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 21 Jul 2016 11:49:25 +0200 Subject: [PATCH 058/413] U4-8756 - fix 7.5 beta2 upgrade --- .../TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs index 1f204192e1..2ea854d824 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs @@ -33,6 +33,11 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer localContext.Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field + var keyConstraints = SqlSyntax.GetConstraintsPerColumn(database).Distinct(); + var fk= keyConstraints + .SingleOrDefault(x => x.Item1 == "umbracoRedirectUrl" && x.Item2 == "contentId" && x.Item3.InvariantStartsWith("PK_") == false); + if (fk != null) + localContext.Delete.ForeignKey(fk.Item3).OnTable("umbracoRedirectUrl"); localContext.Delete.Column("contentId").FromTable("umbracoRedirectUrl"); // SQL CE does not want to alter-add non-nullable columns ;-( From 6c6c56645d9df4b5e5efcb9a32414ac0125d619f Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 21 Jul 2016 15:20:45 +0200 Subject: [PATCH 059/413] U4-8720 - add documentation --- .../Models/DynamicPublishedContent.cs | 6 +- src/Umbraco.Web/PublishedContentExtensions.cs | 11 ++ src/Umbraco.Web/UmbracoHelper.cs | 143 ++++++++++++------ 3 files changed, 106 insertions(+), 54 deletions(-) diff --git a/src/Umbraco.Web/Models/DynamicPublishedContent.cs b/src/Umbraco.Web/Models/DynamicPublishedContent.cs index 26a5517039..141eeefae1 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContent.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContent.cs @@ -1101,11 +1101,9 @@ namespace Umbraco.Web.Models } /// - /// A shortcut method for AncestorOrSelf(1) + /// Gets the 'site' content for this content. /// - /// - /// The site homepage - /// + /// The 'site' content ie AncestorOrSelf(1). public DynamicPublishedContent Site() { return AncestorOrSelf(1); diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 95ae867e73..5caa08729a 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1746,6 +1746,12 @@ namespace Umbraco.Web return content.Children().Where(predicate); } + /// + /// Gets the children of the content, of any of the specified types. + /// + /// The content. + /// One or more content type alias. + /// The children of the content, of any of the specified types. public static IEnumerable Children(this IPublishedContent content, params string[] alias) { return content.Children(x => alias.InvariantContains(x.DocumentTypeAlias)); @@ -1861,6 +1867,11 @@ namespace Umbraco.Web #region Axes: custom // todo: in v8, rename this 'Root' + /// + /// Gets the 'site' content for this content. + /// + /// The content. + /// The 'site' content ie AncestorOrSelf(1). public static IPublishedContent Site(this IPublishedContent content) { return content.AncestorOrSelf(1); diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 587c821daa..2508d47b58 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -30,10 +30,10 @@ namespace Umbraco.Web private readonly UmbracoContext _umbracoContext; private readonly IPublishedContent _currentPage; private readonly ITypedPublishedContentQuery _typedQuery; - private readonly IDynamicPublishedContentQuery _dynamicQuery; + private readonly IDynamicPublishedContentQuery _dynamicQuery; private readonly HtmlStringUtilities _stringUtilities = new HtmlStringUtilities(); - private IUmbracoComponentRenderer _componentRenderer; + private IUmbracoComponentRenderer _componentRenderer; private PublishedContentQuery _query; private MembershipHelper _membershipHelper; private TagQuery _tag; @@ -224,7 +224,7 @@ namespace Umbraco.Web { if (query == null) throw new ArgumentNullException("query"); _query = query; - } + } #endregion /// @@ -233,7 +233,7 @@ namespace Umbraco.Web /// /// Note that this is the assigned IPublishedContent item to the UmbracoHelper, this is not necessarily the Current IPublishedContent item /// being rendered. This IPublishedContent object is contextual to the current UmbracoHelper instance. - /// + /// /// In some cases accessing this property will throw an exception if there is not IPublishedContent assigned to the Helper /// this will only ever happen if the Helper is constructed with an UmbracoContext and it is not a front-end request /// @@ -244,7 +244,7 @@ namespace Umbraco.Web { if (_currentPage == null) throw new InvalidOperationException("Cannot return the " + typeof(IPublishedContent).Name + " because the " + typeof(UmbracoHelper).Name + " was constructed with an " + typeof(UmbracoContext).Name + " and the current request is not a front-end request."); - + return _currentPage; } } @@ -316,15 +316,15 @@ namespace Umbraco.Web /// //// /// - public IHtmlString Field(string fieldAlias, + public IHtmlString Field(string fieldAlias, string altFieldAlias = "", string altText = "", string insertBefore = "", string insertAfter = "", bool recursive = false, bool convertLineBreaks = false, bool removeParagraphTags = false, RenderFieldCaseType casing = RenderFieldCaseType.Unchanged, - RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, - bool formatAsDate = false, + RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, + bool formatAsDate = false, bool formatAsDateWithTime = false, string formatAsDateWithTimeSeparator = "") - { + { return UmbracoComponentRenderer.Field(AssignedContentItem, fieldAlias, altFieldAlias, altText, insertBefore, insertAfter, recursive, convertLineBreaks, removeParagraphTags, casing, encoding, formatAsDate, formatAsDateWithTime, formatAsDateWithTimeSeparator); @@ -349,11 +349,11 @@ namespace Umbraco.Web /// //// /// - public IHtmlString Field(IPublishedContent currentPage, string fieldAlias, + public IHtmlString Field(IPublishedContent currentPage, string fieldAlias, string altFieldAlias = "", string altText = "", string insertBefore = "", string insertAfter = "", bool recursive = false, bool convertLineBreaks = false, bool removeParagraphTags = false, RenderFieldCaseType casing = RenderFieldCaseType.Unchanged, - RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, + RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, bool formatAsDate = false, bool formatAsDateWithTime = false, string formatAsDateWithTimeSeparator = "") @@ -425,7 +425,7 @@ namespace Umbraco.Web /// Check if the current user has access to a document /// /// The full path of the document object to check - /// True if the current user has access or if the current document isn't protected + /// True if the current user has access or if the current document isn't protected public bool MemberHasAccess(string path) { if (IsProtected(path)) @@ -456,7 +456,7 @@ namespace Umbraco.Web public bool MemberIsLoggedOn() { return MembershipHelper.IsLoggedIn(); - } + } #endregion @@ -496,7 +496,7 @@ namespace Umbraco.Web } /// - /// This method will always add the domain to the path if the hostnames are set up correctly. + /// This method will always add the domain to the path if the hostnames are set up correctly. /// /// Identifier for the node that should be returned /// String with a friendly url with full domain from a node @@ -561,6 +561,11 @@ namespace Umbraco.Web #region Content + /// + /// Gets a content item from the cache. + /// + /// The unique identifier, or the key, of the content item. + /// The content, or null of the content item is not in the cache. public IPublishedContent TypedContent(object id) { return TypedContentForObject(id); @@ -577,16 +582,31 @@ namespace Umbraco.Web return null; } + /// + /// Gets a content item from the cache. + /// + /// The unique identifier of the content item. + /// The content, or null of the content item is not in the cache. public IPublishedContent TypedContent(int id) { return ContentQuery.TypedContent(id); } + /// + /// Gets a content item from the cache. + /// + /// The key of the content item. + /// The content, or null of the content item is not in the cache. public IPublishedContent TypedContent(Guid id) { return ContentQuery.TypedContent(id); } + /// + /// Gets a content item from the cache. + /// + /// The unique identifier, or the key, of the content item. + /// The content, or null of the content item is not in the cache. public IPublishedContent TypedContent(string id) { return TypedContentForObject(id); @@ -597,6 +617,12 @@ namespace Umbraco.Web return ContentQuery.TypedContentSingleAtXPath(xpath, vars); } + /// + /// Gets content items from the cache. + /// + /// The unique identifiers, or the keys, of the content items. + /// The content items that were found in the cache. + /// Does not support mixing identifiers and keys. public IEnumerable TypedContent(params object[] ids) { return TypedContentForObjects(ids); @@ -615,27 +641,31 @@ namespace Umbraco.Web } /// - /// Gets the contents corresponding to the identifiers. + /// Gets content items from the cache. /// - /// The content identifiers. - /// The existing contents corresponding to the identifiers. - /// If an identifier does not match an existing content, it will be missing in the returned value. + /// The unique identifiers of the content items. + /// The content items that were found in the cache. public IEnumerable TypedContent(params int[] ids) { return ContentQuery.TypedContent(ids); } + /// + /// Gets content items from the cache. + /// + /// The keys of the content items. + /// The content items that were found in the cache. public IEnumerable TypedContent(params Guid[] ids) { return ContentQuery.TypedContent(ids); } /// - /// Gets the contents corresponding to the identifiers. + /// Gets content items from the cache. /// - /// The content identifiers. - /// The existing contents corresponding to the identifiers. - /// If an identifier does not match an existing content, it will be missing in the returned value. + /// The unique identifiers, or the keys, of the content items. + /// The content items that were found in the cache. + /// Does not support mixing identifiers and keys. public IEnumerable TypedContent(params string[] ids) { return TypedContentForObjects(ids); @@ -689,6 +719,11 @@ namespace Umbraco.Web return ContentQuery.TypedContentAtRoot(); } + /// + /// Gets a content item from the cache. + /// + /// The unique identifier, or the key, of the content item. + /// The content, or DynamicNull of the content item is not in the cache. public dynamic Content(object id) { return ContentForObject(id); @@ -705,11 +740,21 @@ namespace Umbraco.Web return DynamicNull.Null; } + /// + /// Gets a content item from the cache. + /// + /// The unique identifier of the content item. + /// The content, or DynamicNull of the content item is not in the cache. public dynamic Content(int id) { return ContentQuery.Content(id); } + /// + /// Gets a content item from the cache. + /// + /// The unique identifier, or the key, of the content item. + /// The content, or DynamicNull of the content item is not in the cache. public dynamic Content(string id) { return ContentForObject(id); @@ -726,11 +771,11 @@ namespace Umbraco.Web } /// - /// Gets the contents corresponding to the identifiers. + /// Gets content items from the cache. /// - /// The content identifiers. - /// The existing contents corresponding to the identifiers. - /// If an identifier does not match an existing content, it will be missing in the returned value. + /// The unique identifiers, or the keys, of the content items. + /// The content items that were found in the cache. + /// Does not support mixing identifiers and keys. public dynamic Content(params object[] ids) { return ContentForObjects(ids); @@ -749,55 +794,53 @@ namespace Umbraco.Web } /// - /// Gets the contents corresponding to the identifiers. + /// Gets content items from the cache. /// - /// The content identifiers. - /// The existing contents corresponding to the identifiers. - /// If an identifier does not match an existing content, it will be missing in the returned value. + /// The unique identifiers of the content items. + /// The content items that were found in the cache. public dynamic Content(params int[] ids) { return ContentQuery.Content(ids); } /// - /// Gets the contents corresponding to the identifiers. + /// Gets content items from the cache. /// - /// The content identifiers. - /// The existing contents corresponding to the identifiers. - /// If an identifier does not match an existing content, it will be missing in the returned value. + /// The unique identifiers, or the keys, of the content items. + /// The content items that were found in the cache. + /// Does not support mixing identifiers and keys. public dynamic Content(params string[] ids) { return ContentForObjects(ids); } /// - /// Gets the contents corresponding to the identifiers. + /// Gets content items from the cache. /// - /// The content identifiers. - /// The existing contents corresponding to the identifiers. - /// If an identifier does not match an existing content, it will be missing in the returned value. + /// The unique identifiers, or the keys, of the content items. + /// The content items that were found in the cache. + /// Does not support mixing identifiers and keys. public dynamic Content(IEnumerable ids) { return ContentForObjects(ids); } /// - /// Gets the contents corresponding to the identifiers. + /// Gets content items from the cache. /// - /// The content identifiers. - /// The existing contents corresponding to the identifiers. - /// If an identifier does not match an existing content, it will be missing in the returned value. + /// The unique identifiers of the content items. + /// The content items that were found in the cache. public dynamic Content(IEnumerable ids) { return ContentQuery.Content(ids); } /// - /// Gets the contents corresponding to the identifiers. + /// Gets content items from the cache. /// - /// The content identifiers. - /// The existing contents corresponding to the identifiers. - /// If an identifier does not match an existing content, it will be missing in the returned value. + /// The unique identifiers, or the keys, of the content items. + /// The content items that were found in the cache. + /// Does not support mixing identifiers and keys. public dynamic Content(IEnumerable ids) { return ContentForObjects(ids); @@ -824,7 +867,7 @@ namespace Umbraco.Web if (s != null) { return int.TryParse(s, out intId); - } + } if (id is int) { intId = (int) id; @@ -892,7 +935,7 @@ namespace Umbraco.Web /// /// /// - /// We accept an object type because GetPropertyValue now returns an 'object', we still want to allow people to pass + /// We accept an object type because GetPropertyValue now returns an 'object', we still want to allow people to pass /// this result in to this method. /// This method will throw an exception if the value is not of type int or string. /// @@ -1097,7 +1140,7 @@ namespace Umbraco.Web } #endregion - + #region Search /// @@ -1344,7 +1387,7 @@ namespace Umbraco.Web #endregion #region canvasdesigner - + [Obsolete("Use EnableCanvasDesigner on the HtmlHelper extensions instead")] public IHtmlString EnableCanvasDesigner() { From 32d59bb2bcfdc69f7c7f775584f0ef098048fb4e Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 21 Jul 2016 16:46:46 +0200 Subject: [PATCH 060/413] U4-8755 - more explicit errors when xml cache is corrupt --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 31 ++++++++++--------- .../umbraco/config/lang/en_us.xml | 3 +- .../PublishedContentCache.cs | 5 ++- .../Redirects/RedirectTrackingEventHandler.cs | 1 - .../Routing/UrlProviderExtensions.cs | 16 +++++++++- 5 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 8012081a1c..36556945db 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -147,7 +147,8 @@ Page Title Properties This document is published but is not visible because the parent '%0%' is unpublished - Oops: this document is published but is not in the cache (internal error) + Oops: this document is published but is not in the cache (internal error - see log) + Oops: could not get the url (internal error - see log) Oops: this document is published but its url would collide with content %0% Publish Publication Status @@ -789,7 +790,7 @@ To manage your website, simply open the Umbraco back office and start adding con Installing... Restarting, please wait... All done, your browser will now refresh, please wait... - + Paste with full formatting (Not recommended) @@ -1294,38 +1295,38 @@ To manage your website, simply open the Umbraco back office and start adding con Value is set to the recommended value: '%0%'. Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. - Found unexpected value '%0%' for '%2%' in configuration file '%3%'. - + Found unexpected value '%0%' for '%2%' in configuration file '%3%'. + + --> Custom errors are set to '%0%'. Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. Custom errors successfully set to '%0%'. - + MacroErrors are set to '%0%'. MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely when there's any errors in macros. Rectifying this will set the value to '%1%'. MacroErrors are now set to '%0%'. - + + --> Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). Try Skip IIS Custom Errors successfully set to '%0%'. - + File does not exist: '%0%'. '%0%' in config file '%1%'.]]> There was an error, check log for full error: %0%. - + Total XML: %0%, Total: %1% Total XML: %0%, Total: %1% Total XML: %0%, Total published: %1% - + Certificate validation error: '%0%' Error pinging the URL %0% - '%1%' You are currently %0% viewing the site using the HTTPS scheme. @@ -1337,12 +1338,12 @@ To manage your website, simply open the Umbraco back office and start adding con Enable HTTPS Sets umbracoSSL setting to true in the appSettings of the web.config file. The appSetting 'umbracoUseSSL' is now set to 'true' in your web.config file, your cookies will be marked as secure. - + Fix Cannot fix a check with a value comparison type of 'ShouldNotEqual'. Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. Value to fix check not provided. - + Debug compilation mode is disabled. Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. Debug compilation mode successfully disabled. @@ -1364,7 +1365,7 @@ To manage your website, simply open the Umbraco back office and start adding con --> %0%.]]> %0%. If they aren't being written to no action need be taken.]]> - + X-Frame-Options used to control whether a site can be IFRAMed by another was found.]]> X-Frame-Options used to control whether a site can be IFRAMed by another was not found.]]> Set Header in Config @@ -1385,5 +1386,5 @@ To manage your website, simply open the Umbraco back office and start adding con %0%.]]> %0%.]]> - + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 8416efa423..5c378bd835 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -148,7 +148,8 @@ Page Title Properties This document is published but is not visible because the parent '%0%' is unpublished - Oops: this document is published but is not in the cache (internal error) + Oops: this document is published but is not in the cache (internal error - see log) + Oops: could not get the url (internal error - see log) Oops: this document is published but its url would collide with content %0% Publish Publication Status diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index f2ef0e348f..884138a9b4 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -409,7 +409,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache internal XmlDocument GetXml(UmbracoContext umbracoContext, bool preview) { - return GetXmlDelegate(umbracoContext, preview); + var xml = GetXmlDelegate(umbracoContext, preview); + if (xml == null) + throw new Exception("The Xml cache is corrupt. Use the Health Check data integrity dashboard to fix it."); + return xml; } #endregion diff --git a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs index 6ba60b3891..42c5e6093b 100644 --- a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs +++ b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs @@ -8,7 +8,6 @@ using Umbraco.Web.Routing; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Cache; -using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Cache; diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 0a20f51da7..c6e08c16c5 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Umbraco.Core.Models; using umbraco; +using Umbraco.Core.Logging; namespace Umbraco.Web.Routing { @@ -30,8 +31,17 @@ namespace Umbraco.Web.Routing return urls; } + string url; var urlProvider = umbracoContext.RoutingContext.UrlProvider; - var url = urlProvider.GetUrl(content.Id); + try + { + url = urlProvider.GetUrl(content.Id); + } + catch (Exception e) + { + LogHelper.Error("GetUrl exception.", e); + url = "#ex"; + } if (url == "#") { // document as a published version yet it's url is "#" => a parent must be @@ -48,6 +58,10 @@ namespace Umbraco.Web.Routing else urls.Add(ui.Text("content", "parentNotPublished", parent.Name, umbracoContext.Security.CurrentUser)); } + else if (url == "#ex") + { + urls.Add(ui.Text("content", "getUrlException", umbracoContext.Security.CurrentUser)); + } else if (url.StartsWith("#err-")) { // route error, report From 5444b65eaf0d9be27d8cea96f60c8dcdbee62eae Mon Sep 17 00:00:00 2001 From: bjarnef Date: Thu, 21 Jul 2016 19:56:57 +0200 Subject: [PATCH 061/413] Open link in new browser window/tab --- src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html index 973fa8c440..d1f500d265 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html @@ -312,7 +312,7 @@
    From 4ab200d538e234f5cd8eb9e69181dbec39fd471e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sat, 23 Jul 2016 11:52:34 +0200 Subject: [PATCH 063/413] Request elevated command promt to install git (necessary for the install to succeed) --- build/InstallGit.cmd | 53 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/build/InstallGit.cmd b/build/InstallGit.cmd index 26f8088f35..e009e2594e 100644 --- a/build/InstallGit.cmd +++ b/build/InstallGit.cmd @@ -1,23 +1,33 @@ @ECHO OFF SETLOCAL -REM SETLOCAL is on, so changes to the path not persist to the actual user's path + :: SETLOCAL is on, so changes to the path not persist to the actual user's path git.exe --version -if %ERRORLEVEL%==9009 GOTO :trydefaultpath +IF %ERRORLEVEL%==9009 GOTO :trydefaultpath GOTO :EOF + :: Git is installed, no need to to anything else :trydefaultpath -path=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH% +PATH=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH% git.exe --version -if %ERRORLEVEL%==9009 GOTO :showerror +IF %ERRORLEVEL%==9009 GOTO :showerror GOTO :EOF + :: Git is installed, no need to to anything else :showerror ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd nor in C:\Program Files\Git\cmd -set /p install=" Do you want to install Git through Chocolatey [y/n]? " %=% -if %install%==y ( +SET /p install=" Do you want to install Git through Chocolatey [y/n]? " %=% +IF %install%==y ( + :: Create a temporary batch file to execute either after elevating to admin or as-is when the user is already admin + ECHO @ECHO OFF > "%temp%\ChocoInstallGit.cmd" + ECHO SETLOCAL >> "%temp%\ChocoInstallGit.cmd" + ECHO ECHO Installing Chocolatey first >> "%temp%\ChocoInstallGit.cmd" + ECHO @powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" >> "%temp%\ChocoInstallGit.cmd" + ECHO SET PATH=%%PATH%%;%%ALLUSERSPROFILE%%\chocolatey\bin >> "%temp%\ChocoInstallGit.cmd" + ECHO choco install git -y >> "%temp%\ChocoInstallGit.cmd" + GOTO :installgit -) else ( +) ELSE ( GOTO :cantcontinue ) @@ -26,7 +36,28 @@ ECHO Can't complete the build without Git being in the path. Please add it to be GOTO :EOF :installgit -ECHO Installing Chocolatey first -@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin -ECHO Installing Git through Chocolatey -choco install git \ No newline at end of file +pushd %~dp0 + :: Running prompt elevated + +:: --> Check for permissions +>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" + +:: --> If error flag set, we do not have admin. +IF '%errorlevel%' NEQ '0' ( + GOTO UACPrompt +) ELSE ( GOTO gotAdmin ) + +:UACPrompt + ECHO You're not currently running this with admin privileges, we'll now try to execute the install of Git through Chocolatey after elevating to admin privileges + ECHO Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs" + ECHO UAC.ShellExecute "%temp%\ChocoInstallGit.cmd", "", "", "runas", 1 >> "%temp%\getadmin.vbs" + + "%temp%\getadmin.vbs" + EXIT /B + +:gotAdmin + IF EXIST "%temp%\getadmin.vbs" ( DEL "%temp%\getadmin.vbs" ) + pushd "%CD%" + CD /D "%~dp0" + + CALL "%temp%\ChocoInstallGit.cmd" \ No newline at end of file From 5f3f698ffde341ad065b6db3458bab2c28519ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Riis-Knudsen?= Date: Sun, 24 Jul 2016 18:09:06 +0200 Subject: [PATCH 064/413] U4-8751: Put format parameter at the end when format is specified in furtherOptions --- src/Umbraco.Web/ImageCropperTemplateExtensions.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index bcdea6eb03..0068f1547e 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -302,7 +302,11 @@ namespace Umbraco.Web } } - if (quality != null) + bool hasFormat = furtherOptions != null && furtherOptions.Contains("&format="); + + //Only put quality here, if we don't have a format specified. + //Otherwise we need to put quality at the end to avoid it being overridden by the format. + if (quality != null && !hasFormat) { imageProcessorUrl.Append("&quality=" + quality); } @@ -351,6 +355,12 @@ namespace Umbraco.Web imageProcessorUrl.Append(furtherOptions); } + //If furtherOptions contains a format, we need to put the quality after the format. + if (quality != null && hasFormat) + { + imageProcessorUrl.Append("&quality=" + quality); + } + if (cacheBusterValue != null) { imageProcessorUrl.Append("&rnd=").Append(cacheBusterValue); From 379661e3026e4cb914dd8effe65512b5ce3e3afb Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Mon, 25 Jul 2016 09:17:27 +0100 Subject: [PATCH 065/413] Fixes U4-8772, adds HasPublishedVersion property to mapping of IContent to ContentItemBasic, then adds existing 'has-unpublished-version' css class to list view row if item is published but has unpublished changes --- src/Umbraco.Web.UI.Client/src/views/components/umb-table.html | 2 +- src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs | 3 +++ src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html index fb5e992d81..9b60e5e92f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html @@ -36,7 +36,7 @@ }" ng-click="selectItem(item, $index, $event)"> -
    +
    diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs index 63a9d849d9..87eff89b58 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs @@ -25,6 +25,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "published")] public bool Published { get; set; } + [DataMember(Name = "hasPublishedVersion")] + public bool HasPublishedVersion { get; set; } + [DataMember(Name = "owner")] public UserBasic Owner { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 125099dd29..5fc642873b 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -87,6 +87,9 @@ namespace Umbraco.Web.Models.Mapping .ForMember( dto => dto.Trashed, expression => expression.MapFrom(content => content.Trashed)) + .ForMember( + dto => dto.HasPublishedVersion, + expression => expression.MapFrom(content => content.HasPublishedVersion)) .ForMember( dto => dto.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) From 9f36d2b7c621199c5bc0ebe417319b13ddfde0d8 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Mon, 25 Jul 2016 11:13:06 +0200 Subject: [PATCH 066/413] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 53070b6917..1471340022 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,11 @@ Umbraco CMS Umbraco is a free open source Content Management System built on the ASP.NET platform. ## Building Umbraco from source ## -The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt dev` in `src\Umbraco.Web.UI.Client`. +The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt vs` in `src\Umbraco.Web.UI.Client`. -If you're interested in making changes to Belle make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). Note that you can always [download a nightly build](http://nightly.umbraco.org/umbraco%207.0.0/) so you don't have to build the code yourself. +If you're interested in making changes to Belle without running Visual Studio make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). + +Note that you can always [download a nightly build](https://ci.appveyor.com/project/Umbraco/umbraco-cms-hs8dx/history/branch/dev-v7) so you don't have to build the code yourself. ## Watch a introduction video ## From de89f555d45c9f51cf2f275640753229b3941b0d Mon Sep 17 00:00:00 2001 From: Jeremy Pyne Date: Mon, 25 Jul 2016 16:46:49 -0400 Subject: [PATCH 067/413] Added missing Sql mapping for standard Replace function: .Where(s => s.Name.Replace(" ", "-") == userName) Should generate valid SQL: REPLACE([dbo].[MyModel].[Name], ' ', '-') --- .../Querying/BaseExpressionHelper.cs | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 4679b9b2ad..0960acc9e5 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -440,6 +440,55 @@ namespace Umbraco.Core.Persistence.Querying } return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType); + + case "Replace": + string searchValue; + + if (methodArgs[0].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + searchValue = getter().ToString(); + } + else + { + searchValue = methodArgs[0].ToString(); + } + + if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + string replaceValue; + + if (methodArgs[1].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[1], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + replaceValue = getter().ToString(); + } + else + { + replaceValue = methodArgs[1].ToString(); + } + + if (methodArgs[1].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[1].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + SqlParameters.Add(RemoveQuote(searchValue)); + + SqlParameters.Add(RemoveQuote(replaceValue)); + + return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1); //case "Substring": // var startIndex = Int32.Parse(args[0].ToString()) + 1; // if (args.Count == 2) @@ -501,7 +550,7 @@ namespace Umbraco.Core.Persistence.Querying //case "As": // return string.Format("{0} As {1}", r, // GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); - + default: throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name); From 67234ef115f15f69eabf95047c5e946822def593 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 26 Jul 2016 10:34:29 +0200 Subject: [PATCH 068/413] Fixes: U4-8713 New packager - avoid horizontal scrollbar for narrow screens --- .../src/less/components/umb-packages.less | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less index d3af5164f1..cfbf425bef 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -9,6 +9,14 @@ padding: 20px 60px; } +@media (max-width: 768px) { + + .umb-packages-view-wrapper { + padding: 0; + } + +} + .umb-packages-section { margin-bottom: 40px; } @@ -426,17 +434,35 @@ a.umb-package-details__back-link { } -@sidebarwidthFlax: 350px; // Width of sidebar. Ugly hack because of old version of Less +@sidebarwidth: 350px; // Width of sidebar. Ugly hack because of old version of Less .umb-package-details__main-content { flex: 1 1 auto; margin-right: 40px; - - width: ~"calc(100% - @{sidebarwidthFlax})"; // Make sure that the main content area doesn't gets affected by inline styling + width: ~"(calc(~'100%' - ~'@{sidebarwidth}' - ~'40px'))"; // Make sure that the main content area doesn't gets affected by inline styling } .umb-package-details__sidebar { - flex: 0 0 350px; + flex: 0 0 @sidebarwidth; +} + +@media (max-width: 768px) { + + .umb-package-details { + flex-direction: column; + } + + .umb-package-details__main-content { + flex: 1 1 auto; + width: 100%; + margin-bottom: 30px; + margin-right: 0; + } + + .umb-package-details__sidebar { + flex: 1 1 auto; + width: 100%; + } } .umb-package-details__section { @@ -515,6 +541,7 @@ a.umb-package-details__back-link { } .umb-package-details__owner-profile-avatar { margin-right: 15px; + flex: 0 0 auto; } .umb-package-details__owner-profile-name { From 4acfd1dcca416694ee4618672810c5a2329ba807 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 26 Jul 2016 11:31:39 +0200 Subject: [PATCH 069/413] better error message for errors when embedding media. --- .../src/views/common/dialogs/rteembed.controller.js | 4 ++-- .../src/views/common/overlays/embed/embed.controller.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js index f2052ccc65..0c1cb5b62e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js @@ -32,7 +32,7 @@ break; case 1: //error - $scope.form.info = "Computer says no"; + $scope.form.info = "Could not embed media - please ensure the URL is valid"; break; case 2: $scope.form.preview = data.Markup; @@ -44,7 +44,7 @@ .error(function () { $scope.form.supportsDimensions = false; $scope.form.preview = ""; - $scope.form.info = "Computer says no"; + $scope.form.info = "Could not embed media - please ensure the URL is valid"; }); } else { $scope.form.supportsDimensions = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.controller.js index 6fae3cd1d2..0ae93baf27 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.controller.js @@ -53,7 +53,7 @@ break; case 1: //error - $scope.model.embed.info = "Computer says no"; + $scope.model.embed.info = "Could not embed media - please ensure the URL is valid"; break; case 2: $scope.model.embed.preview = data.Markup; @@ -65,7 +65,7 @@ .error(function() { $scope.model.embed.supportsDimensions = false; $scope.model.embed.preview = ""; - $scope.model.embed.info = "Computer says no"; + $scope.model.embed.info = "Could not embed media - please ensure the URL is valid"; }); } else { $scope.model.embed.supportsDimensions = false; From 8fd1743bcea057fdce0d81f3248f54934440a1cd Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 26 Jul 2016 12:01:24 +0200 Subject: [PATCH 070/413] only render actions div if there are any actions --- .../src/views/dashboard/developer/healthcheck.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html index 382ccbb0bb..706381337c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html @@ -83,7 +83,7 @@
    -
    +
    Set new value: From c206965f8884090566b795b1c9c07a4f29ef2eb0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 26 Jul 2016 12:09:05 +0200 Subject: [PATCH 071/413] U4-8777 ImageProcessor fails to generate thumbnail for animated gif #U4-8777 Fixed Updates to the latest ImageProcessor to ship in the zip release --- src/Umbraco.Core/Umbraco.Core.csproj | 4 ++-- src/Umbraco.Core/packages.config | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 8 ++++---- src/Umbraco.Web.UI/packages.config | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 22aa68ab7a..374e994a12 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -52,8 +52,8 @@ ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - ..\packages\ImageProcessor.2.4.1.0\lib\net45\ImageProcessor.dll + + ..\packages\ImageProcessor.2.4.3.0\lib\net45\ImageProcessor.dll True diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index a9241f46c8..0216520aa2 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index e14a05a1c7..da577d4ad0 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -136,12 +136,12 @@ False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - ..\packages\ImageProcessor.2.4.1.0\lib\net45\ImageProcessor.dll + + ..\packages\ImageProcessor.2.4.3.0\lib\net45\ImageProcessor.dll True - - ..\packages\ImageProcessor.Web.4.6.1.0\lib\net45\ImageProcessor.Web.dll + + ..\packages\ImageProcessor.Web.4.6.3.0\lib\net45\ImageProcessor.Web.dll True diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index 70b8412ec8..83ff177eb1 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -5,8 +5,8 @@ - - + + From 379a0d68f7b39658c5115a2e9be99695fdba97cf Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 26 Jul 2016 13:38:06 +0200 Subject: [PATCH 072/413] U4-8733 Trying to include a stylesheet in a package results in a YSOD --- .../developer/Packages/editPackage.aspx.cs | 18 +++++---- .../PackageInstance/CreatedPackage.cs | 9 ++--- .../Packager/PackageInstance/utill.cs | 38 ++++++++++--------- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs index e58cc2f045..05989ba28e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs @@ -114,16 +114,18 @@ namespace umbraco.presentation.developer.packages } /*Stylesheets */ - StyleSheet[] sheets = StyleSheet.GetAll(); - foreach (StyleSheet st in sheets) + var sheets = Services.FileService.GetStylesheets(); + foreach (var st in sheets) { - ListItem li = new ListItem(st.Text, st.Id.ToString()); - if (pack.Stylesheets.Contains(st.Id.ToString())) - li.Selected = true; - - stylesheets.Items.Add(li); - + if (string.IsNullOrEmpty(st.Name) == false) + { + var li = new ListItem(st.Alias, st.Name); + if (pack.Stylesheets.Contains(st.Name)) + li.Selected = true; + stylesheets.Items.Add(li); + } } + /* MACROS */ Macro[] umbMacros = Macro.GetAll(); foreach (Macro m in umbMacros) diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs index aa3a788be4..3d9ef6a827 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs @@ -246,13 +246,10 @@ namespace umbraco.cms.businesslogic.packager //Stylesheets var stylesheets = _packageManifest.CreateElement("Stylesheets"); - foreach (var ssId in pack.Stylesheets) + foreach (var stylesheetName in pack.Stylesheets) { - if (int.TryParse(ssId, out outInt)) - { - var s = new StyleSheet(outInt); - stylesheets.AppendChild(s.ToXml(_packageManifest)); - } + var stylesheetXmlNode = utill.Stylesheet(stylesheetName, true, _packageManifest); + stylesheets.AppendChild(stylesheetXmlNode); } AppendElement(stylesheets); diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/utill.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/utill.cs index 5c1d6cf662..f31862b842 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/utill.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/utill.cs @@ -92,37 +92,39 @@ namespace umbraco.cms.businesslogic.packager { return template; } - /// /// Converts a umbraco stylesheet to a package xml node /// - /// The ss id. - /// if set to true [incluce properties]. + /// The name of the stylesheet. + /// if set to true [incluce properties]. /// The doc. /// - public static XmlNode Stylesheet(int ssId, bool incluceProperties, XmlDocument doc) { - - StyleSheet sts = new StyleSheet(ssId); - XmlNode stylesheet = doc.CreateElement("Stylesheet"); - stylesheet.AppendChild(_node("Name", sts.Text, doc)); - stylesheet.AppendChild(_node("FileName", sts.Filename, doc)); + public static XmlNode Stylesheet(string name, bool includeProperties, XmlDocument doc) + { + if (ApplicationContext.Current == null) + throw new NullReferenceException("ApplicationContext is null"); + var fileService = ApplicationContext.Current.Services.FileService; + + var sts = fileService.GetStylesheetByName(name); + var stylesheet = doc.CreateElement("Stylesheet"); + stylesheet.AppendChild(_node("Name", sts.Alias, doc)); + stylesheet.AppendChild(_node("FileName", sts.Name, doc)); stylesheet.AppendChild(_node("Content", "", doc)); - if (incluceProperties) { - XmlNode properties = doc.CreateElement("Properties"); - foreach (StylesheetProperty ssP in sts.Properties) { - XmlNode property = doc.CreateElement("Property"); - property.AppendChild(_node("Name", ssP.Text, doc)); + if (includeProperties) + { + var properties = doc.CreateElement("Properties"); + foreach (var ssP in sts.Properties) + { + var property = doc.CreateElement("Property"); + property.AppendChild(_node("Name", ssP.Name, doc)); property.AppendChild(_node("Alias", ssP.Alias, doc)); - property.AppendChild(_node("Value", ssP.value, doc)); - - //xnode += "" + ssP.Text + "" + ssP.Alias + "" + ssP.value + "\n"; + property.AppendChild(_node("Value", ssP.Value, doc)); } stylesheet.AppendChild(properties); } return stylesheet; } - /// /// Converts a macro to a package xml node /// From d37b65adcf580d25b2e83707610df7cb32391a39 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Tue, 26 Jul 2016 14:45:03 +0200 Subject: [PATCH 073/413] Extend model with extension property, display svg in and font icon for other files --- .../components/umbmediagrid.directive.js | 16 ++++++++++++ .../src/less/property-editors.less | 25 +++++++++++++++++++ .../src/views/components/umb-media-grid.html | 10 ++++---- .../fileupload/fileupload.controller.js | 3 +++ .../fileupload/fileupload.html | 11 +++++--- 5 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 7d2da34988..e9e7395761 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -138,6 +138,22 @@ Use this directive to generate a thumbnail grid of media items. if (!item.isFolder) { item.thumbnail = mediaHelper.resolveFile(item, true); item.image = mediaHelper.resolveFile(item, false); + + var fileProp = _.find(item.properties, function (v) { + return (v.alias === "umbracoFile"); + }); + + if (fileProp && fileProp.value) { + item.file = fileProp.value; + } + + var extensionProp = _.find(item.properties, function (v) { + return (v.alias === "umbracoExtension"); + }); + + if (extensionProp && extensionProp.value) { + item.extension = extensionProp.value; + } } } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 85a1d9e36c..16d48807eb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -704,6 +704,31 @@ ul.color-picker li a { padding-top: 27px; } +.umb-fileupload .file-icon { + text-align: center; + display: block; + position: relative; + padding: 5px 0; + + > .icon { + font-size: 70px; + line-height: 110%; + color: #666; + text-align: center; + } + + > span { + color: #fff; + background: #666; + padding: 1px 3px; + font-size: 12px; + line-height: 130%; + position: absolute; + top: 45px; + left: 110px; + } +} + .umb-fileupload input { font-size: 12px; line-height: 1; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html index 5cd6d5c82f..5e0a8c1ea5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -1,7 +1,6 @@
    -
    @@ -9,11 +8,12 @@
    {{item.name}}
    -
    - {{item.name}} - {{item.name}} +
    + {{item.name}} + {{item.name}} + {{item.name}} - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index 4285cf0f77..df21541f09 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -71,7 +71,10 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag "GetBigThumbnail", [{ originalImagePath: file.file }]); + var extension = file.file.substring(file.file.lastIndexOf(".") + 1, file.file.length); + file.thumbnail = thumbnailUrl; + file.extension = extension.toLowerCase(); }); $scope.clearFiles = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html index c213378b23..0905de07f9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html @@ -1,10 +1,15 @@ 
    - +
    • - - {{file.file}} + + {{file.file}} + {{file.file}} + + + .{{file.extension}} + {{file.file}}
    • From c6a2d41888d5cc790e4622dfca68d99c9a2dd640 Mon Sep 17 00:00:00 2001 From: Jeremy Pyne Date: Tue, 26 Jul 2016 09:45:45 -0400 Subject: [PATCH 074/413] Added test. --- .../Persistence/Querying/ExpressionTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index ab94d4d6d5..5148a6f17f 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -117,5 +117,19 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual("mydomain\\myuser%", modelToSqlExpressionHelper.GetSqlParameters()[0]); } + [Test] + public void Sql_Replace_Mapped() + { + Expression> predicate = user => user.Username.Replace("@world", "@test") == "hello@test.com"; + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var result = modelToSqlExpressionHelper.Visit(predicate); + + Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + + Assert.AreEqual("replace([umbracoUser].[userLogin], @0, @1", result); + Assert.AreEqual("@world", modelToSqlExpressionHelper.GetSqlParameters()[0]); + Assert.AreEqual("@test", modelToSqlExpressionHelper.GetSqlParameters()[0]); + } + } } \ No newline at end of file From dcdd47dff469b5cdaaac8bbf58642bf01b5270e4 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 26 Jul 2016 16:05:30 +0200 Subject: [PATCH 075/413] adding a few unit tests to MemberService. --- .../Services/MemberServiceTests.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 9e2b440815..cc2bc05060 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -33,6 +33,34 @@ namespace Umbraco.Tests.Services base.TearDown(); } + [Test] + public void Can_Create_Member() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + + Assert.AreNotEqual(0, member.Id); + var foundMember = ServiceContext.MemberService.GetById(member.Id); + Assert.IsNotNull(foundMember); + Assert.AreEqual("test@test.com", foundMember.Email); + } + + [Test] + public void Can_Create_Member_With_Long_TLD_In_Email() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.marketing", "pass", "test"); + ServiceContext.MemberService.Save(member); + + Assert.AreNotEqual(0, member.Id); + var foundMember = ServiceContext.MemberService.GetById(member.Id); + Assert.IsNotNull(foundMember); + Assert.AreEqual("test@test.marketing", foundMember.Email); + } + [Test] public void Can_Create_Role() { From a83b524130f8188ac17556862b10bd74a3e398f0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 26 Jul 2016 16:18:00 +0200 Subject: [PATCH 076/413] DetermineInstalledVersionByMigrations needs to check if the table exists, not the code asking for a version --- src/Umbraco.Core/DatabaseContext.cs | 9 ++------- .../Migrations/Initial/DatabaseSchemaResult.cs | 9 +++++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 7f8cbbf0f7..36da6007f6 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -625,13 +625,8 @@ namespace Umbraco.Core var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion()); - var installedMigrationVersion = new SemVersion(0); - //we cannot check the migrations table if it doesn't exist, this will occur when upgrading to 7.3 - if (schemaResult.ValidTables.Any(x => x.InvariantEquals("umbracoMigration"))) - { - installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService); - } - + 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 diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 9af6d46fbb..e459f96c6d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -43,8 +43,13 @@ namespace Umbraco.Core.Persistence.Migrations.Initial /// public SemVersion DetermineInstalledVersionByMigrations(IMigrationEntryService migrationEntryService) { - var allMigrations = migrationEntryService.GetAll(GlobalSettings.UmbracoMigrationName); - var mostrecent = allMigrations.OrderByDescending(x => x.Version).Select(x => x.Version).FirstOrDefault(); + SemVersion mostrecent = null; + + if (ValidTables.Any(x => x.InvariantEquals("umbracoMigrations"))) + { + var allMigrations = migrationEntryService.GetAll(GlobalSettings.UmbracoMigrationName); + mostrecent = allMigrations.OrderByDescending(x => x.Version).Select(x => x.Version).FirstOrDefault(); + } return mostrecent ?? new SemVersion(new Version(0, 0, 0)); } From bb376367d6e3c2ca43e08e2b6125f6955b949f11 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 26 Jul 2016 16:19:17 +0200 Subject: [PATCH 077/413] If the umbracoMigration table does not yet exist, then get the configured version from the web.config and parse it --- .../Install/InstallSteps/UpgradeStep.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs b/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs index 42bca03498..5ef2577179 100644 --- a/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs @@ -1,3 +1,5 @@ +using System; +using System.Linq; using Semver; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -52,12 +54,35 @@ namespace Umbraco.Web.Install.InstallSteps //If we have a db context available, if we don't then we are not installed anyways if (ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured && ApplicationContext.Current.DatabaseContext.CanConnect) - { version = ApplicationContext.Current.DatabaseContext.ValidateDatabaseSchema().DetermineInstalledVersionByMigrations(ApplicationContext.Current.Services.MigrationEntryService); + + if (version != new SemVersion(0)) + return version; + + // If we aren't able to get a result from the umbracoMigrations table then use the version in web.config, if it's available + if (string.IsNullOrWhiteSpace(GlobalSettings.ConfigurationStatus)) + return version; + + var configuredVersion = GlobalSettings.ConfigurationStatus; + + string currentComment = null; + + var current = configuredVersion.Split('-'); + if (current.Length > 1) + currentComment = current[1]; + + Version currentVersion; + if (Version.TryParse(current[0], out currentVersion)) + { + version = new SemVersion( + currentVersion.Major, + currentVersion.Minor, + currentVersion.Build, + string.IsNullOrWhiteSpace(currentComment) ? null : currentComment, + currentVersion.Revision > 0 ? currentVersion.Revision.ToString() : null); } return version; } - } } \ No newline at end of file From c955cce28deb88b2993b3be1df982c9a6de1a85b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 26 Jul 2016 16:20:22 +0200 Subject: [PATCH 078/413] Because the cmsStylesheetProperty table was already deleted by an earlier migration, it can't be deleted again However because all of this runs in one transaction, it will try anyway, we need to do this in a "local" context instead --- .../RemoveStylesheetDataAndTablesAgain.cs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs index 48135f35d7..96523e25e8 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs @@ -19,22 +19,32 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer public override void Up() { + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } + + private string MigrationCode(Database database) + { + var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); + //Clear all stylesheet data if the tables exist var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); if (tables.InvariantContains("cmsStylesheetProperty")) { - Delete.FromTable("cmsStylesheetProperty").AllRows(); - Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.StylesheetProperty) }); + localContext.Delete.FromTable("cmsStylesheetProperty").AllRows(); + localContext.Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.StylesheetProperty) }); - Delete.Table("cmsStylesheetProperty"); + localContext.Delete.Table("cmsStylesheetProperty"); } if (tables.InvariantContains("cmsStylesheet")) { - Delete.FromTable("cmsStylesheet").AllRows(); - Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.Stylesheet) }); + localContext.Delete.FromTable("cmsStylesheet").AllRows(); + localContext.Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.Stylesheet) }); - Delete.Table("cmsStylesheet"); + localContext.Delete.Table("cmsStylesheet"); } + + return localContext.GetSql(); } public override void Down() From 2cad3838f47fc999601e4f255cda1f5dad1476f2 Mon Sep 17 00:00:00 2001 From: Jeremy Pyne Date: Tue, 26 Jul 2016 10:27:19 -0400 Subject: [PATCH 079/413] Update ExpressionTests.cs Fixes for SQL replace test. --- .../Persistence/Querying/ExpressionTests.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index 5148a6f17f..2d779d0eaf 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -126,10 +126,11 @@ namespace Umbraco.Tests.Persistence.Querying Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); - Assert.AreEqual("replace([umbracoUser].[userLogin], @0, @1", result); - Assert.AreEqual("@world", modelToSqlExpressionHelper.GetSqlParameters()[0]); - Assert.AreEqual("@test", modelToSqlExpressionHelper.GetSqlParameters()[0]); + Assert.AreEqual("(replace([umbracoUser].[userLogin], @1, @2) = @0)", result); + Assert.AreEqual("hello@test.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); + Assert.AreEqual("@world", modelToSqlExpressionHelper.GetSqlParameters()[1]); + Assert.AreEqual("@test", modelToSqlExpressionHelper.GetSqlParameters()[2]); } } -} \ No newline at end of file +} From c5b9e6a1945f79f168c908bbccf99eb05a133152 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Wed, 27 Jul 2016 01:36:17 +0200 Subject: [PATCH 080/413] Load svg in to have access to events and be able to manipulate svg --- .../src/views/components/umb-media-grid.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html index 5e0a8c1ea5..6d6ff0d599 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -1,5 +1,5 @@
      -
      +
      @@ -10,7 +10,8 @@
      {{item.name}} - {{item.name}} + + {{item.name}} From 29be571afd0a3a6c40909afc4371e0af8546d12f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jul 2016 09:31:01 +0200 Subject: [PATCH 081/413] Add button to check all groups + a bit of style polish --- .../src/less/healthcheck.less | 51 +++++++------ .../developer/healthcheck.controller.js | 20 ++--- .../dashboard/developer/healthcheck.html | 76 +++++++++++-------- 3 files changed, 83 insertions(+), 64 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less index a5f2d454f1..8e4fb55d15 100644 --- a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less +++ b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less @@ -1,12 +1,20 @@ -/* Vars */ - @red-orange: #FF3F34; - @sunrise: #F5D226; - @emerald: #50C878; - .umb-healthcheck { display: flex; flex-wrap: wrap; + margin-left: -10px; + margin-right: -10px; +} + +.umb-healthcheck-help-text { + line-height: 1.6em; + margin-bottom: 30px; +} + +.umb-healthcheck-action-bar { + display: flex; + justify-content: flex-end; + margin-bottom: 20px; } @@ -42,7 +50,6 @@ /* Title */ .umb-healthcheck-title { - margin-bottom: 15px; font-size: 14px; font-weight: bold; } @@ -50,28 +57,26 @@ /* Messages */ .umb-healthcheck-messages { - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; + margin-top: 15px; } .umb-healthcheck-message { position: relative; background: #fff; border-radius: 50px; - display: flex; - align-items: center; - flex: 1 1 auto; - padding: 5px 10px; + display: inline-flex; + align-items: center; + padding-left: 8px; + padding-right: 8px; margin-bottom: 5px; color: #000; font-weight: bold; + font-size: 13px; } .umb-healthcheck-message i { - font-size: 20px; - margin-right: 5px; + font-size: 15px; + margin-right: 3px; } .umb-healthcheck-details-link { @@ -99,11 +104,11 @@ font-size: 14px; font-weight: bold; - height: 38px; + height: 30px; line-height: 1; max-width: 100%; - padding: 0 18px; + padding: 0 15px; color: #484848; background-color: #e0e0e0; @@ -235,7 +240,7 @@ } .umb-healthcheck-group__details-checks { - border: 2px solid @grayLight; + border: 1px solid @grayLight; border-top: none; border-radius: 0 0 3px 3px; } @@ -252,13 +257,13 @@ font-size: 14px; color: @black; font-weight: bold; + margin-bottom: 2px; } .umb-healthcheck-group__details-check-description { font-size: 12px; color: @grayMed; - line-height: 1.6rem; - //margin-top: 10px; + line-height: 1.6em; } .umb-healthcheck-group__details-status { @@ -328,6 +333,6 @@ } .umb-healthcheck-group__details-status-action-description { - margin-top: 10px; - font-size: 13px; + margin-top: 5px; + font-size: 11px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js index e335ddd365..3ca2bee2f1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js @@ -16,6 +16,7 @@ vm.getStatus = getStatus; vm.executeAction = executeAction; + vm.checkAllGroups = checkAllGroups; vm.checkAllInGroup = checkAllInGroup; vm.openGroup = openGroup; vm.setViewState = setViewState; @@ -23,16 +24,7 @@ // Get a (grouped) list of all health checks healthCheckResource.getAllChecks().then( function(response) { - - // set number of checks which has been executed - for (var i = 0; i < response.length; i++) { - var group = response[i]; - group.checkCounter = 0; - checkAllInGroup(group, group.checks); - } - vm.groups = response; - } ); @@ -86,6 +78,16 @@ }); } + function checkAllGroups(groups) { + // set number of checks which has been executed + for (var i = 0; i < groups.length; i++) { + var group = groups[i]; + console.log(group); + checkAllInGroup(group, group.checks); + } + vm.groups = groups; + } + function checkAllInGroup(group, checks) { group.checkCounter = 0; diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html index 706381337c..2214d48993 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html @@ -1,43 +1,55 @@
      -

      Health Check

      -

      The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button.
      - You can add your own health checks, have a look at the documentation for more information about custom health checks.

      -
      +
      -
      -
      -
      {{group.name}}
      +

      Health Check

      +
      +

      The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button. + You can add your own health checks, have a look at the documentation for more information about custom health checks.

      +
      -
      - -
      +
      + +
      -
      +
      -
      - - {{ group.totalSuccess }} +
      +
      + +
      {{group.name}}
      + +
      +
      -
      - - {{ group.totalWarning }} -
      +
      -
      - - {{ group.totalError }} -
      +
      + + {{ group.totalSuccess }} +
      + +
      + + {{ group.totalWarning }} +
      + +
      + + {{ group.totalError }} +
      + +
      + + {{ group.totalInfo }} +
      -
      - - {{ group.totalInfo }}
      -
      +
      @@ -46,7 +58,7 @@ - ← Take me back + ← Back to overview @@ -55,7 +67,7 @@
      {{ vm.selectedGroup.name }}
      - +
      @@ -85,11 +97,11 @@
      - - Set new value: +
      +
      - - +
      +
      From ba6736dbb3ec245bf19ca62ceb793e6a35d2b94f Mon Sep 17 00:00:00 2001 From: bjarnef Date: Wed, 27 Jul 2016 10:31:18 +0200 Subject: [PATCH 082/413] Fix correct syntax of calc function in less --- src/Umbraco.Web.UI.Client/src/less/application/grid.less | 2 +- src/Umbraco.Web.UI.Client/src/less/sections.less | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index e909d0376d..9d1ec8cd32 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -120,7 +120,7 @@ body { @media (max-width: 500px) { #search-form .form-search { - width: ~"calc(100% - 80px)" + width: ~"(calc(~'100%' - ~'80px'))"; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index 26abf4404f..c764280c8d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -111,7 +111,7 @@ ul.sections li.help { bottom: 0; left: 0; display: block; - width: ~"calc(100% - 5px)"; //subtract 4px orange border + 1px border-right for sections + width: ~"(calc(~'100%' - ~'5px'))"; //subtract 4px orange border + 1px border-right for sections } ul.sections li.help a { From 1636c1695420ab747bed46eb00d4f30d3409b33a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jul 2016 12:40:45 +0200 Subject: [PATCH 083/413] move loading indicator one level up in detail view, so it's visible first time the checks are run. --- src/Umbraco.Web.UI.Client/src/less/healthcheck.less | 2 +- .../src/views/dashboard/developer/healthcheck.html | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less index 8e4fb55d15..001e3fdc2b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less +++ b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less @@ -246,6 +246,7 @@ } .umb-healthcheck-group__details-check { + position: relative; } .umb-healthcheck-group__details-check-title { @@ -270,7 +271,6 @@ padding: 15px 0; display: flex; border-bottom: 2px solid @grayLighter; - position: relative; } .umb-healthcheck-group__details-status-overlay { diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html index 2214d48993..181140733a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html @@ -108,11 +108,11 @@
      -
      -
      - -
      +
      +
      +
      +
      From b1f13cdbb21d64478792e83fafe3ff6add209ea4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jul 2016 13:15:22 +0200 Subject: [PATCH 084/413] health check overview - hide status when group is loading --- .../src/views/dashboard/developer/healthcheck.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html index 181140733a..ba355c22a2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html @@ -23,7 +23,7 @@
      -
      +
      From 9ae339f17be24e0670e9e2d6141611bd551d8e44 Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 27 Jul 2016 13:59:10 +0200 Subject: [PATCH 085/413] removing a console.log. whitespace and some formatting. --- .../developer/examinemgmt.controller.js | 138 +++++++++--------- .../developer/healthcheck.controller.js | 130 ++++++++--------- .../xmldataintegrityreport.controller.js | 18 +-- .../developer/xmldataintegrityreport.html | 2 - 4 files changed, 144 insertions(+), 144 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js index 4a19ea9926..5f6bb23001 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js @@ -1,4 +1,4 @@ -function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) { +function ExamineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) { $scope.indexerDetails = []; $scope.searcherDetails = []; @@ -6,7 +6,9 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo function checkProcessing(indexer, checkActionName) { umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", checkActionName, { indexerName: indexer.name })), + $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + checkActionName, + { indexerName: indexer.name })), 'Failed to check index processing') .then(function(data) { @@ -17,70 +19,73 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo indexer[k] = data[k]; } indexer.isProcessing = false; - } - else { - $timeout(function () { - //don't continue if we've tried 100 times - if (indexer.processingAttempts < 100) { - checkProcessing(indexer, checkActionName); - //add an attempt - indexer.processingAttempts++; - } - else { - //we've exceeded 100 attempts, stop processing - indexer.isProcessing = false; - } - }, 1000); + } else { + $timeout(function() { + //don't continue if we've tried 100 times + if (indexer.processingAttempts < 100) { + checkProcessing(indexer, checkActionName); + //add an attempt + indexer.processingAttempts++; + } else { + //we've exceeded 100 attempts, stop processing + indexer.isProcessing = false; + } + }, + 1000); } }); } - $scope.search = function (searcher, e) { + $scope.search = function(searcher, e) { if (e && e.keyCode !== 13) { return; } umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearchResults", { - searcherName: searcher.name, - query: encodeURIComponent(searcher.searchText), - queryType: searcher.searchType - })), + $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + "GetSearchResults", + { + searcherName: searcher.name, + query: encodeURIComponent(searcher.searchText), + queryType: searcher.searchType + })), 'Failed to search') .then(function(searchResults) { searcher.isSearching = true; searcher.searchResults = searchResults; }); } - + $scope.toggle = function(provider, propName) { if (provider[propName] !== undefined) { provider[propName] = !provider[propName]; - } - else { + } else { provider[propName] = true; } } $scope.rebuildIndex = function(indexer) { if (confirm("This will cause the index to be rebuilt. " + - "Depending on how much content there is in your site this could take a while. " + - "It is not recommended to rebuild an index during times of high website traffic " + - "or when editors are editing content.")) { + "Depending on how much content there is in your site this could take a while. " + + "It is not recommended to rebuild an index during times of high website traffic " + + "or when editors are editing content.")) { indexer.isProcessing = true; indexer.processingAttempts = 0; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "PostRebuildIndex", { indexerName: indexer.name })), + $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + "PostRebuildIndex", + { indexerName: indexer.name })), 'Failed to rebuild index') - .then(function () { + .then(function() { //rebuilding has started, nothing is returned accept a 200 status code. //lets poll to see if it is done. - $timeout(function () { - checkProcessing(indexer, "PostCheckRebuildIndex"); - }, 1000); + $timeout(function() { + checkProcessing(indexer, "PostCheckRebuildIndex"); + }, + 1000); }); } @@ -88,20 +93,23 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo $scope.optimizeIndex = function(indexer) { if (confirm("This will cause the index to be optimized which will improve its performance. " + - "It is not recommended to optimize an index during times of high website traffic " + - "or when editors are editing content.")) { + "It is not recommended to optimize an index during times of high website traffic " + + "or when editors are editing content.")) { indexer.isProcessing = true; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "PostOptimizeIndex", { indexerName: indexer.name })), + $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + "PostOptimizeIndex", + { indexerName: indexer.name })), 'Failed to optimize index') - .then(function () { + .then(function() { //optimizing has started, nothing is returned accept a 200 status code. //lets poll to see if it is done. - $timeout(function () { - checkProcessing(indexer, "PostCheckOptimizeIndex"); - }, 1000); + $timeout(function() { + checkProcessing(indexer, "PostCheckOptimizeIndex"); + }, + 1000); }); } @@ -111,36 +119,34 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo searcher.isSearching = true; } - //go get the data //combine two promises and execute when they are both done $q.all([ - //get the indexer details - umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetIndexerDetails")), - 'Failed to retrieve indexer details') - .then(function(data) { - $scope.indexerDetails = data; - }), - - //get the searcher details - umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearcherDetails")), - 'Failed to retrieve searcher details') - .then(function(data) { - $scope.searcherDetails = data; - for (var s in $scope.searcherDetails) { - $scope.searcherDetails[s].searchType = "text"; - } - }) - - ]).then(function () { - //all init loading is complete - $scope.loading = false; - }); - + //get the indexer details + umbRequestHelper.resourcePromise( + $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetIndexerDetails")), + 'Failed to retrieve indexer details') + .then(function(data) { + $scope.indexerDetails = data; + }), + //get the searcher details + umbRequestHelper.resourcePromise( + $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearcherDetails")), + 'Failed to retrieve searcher details') + .then(function(data) { + $scope.searcherDetails = data; + for (var s in $scope.searcherDetails) { + $scope.searcherDetails[s].searchType = "text"; + } + }) + ]) + .then(function() { + //all init loading is complete + $scope.loading = false; + }); } -angular.module("umbraco").controller("Umbraco.Dashboard.ExamineMgmtController", examineMgmtController); \ No newline at end of file + +angular.module("umbraco").controller("Umbraco.Dashboard.ExamineMgmtController", ExamineMgmtController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js index 3ca2bee2f1..ea25195ffa 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js @@ -1,8 +1,7 @@ -(function () { +(function() { "use strict"; function HealthCheckController($scope, healthCheckResource) { - var SUCCESS = 0; var WARNING = 1; var ERROR = 2; @@ -11,49 +10,49 @@ var vm = this; vm.viewState = "list"; - vm.groups = []; + vm.groups = []; vm.selectedGroup = {}; - vm.getStatus = getStatus; - vm.executeAction = executeAction; + vm.getStatus = getStatus; + vm.executeAction = executeAction; vm.checkAllGroups = checkAllGroups; - vm.checkAllInGroup = checkAllInGroup; + vm.checkAllInGroup = checkAllInGroup; vm.openGroup = openGroup; vm.setViewState = setViewState; - // Get a (grouped) list of all health checks - healthCheckResource.getAllChecks().then( - function(response) { - vm.groups = response; - } - ); + // Get a (grouped) list of all health checks + healthCheckResource.getAllChecks() + .then(function(response) { + vm.groups = response; + }); function setGroupGlobalResultType(group) { - var totalSuccess = 0; var totalError = 0; var totalWarning = 0; var totalInfo = 0; // count total number of statusses - angular.forEach(group.checks, function(check){ - angular.forEach(check.status, function(status){ - switch(status.resultType) { - case SUCCESS: - totalSuccess = totalSuccess + 1; - break; - case WARNING: - totalWarning = totalWarning + 1; - break; - case ERROR: - totalError = totalError + 1; - break; - case INFO: - totalInfo = totalInfo + 1; - break; - } + angular.forEach(group.checks, + function(check) { + angular.forEach(check.status, + function(status) { + switch (status.resultType) { + case SUCCESS: + totalSuccess = totalSuccess + 1; + break; + case WARNING: + totalWarning = totalWarning + 1; + break; + case ERROR: + totalError = totalError + 1; + break; + case INFO: + totalInfo = totalInfo + 1; + break; + } + }); }); - }); group.totalSuccess = totalSuccess; group.totalError = totalError; @@ -62,56 +61,56 @@ } - // Get the status of an individual check - function getStatus(check) { - check.loading = true; - check.status = null; - healthCheckResource.getStatus(check.id).then(function(response) { - check.loading = false; - check.status = response; - }); - } + // Get the status of an individual check + function getStatus(check) { + check.loading = true; + check.status = null; + healthCheckResource.getStatus(check.id) + .then(function(response) { + check.loading = false; + check.status = response; + }); + } - function executeAction(check, index, action) { - healthCheckResource.executeAction(action).then(function (response) { - check.status[index] = response; - }); - } + function executeAction(check, index, action) { + healthCheckResource.executeAction(action) + .then(function(response) { + check.status[index] = response; + }); + } function checkAllGroups(groups) { // set number of checks which has been executed for (var i = 0; i < groups.length; i++) { var group = groups[i]; - console.log(group); checkAllInGroup(group, group.checks); } vm.groups = groups; } - function checkAllInGroup(group, checks) { - + function checkAllInGroup(group, checks) { group.checkCounter = 0; group.loading = true; - angular.forEach(checks, function(check) { + angular.forEach(checks, + function(check) { - check.loading = true; + check.loading = true; - healthCheckResource.getStatus(check.id).then(function(response) { - check.status = response; - group.checkCounter = group.checkCounter + 1; - check.loading = false; + healthCheckResource.getStatus(check.id) + .then(function(response) { + check.status = response; + group.checkCounter = group.checkCounter + 1; + check.loading = false; - // when all checks are done, set global group result - if (group.checkCounter === checks.length) { - setGroupGlobalResultType(group); - group.loading = false; - } - - }); - }); - - } + // when all checks are done, set global group result + if (group.checkCounter === checks.length) { + setGroupGlobalResultType(group); + group.loading = false; + } + }); + }); + } function openGroup(group) { vm.selectedGroup = group; @@ -121,17 +120,14 @@ function setViewState(state) { vm.viewState = state; - if(state === 'list') { + if (state === 'list') { for (var i = 0; i < vm.groups.length; i++) { var group = vm.groups[i]; setGroupGlobalResultType(group); } - } - } - } angular.module("umbraco").controller("Umbraco.Dashboard.HealthCheckController", HealthCheckController); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js index 09b638b05f..bb8696aca0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js @@ -1,4 +1,4 @@ -function xmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http, $q, $timeout) { +function XmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http) { function check(item) { var action = item.check; @@ -20,12 +20,12 @@ function xmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http, "or when editors are editing content.")) { item.fixing = true; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)), - 'Failed to retrieve data integrity status') - .then(function (result) { - item.fixing = false; - item.invalid = result === "false"; - }); + $http.post(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)), + 'Failed to retrieve data integrity status') + .then(function(result) { + item.fixing = false; + item.invalid = result === "false"; + }); } } } @@ -57,6 +57,6 @@ function xmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http, for (var i in $scope.items) { check($scope.items[i]); } - } -angular.module("umbraco").controller("Umbraco.Dashboard.XmlDataIntegrityReportController", xmlDataIntegrityReportController); \ No newline at end of file + +angular.module("umbraco").controller("Umbraco.Dashboard.XmlDataIntegrityReportController", XmlDataIntegrityReportController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html index df235fb254..11f1834ae4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html @@ -26,6 +26,4 @@
      - -
      From 5f7859a8c11fe7d2deeff8071a900aa2722c1c34 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 28 Jul 2016 12:25:58 +0200 Subject: [PATCH 086/413] truncate breadcrumb items --- .../src/less/components/umb-breadcrumbs.less | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less index 5854599722..54b201182e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less @@ -10,8 +10,12 @@ .umb-breadcrumbs__ancestor-link, .umb-breadcrumbs__ancestor-text { - font-size: 11px; - color: #555; + font-size: 11px; + color: #555; + max-width: 150px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .umb-breadcrumbs__ancestor-link { From b631db14c3264bef40ec7d47501638ad45ee9219 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 28 Jul 2016 12:26:52 +0200 Subject: [PATCH 087/413] show full breadcrumb item on hover --- .../src/views/components/editor/umb-breadcrumbs.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-breadcrumbs.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-breadcrumbs.html index a63880819b..219e90fc4c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-breadcrumbs.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-breadcrumbs.html @@ -1,10 +1,10 @@ From 3cdf80a82b58e1827e4b8dadfb164b28763f04b5 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 28 Jul 2016 12:33:32 +0200 Subject: [PATCH 088/413] change main editor from absolute positioning to flex box. Sets auto height on editor footer so it can expand --- .../src/less/components/editor.less | 38 +++++++++---------- .../src/less/components/umb-breadcrumbs.less | 4 +- src/Umbraco.Web.UI.Client/src/less/panel.less | 4 +- .../components/editor/umb-editor-header.html | 2 +- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index 6d43286f2d..bc44156cc3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -5,29 +5,31 @@ .umb-editor-wrapper{ background: white; position: absolute; - top: 0px; bottom: 0px; left: 0px; right: 0px; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; } -.umb-editor-header{ +.umb-editor-header { background: @grayLighter; border-bottom: 1px solid @grayLight; - position: absolute; - height: 99px; - top: 0px; - right: 0px; - left: 0px; + flex: 0 0 99px; + position: relative; } .umb-editor-container { - top: 101px; - left: 0px; - right: 0px; - bottom: 52px; - position: absolute; - clear: both; + position: relative; + top: 0; + right: 0; + bottom: 0; + left: 0; overflow: auto; + flex: 1 1 100%; } .umb-editor-wrapper.-no-footer .umb-editor-container { @@ -38,18 +40,12 @@ overflow: hidden; } -.umb-editor-drawer{ +.umb-editor-drawer { margin: 0; padding: 10px 20px; - z-index: 999; - position: absolute; - bottom: 0px; - left: 0px; - right: 0px; - height: 31px; - background: @grayLighter; border-top: 1px solid @grayLight; + flex: 1 0 31px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less index 54b201182e..d09946dfa3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less @@ -2,10 +2,12 @@ list-style: none; margin-bottom: 0; margin-left: 0; + display: flex; + flex-wrap: wrap; } .umb-breadcrumbs__ancestor { - display: inline-block; + display: flex; } .umb-breadcrumbs__ancestor-link, diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index aab0c5987c..c80f72921c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -483,13 +483,15 @@ input.umb-panel-header-description { .umb-editor-drawer-content { display: flex; align-items: center; - //justify-content: space-between; } .umb-editor-drawer-content__right-side { margin-left: auto; + flex: 0 0 auto; + padding-left: 10px; } .umb-editor-drawer-content__left-side { margin-right: auto; + padding-right: 10px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html index 46c666b8cc..57a3ec4754 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html @@ -1,4 +1,4 @@ -
      +
      From be8a52e7b67b5b1b15f80423dbb9746a65da372c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 28 Jul 2016 12:34:04 +0200 Subject: [PATCH 089/413] fix positioning of sub header when scrolling --- .../less/components/editor/subheader/umb-editor-sub-header.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 8c1acd97d8..0792925571 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -12,7 +12,7 @@ .umb-editor-sub-header.-umb-sticky-bar { box-shadow: 0 5px 0 rgba(0, 0, 0, 0.08), 0 1px 0 rgba(0, 0, 0, 0.16); transition: box-shadow 1s; - top: 101px; /* height of header: 100px + its bottom-border: 1px */ + top: 100px; /* height of header */ margin-top: 0; margin-bottom: 0; } From 385c53936e4d17276d895615b949300bc997b07e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Riis-Knudsen?= Date: Thu, 28 Jul 2016 22:18:17 +0200 Subject: [PATCH 090/413] U4-8749: Initial implementation of the default sort order using the columns added instead of a fixed list --- .../listview/sortby.prevalues.controller.js | 93 ++++++++++++------- .../listview/sortby.prevalues.html | 2 +- 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js index 4498e281dd..6d93139017 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js @@ -1,40 +1,67 @@ -function sortByPreValsController($rootScope, $scope, localizationService) { +function sortByPreValsController($rootScope, $scope, localizationService, editorState) { + var propsPreValue = _.findWhere(editorState.current.preValues, { key: "includeProperties" }); - $scope.sortByFields = [ - { value: "SortOrder", key: "general_sort" }, - { value: "Name", key: "general_name" }, - { value: "VersionDate", key: "content_updateDate" }, - { value: "Updater", key: "content_updatedBy" }, - { value: "CreateDate", key: "content_createDate" }, - { value: "Owner", key: "content_createBy" }, - { value: "ContentTypeAlias", key: "content_documentType" }, - { value: "Published", key: "content_isPublished" }, - { value: "Email", key: "general_email" }, - { value: "Username", key: "general_username" } - ]; - - //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared - _.each($scope.sortByFields, function (e, i) { - localizationService.localize(e.key).then(function (v) { - e.name = v; + if(propsPreValue != undefined){ + $scope.sortByFields = propsPreValue.value; + } + else { + $scope.sortByFields = []; + } - switch (e.value) { - case "Updater": - e.name += " (Content only)"; - break; - case "Published": - e.name += " (Content only)"; - break; - case "Email": - e.name += " (Members only)"; - break; - case "Username": - e.name += " (Members only)"; - break; - } - }); + var existingValue = _.find($scope.sortByFields, function (e) { + return e.alias.toLowerCase() === $scope.model.value; }); + if (existingValue === undefined) { + //Existing value not found + + //Set to first value + if($scope.sortByFields.length > 0){ + $scope.model.value = $scope.sortByFields[0].alias; + } + + } + else { + //Set the existing value + //The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set + $scope.model.value = existingValue.alias; + } + + //$scope.sortByFields = [ + // { value: "SortOrder", key: "general_sort" }, + // { value: "Name", key: "general_name" }, + // { value: "VersionDate", key: "content_updateDate" }, + // { value: "Updater", key: "content_updatedBy" }, + // { value: "CreateDate", key: "content_createDate" }, + // { value: "Owner", key: "content_createBy" }, + // { value: "ContentTypeAlias", key: "content_documentType" }, + // { value: "Published", key: "content_isPublished" }, + // { value: "Email", key: "general_email" }, + // { value: "Username", key: "general_username" } + //]; + + ////now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared + //_.each($scope.sortByFields, function (e, i) { + // localizationService.localize(e.key).then(function (v) { + // e.name = v; + + // switch (e.value) { + // case "Updater": + // e.name += " (Content only)"; + // break; + // case "Published": + // e.name += " (Content only)"; + // break; + // case "Email": + // e.name += " (Members only)"; + // break; + // case "Username": + // e.name += " (Members only)"; + // break; + // } + // }); + //}); + } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html index 2b35aa2ca4..43a8a674f4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html @@ -1,5 +1,5 @@  \ No newline at end of file From e53b81b627156cee325902e57ceab1f84516a747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Riis-Knudsen?= Date: Thu, 28 Jul 2016 22:21:21 +0200 Subject: [PATCH 091/413] U4-8749: Remove commented out code --- .../listview/sortby.prevalues.controller.js | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js index 6d93139017..ec22bca8f7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js @@ -26,42 +26,6 @@ function sortByPreValsController($rootScope, $scope, localizationService, editor //The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set $scope.model.value = existingValue.alias; } - - //$scope.sortByFields = [ - // { value: "SortOrder", key: "general_sort" }, - // { value: "Name", key: "general_name" }, - // { value: "VersionDate", key: "content_updateDate" }, - // { value: "Updater", key: "content_updatedBy" }, - // { value: "CreateDate", key: "content_createDate" }, - // { value: "Owner", key: "content_createBy" }, - // { value: "ContentTypeAlias", key: "content_documentType" }, - // { value: "Published", key: "content_isPublished" }, - // { value: "Email", key: "general_email" }, - // { value: "Username", key: "general_username" } - //]; - - ////now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared - //_.each($scope.sortByFields, function (e, i) { - // localizationService.localize(e.key).then(function (v) { - // e.name = v; - - // switch (e.value) { - // case "Updater": - // e.name += " (Content only)"; - // break; - // case "Published": - // e.name += " (Content only)"; - // break; - // case "Email": - // e.name += " (Members only)"; - // break; - // case "Username": - // e.name += " (Members only)"; - // break; - // } - // }); - //}); - } From ccd03cb1f2a65361a509fd213700f8ed7f317b5b Mon Sep 17 00:00:00 2001 From: Claus Date: Fri, 29 Jul 2016 10:38:00 +0200 Subject: [PATCH 092/413] fixes overlay width on small screens (mads). --- src/Umbraco.Web.UI.Client/src/less/components/overlays.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index bcdd1ba572..7a7ac15c6b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -155,7 +155,7 @@ @media (max-width: 500px) { .umb-overlay.umb-overlay-left { margin-left: 41px; - width: auto; + width: ~"(calc(~'100%' - ~'41px'))"; } } From b686fa74c9fcabe14a0f9359faae0678597a79b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Riis-Knudsen?= Date: Fri, 29 Jul 2016 23:42:59 +0200 Subject: [PATCH 093/413] U4-8749: Fixes by @AndyButland --- .../listview/listview.controller.js | 22 +++-- .../listview/sortby.prevalues.controller.js | 82 +++++++++++++++---- .../PropertyEditors/ListViewPropertyEditor.cs | 15 ++-- 3 files changed, 88 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 594475a7d2..f556232543 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -151,7 +151,6 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie layouts: $scope.model.config.layouts, activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) }, - orderBySystemField: true, allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, @@ -159,6 +158,15 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete }; + // Check if selected order by field is actually custom field + for (var j = 0; j < $scope.options.includeProperties.length; j++) { + var includedProperty = $scope.options.includeProperties[j]; + if (includedProperty.alias.toLowerCase() === $scope.options.orderBy.toLowerCase()) { + $scope.options.orderBySystemField = includedProperty.isSystem === 1; + break; + } + } + //update all of the system includeProperties to enable sorting _.each($scope.options.includeProperties, function (e, i) { @@ -180,12 +188,12 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie } if (e.isSystem) { - //localize the header - var key = getLocalizedKey(e.alias); - localizationService.localize(key).then(function (v) { - e.header = v; - }); - } + //localize the header + var key = getLocalizedKey(e.alias); + localizationService.localize(key).then(function (v) { + e.header = v; + }); + } }); $scope.selectLayout = function (selectedLayout) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js index ec22bca8f7..97775b18bb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js @@ -1,31 +1,77 @@ function sortByPreValsController($rootScope, $scope, localizationService, editorState) { + // Helper to find a particular value from the list of sort by options + function findFromSortByFields(value) { + return _.find($scope.sortByFields, function (e) { + return e.value.toLowerCase() === value.toLowerCase(); + }); + } + + // Get list of properties assigned as columns of the list view var propsPreValue = _.findWhere(editorState.current.preValues, { key: "includeProperties" }); - if(propsPreValue != undefined){ - $scope.sortByFields = propsPreValue.value; - } - else { - $scope.sortByFields = []; + // Populate list of options for the default sort (all the columns plus then node name) + $scope.sortByFields = []; + $scope.sortByFields.push({ value: "name", name: "Name", isSystem: 1 }); + if (propsPreValue != undefined) { + for (var i = 0; i < propsPreValue.value.length; i++) { + var value = propsPreValue.value[i]; + $scope.sortByFields.push({ + value: value.alias, + name: value.header, + isSystem: value.isSystem + }); + } } - var existingValue = _.find($scope.sortByFields, function (e) { - return e.alias.toLowerCase() === $scope.model.value; + // Localize the system fields, for some reason the directive doesn't work inside of the select group with an ng-model declared + var systemFields = [ + { value: "SortOrder", key: "general_sort" }, + { value: "Name", key: "general_name" }, + { value: "VersionDate", key: "content_updateDate" }, + { value: "Updater", key: "content_updatedBy" }, + { value: "CreateDate", key: "content_createDate" }, + { value: "Owner", key: "content_createBy" }, + { value: "ContentTypeAlias", key: "content_documentType" }, + { value: "Published", key: "content_isPublished" }, + { value: "Email", key: "general_email" }, + { value: "Username", key: "general_username" } + ]; + _.each(systemFields, function (e) { + localizationService.localize(e.key).then(function (v) { + + var sortByListValue = findFromSortByFields(e.value); + if (sortByListValue) { + sortByListValue.name = v; + switch (e.value) { + case "Updater": + e.name += " (Content only)"; + break; + case "Published": + e.name += " (Content only)"; + break; + case "Email": + e.name += " (Members only)"; + break; + case "Username": + e.name += " (Members only)"; + break; + } + } + }); }); - if (existingValue === undefined) { - //Existing value not found - - //Set to first value - if($scope.sortByFields.length > 0){ - $scope.model.value = $scope.sortByFields[0].alias; - } - + // Check existing model value is available in list and ensure a value is set + var existingValue = findFromSortByFields($scope.model.value); + if (existingValue) { + // Set the existing value + // The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set + $scope.model.value = existingValue.value; } else { - //Set the existing value - //The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set - $scope.model.value = existingValue.alias; + // Existing value not found, set to first value + $scope.model.value = $scope.sortByFields[0].value; } + } diff --git a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs index 0e1a0abf2d..f744e4d807 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs @@ -53,9 +53,17 @@ namespace Umbraco.Web.PropertyEditors { [PreValueField("displayAtTabNumber", "Display At Tab Number", "number", Description = "Which tab position that the list of child items will be displayed")] public int DisplayAtTabNumber { get; set; } + [PreValueField("pageSize", "Page Size", "number", Description = "Number of items per page")] public int PageSize { get; set; } + [PreValueField("layouts", "Layouts", "views/propertyeditors/listview/layouts.prevalues.html")] + public int Layouts { get; set; } + + [PreValueField("includeProperties", "Columns Displayed", "views/propertyeditors/listview/includeproperties.prevalues.html", + Description = "The properties that will be displayed for each column")] + public object IncludeProperties { get; set; } + [PreValueField("orderBy", "Order By", "views/propertyeditors/listview/sortby.prevalues.html", Description = "The default sort order for the list")] public int OrderBy { get; set; } @@ -63,15 +71,10 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("orderDirection", "Order Direction", "views/propertyeditors/listview/orderdirection.prevalues.html")] public int OrderDirection { get; set; } - [PreValueField("layouts", "Layouts", "views/propertyeditors/listview/layouts.prevalues.html")] - public int Layouts { get; set; } - - [PreValueField("includeProperties", "Columns Displayed", "views/propertyeditors/listview/includeproperties.prevalues.html", - Description = "The properties that will be displayed for each column")] - public object IncludeProperties { get; set; } [PreValueField("bulkActionPermissions", "Bulk Action Permissions", "views/propertyeditors/listview/bulkactionpermissions.prevalues.html", Description = "The bulk actions that are allowed from the list view")] public BulkActionPermissionSettings BulkActionPermissions { get; set; } + internal class BulkActionPermissionSettings { public bool AllowBulkPublish { get; set; } From 6837dd2ce876daa0610006d818cf678103b78a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Riis-Knudsen?= Date: Fri, 29 Jul 2016 23:55:20 +0200 Subject: [PATCH 094/413] U4-8749: One more fix by @AndyButland --- .../src/views/propertyeditors/listview/sortby.prevalues.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html index 43a8a674f4..754afd9b60 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html @@ -1,5 +1,4 @@  \ No newline at end of file From 6adccb66dd83ed102b120c272c12d5d8449a969b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Riis-Knudsen?= Date: Sat, 30 Jul 2016 00:30:50 +0200 Subject: [PATCH 095/413] U4-8749: Use watch do dynamically refresh sort column list --- .../listview/sortby.prevalues.controller.js | 126 +++++++++--------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js index 97775b18bb..a6e42c3d31 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js @@ -1,77 +1,73 @@ function sortByPreValsController($rootScope, $scope, localizationService, editorState) { - // Helper to find a particular value from the list of sort by options - function findFromSortByFields(value) { - return _.find($scope.sortByFields, function (e) { - return e.value.toLowerCase() === value.toLowerCase(); - }); - } + //Watch the prevalues + $scope.$watch(function () { + return _.findWhere(editorState.current.preValues, { key: "includeProperties" }).value; + }, function () { + populateFields(); + }, true); //Use deep watching, otherwise we won't pick up header changes - // Get list of properties assigned as columns of the list view - var propsPreValue = _.findWhere(editorState.current.preValues, { key: "includeProperties" }); - - // Populate list of options for the default sort (all the columns plus then node name) - $scope.sortByFields = []; - $scope.sortByFields.push({ value: "name", name: "Name", isSystem: 1 }); - if (propsPreValue != undefined) { - for (var i = 0; i < propsPreValue.value.length; i++) { - var value = propsPreValue.value[i]; - $scope.sortByFields.push({ - value: value.alias, - name: value.header, - isSystem: value.isSystem + function populateFields() { + // Helper to find a particular value from the list of sort by options + function findFromSortByFields(value) { + return _.find($scope.sortByFields, function (e) { + return e.value.toLowerCase() === value.toLowerCase(); }); } - } - // Localize the system fields, for some reason the directive doesn't work inside of the select group with an ng-model declared - var systemFields = [ - { value: "SortOrder", key: "general_sort" }, - { value: "Name", key: "general_name" }, - { value: "VersionDate", key: "content_updateDate" }, - { value: "Updater", key: "content_updatedBy" }, - { value: "CreateDate", key: "content_createDate" }, - { value: "Owner", key: "content_createBy" }, - { value: "ContentTypeAlias", key: "content_documentType" }, - { value: "Published", key: "content_isPublished" }, - { value: "Email", key: "general_email" }, - { value: "Username", key: "general_username" } - ]; - _.each(systemFields, function (e) { - localizationService.localize(e.key).then(function (v) { + // Get list of properties assigned as columns of the list view + var propsPreValue = _.findWhere(editorState.current.preValues, { key: "includeProperties" }); - var sortByListValue = findFromSortByFields(e.value); - if (sortByListValue) { - sortByListValue.name = v; - switch (e.value) { - case "Updater": - e.name += " (Content only)"; - break; - case "Published": - e.name += " (Content only)"; - break; - case "Email": - e.name += " (Members only)"; - break; - case "Username": - e.name += " (Members only)"; - break; - } + // Populate list of options for the default sort (all the columns plus then node name) + $scope.sortByFields = []; + $scope.sortByFields.push({ value: "name", name: "Name", isSystem: 1 }); + if (propsPreValue != undefined) { + for (var i = 0; i < propsPreValue.value.length; i++) { + var value = propsPreValue.value[i]; + $scope.sortByFields.push({ + value: value.alias, + name: value.header, + isSystem: value.isSystem + }); } + } + + // Localize the system fields, for some reason the directive doesn't work inside of the select group with an ng-model declared + var systemFields = [ + { value: "SortOrder", key: "general_sort" }, + { value: "Name", key: "general_name" }, + { value: "VersionDate", key: "content_updateDate" }, + { value: "Updater", key: "content_updatedBy" }, + { value: "CreateDate", key: "content_createDate" }, + { value: "Owner", key: "content_createBy" }, + { value: "ContentTypeAlias", key: "content_documentType" }, + { value: "Published", key: "content_isPublished" }, + { value: "Email", key: "general_email" }, + { value: "Username", key: "general_username" } + ]; + _.each(systemFields, function (e) { + localizationService.localize(e.key).then(function (v) { + + var sortByListValue = findFromSortByFields(e.value); + if (sortByListValue) { + sortByListValue.name = v; + switch (e.value) { + case "Updater": + e.name += " (Content only)"; + break; + case "Published": + e.name += " (Content only)"; + break; + case "Email": + e.name += " (Members only)"; + break; + case "Username": + e.name += " (Members only)"; + break; + } + } + }); }); - }); - - // Check existing model value is available in list and ensure a value is set - var existingValue = findFromSortByFields($scope.model.value); - if (existingValue) { - // Set the existing value - // The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set - $scope.model.value = existingValue.value; } - else { - // Existing value not found, set to first value - $scope.model.value = $scope.sortByFields[0].value; - } - } From 1f6caa0164d32c2992fa2d44881804a6a5ee7e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Riis-Knudsen?= Date: Sat, 30 Jul 2016 00:35:06 +0200 Subject: [PATCH 096/413] U4-8749: add back some acceidentally deleted code to deal with existing values --- .../listview/sortby.prevalues.controller.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js index a6e42c3d31..480a78ee2d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js @@ -67,6 +67,18 @@ function sortByPreValsController($rootScope, $scope, localizationService, editor } }); }); + + // Check existing model value is available in list and ensure a value is set + var existingValue = findFromSortByFields($scope.model.value); + if (existingValue) { + // Set the existing value + // The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set + $scope.model.value = existingValue.value; + } + else { + // Existing value not found, set to first value + $scope.model.value = $scope.sortByFields[0].value; + } } } From 3ebb3dc5444393378b37d68e1a792d9da131f45a Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Aug 2016 12:04:02 +0200 Subject: [PATCH 097/413] U4-7813 Enable MiniProfiler to profiler during startup --- src/Umbraco.Core/Umbraco.Core.csproj | 1 - .../Profiling/StartupWebProfilerProvider.cs | 159 ++++++++ .../Profiling/WebProfiler.cs | 349 +++++++++--------- src/Umbraco.Web/Umbraco.Web.csproj | 2 + src/Umbraco.Web/WebBootManager.cs | 5 +- 5 files changed, 349 insertions(+), 167 deletions(-) create mode 100644 src/Umbraco.Web/Profiling/StartupWebProfilerProvider.cs rename src/{Umbraco.Core => Umbraco.Web}/Profiling/WebProfiler.cs (80%) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 374e994a12..5faa31a241 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1095,7 +1095,6 @@ - diff --git a/src/Umbraco.Web/Profiling/StartupWebProfilerProvider.cs b/src/Umbraco.Web/Profiling/StartupWebProfilerProvider.cs new file mode 100644 index 0000000000..72a398f17f --- /dev/null +++ b/src/Umbraco.Web/Profiling/StartupWebProfilerProvider.cs @@ -0,0 +1,159 @@ +using System.Threading; +using System.Web; +using StackExchange.Profiling; +using Umbraco.Core; + +namespace Umbraco.Web.Profiling +{ + /// + /// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows + /// us to profile items during app startup - before an HttpRequest is created + /// + /// + /// Once the boot phase is changed to StartupPhase.Request then the base class (default) provider will handle all + /// profiling data and this sub class no longer performs any logic. + /// + internal class StartupWebProfilerProvider : WebRequestProfilerProvider + { + public StartupWebProfilerProvider() + { + _startupPhase = StartupPhase.Boot; + //create the startup profiler + _startupProfiler = new MiniProfiler("http://localhost/umbraco-startup", ProfileLevel.Verbose) + { + Name = "StartupProfiler" + }; + } + + private MiniProfiler _startupProfiler; + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + + /// + /// Used to determine which phase the boot process is in + /// + private enum StartupPhase + { + None = 0, + Boot = 1, + Request = 2 + } + + private volatile StartupPhase _startupPhase; + + /// + /// Executed once the application boot process is complete and changes the phase to Request + /// + public void BootComplete() + { + using (new ReadLock(_locker)) + { + if (_startupPhase != StartupPhase.Boot) return; + } + + using (var l = new UpgradeableReadLock(_locker)) + { + if (_startupPhase == StartupPhase.Boot) + { + l.UpgradeToWriteLock(); + _startupPhase = StartupPhase.Request; + } + } + } + + /// + /// Executed when a profiling operation is completed + /// + /// + /// + /// This checks if the bootup phase is None, if so, it just calls the base class, otherwise it checks + /// if a profiler is active (i.e. in startup), then sets the phase to None so that the base class will be used + /// for all subsequent calls. + /// + public override void Stop(bool discardResults) + { + using (new ReadLock(_locker)) + { + if (_startupPhase == StartupPhase.None) + { + base.Stop(discardResults); + return; + } + } + + using (var l = new UpgradeableReadLock(_locker)) + { + if (_startupPhase > 0 && base.GetCurrentProfiler() == null) + { + l.UpgradeToWriteLock(); + + _startupPhase = StartupPhase.None; + + //This is required to pass the mini profiling context from before a request + // to the current startup request. + if (HttpContext.Current != null) + { + HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; + base.Stop(discardResults); + _startupProfiler = null; + } + } + else + { + base.Stop(discardResults); + } + } + } + + /// + /// Executed when a profiling operation is started + /// + /// + /// + /// + /// This checks if the startup phase is not None, if this is the case and the current profiler is NULL + /// then this sets the startup profiler to be active. Otherwise it just calls the base class Start method. + /// + public override MiniProfiler Start(ProfileLevel level) + { + using (new ReadLock(_locker)) + { + if (_startupPhase > 0 && base.GetCurrentProfiler() == null) + { + SetProfilerActive(_startupProfiler); + return _startupProfiler; + } + + return base.Start(level); + } + } + + /// + /// This returns the current profiler + /// + /// + /// + /// If the boot phase is not None, then this will return the startup profiler (this), otherwise + /// returns the base class + /// + public override MiniProfiler GetCurrentProfiler() + { + using (new ReadLock(_locker)) + { + if (_startupPhase > 0) + { + try + { + var current = base.GetCurrentProfiler(); + if (current == null) return _startupProfiler; + } + catch + { + return _startupProfiler; + } + } + + return base.GetCurrentProfiler(); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Profiling/WebProfiler.cs b/src/Umbraco.Web/Profiling/WebProfiler.cs similarity index 80% rename from src/Umbraco.Core/Profiling/WebProfiler.cs rename to src/Umbraco.Web/Profiling/WebProfiler.cs index 00d088bca7..a1998c8761 100644 --- a/src/Umbraco.Core/Profiling/WebProfiler.cs +++ b/src/Umbraco.Web/Profiling/WebProfiler.cs @@ -1,166 +1,185 @@ -using System; -using System.Web; -using StackExchange.Profiling; -using StackExchange.Profiling.SqlFormatters; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Profiling -{ - /// - /// A profiler used for web based activity based on the MiniProfiler framework - /// - internal class WebProfiler : IProfiler - { - - /// - /// Constructor - /// - /// - /// Binds to application events to enable the MiniProfiler - /// - internal WebProfiler() - { - UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; - } - - /// - /// Handle the Init event o fthe UmbracoApplication which allows us to subscribe to the HttpApplication events - /// - /// - /// - void UmbracoApplicationApplicationInit(object sender, EventArgs e) - { - var app = sender as HttpApplication; - if (app == null) return; - - if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) - { - //If we don't have a high enough trust level we cannot bind to the events - LogHelper.Info("Cannot start the WebProfiler since the application is running in Medium trust"); - } - else - { - app.BeginRequest += UmbracoApplicationBeginRequest; - app.EndRequest += UmbracoApplicationEndRequest; - } - } - - /// - /// Handle the begin request event - /// - /// - /// - void UmbracoApplicationEndRequest(object sender, EventArgs e) - { - if (CanPerformProfilingAction(sender)) - { - Stop(); - } - } - - /// - /// Handle the end request event - /// - /// - /// - void UmbracoApplicationBeginRequest(object sender, EventArgs e) - { - if (CanPerformProfilingAction(sender)) - { - Start(); - } - } - - private bool CanPerformProfilingAction(object sender) - { - if (GlobalSettings.DebugMode == false) - return false; - - //will not run in medium trust - if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) - return false; - - var request = TryGetRequest(sender); - - if (request.Success == false || request.Result.Url.IsClientSideRequest()) - return false; - - if (string.IsNullOrEmpty(request.Result.QueryString["umbDebug"])) - return true; - - if (request.Result.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) - return true; - - return true; - } - - /// - /// Render the UI to display the profiler - /// - /// - /// - /// Generally used for HTML displays - /// - public string Render() - { - return MiniProfiler.RenderIncludes(RenderPosition.Right).ToString(); - } - - /// - /// Profile a step - /// - /// - /// - /// - /// Use the 'using(' syntax - /// - public IDisposable Step(string name) - { - return GlobalSettings.DebugMode == false ? null : MiniProfiler.Current.Step(name); - } - - /// - /// Start the profiler - /// - public void Start() - { - MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); - MiniProfiler.Settings.StackMaxLength = 5000; - MiniProfiler.Start(); - } - - /// - /// Start the profiler - /// - /// - /// set discardResults to false when you want to abandon all profiling, this is useful for - /// when someone is not authenticated or you want to clear the results based on some other mechanism. - /// - public void Stop(bool discardResults = false) - { - MiniProfiler.Stop(discardResults); - } - - /// - /// Gets the request object from the app instance if it is available - /// - /// The application object - /// - private Attempt TryGetRequest(object sender) - { - var app = sender as HttpApplication; - if (app == null) return Attempt.Fail(); - - try - { - var req = app.Request; - return Attempt.Succeed(new HttpRequestWrapper(req)); - } - catch (HttpException ex) - { - return Attempt.Fail(ex); - } - } - } +using System; +using System.Web; +using StackExchange.Profiling; +using StackExchange.Profiling.SqlFormatters; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Profiling; + +namespace Umbraco.Web.Profiling +{ + /// + /// A profiler used for web based activity based on the MiniProfiler framework + /// + internal class WebProfiler : IProfiler + { + private StartupWebProfilerProvider _startupWebProfilerProvider; + + /// + /// Constructor + /// + internal WebProfiler() + { + //setup some defaults + MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); + MiniProfiler.Settings.StackMaxLength = 5000; + + //At this point we know that we've been constructed during app startup, there won't be an HttpRequest in the HttpContext + // since it hasn't started yet. So we need to do some hacking to enable profiling during startup. + _startupWebProfilerProvider = new StartupWebProfilerProvider(); + //this should always be the case during startup, we'll need to set a custom profiler provider + MiniProfiler.Settings.ProfilerProvider = _startupWebProfilerProvider; + + //Binds to application events to enable the MiniProfiler with a real HttpRequest + UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; + } + + /// + /// Handle the Init event o fthe UmbracoApplication which allows us to subscribe to the HttpApplication events + /// + /// + /// + void UmbracoApplicationApplicationInit(object sender, EventArgs e) + { + var app = sender as HttpApplication; + if (app == null) return; + + if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) + { + //If we don't have a high enough trust level we cannot bind to the events + LogHelper.Info("Cannot start the WebProfiler since the application is running in Medium trust"); + } + else + { + app.BeginRequest += UmbracoApplicationBeginRequest; + app.EndRequest += UmbracoApplicationEndRequest; + } + } + + /// + /// Handle the begin request event + /// + /// + /// + void UmbracoApplicationEndRequest(object sender, EventArgs e) + { + if (_startupWebProfilerProvider != null) + { + Stop(); + _startupWebProfilerProvider = null; + } + else if (CanPerformProfilingAction(sender)) + { + Stop(); + } + } + + /// + /// Handle the end request event + /// + /// + /// + void UmbracoApplicationBeginRequest(object sender, EventArgs e) + { + if (_startupWebProfilerProvider != null) + { + _startupWebProfilerProvider.BootComplete(); + } + + if (CanPerformProfilingAction(sender)) + { + Start(); + } + } + + private bool CanPerformProfilingAction(object sender) + { + if (GlobalSettings.DebugMode == false) + return false; + + //will not run in medium trust + if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) + return false; + + var request = TryGetRequest(sender); + + if (request.Success == false || request.Result.Url.IsClientSideRequest()) + return false; + + if (string.IsNullOrEmpty(request.Result.QueryString["umbDebug"])) + return true; + + if (request.Result.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) + return true; + + return true; + } + + /// + /// Render the UI to display the profiler + /// + /// + /// + /// Generally used for HTML displays + /// + public string Render() + { + return MiniProfiler.RenderIncludes(RenderPosition.Right).ToString(); + } + + /// + /// Profile a step + /// + /// + /// + /// + /// Use the 'using(' syntax + /// + public IDisposable Step(string name) + { + return GlobalSettings.DebugMode == false ? null : MiniProfiler.Current.Step(name); + } + + /// + /// Start the profiler + /// + public void Start() + { + MiniProfiler.Start(); + } + + /// + /// Start the profiler + /// + /// + /// set discardResults to false when you want to abandon all profiling, this is useful for + /// when someone is not authenticated or you want to clear the results based on some other mechanism. + /// + public void Stop(bool discardResults = false) + { + MiniProfiler.Stop(discardResults); + } + + /// + /// Gets the request object from the app instance if it is available + /// + /// The application object + /// + private Attempt TryGetRequest(object sender) + { + var app = sender as HttpApplication; + if (app == null) return Attempt.Fail(); + + try + { + var req = app.Request; + return Attempt.Succeed(new HttpRequestWrapper(req)); + } + catch (HttpException ex) + { + return Attempt.Fail(ex); + } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 68ab86ea01..d1cab1b52d 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -383,6 +383,8 @@ + + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index f0f8f5372d..bc41c9c877 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -48,6 +48,7 @@ using Umbraco.Core.Publishing; using Umbraco.Core.Services; using Umbraco.Web.Editors; using Umbraco.Web.HealthCheck; +using Umbraco.Web.Profiling; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using ProfilingViewEngine = Umbraco.Core.Profiling.ProfilingViewEngine; @@ -186,7 +187,9 @@ namespace Umbraco.Web { base.InitializeProfilerResolver(); //Set the profiler to be the web profiler - ProfilerResolver.Current.SetProfiler(new WebProfiler()); + var profiler = new WebProfiler(); + ProfilerResolver.Current.SetProfiler(profiler); + profiler.Start(); } /// From 1ca66740956380b30266f8d389bf6730c58a9c26 Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Tue, 2 Aug 2016 09:41:01 +0200 Subject: [PATCH 098/413] Fixes http://issues.umbraco.org/issue/U4-8770 --- .../src/less/components/umb-lightbox.less | 11 +++++++++-- src/Umbraco.Web.UI.Client/src/less/healthcheck.less | 2 +- .../src/views/components/umb-lightbox.html | 7 +++++-- .../src/views/packager/views/repo.html | 11 ++++++----- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 ++-- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 4 ++-- 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less index e8477396bf..9f1ef219a9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less @@ -19,7 +19,7 @@ right: 0; bottom: 0; left: 0; - background: rgba(0, 0, 0, 0.2); + background: rgba(21, 21, 23, 0.7); width: 100%; height: 100%; } @@ -27,7 +27,14 @@ .umb-lightbox__close { position: absolute; top: 20px; - right: 20px; + right: 60px; +} + +.umb-lightbox__close i { + font-size: 20px; + cursor: pointer; + height: 40px; + width: 40px; } .umb-lightbox__images { diff --git a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less index 001e3fdc2b..96b8a611db 100644 --- a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less +++ b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less @@ -104,7 +104,7 @@ font-size: 14px; font-weight: bold; - height: 30px; + height: 40px; line-height: 1; max-width: 100%; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-lightbox.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-lightbox.html index f0a53a7f44..1379072db2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-lightbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-lightbox.html @@ -1,6 +1,9 @@
      +
      + +
      @@ -8,11 +11,11 @@
      -
      +
      -
      +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html index d1f500d265..b19eb63232 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html @@ -153,12 +153,13 @@
      - -
      - The package and version chosen is already installed -
      + +
      @@ -334,7 +335,7 @@
      This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}}
      - +

      {{vm.installState.status}}

      diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index c86922cb37..439cc1aaab 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -212,7 +212,7 @@ Done - + Deleted %0% item Deleted %0% items Deleted %0% out of %1% item @@ -810,7 +810,7 @@ To manage your website, simply open the Umbraco back office and start adding con Package version Package version history View package website - The package and version chosen is already installed + Package already installed This package cannot be installed, it requires a minimum Umbraco version of %0% Uninstalling... Downloading... diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 5241465cc2..c2c3417e66 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -214,7 +214,7 @@ Done - + Deleted %0% item Deleted %0% items Deleted %0% out of %1% item @@ -809,7 +809,7 @@ To manage your website, simply open the Umbraco back office and start adding con Package version Package version history View package website - The package and version chosen is already installed + Package already installed This package cannot be installed, it requires a minimum Umbraco version of %0% Uninstalling... Downloading... From 7d0c92937a29007de0690eaef1e3c8fc7773d4c2 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 2 Aug 2016 12:11:35 +0200 Subject: [PATCH 099/413] U4-8754 Single content queries like Get(int id) for content, media and members needs to execute a TOP 1 (or LIMIT 1) updated a bunch of the remaining repositories to also use this pattern where applicable (repos using mappers aren't compatible without more work). --- .../Persistence/Repositories/ContentRepository.cs | 4 ++-- .../Persistence/Repositories/EntityContainerRepository.cs | 2 +- .../Persistence/Repositories/ExternalLoginRepository.cs | 6 +++--- .../Persistence/Repositories/MediaRepository.cs | 4 ++-- .../Persistence/Repositories/MemberGroupRepository.cs | 2 +- .../Persistence/Repositories/MemberRepository.cs | 4 ++-- .../Persistence/Repositories/MigrationEntryRepository.cs | 2 +- .../Persistence/Repositories/RedirectUrlRepository.cs | 2 +- .../Persistence/Repositories/RelationRepository.cs | 2 +- .../Persistence/Repositories/RelationTypeRepository.cs | 2 +- src/Umbraco.Core/Persistence/Repositories/TagRepository.cs | 3 +-- src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs | 2 +- .../Persistence/Repositories/TaskTypeRepository.cs | 2 +- .../Persistence/SqlSyntax/ISqlSyntaxProvider.cs | 2 ++ .../Persistence/SqlSyntax/MySqlSyntaxProvider.cs | 5 +++++ .../Persistence/SqlSyntax/SqlCeSyntaxProvider.cs | 5 +++++ .../Persistence/SqlSyntax/SqlServerSyntaxProvider.cs | 5 +++++ .../Persistence/SqlSyntax/SqlSyntaxProviderBase.cs | 2 ++ 18 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 4a3c374e5a..90442c5e7f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -63,9 +63,9 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate); + .OrderByDescending(x => x.VersionDate, SqlSyntax); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index 49d52ffffa..cc13275798 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false).Where(GetBaseWhereClause(), new { id = id, NodeObjectType = NodeObjectTypeId }); - var nodeDto = Database.Fetch(sql).FirstOrDefault(); + var nodeDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return nodeDto == null ? null : CreateEntity(nodeDto); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs index 276f4b0f89..7c80bd10b7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs @@ -58,12 +58,12 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var macroDto = Database.Fetch(sql).FirstOrDefault(); - if (macroDto == null) + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + if (dto == null) return null; var factory = new ExternalLoginFactory(); - var entity = factory.BuildEntity(macroDto); + var entity = factory.BuildEntity(dto); //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 54a8b4409f..603bef04c8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -52,9 +52,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 40121223a2..2c0e910428 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return dto == null ? null : _modelFactory.BuildEntity(dto); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index dfbcf61b7f..5253e81a05 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -52,9 +52,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs index 19df777666..b4a21f5d21 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.First(sql); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs index 3cde41fd18..cb80cdbbcf 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Repositories protected override IRedirectUrl PerformGet(int id) { var sql = GetBaseQuery(false).Where(x => x.Id == id); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return dto == null ? null : Map(dto); } diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs index 69e0307c09..be0808bb19 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.FirstOrDefault(sql); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs index 228674d829..df0ae0b224 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.FirstOrDefault(sql); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs index 8b76330dbe..68f5496101 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -26,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var tagDto = Database.Fetch(sql).FirstOrDefault(); + var tagDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (tagDto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs index f30f945851..c02362f8fd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var taskDto = Database.Fetch(sql).FirstOrDefault(); + var taskDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (taskDto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs index 564e949172..a970880669 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var taskDto = Database.Fetch(sql).FirstOrDefault(); + var taskDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (taskDto == null) return null; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index e61b23c8ec..366376b953 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; @@ -66,6 +67,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string Format(ForeignKeyDefinition foreignKey); string FormatColumnRename(string tableName, string oldName, string newName); string FormatTableRename(string oldName, string newName); + Sql SelectTop(Sql sql, int top); bool SupportsClustered(); bool SupportsIdentityInsert(); bool? SupportsCaseInsensitiveQueries(Database db); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 7167a5af95..5bd019c4f4 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -178,6 +178,11 @@ ORDER BY TABLE_NAME, INDEX_NAME", return result > 0; } + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(string.Concat(sql.SQL, " LIMIT ", top), sql.Arguments); + } + public override bool SupportsClustered() { return true; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 23e0a1bb87..383b310b51 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -18,6 +18,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax } + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); + } + public override bool SupportsClustered() { return false; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 9cb0ce327d..57bc14aebf 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -104,6 +104,11 @@ order by T.name, I.name"); return column.IsIdentity ? GetIdentityString(column) : string.Empty; } + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); + } + private static string GetIdentityString(ColumnDefinition column) { return "IDENTITY(1,1)"; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 50417761aa..a2baa8132d 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -503,6 +503,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax protected abstract string FormatIdentity(ColumnDefinition column); + public abstract Sql SelectTop(Sql sql, int top); + public virtual string DeleteDefaultConstraint { get From 080da771ffb7d222a2c14be531f4986cc17e3688 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 2 Aug 2016 14:36:18 +0200 Subject: [PATCH 100/413] U4-8457 Ensure default is updated from legacy inline setting to newer throw setting #U4-8457 Fixed --- src/Umbraco.Web.UI/config/umbracoSettings.Release.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 7aabc1ac63..4d229c7dcc 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -48,7 +48,7 @@ - throw - Throw an exception which can be caught by the global error handler defined in Application_OnError. If no such error handler is defined then you'll see the Yellow Screen Of Death (YSOD) error page. Note the error can also be handled by the umbraco.macro.Error event, where you can log/alarm with your own code and change the behaviour per event. --> - inline + throw ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,html,htm,svg,php,htaccess From 6788e8954edcc02b39e51717c967e4b5cb402e10 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 2 Aug 2016 14:36:39 +0200 Subject: [PATCH 101/413] U4-8509 woff2 mime type --- build/NuSpecs/tools/Web.config.install.xdt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index dfb9925840..987db6c3d6 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -292,6 +292,8 @@ + + From 2df8f550324f014cb1fa326f6d219e1c757a8bd7 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 2 Aug 2016 14:45:57 +0200 Subject: [PATCH 102/413] U4-8794 Allow Newtosoft JSON 9.0.1 to be installed #U4-8794 Fixed --- build/NuSpecs/UmbracoCms.Core.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 0cc09cd331..3a40527159 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -31,7 +31,7 @@ - + From e020779a147feb37893f0c859eb71a3e4974b8b9 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 2 Aug 2016 15:07:25 +0200 Subject: [PATCH 103/413] U4-8751 GetCropUrl method should have a parameter for specifying the image format Additional fix asking for InvariantContains instead of just Contains --- src/Umbraco.Web/ImageCropperTemplateExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index 0068f1547e..e518c5e246 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -302,11 +302,11 @@ namespace Umbraco.Web } } - bool hasFormat = furtherOptions != null && furtherOptions.Contains("&format="); + var hasFormat = furtherOptions != null && furtherOptions.InvariantContains("&format="); //Only put quality here, if we don't have a format specified. //Otherwise we need to put quality at the end to avoid it being overridden by the format. - if (quality != null && !hasFormat) + if (quality != null && hasFormat == false) { imageProcessorUrl.Append("&quality=" + quality); } From b9fcda8f395745de037fb386eec47f7dbb13e14c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 2 Aug 2016 17:01:32 +0200 Subject: [PATCH 104/413] U4-8361 301 Url Tracking Adds the ability to add certain headers to a PublishedContentRequest - these are internal for now until we're sure we want to expose them Adds response headers to tell browsers not to cache the 301 redirects so people can easily change their mind later --- .../Routing/ContentFinderByRedirectUrl.cs | 12 +- .../Routing/PublishedContentRequest.cs | 415 ++++++++++-------- src/Umbraco.Web/UmbracoModule.cs | 8 + 3 files changed, 244 insertions(+), 191 deletions(-) diff --git a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs index aa68922f21..c6af13e874 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs @@ -1,4 +1,6 @@ -using Umbraco.Core; +using System.Collections.Generic; +using System.Web; +using Umbraco.Core; using Umbraco.Core.Logging; namespace Umbraco.Web.Routing @@ -27,6 +29,14 @@ namespace Umbraco.Web.Routing var service = contentRequest.RoutingContext.UmbracoContext.Application.Services.RedirectUrlService; var redirectUrl = service.GetMostRecentRedirectUrl(route); + // From: http://stackoverflow.com/a/22468386/5018 + // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 + // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads + // to problems if you rename a page back to it's original name or create a new page with the original name + contentRequest.Cacheability = HttpCacheability.NoCache; + contentRequest.CacheExtensions = new List { "no-store, must-revalidate" }; + contentRequest.Headers = new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }; + if (redirectUrl == null) { LogHelper.Debug("No match for route: \"{0}\".", () => route); diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs index 1ed0cf8150..2081fd69b5 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; +using System.Web; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -14,14 +15,14 @@ using RenderingEngine = Umbraco.Core.RenderingEngine; namespace Umbraco.Web.Routing { - /// - /// Represents a request for one specified Umbraco IPublishedContent to be rendered - /// by one specified template, using one specified Culture and RenderingEngine. - /// - public class PublishedContentRequest - { - private bool _readonly; - private bool _readonlyUri; + /// + /// Represents a request for one specified Umbraco IPublishedContent to be rendered + /// by one specified template, using one specified Culture and RenderingEngine. + /// + public class PublishedContentRequest + { + private bool _readonly; + private bool _readonlyUri; /// /// Triggers before the published content request is prepared. @@ -31,65 +32,65 @@ namespace Umbraco.Web.Routing /// that might have been modified by an in-between equipement such as a load-balancer. public static event EventHandler Preparing; - /// - /// Triggers once the published content request has been prepared, but before it is processed. - /// - /// When the event triggers, preparation is done ie domain, culture, document, template, - /// rendering engine, etc. have been setup. It is then possible to change anything, before - /// the request is actually processed and rendered by Umbraco. - public static event EventHandler Prepared; + /// + /// Triggers once the published content request has been prepared, but before it is processed. + /// + /// When the event triggers, preparation is done ie domain, culture, document, template, + /// rendering engine, etc. have been setup. It is then possible to change anything, before + /// the request is actually processed and rendered by Umbraco. + public static event EventHandler Prepared; - // the engine that does all the processing - // because in order to keep things clean and separated, - // the content request is just a data holder - private readonly PublishedContentRequestEngine _engine; + // the engine that does all the processing + // because in order to keep things clean and separated, + // the content request is just a data holder + private readonly PublishedContentRequestEngine _engine; // the cleaned up uri // the cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. private Uri _uri; - /// - /// Initializes a new instance of the class with a specific Uri and routing context. - /// - /// The request Uri. - /// A routing context. + /// + /// Initializes a new instance of the class with a specific Uri and routing context. + /// + /// The request Uri. + /// A routing context. /// A callback method to return the roles for the provided login name when required /// - public PublishedContentRequest(Uri uri, RoutingContext routingContext, IWebRoutingSection routingConfig, Func> getRolesForLogin) - { + public PublishedContentRequest(Uri uri, RoutingContext routingContext, IWebRoutingSection routingConfig, Func> getRolesForLogin) + { if (uri == null) throw new ArgumentNullException("uri"); if (routingContext == null) throw new ArgumentNullException("routingContext"); Uri = uri; RoutingContext = routingContext; - GetRolesForLogin = getRolesForLogin; + GetRolesForLogin = getRolesForLogin; - _engine = new PublishedContentRequestEngine( + _engine = new PublishedContentRequestEngine( routingConfig, this); RenderingEngine = RenderingEngine.Unknown; - } + } [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use the constructor specifying all dependencies instead")] - public PublishedContentRequest(Uri uri, RoutingContext routingContext) + public PublishedContentRequest(Uri uri, RoutingContext routingContext) : this(uri, routingContext, UmbracoConfig.For.UmbracoSettings().WebRouting, s => Roles.Provider.GetRolesForUser(s)) - { - } + { + } - /// - /// Gets the engine associated to the request. - /// - internal PublishedContentRequestEngine Engine { get { return _engine; } } + /// + /// Gets the engine associated to the request. + /// + internal PublishedContentRequestEngine Engine { get { return _engine; } } - /// - /// Prepares the request. - /// + /// + /// Prepares the request. + /// public void Prepare() - { - _engine.PrepareRequest(); - } + { + _engine.PrepareRequest(); + } /// /// Called to configure the request @@ -104,56 +105,57 @@ namespace Umbraco.Web.Routing _engine.ConfigureRequest(); } - /// - /// Updates the request when there is no template to render the content. - /// - internal void UpdateOnMissingTemplate() - { + /// + /// Updates the request when there is no template to render the content. + /// + internal void UpdateOnMissingTemplate() + { var __readonly = _readonly; - _readonly = false; - _engine.UpdateRequestOnMissingTemplate(); - _readonly = __readonly; - } + _readonly = false; + _engine.UpdateRequestOnMissingTemplate(); + _readonly = __readonly; + } /// /// Triggers the Preparing event. /// internal void OnPreparing() - { - var handler = Preparing; + { + var handler = Preparing; if (handler != null) handler(this, EventArgs.Empty); - _readonlyUri = true; - } + _readonlyUri = true; + } - /// - /// Triggers the Prepared event. - /// - internal void OnPrepared() - { + /// + /// Triggers the Prepared event. + /// + internal void OnPrepared() + { var handler = Prepared; if (handler != null) handler(this, EventArgs.Empty); - if (HasPublishedContent == false) + if (HasPublishedContent == false) Is404 = true; // safety - _readonly = true; - } + _readonly = true; + } - /// - /// Gets or sets the cleaned up Uri used for routing. - /// - /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - public Uri Uri { - get - { - return _uri; - } - set - { + /// + /// Gets or sets the cleaned up Uri used for routing. + /// + /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. + public Uri Uri + { + get + { + return _uri; + } + set + { if (_readonlyUri) throw new InvalidOperationException("Cannot modify Uri after Preparing has triggered."); - _uri = value; - } + _uri = value; + } } private void EnsureWriteable() @@ -162,37 +164,37 @@ namespace Umbraco.Web.Routing throw new InvalidOperationException("Cannot modify a PublishedContentRequest once it is read-only."); } - #region PublishedContent + #region PublishedContent - /// - /// The requested IPublishedContent, if any, else null. - /// - private IPublishedContent _publishedContent; + /// + /// The requested IPublishedContent, if any, else null. + /// + private IPublishedContent _publishedContent; - /// - /// The initial requested IPublishedContent, if any, else null. - /// - /// The initial requested content is the content that was found by the finders, - /// before anything such as 404, redirect... took place. - private IPublishedContent _initialPublishedContent; + /// + /// The initial requested IPublishedContent, if any, else null. + /// + /// The initial requested content is the content that was found by the finders, + /// before anything such as 404, redirect... took place. + private IPublishedContent _initialPublishedContent; - /// - /// Gets or sets the requested content. - /// - /// Setting the requested content clears Template. - public IPublishedContent PublishedContent - { - get { return _publishedContent; } - set - { + /// + /// Gets or sets the requested content. + /// + /// Setting the requested content clears Template. + public IPublishedContent PublishedContent + { + get { return _publishedContent; } + set + { EnsureWriteable(); - _publishedContent = value; + _publishedContent = value; IsInternalRedirectPublishedContent = false; - TemplateModel = null; - } - } + TemplateModel = null; + } + } - /// + /// /// Sets the requested content, following an internal redirect. /// /// The requested content. @@ -200,8 +202,8 @@ namespace Umbraco.Web.Routing /// preserve or reset the template, if any. public void SetInternalRedirectPublishedContent(IPublishedContent content) { - if (content == null) throw new ArgumentNullException("content"); - EnsureWriteable(); + if (content == null) throw new ArgumentNullException("content"); + EnsureWriteable(); // unless a template has been set already by the finder, // template should be null at that point. @@ -225,7 +227,7 @@ namespace Umbraco.Web.Routing // set published content - this resets the template, and sets IsInternalRedirect to false PublishedContent = content; - IsInternalRedirectPublishedContent = isInternalRedirect; + IsInternalRedirectPublishedContent = isInternalRedirect; // must restore the template if it's an internal redirect & the config option is set if (isInternalRedirect && UmbracoConfig.For.UmbracoSettings().WebRouting.InternalRedirectPreservesTemplate) @@ -243,16 +245,16 @@ namespace Umbraco.Web.Routing /// before anything such as 404, redirect... took place. public IPublishedContent InitialPublishedContent { get { return _initialPublishedContent; } } - /// - /// Gets value indicating whether the current published content is the initial one. - /// - public bool IsInitialPublishedContent - { - get - { - return _initialPublishedContent != null && _initialPublishedContent == _publishedContent; - } - } + /// + /// Gets value indicating whether the current published content is the initial one. + /// + public bool IsInitialPublishedContent + { + get + { + return _initialPublishedContent != null && _initialPublishedContent == _publishedContent; + } + } /// /// Indicates that the current PublishedContent is the initial one. @@ -282,19 +284,19 @@ namespace Umbraco.Web.Routing get { return PublishedContent != null; } } - #endregion + #endregion - #region Template + #region Template /// /// The template model, if any, else null. /// private ITemplate _template; - /// + /// /// Gets or sets the template model to use to display the requested content. /// - internal ITemplate TemplateModel + internal ITemplate TemplateModel { get { @@ -316,9 +318,9 @@ namespace Umbraco.Web.Routing /// public string TemplateAlias { - get - { - return _template == null ? null : _template.Alias; + get + { + return _template == null ? null : _template.Alias; } } @@ -348,7 +350,7 @@ namespace Umbraco.Web.Routing if (model == null) return false; - TemplateModel = model; + TemplateModel = model; return true; } @@ -368,10 +370,10 @@ namespace Umbraco.Web.Routing /// /// The RenderingEngine becomes unknown. public void ResetTemplate() - { - EnsureWriteable(); - TemplateModel = null; - } + { + EnsureWriteable(); + TemplateModel = null; + } /// /// Gets a value indicating whether the content request has a template. @@ -381,15 +383,15 @@ namespace Umbraco.Web.Routing get { return _template != null; } } - #endregion + #endregion - #region Domain and Culture + #region Domain and Culture - [Obsolete("Do not use this property, use the non-legacy UmbracoDomain property instead")] - public Domain Domain - { - get { return new Domain(UmbracoDomain); } - } + [Obsolete("Do not use this property, use the non-legacy UmbracoDomain property instead")] + public Domain Domain + { + get { return new Domain(UmbracoDomain); } + } //TODO: Should we publicize the setter now that we are using a non-legacy entity?? /// @@ -397,85 +399,85 @@ namespace Umbraco.Web.Routing /// public IDomain UmbracoDomain { get; internal set; } - /// - /// Gets or sets the content request's domain Uri. - /// - /// The Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". - public Uri DomainUri { get; internal set; } + /// + /// Gets or sets the content request's domain Uri. + /// + /// The Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". + public Uri DomainUri { get; internal set; } - /// - /// Gets a value indicating whether the content request has a domain. - /// - public bool HasDomain - { - get { return UmbracoDomain != null; } - } + /// + /// Gets a value indicating whether the content request has a domain. + /// + public bool HasDomain + { + get { return UmbracoDomain != null; } + } - private CultureInfo _culture; + private CultureInfo _culture; - /// - /// Gets or sets the content request's culture. - /// - public CultureInfo Culture - { + /// + /// Gets or sets the content request's culture. + /// + public CultureInfo Culture + { get { return _culture; } set { EnsureWriteable(); _culture = value; } - } + } - // note: do we want to have an ordered list of alternate cultures, + // note: do we want to have an ordered list of alternate cultures, // to allow for fallbacks when doing dictionnary lookup and such? - #endregion + #endregion - #region Rendering + #region Rendering - /// - /// Gets or sets whether the rendering engine is MVC or WebForms. - /// - public RenderingEngine RenderingEngine { get; internal set; } + /// + /// Gets or sets whether the rendering engine is MVC or WebForms. + /// + public RenderingEngine RenderingEngine { get; internal set; } - #endregion + #endregion - /// - /// Gets or sets the current RoutingContext. - /// - public RoutingContext RoutingContext { get; private set; } + /// + /// Gets or sets the current RoutingContext. + /// + public RoutingContext RoutingContext { get; private set; } - internal Func> GetRolesForLogin { get; private set; } + internal Func> GetRolesForLogin { get; private set; } - /// - /// The "umbraco page" object. - /// - private page _umbracoPage; + /// + /// The "umbraco page" object. + /// + private page _umbracoPage; - /// - /// Gets or sets the "umbraco page" object. - /// - /// - /// This value is only used for legacy/webforms code. - /// - internal page UmbracoPage - { - get - { - if (_umbracoPage == null) - throw new InvalidOperationException("The UmbracoPage object has not been initialized yet."); + /// + /// Gets or sets the "umbraco page" object. + /// + /// + /// This value is only used for legacy/webforms code. + /// + internal page UmbracoPage + { + get + { + if (_umbracoPage == null) + throw new InvalidOperationException("The UmbracoPage object has not been initialized yet."); - return _umbracoPage; - } - set { _umbracoPage = value; } - } + return _umbracoPage; + } + set { _umbracoPage = value; } + } - #region Status + #region Status - /// + /// /// Gets or sets a value indicating whether the requested content could not be found. /// - /// This is set in the PublishedContentRequestBuilder. + /// This is set in the PublishedContentRequestBuilder. public bool Is404 { get; internal set; } /// @@ -581,7 +583,40 @@ namespace Umbraco.Web.Routing ResponseStatusCode = code; ResponseStatusDescription = description; } - - #endregion + + #endregion + + /// + /// Gets or sets the System.Web.HttpCacheability + /// + /// Is set to System.Web.HttpCacheability.Private by default, which is the ASP.NET default. + private HttpCacheability _cacheability = HttpCacheability.Private; + internal HttpCacheability Cacheability + { + get { return _cacheability; } + set { _cacheability = value; } + } + + /// + /// Gets or sets a list of Extensions to append to the Response.Cache object + /// + private List _cacheExtensions = new List(); + internal List CacheExtensions + { + get { return _cacheExtensions; } + set { _cacheExtensions = value; } + } + + /// + /// Gets or sets a dictionary of Headers to append to the Response object + /// + private Dictionary _headers = new Dictionary(); + internal Dictionary Headers + { + get { return _headers; } + set { _headers = value; } + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 762a5f3e67..517fd112c9 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -323,6 +323,14 @@ namespace Umbraco.Web LogHelper.Debug("Response status: Redirect={0}, Is404={1}, StatusCode={2}", () => pcr.IsRedirect ? (pcr.IsRedirectPermanent ? "permanent" : "redirect") : "none", () => pcr.Is404 ? "true" : "false", () => pcr.ResponseStatusCode); + + response.Cache.SetCacheability(pcr.Cacheability); + + foreach (var cacheExtension in pcr.CacheExtensions) + response.Cache.AppendCacheExtension(cacheExtension); + + foreach (var header in pcr.Headers) + response.Cache.AppendCacheExtension(string.Format("{0}, {1}", header.Key, header.Value)); if (pcr.IsRedirect) { From c8d7a62a47a7392823701c08411903dce6a3021c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 3 Aug 2016 09:53:55 +0200 Subject: [PATCH 105/413] Fixes: U4-8747 Actions menu button not aligned with heading input --- .../src/less/components/editor.less | 3 +++ .../components/editor/umb-editor-header.html | 26 ++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index bc44156cc3..dba5e29e74 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -21,6 +21,9 @@ position: relative; } +.umb-editor-header__actions-menu { + margin-left: auto; +} .umb-editor-container { position: relative; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html index 57a3ec4754..bad013720e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html @@ -31,10 +31,10 @@
      {{ name }}
      - @@ -48,14 +48,26 @@
      + + +
      - - + +
      - + +
      From b4ea03cdf3ed4e11828a5615a6b43e99084af1c2 Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 3 Aug 2016 12:43:46 +0200 Subject: [PATCH 106/413] U4-8779 Sorting not working in Members List View in 7.5 Beta2 --- .../Repositories/VersionableRepositoryBase.cs | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index e062f49235..58ea8a5dd7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -236,7 +236,7 @@ namespace Umbraco.Core.Persistence.Repositories private Sql GetFilteredSqlForPagedResults(Sql sql, Func> defaultFilter = null) { Sql filteredSql; - + // Apply filter if (defaultFilter != null) { @@ -262,7 +262,7 @@ namespace Umbraco.Core.Persistence.Repositories return filteredSql; } - private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField) + private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField, Tuple nodeIdSelect) { //copy to var so that the original isn't changed @@ -286,30 +286,47 @@ namespace Umbraco.Core.Persistence.Repositories } else { - // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie + // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through value // from most recent content version for the given order by field var sortedInt = string.Format(SqlSyntax.ConvertIntegerToOrderableString, "dataInt"); var sortedDate = string.Format(SqlSyntax.ConvertDateToOrderableString, "dataDate"); var sortedString = string.Format("COALESCE({0},'')", "dataNvarchar"); var sortedDecimal = string.Format(SqlSyntax.ConvertDecimalToOrderableString, "dataDecimal"); - var innerJoinTempTable = string.Format(@"INNER JOIN ( - SELECT CASE - WHEN dataInt Is Not Null THEN {0} - WHEN dataDecimal Is Not Null THEN {1} - WHEN dataDate Is Not Null THEN {2} - ELSE {3} - END AS CustomPropVal, - cd.nodeId AS CustomPropValContentId - FROM cmsDocument cd - INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = cd.nodeId AND cpd.versionId = cd.versionId - INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId - WHERE cpt.Alias = @{4} AND cd.newest = 1) AS CustomPropData - ON CustomPropData.CustomPropValContentId = umbracoNode.id - ", sortedInt, sortedDecimal, sortedDate, sortedString, sortedSql.Arguments.Length); + //these are defaults that will be used in the query - they can be overridden for non-versioned entities or document entities + var versionQuery = " AND cpd.versionId = cd.versionId"; + var newestQuery = string.Empty; + + //cmsDocument needs to filter by the 'newest' parameter in the query + if (nodeIdSelect.Item1 == "cmsDocument") + newestQuery = " AND cd.newest = 1"; + + //members do not use versions so clear the versionQuery string + if (nodeIdSelect.Item1 == "cmsMember") + versionQuery = string.Empty; + + var innerJoinTempTable = string.Format(@"INNER JOIN ( + SELECT CASE + WHEN dataInt Is Not Null THEN {0} + WHEN dataDecimal Is Not Null THEN {1} + WHEN dataDate Is Not Null THEN {2} + ELSE {3} + END AS CustomPropVal, + cd.{4} AS CustomPropValContentId + FROM {5} cd + INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = cd.{4}{6} + INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId + WHERE cpt.Alias = @{7}{8}) AS CustomPropData + ON CustomPropData.CustomPropValContentId = umbracoNode.id + ", sortedInt, sortedDecimal, sortedDate, sortedString, nodeIdSelect.Item2, nodeIdSelect.Item1, versionQuery, sortedSql.Arguments.Length, newestQuery); + + //insert this just above the first LEFT OUTER JOIN (for cmsDocument) or the last WHERE (everything else) + string newSql; + if (nodeIdSelect.Item1 == "cmsDocument") + newSql = sortedSql.SQL.Insert(sortedSql.SQL.IndexOf("LEFT OUTER JOIN"), innerJoinTempTable); + else + newSql = sortedSql.SQL.Insert(sortedSql.SQL.LastIndexOf("WHERE"), innerJoinTempTable); - //insert this just above the LEFT OUTER JOIN - var newSql = sortedSql.SQL.Insert(sortedSql.SQL.IndexOf("LEFT OUTER JOIN"), innerJoinTempTable); var newArgs = sortedSql.Arguments.ToList(); newArgs.Add(orderBy); @@ -319,11 +336,11 @@ namespace Umbraco.Core.Persistence.Repositories if (orderDirection == Direction.Descending) { sortedSql.Append(" DESC"); - } + } } return sortedSql; - + } /// @@ -371,7 +388,7 @@ namespace Umbraco.Core.Persistence.Repositories //get sorted and filtered sql var sqlNodeIdsWithSort = GetSortedSqlForPagedResults( GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter), - orderDirection, orderBy, orderBySystemField); + orderDirection, orderBy, orderBySystemField, nodeIdSelect); // Get page of results and total count IEnumerable result; @@ -409,7 +426,7 @@ namespace Umbraco.Core.Persistence.Repositories //get sorted and filtered sql var fullQuery = GetSortedSqlForPagedResults( GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), - orderDirection, orderBy, orderBySystemField); + orderDirection, orderBy, orderBySystemField, nodeIdSelect); return processQuery(fullQuery); } else From 67cd4aeb59aabea4adde6826c617a7a4c3fc2d91 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Aug 2016 14:04:25 +0200 Subject: [PATCH 107/413] fixes user mgr logic --- src/Umbraco.Core/Security/BackOfficeUserManager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs index d9c96e5967..683b68fe56 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -26,9 +26,8 @@ namespace Umbraco.Core.Security MembershipProviderBase membershipProvider) : base(store) { - if (options == null) throw new ArgumentNullException("options"); - var manager = new BackOfficeUserManager(store); - InitUserManager(manager, membershipProvider, options); + if (options == null) throw new ArgumentNullException("options");; + InitUserManager(this, membershipProvider, options); } #region Static Create methods From a5f3b50699c5eaf5a3a50609e143b1d7b4990c26 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 3 Aug 2016 17:52:27 +0200 Subject: [PATCH 108/413] Needed to be fixed, just like for the ascending OrderBy - see U4-4474 --- src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs index cecb0e2982..04f4147155 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs @@ -77,7 +77,8 @@ namespace Umbraco.Core.Persistence var type = typeof(TColumn); var tableName = type.GetTableName(); - var syntax = string.Format("{0}.{1} DESC", + //need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 + var syntax = string.Format("({0}.{1}) DESC", sqlSyntax.GetQuotedTableName(tableName), sqlSyntax.GetQuotedColumnName(columnName)); From 11cafa2c4c59f333f968724064503790a68d7330 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 3 Aug 2016 17:54:21 +0200 Subject: [PATCH 109/413] Adds a setting to be able to disable redirect URL tracking --- .../UmbracoSettings/IWebRoutingSection.cs | 2 + .../UmbracoSettings/WebRoutingElement.cs | 6 +++ .../WebRoutingElementDefaultTests.cs | 6 +++ .../Redirects/RedirectTrackingEventHandler.cs | 40 +++++++++++-------- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs index 2998fc2f78..9eb6d02aa7 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs @@ -9,6 +9,8 @@ bool DisableAlternativeTemplates { get; } bool DisableFindContentByIdPath { get; } + + bool DisableRedirectUrlTracking { get; } string UrlProviderMode { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs index 1ed9bc034c..82f5d46b28 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs @@ -27,6 +27,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (bool) base["disableFindContentByIdPath"]; } } + [ConfigurationProperty("disableRedirectUrlTracking", DefaultValue = "false")] + public bool DisableRedirectUrlTracking + { + get { return (bool) base["disableRedirectUrlTracking"]; } + } + [ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")] public string UrlProviderMode { diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs index 47b5e15b69..1e568c608e 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs @@ -28,5 +28,11 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings { Assert.IsTrue(SettingsSection.WebRouting.DisableFindContentByIdPath == false); } + + [Test] + public void DisableRedirectUrlTracking() + { + Assert.IsTrue(SettingsSection.WebRouting.DisableRedirectUrlTracking == false); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs index 42c5e6093b..82c06ca7d5 100644 --- a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs +++ b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs @@ -8,6 +8,7 @@ using Umbraco.Web.Routing; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Cache; @@ -30,24 +31,31 @@ namespace Umbraco.Web.Redirects /// protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { - // if any of these dlls are loaded we don't want to run our finder - var dlls = new[] + if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableRedirectUrlTracking) { - "InfoCaster.Umbraco.UrlTracker", - "SEOChecker", - "Simple301", - "Terabyte.Umbraco.Modules.PermanentRedirect", - "CMUmbracoTools", - "PWUrlRedirect" - }; - - // assuming all assemblies have been loaded already - // check if any of them matches one of the above dlls - var found = AppDomain.CurrentDomain.GetAssemblies() - .Select(x => x.FullName.Split(',')[0]) - .Any(x => dlls.Contains(x)); - if (found) ContentFinderResolver.Current.RemoveType(); + } + else + { + // if any of these dlls are loaded we don't want to run our finder + var dlls = new[] + { + "InfoCaster.Umbraco.UrlTracker", + "SEOChecker", + "Simple301", + "Terabyte.Umbraco.Modules.PermanentRedirect", + "CMUmbracoTools", + "PWUrlRedirect" + }; + + // assuming all assemblies have been loaded already + // check if any of them matches one of the above dlls + var found = AppDomain.CurrentDomain.GetAssemblies() + .Select(x => x.FullName.Split(',')[0]) + .Any(x => dlls.Contains(x)); + if (found) + ContentFinderResolver.Current.RemoveType(); + } } /// From b4d5140d077c567e87423725e45cb0282d1c0b43 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 3 Aug 2016 17:57:01 +0200 Subject: [PATCH 110/413] WIP Starting with taking Marc's code to get a rudimentary dashboard for the 301 URL tracker --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 6 +- src/Umbraco.Web.UI/config/Dashboard.config | 10 +++ .../RedirectUrlManagementController.cs | 65 +++++++++++++++++++ .../Redirects/RedirectUrlSearchResults.cs | 16 +++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + 5 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs create mode 100644 src/Umbraco.Web/Redirects/RedirectUrlSearchResults.cs diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index da577d4ad0..f2be24e528 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -40,8 +40,7 @@ v4.5 true - - + 44319 @@ -579,6 +578,8 @@ + + @@ -638,6 +639,7 @@ + 404handlers.config diff --git a/src/Umbraco.Web.UI/config/Dashboard.config b/src/Umbraco.Web.UI/config/Dashboard.config index df45708e0f..cc20f70654 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.config +++ b/src/Umbraco.Web.UI/config/Dashboard.config @@ -109,4 +109,14 @@ +
      + + developer + + + + /App_Plugins/RedirectUrlDashboard/redirecturlsearch.html + + +
      diff --git a/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs b/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs new file mode 100644 index 0000000000..27791136e1 --- /dev/null +++ b/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs @@ -0,0 +1,65 @@ +using System.Net.Http; +using System.Text; +using System.Web.Http; +using Umbraco.Web.WebApi; + +namespace Umbraco.Web.Redirects +{ + public class RedirectUrlManagementController : UmbracoAuthorizedApiController + { + //add paging + [HttpGet] + public RedirectUrlSearchResult SearchRedirectUrls(string searchTerm, int page = 0) + { + + int pageSize = 20; + var searchResult = new RedirectUrlSearchResult(); + var redirectUrlService = Services.RedirectUrlService; + long resultCount = 0L; + // need endpoint for search functionality + // by url, by domain ? it's the url that you want to find them by, that's what you see.. + + var redirects = redirectUrlService.GetAllRedirectUrls(page, 20, out resultCount); + searchResult.SearchResults = redirects; + searchResult.TotalCount = resultCount; + searchResult.CurrentPage = page; + //hmm how many results 'could there be ? + searchResult.PageCount = ((int)resultCount + pageSize - 1) / pageSize; + + searchResult.HasSearchResults = resultCount > 0; + searchResult.HasExactMatch = (resultCount == 1); + return searchResult; + + } + + [HttpGet] + public HttpResponseMessage GetPublishedUrl(int id) + { + var publishedUrl = "#"; + if (id > 0) + { + publishedUrl = Umbraco.Url(id); + } + + return new HttpResponseMessage { Content = new StringContent(publishedUrl, Encoding.UTF8, "text/html") }; + + } + + [HttpPost] + public HttpResponseMessage DeleteRedirectUrl(int id) + { + + var redirectUrlService = Services.RedirectUrlService; + // has the redirect already been deleted ? + //var redirectUrl = redirectUrlService.GetById(redirectUrl.Id); + //if (redirectUrl== null) + //{ + // return new HttpResponseMessage(System.Net.HttpStatusCode.NotFound); + //} + redirectUrlService.Delete(id); + return new HttpResponseMessage(System.Net.HttpStatusCode.OK); + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Redirects/RedirectUrlSearchResults.cs b/src/Umbraco.Web/Redirects/RedirectUrlSearchResults.cs new file mode 100644 index 0000000000..1dac259a88 --- /dev/null +++ b/src/Umbraco.Web/Redirects/RedirectUrlSearchResults.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Redirects +{ + public class RedirectUrlSearchResult + { + public string StatusMessage { get; set; } + public IEnumerable SearchResults { get; set; } + public bool HasSearchResults { get; set; } + public bool HasExactMatch { get; set; } + public long TotalCount { get; set; } + public int PageCount { get; set; } + public int CurrentPage { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d1cab1b52d..78b88577a4 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -391,6 +391,8 @@ + + From c53a04d6585dc5288832342667e05643858e656e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 3 Aug 2016 17:52:27 +0200 Subject: [PATCH 111/413] Needed to be fixed, just like for the ascending OrderBy - see U4-4474 --- src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs index cecb0e2982..04f4147155 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs @@ -77,7 +77,8 @@ namespace Umbraco.Core.Persistence var type = typeof(TColumn); var tableName = type.GetTableName(); - var syntax = string.Format("{0}.{1} DESC", + //need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 + var syntax = string.Format("({0}.{1}) DESC", sqlSyntax.GetQuotedTableName(tableName), sqlSyntax.GetQuotedColumnName(columnName)); From 85a3abf6431cd2852bd9c7dab315697614f876bb Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 3 Aug 2016 17:54:21 +0200 Subject: [PATCH 112/413] Adds a setting to be able to disable redirect URL tracking --- .../UmbracoSettings/IWebRoutingSection.cs | 2 + .../UmbracoSettings/WebRoutingElement.cs | 6 +++ .../WebRoutingElementDefaultTests.cs | 6 +++ .../Redirects/RedirectTrackingEventHandler.cs | 40 +++++++++++-------- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs index 2998fc2f78..9eb6d02aa7 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs @@ -9,6 +9,8 @@ bool DisableAlternativeTemplates { get; } bool DisableFindContentByIdPath { get; } + + bool DisableRedirectUrlTracking { get; } string UrlProviderMode { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs index 1ed9bc034c..82f5d46b28 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs @@ -27,6 +27,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (bool) base["disableFindContentByIdPath"]; } } + [ConfigurationProperty("disableRedirectUrlTracking", DefaultValue = "false")] + public bool DisableRedirectUrlTracking + { + get { return (bool) base["disableRedirectUrlTracking"]; } + } + [ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")] public string UrlProviderMode { diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs index 47b5e15b69..1e568c608e 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs @@ -28,5 +28,11 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings { Assert.IsTrue(SettingsSection.WebRouting.DisableFindContentByIdPath == false); } + + [Test] + public void DisableRedirectUrlTracking() + { + Assert.IsTrue(SettingsSection.WebRouting.DisableRedirectUrlTracking == false); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs index 42c5e6093b..82c06ca7d5 100644 --- a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs +++ b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs @@ -8,6 +8,7 @@ using Umbraco.Web.Routing; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Cache; @@ -30,24 +31,31 @@ namespace Umbraco.Web.Redirects /// protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { - // if any of these dlls are loaded we don't want to run our finder - var dlls = new[] + if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableRedirectUrlTracking) { - "InfoCaster.Umbraco.UrlTracker", - "SEOChecker", - "Simple301", - "Terabyte.Umbraco.Modules.PermanentRedirect", - "CMUmbracoTools", - "PWUrlRedirect" - }; - - // assuming all assemblies have been loaded already - // check if any of them matches one of the above dlls - var found = AppDomain.CurrentDomain.GetAssemblies() - .Select(x => x.FullName.Split(',')[0]) - .Any(x => dlls.Contains(x)); - if (found) ContentFinderResolver.Current.RemoveType(); + } + else + { + // if any of these dlls are loaded we don't want to run our finder + var dlls = new[] + { + "InfoCaster.Umbraco.UrlTracker", + "SEOChecker", + "Simple301", + "Terabyte.Umbraco.Modules.PermanentRedirect", + "CMUmbracoTools", + "PWUrlRedirect" + }; + + // assuming all assemblies have been loaded already + // check if any of them matches one of the above dlls + var found = AppDomain.CurrentDomain.GetAssemblies() + .Select(x => x.FullName.Split(',')[0]) + .Any(x => dlls.Contains(x)); + if (found) + ContentFinderResolver.Current.RemoveType(); + } } /// From 77cbdcf38f46eae183784e64f3cb81c6ab48c94a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 3 Aug 2016 19:05:04 +0200 Subject: [PATCH 113/413] js files are ignored by default, need to force add them if we really want them --- .../redirecturlsearch.controller.js | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js diff --git a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js new file mode 100644 index 0000000000..a3f809187b --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js @@ -0,0 +1,106 @@ +angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($scope, $http, angularHelper, notificationsService, entityResource, $routeParams) { + + //...todo + //search by url or url part + //search by domain + //display domain in dashboard results? + + $scope.isSearch = false; + $scope.hasResults = false; + + $scope.pagination = []; + $scope.isNew = false; + $scope.actionInProgress = false; + $scope.listViewResultSet = { + totalPages: 0, + items: [] + }; + + $scope.options = { + pageSize: 10, + pageNumber: ($routeParams.page && Number($routeParams.page) !== NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1 + }; + + $scope.next = function () { + if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) { + $scope.options.pageNumber++; + $scope.reloadView(); + } + }; + + $scope.goToPage = function (pageNumber) { + $scope.options.pageNumber = pageNumber + 1; + $scope.reloadView(); + }; + + $scope.prev = function () { + if ($scope.options.pageNumber > 1) { + $scope.options.pageNumber--; + $scope.reloadView(); + } + }; + + $scope.search = function () { + + //do we want to search by url and by domain... + + var searchTerm = $scope.searchTerm; + + $http.get("backoffice/api/RedirectUrlManagement/SearchRedirectUrls/?searchTerm=" + searchTerm + "&page=" + $scope.options.pageNumber + "&pageSize=" + $scope.options.pageSize).then(function (response) { + var matchingItems = response.data; + $scope.isSearch = true; + $scope.StatusMessage = matchingItems.StatusMessage; + $scope.hasResults = matchingItems.HasSearchResults; + $scope.redirectUrls = matchingItems.SearchResults; + angular.forEach($scope.redirectUrls, function (item) { + $http.get("backoffice/api/RedirectUrlManagement/GetPublishedUrl/?id=" + item.ContentId).then(function (response) { + item.ContentUrl = response.data; + }); + }); + }); + }; + + $scope.load = function () { + // $scope.searchTerm = ""; + $scope.search(); + }; + + $scope.removeRedirect = function (redirectToDelete) { + $http.post("backoffice/api/RedirectUrlManagement/DeleteRedirectUrl/" + redirectToDelete.Id).then(function (response) { + if (response.status === 200) { + notificationsService.success('Redirect Url Removed!', 'Redirect Url ' + redirectToDelete.Url + ' has been deleted'); + // now remove from table client sides + var index = -1; + var urlArr = eval($scope.redirectUrls); + for (var i = 0; i < urlArr.length; i++) { + if (urlArr[i].Id === redirectToDelete.Id) { + index = i; + break; + } + } + if (index === -1) { + notificationsService.warning('Redirect Url Removal Error!', 'Redirect Url ' + redirectToDelete.Url + ' may have already been removed'); + } + $scope.redirectUrls.splice(index, 1); + } + else { + notificationsService.warning('Redirect Url Error!','Redirect Url ' + redirectToDelete.Url + ' was not deleted'); + } + }); + }; + + $scope.disableUrlTracker = function() { + var disable = confirm("Are you sure you want to disable the URL tracker completely?"); + if (disable) { + $http.post("backoffice/api/RedirectUrlManagement/DisableUrlTracker/").then(function(response) { + if (response.status === 200) { + notificationsService.success("URL Tracker has now been diabled."); + } else { + notificationsService.warning("Error diabling the URL Tracker, more information can be found in your log file."); + } + }); + } + }; + + $scope.load(); +}); \ No newline at end of file From 6237d4a3e5e197705be33d5568eb8ae4d92323ee Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 3 Aug 2016 20:01:43 +0200 Subject: [PATCH 114/413] Adds a toggle to disable/enable the URL tracker Adds paging, adapted from https://github.com/robertjf/umbMemberListView/blob/master/MemberListView/App_Plugins/MemberManager/backoffice/dashboard/memberListView.controller.js --- .../redirecturlsearch.controller.js | 89 +++++++++++++++---- .../ImageProcessorValidation.cs | 54 +++++++++++ .../RedirectUrlManagementController.cs | 40 +++++++-- .../Redirects/RedirectUrlSearchResults.cs | 2 + 4 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 src/Umbraco.Web.UI/ImageProcessorValidation.cs diff --git a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js index a3f809187b..76101ab5c7 100644 --- a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js +++ b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js @@ -1,5 +1,4 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($scope, $http, angularHelper, notificationsService, entityResource, $routeParams) { - //...todo //search by url or url part //search by domain @@ -17,26 +16,26 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco }; $scope.options = { - pageSize: 10, + pageSize: 20, pageNumber: ($routeParams.page && Number($routeParams.page) !== NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1 }; $scope.next = function () { - if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) { + if ($scope.options.pageNumber < $scope.pageCount) { $scope.options.pageNumber++; - $scope.reloadView(); + $scope.load(); } }; $scope.goToPage = function (pageNumber) { $scope.options.pageNumber = pageNumber + 1; - $scope.reloadView(); + $scope.load(); }; $scope.prev = function () { if ($scope.options.pageNumber > 1) { $scope.options.pageNumber--; - $scope.reloadView(); + $scope.load(); } }; @@ -52,6 +51,62 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco $scope.StatusMessage = matchingItems.StatusMessage; $scope.hasResults = matchingItems.HasSearchResults; $scope.redirectUrls = matchingItems.SearchResults; + + $scope.urlTrackerDisabled = matchingItems.UrlTrackerDisabled; + $scope.action = ""; + if ($scope.urlTrackerDisabled !== true) { + $scope.action = "Disable"; + } else { + $scope.action = "Enable"; + } + + $scope.pageCount = matchingItems.PageCount; + $scope.totalCount = matchingItems.TotalCount; + $scope.options.pageNumber = matchingItems.CurrentPage; + + if ($scope.options.pageNumber > $scope.pageCount) { + $scope.options.pageNumber = $scope.pageCount; + } + + $scope.pagination = []; + + var i; + if ($scope.pageCount <= 10) { + for (i = 0; i < $scope.pageCount; i++) { + $scope.pagination.push({ + val: (i + 1), + isActive: $scope.options.pageNumber === (i + 1) + }); + } + } + else { + //if there is more than 10 pages, we need to do some fancy bits + + //get the max index to start + var maxIndex = $scope.pageCount - 10; + //set the start, but it can't be below zero + var start = Math.max($scope.options.pageNumber - 5, 0); + //ensure that it's not too far either + start = Math.min(maxIndex, start); + + for (i = start; i < (10 + start) ; i++) { + $scope.pagination.push({ + val: (i + 1), + isActive: $scope.options.pageNumber === (i + 1) + }); + } + + //now, if the start is greater than 0 then '1' will not be displayed, so do the elipses thing + if (start > 0) { + $scope.pagination.unshift({ name: "First", val: 1, isActive: false }, { val: "...", isActive: false }); + } + + //same for the end + if (start < maxIndex) { + $scope.pagination.push({ val: "...", isActive: false }, { name: "Last", val: $scope.pageCount, isActive: false }); + } + } + angular.forEach($scope.redirectUrls, function (item) { $http.get("backoffice/api/RedirectUrlManagement/GetPublishedUrl/?id=" + item.ContentId).then(function (response) { item.ContentUrl = response.data; @@ -68,7 +123,7 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco $scope.removeRedirect = function (redirectToDelete) { $http.post("backoffice/api/RedirectUrlManagement/DeleteRedirectUrl/" + redirectToDelete.Id).then(function (response) { if (response.status === 200) { - notificationsService.success('Redirect Url Removed!', 'Redirect Url ' + redirectToDelete.Url + ' has been deleted'); + notificationsService.success("Redirect Url Removed!", "Redirect Url " + redirectToDelete.Url + " has been deleted"); // now remove from table client sides var index = -1; var urlArr = eval($scope.redirectUrls); @@ -79,26 +134,28 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco } } if (index === -1) { - notificationsService.warning('Redirect Url Removal Error!', 'Redirect Url ' + redirectToDelete.Url + ' may have already been removed'); + notificationsService.warning("Redirect Url Removal Error!", "Redirect Url " + redirectToDelete.Url + " may have already been removed"); } $scope.redirectUrls.splice(index, 1); } else { - notificationsService.warning('Redirect Url Error!','Redirect Url ' + redirectToDelete.Url + ' was not deleted'); + notificationsService.warning("Redirect Url Error!", "Redirect Url " + redirectToDelete.Url + " was not deleted"); } }); }; - $scope.disableUrlTracker = function() { - var disable = confirm("Are you sure you want to disable the URL tracker completely?"); - if (disable) { - $http.post("backoffice/api/RedirectUrlManagement/DisableUrlTracker/").then(function(response) { + $scope.toggleUrlTracker = function () { + var toggleConfirm = confirm("Are you sure you want to " + $scope.action.toLowerCase() + " the URL tracker?"); + if (toggleConfirm) { + $http.post("backoffice/api/RedirectUrlManagement/ToggleUrlTracker/?disable=" + (!$scope.urlTrackerDisabled)).then(function (response) { if (response.status === 200) { - notificationsService.success("URL Tracker has now been diabled."); + notificationsService.success("URL Tracker has now been " + $scope.action.toLowerCase() + "d."); + + $scope.load(); } else { - notificationsService.warning("Error diabling the URL Tracker, more information can be found in your log file."); + notificationsService.warning("Error " + $scope.action.toLowerCase() + "ing the URL Tracker, more information can be found in your log file."); } - }); + }); } }; diff --git a/src/Umbraco.Web.UI/ImageProcessorValidation.cs b/src/Umbraco.Web.UI/ImageProcessorValidation.cs new file mode 100644 index 0000000000..f788dcf30a --- /dev/null +++ b/src/Umbraco.Web.UI/ImageProcessorValidation.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Web; +using ImageProcessor.Web.Processors; +using ImageProcessor.Web.Configuration; +using ImageProcessor.Web.HttpModules; +using Umbraco.Core; + +namespace Umbraco.Web.UI +{ + public class ImageProcessorValidation : ApplicationEventHandler + { + protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + ImageProcessingModule.ValidatingRequest += ImageProcessingModule_ValidatingRequest; + } + + private static void ImageProcessingModule_ValidatingRequest(object sender, ImageProcessor.Web.Helpers.ValidatingRequestEventArgs e) + { +// Blur is disabled by default, add it to the list of available processors again +var configuration = ImageProcessorConfiguration.Instance; +var settings = new Dictionary +{ + { "MaxSize", "15" }, + { "MaxSigma", "1.5" }, + { "MaxThreshold", "10" } +}; + +configuration.AvailableWebGraphicsProcessors.TryAdd(typeof(GaussianBlur), settings); + + // Nothing to process, return immediately + if (string.IsNullOrWhiteSpace(e.QueryString)) + return; + + // Don't support alpha whatsoever + var queryCollection = HttpUtility.ParseQueryString(e.QueryString); + if (queryCollection.AllKeys.Contains("alpha")) + { + e.Cancel = true; + return; + } + + // If there's a crop parameter, force it to always just be a specific value + if (queryCollection.AllKeys.Contains("crop")) + { + queryCollection["crop"] = "100,100,100,100"; + // this performs the reverse of ParseQueryString since the result of ParseQueryString + // is actually an instance of System.Web.HttpValueCollection + e.QueryString = queryCollection.ToString(); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs b/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs index 27791136e1..7f3334d3f2 100644 --- a/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs @@ -1,6 +1,10 @@ -using System.Net.Http; +using System.IO; +using System.Net.Http; using System.Text; +using System.Web; using System.Web.Http; +using System.Xml; +using Umbraco.Core.Configuration; using Umbraco.Web.WebApi; namespace Umbraco.Web.Redirects @@ -9,20 +13,19 @@ namespace Umbraco.Web.Redirects { //add paging [HttpGet] - public RedirectUrlSearchResult SearchRedirectUrls(string searchTerm, int page = 0) + public RedirectUrlSearchResult SearchRedirectUrls(string searchTerm, int page = 0, int pageSize = 10) { - - int pageSize = 20; - var searchResult = new RedirectUrlSearchResult(); + page = page - 1; + var searchResult = new RedirectUrlSearchResult { UrlTrackerDisabled = UmbracoConfig.For.UmbracoSettings().WebRouting.DisableRedirectUrlTracking }; var redirectUrlService = Services.RedirectUrlService; long resultCount = 0L; // need endpoint for search functionality // by url, by domain ? it's the url that you want to find them by, that's what you see.. - var redirects = redirectUrlService.GetAllRedirectUrls(page, 20, out resultCount); + var redirects = redirectUrlService.GetAllRedirectUrls(page, pageSize, out resultCount); searchResult.SearchResults = redirects; searchResult.TotalCount = resultCount; - searchResult.CurrentPage = page; + searchResult.CurrentPage = page + 1; //hmm how many results 'could there be ? searchResult.PageCount = ((int)resultCount + pageSize - 1) / pageSize; @@ -60,6 +63,29 @@ namespace Umbraco.Web.Redirects return new HttpResponseMessage(System.Net.HttpStatusCode.OK); } + [HttpPost] + public HttpResponseMessage ToggleUrlTracker(bool disable) + { + var configFilePath = HttpContext.Current.Server.MapPath("~/config/umbracoSettings.config"); + if (File.Exists(configFilePath)) + { + var umbracoConfig = new XmlDocument {PreserveWhitespace = true}; + umbracoConfig.Load(configFilePath); + + var webRoutingElement = umbracoConfig.SelectSingleNode("//web.routing") as XmlElement; + if (webRoutingElement != null) + { + // note: this adds the attribute if it does not exist + webRoutingElement.SetAttribute("disableRedirectUrlTracking", disable.ToString().ToLowerInvariant()); + umbracoConfig.Save(configFilePath); + } + + + return new HttpResponseMessage(System.Net.HttpStatusCode.OK); + } + + return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Redirects/RedirectUrlSearchResults.cs b/src/Umbraco.Web/Redirects/RedirectUrlSearchResults.cs index 1dac259a88..4fe790e588 100644 --- a/src/Umbraco.Web/Redirects/RedirectUrlSearchResults.cs +++ b/src/Umbraco.Web/Redirects/RedirectUrlSearchResults.cs @@ -12,5 +12,7 @@ namespace Umbraco.Web.Redirects public long TotalCount { get; set; } public int PageCount { get; set; } public int CurrentPage { get; set; } + + public bool UrlTrackerDisabled { get; set; } } } \ No newline at end of file From 1c881174986bb70b06f9c95f70cbe1eb93a8cd48 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 3 Aug 2016 20:17:43 +0200 Subject: [PATCH 115/413] U4-8803 Add "Create package" back as a right-click item on the "Created Packages" node #U4-8803 Fixed --- src/Umbraco.Web/Trees/PackagesTreeController.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web/Trees/PackagesTreeController.cs b/src/Umbraco.Web/Trees/PackagesTreeController.cs index d1eb4dc8d2..d6cfbbef13 100644 --- a/src/Umbraco.Web/Trees/PackagesTreeController.cs +++ b/src/Umbraco.Web/Trees/PackagesTreeController.cs @@ -101,6 +101,9 @@ namespace Umbraco.Web.Trees } else if (id == "created") { + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))) + .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue("application")); + menu.Items.Add( Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); } From f1d9775a0f4455cffdf89a3291f571b33a4a89e0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 3 Aug 2016 21:15:27 +0200 Subject: [PATCH 116/413] Fix the build --- .../RedirectUrlDashboard/package.manifest | Bin 0 -> 336 bytes .../redirecturlsearch.html | 73 ++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/package.manifest create mode 100644 src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html diff --git a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/package.manifest b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/package.manifest new file mode 100644 index 0000000000000000000000000000000000000000..996d847a970ac78a86a2bf8587df2fe0e4afc821 GIT binary patch literal 336 zcmaKo%?g505QV?3LGK`!mQnk5K@Si`tB^3QEKEhSP1K`XXD$Vgp-;26=6ka6Hjta_%tW6GRL*N!DQEho+tBB!1zR2`*gxUuf3CtTDkMLNdb!yGQ! zd)I9~wUm`q=Cl10#=QPGuH=+TSL>ZfyU|z9!f(vj%P&?Z)dnN_Wb4UBT_fx-Cwf}G io9a?qa?NUWEw}S|>|_F>Uj9Tgn}+^ueyJl*KluP=xHyXd literal 0 HcmV?d00001 diff --git a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html new file mode 100644 index 0000000000..9a025a796b --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html @@ -0,0 +1,73 @@ +
      + +
      + +
      + +

      Redirect Url Search

      +

      When you move and rename pages in Umbraco, 301 permanent redirects are automatically created

      +

      Here is a list of the redirects

      + + + +
      + + + + + + + + + + + + + + + + + + + + +
      Original UrlRedirected ToDate CreatedRemove
      {{redirectUrl.Url}}{{redirectUrl.ContentUrl}} {{redirectUrl.CreateDateUtc | date:'medium'}}
      +
      +
      + + +
      +
      + From e66eefbbd76b905efdc7688ff620bcc7a36df6db Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 3 Aug 2016 21:42:26 +0200 Subject: [PATCH 117/413] Fixes unit test --- .../Persistence/Querying/ContentRepositorySqlClausesTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs index e97b8ac92b..c015410dfa 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs @@ -98,7 +98,7 @@ namespace Umbraco.Tests.Persistence.Querying .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")) .Where("([umbracoNode].[id] = @0)", 1050) .Where("([cmsContentVersion].[VersionId] = @0)", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07")) - .OrderBy("[cmsContentVersion].[VersionDate] DESC"); + .OrderBy("([cmsContentVersion].[VersionDate]) DESC"); var sql = new Sql(); sql.Select("*") From 2ddbca8861ff43fca859145be31917fef35f0020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Riis-Knudsen?= Date: Wed, 3 Aug 2016 22:07:06 +0200 Subject: [PATCH 118/413] U4-8749: Add a service to allow acces to prevalues from inline listview editor --- .../umblistviewsettings.directive.js | 3 +- .../listviewprevaluehelper.service.js | 61 +++++++++++++++++++ .../listview/sortby.prevalues.controller.js | 16 ++++- 3 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/services/listviewprevaluehelper.service.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js index ae9da02237..f21f7b8d3d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js @@ -1,7 +1,7 @@ (function() { 'use strict'; - function ListViewSettingsDirective(contentTypeResource, dataTypeResource, dataTypeHelper) { + function ListViewSettingsDirective(contentTypeResource, dataTypeResource, dataTypeHelper, listViewPrevalueHelper) { function link(scope, el, attr, ctrl) { @@ -20,6 +20,7 @@ scope.dataType = dataType; + listViewPrevalueHelper.setPrevalues(dataType.preValues); scope.customListViewCreated = checkForCustomListView(); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewprevaluehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewprevaluehelper.service.js new file mode 100644 index 0000000000..70bba2d26a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewprevaluehelper.service.js @@ -0,0 +1,61 @@ +/** + @ngdoc service + * @name umbraco.services.listViewPrevalueHelper + * + * + * @description + * Service for accessing the prevalues of a list view being edited in the inline list view editor in the doctype editor + */ +(function () { + 'use strict'; + + function listViewPrevalueHelper() { + + var prevalues = []; + + /** + * @ngdoc method + * @name umbraco.services.listViewPrevalueHelper#getPrevalues + * @methodOf umbraco.services.listViewPrevalueHelper + * + * @description + * Set the collection of prevalues + */ + + function getPrevalues() { + return prevalues; + } + + /** + * @ngdoc method + * @name umbraco.services.listViewPrevalueHelper#setPrevalues + * @methodOf umbraco.services.listViewPrevalueHelper + * + * @description + * Changes the current layout used by the listview to the layout passed in. Stores selection in localstorage + * + * @param {Array} values Array of prevalues + */ + + function setPrevalues(values) { + prevalues = values; + } + + + + var service = { + + getPrevalues: getPrevalues, + setPrevalues: setPrevalues + + }; + + return service; + + } + + + angular.module('umbraco.services').factory('listViewPrevalueHelper', listViewPrevalueHelper); + + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js index 480a78ee2d..333b91f81f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js @@ -1,7 +1,17 @@ -function sortByPreValsController($rootScope, $scope, localizationService, editorState) { +function sortByPreValsController($rootScope, $scope, localizationService, editorState, listViewPrevalueHelper) { + //Get the prevalue from the correct place + function getPrevalues() { + if (editorState.current.preValues) { + return editorState.current.preValues; + } + else { + return listViewPrevalueHelper.getPrevalues(); + } + } + //Watch the prevalues $scope.$watch(function () { - return _.findWhere(editorState.current.preValues, { key: "includeProperties" }).value; + return _.findWhere(getPrevalues(), { key: "includeProperties" }).value; }, function () { populateFields(); }, true); //Use deep watching, otherwise we won't pick up header changes @@ -15,7 +25,7 @@ function sortByPreValsController($rootScope, $scope, localizationService, editor } // Get list of properties assigned as columns of the list view - var propsPreValue = _.findWhere(editorState.current.preValues, { key: "includeProperties" }); + var propsPreValue = _.findWhere(getPrevalues(), { key: "includeProperties" }); // Populate list of options for the default sort (all the columns plus then node name) $scope.sortByFields = []; From 52d366bb6f58362e1951d501bb5fcf2b92bdc206 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 4 Aug 2016 08:58:30 +0200 Subject: [PATCH 119/413] MD5CryptoServiceProvider is disposable --- .../Repositories/RedirectUrlRepository.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs index 3cde41fd18..c87890ff81 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Persistence.Repositories { internal class RedirectUrlRepository : PetaPocoRepositoryBase, IRedirectUrlRepository { - public RedirectUrlRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + public RedirectUrlRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } @@ -196,10 +196,12 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); private static string HashUrl(string url) { - var crypto = new MD5CryptoServiceProvider(); - var inputBytes = Encoding.UTF8.GetBytes(url); - var hashedBytes = crypto.ComputeHash(inputBytes); - return Encoding.UTF8.GetString(hashedBytes); + using (var crypto = new MD5CryptoServiceProvider()) + { + var inputBytes = Encoding.UTF8.GetBytes(url); + var hashedBytes = crypto.ComputeHash(inputBytes); + return Encoding.UTF8.GetString(hashedBytes); + } } } } From 6e480058b992067b507163a9a6980f8afc018e0e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 4 Aug 2016 09:46:36 +0200 Subject: [PATCH 120/413] Adds R# rule to give a little hint when newing up a disposable --- src/umbraco.sln.DotSettings | 54 +++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/umbraco.sln.DotSettings b/src/umbraco.sln.DotSettings index 760993864e..19aedb76e3 100644 --- a/src/umbraco.sln.DotSettings +++ b/src/umbraco.sln.DotSettings @@ -1,27 +1,29 @@ - - <data><IncludeFilters /><ExcludeFilters /></data> - <data /> - True - Replace with $var$ == false for readability - True - False - - ExpressionPlaceholder - True - CSHARP - Replace with $var$ == false for readability - $var$ == false - !$var$ - SUGGESTION - False - - - - - - - - - - + + <data><IncludeFilters /><ExcludeFilters /></data> + <data /> + True + Replace with $var$ == false for readability + True + False + + ExpressionPlaceholder + True + CSHARP + Replace with $var$ == false for readability + $var$ == false + !$var$ + SUGGESTION + Disposable construction + HINT + False + + + + + + + + + + \ No newline at end of file From b365a9944f71078ae868b475183804b26b432849 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 4 Aug 2016 10:29:36 +0200 Subject: [PATCH 121/413] Fix js errors for grunt to build --- .../directives/components/grid/grid.rte.directive.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index 263d6be1cb..5dd7d266df 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -64,6 +64,7 @@ angular.module("umbraco.directives") await.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) { angular.forEach(rules, function (rule) { var r = {}; + var split = ""; r.title = rule.name; if (rule.selector[0] === ".") { r.inline = "span"; @@ -74,12 +75,12 @@ angular.module("umbraco.directives") // since only one element can have one id. r.inline = "span"; r.attributes = { id: rule.selector.substring(1) }; - }else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) { - var split = rule.selector.split("."); + }else if (rule.selector[0] !== "." && rule.selector.indexOf(".") > -1) { + split = rule.selector.split("."); r.block = split[0]; r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); - }else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) { - var split = rule.selector.split("#"); + }else if (rule.selector[0] !== "#" && rule.selector.indexOf("#") > -1) { + split = rule.selector.split("#"); r.block = split[0]; r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); }else { From ba5be0e22af25206a9abd3b6513f7b0cb4eb41e4 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 4 Aug 2016 11:21:42 +0200 Subject: [PATCH 122/413] Adds search function --- .../Interfaces/IRedirectUrlRepository.cs | 10 ++++++++++ .../Repositories/RedirectUrlRepository.cs | 14 +++++++++++++- .../Services/IRedirectUrlService.cs | 10 ++++++++++ .../Services/RedirectUrlService.cs | 10 ++++++++++ .../redirecturlsearch.controller.js | 17 ++++++++++------- .../redirecturlsearch.html | 8 ++++---- .../RedirectUrlManagementController.cs | 18 +++++++++--------- 7 files changed, 66 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs index 82f2e0e516..8af394d71e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs @@ -66,5 +66,15 @@ namespace Umbraco.Core.Persistence.Repositories /// The total count of redirect urls. /// The redirect urls. IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total); + + /// + /// Searches for all redirect urls that contain a given search term in their URL property. + /// + /// The term to search for. + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs index 3cde41fd18..16e0c44da2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs @@ -185,7 +185,19 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); public IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total) { var sql = GetBaseQuery(false) - .Where("umbracoNode.path LIKE @path", new { path = "%," + rootContentId + ",%" }) + .Where("[umbracoNode].[path] LIKE @path", new { path = "%," + rootContentId + ",%" }) + .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); + var result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + + var rules = result.Items.Select(Map); + return rules; + } + + public IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total) + { + var sql = GetBaseQuery(false) + .Where("[umbracoRedirectUrl].[Url] LIKE @url", new { url = "%" + searchTerm.Trim().ToLowerInvariant() + "%" }) .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); var result = Database.Page(pageIndex + 1, pageSize, sql); total = Convert.ToInt32(result.TotalItems); diff --git a/src/Umbraco.Core/Services/IRedirectUrlService.cs b/src/Umbraco.Core/Services/IRedirectUrlService.cs index 7b21fa9bb7..453c296af4 100644 --- a/src/Umbraco.Core/Services/IRedirectUrlService.cs +++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs @@ -72,5 +72,15 @@ namespace Umbraco.Core.Services /// The total count of redirect urls. /// The redirect urls. IEnumerable GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total); + + /// + /// Searches for all redirect urls that contain a given search term in their URL property. + /// + /// The term to search for. + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total); } } diff --git a/src/Umbraco.Core/Services/RedirectUrlService.cs b/src/Umbraco.Core/Services/RedirectUrlService.cs index de698509d0..d71a38b8e9 100644 --- a/src/Umbraco.Core/Services/RedirectUrlService.cs +++ b/src/Umbraco.Core/Services/RedirectUrlService.cs @@ -112,5 +112,15 @@ namespace Umbraco.Core.Services return rules; } } + public IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total) + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + var rules = repo.SearchUrls(searchTerm, pageIndex, pageSize, out total); + uow.Commit(); + return rules; + } + } } } diff --git a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js index 76101ab5c7..5dc8f901b2 100644 --- a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js +++ b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js @@ -23,29 +23,32 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco $scope.next = function () { if ($scope.options.pageNumber < $scope.pageCount) { $scope.options.pageNumber++; + $scope.currentPage++; $scope.load(); } }; $scope.goToPage = function (pageNumber) { $scope.options.pageNumber = pageNumber + 1; + $scope.currentPage = pageNumber; $scope.load(); }; $scope.prev = function () { if ($scope.options.pageNumber > 1) { $scope.options.pageNumber--; + $scope.currentPage--; $scope.load(); } }; $scope.search = function () { - - //do we want to search by url and by domain... - var searchTerm = $scope.searchTerm; + if (searchTerm === undefined) { + searchTerm = ""; + } - $http.get("backoffice/api/RedirectUrlManagement/SearchRedirectUrls/?searchTerm=" + searchTerm + "&page=" + $scope.options.pageNumber + "&pageSize=" + $scope.options.pageSize).then(function (response) { + $http.get("backoffice/api/RedirectUrlManagement/SearchRedirectUrls/?searchTerm=" + searchTerm + "&page=" + $scope.currentPage + "&pageSize=" + $scope.options.pageSize).then(function (response) { var matchingItems = response.data; $scope.isSearch = true; $scope.StatusMessage = matchingItems.StatusMessage; @@ -62,7 +65,8 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco $scope.pageCount = matchingItems.PageCount; $scope.totalCount = matchingItems.TotalCount; - $scope.options.pageNumber = matchingItems.CurrentPage; + $scope.currentPage = matchingItems.CurrentPage; + $scope.options.pageNumber = matchingItems.CurrentPage + 1; if ($scope.options.pageNumber > $scope.pageCount) { $scope.options.pageNumber = $scope.pageCount; @@ -78,8 +82,7 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco isActive: $scope.options.pageNumber === (i + 1) }); } - } - else { + } else { //if there is more than 10 pages, we need to do some fancy bits //get the max index to start diff --git a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html index 9a025a796b..dcc3c01b90 100644 --- a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html +++ b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html @@ -4,11 +4,11 @@
      -

      Redirect Url Search

      +

      Redirect Url Management

      When you move and rename pages in Umbraco, 301 permanent redirects are automatically created

      -

      Here is a list of the redirects

      +

      Here is a list of the redirects you

      - +
      diff --git a/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs b/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs index 7f3334d3f2..604dea8cc5 100644 --- a/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs @@ -1,11 +1,11 @@ -using System.IO; -using System.Net.Http; +using System.Net.Http; using System.Text; using System.Web; using System.Web.Http; using System.Xml; using Umbraco.Core.Configuration; using Umbraco.Web.WebApi; +using File = System.IO.File; namespace Umbraco.Web.Redirects { @@ -15,17 +15,17 @@ namespace Umbraco.Web.Redirects [HttpGet] public RedirectUrlSearchResult SearchRedirectUrls(string searchTerm, int page = 0, int pageSize = 10) { - page = page - 1; var searchResult = new RedirectUrlSearchResult { UrlTrackerDisabled = UmbracoConfig.For.UmbracoSettings().WebRouting.DisableRedirectUrlTracking }; var redirectUrlService = Services.RedirectUrlService; - long resultCount = 0L; - // need endpoint for search functionality - // by url, by domain ? it's the url that you want to find them by, that's what you see.. + var resultCount = 0L; + + var redirects = string.IsNullOrWhiteSpace(searchTerm) + ? redirectUrlService.GetAllRedirectUrls(page, pageSize, out resultCount) + : redirectUrlService.SearchRedirectUrls(searchTerm, page, pageSize, out resultCount); - var redirects = redirectUrlService.GetAllRedirectUrls(page, pageSize, out resultCount); searchResult.SearchResults = redirects; searchResult.TotalCount = resultCount; - searchResult.CurrentPage = page + 1; + searchResult.CurrentPage = page; //hmm how many results 'could there be ? searchResult.PageCount = ((int)resultCount + pageSize - 1) / pageSize; @@ -70,7 +70,7 @@ namespace Umbraco.Web.Redirects if (File.Exists(configFilePath)) { - var umbracoConfig = new XmlDocument {PreserveWhitespace = true}; + var umbracoConfig = new XmlDocument { PreserveWhitespace = true }; umbracoConfig.Load(configFilePath); var webRoutingElement = umbracoConfig.SelectSingleNode("//web.routing") as XmlElement; From b7264b58f9ddddf611547e50e143cc26e875348f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 4 Aug 2016 12:18:27 +0200 Subject: [PATCH 123/413] Fixed the mapping for HasPublishedVersion --- src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs | 8 +++++++- src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs | 7 +++++-- src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs | 8 ++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 5fc642873b..7f50b618d4 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -56,6 +56,9 @@ namespace Umbraco.Web.Models.Mapping expression => expression.MapFrom(content => GetPublishedDate(content, applicationContext))) .ForMember( dto => dto.TemplateAlias, expression => expression.MapFrom(content => content.Template.Alias)) + .ForMember( + dto => dto.HasPublishedVersion, + expression => expression.MapFrom(content => content.HasPublishedVersion)) .ForMember( dto => dto.Urls, expression => expression.MapFrom(content => @@ -87,7 +90,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember( dto => dto.Trashed, expression => expression.MapFrom(content => content.Trashed)) - .ForMember( + .ForMember( dto => dto.HasPublishedVersion, expression => expression.MapFrom(content => content.HasPublishedVersion)) .ForMember( @@ -100,6 +103,9 @@ namespace Umbraco.Web.Models.Mapping .ForMember( dto => dto.Owner, expression => expression.ResolveUsing>()) + .ForMember( + dto => dto.HasPublishedVersion, + expression => expression.MapFrom(content => content.HasPublishedVersion)) .ForMember(display => display.Updater, expression => expression.Ignore()) .ForMember(display => display.Icon, expression => expression.Ignore()) .ForMember(display => display.Alias, expression => expression.Ignore()); diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 799a93a220..44df5e4d52 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -53,6 +53,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Updater, expression => expression.Ignore()) .ForMember(display => display.Alias, expression => expression.Ignore()) .ForMember(display => display.IsContainer, expression => expression.Ignore()) + .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()) .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) .AfterMap((media, display) => AfterMap(media, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, applicationContext.ProfilingLogger.Logger)); @@ -72,7 +73,8 @@ namespace Umbraco.Web.Models.Mapping expression => expression.MapFrom(content => content.ContentType.Alias)) .ForMember(x => x.Published, expression => expression.Ignore()) .ForMember(x => x.Updater, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()); + .ForMember(x => x.Alias, expression => expression.Ignore()) + .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()); //FROM IMedia TO ContentItemDto config.CreateMap>() @@ -82,7 +84,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.Published, expression => expression.Ignore()) .ForMember(x => x.Updater, expression => expression.Ignore()) .ForMember(x => x.Icon, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()); + .ForMember(x => x.Alias, expression => expression.Ignore()) + .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()); } private static void AfterMap(IMedia media, MediaItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, ILogger logger) diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 52fadd7a6a..cc677b4688 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -90,6 +90,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Trashed, expression => expression.Ignore()) .ForMember(display => display.IsContainer, expression => expression.Ignore()) .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) + .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()) .AfterMap((member, display) => MapGenericCustomProperties(applicationContext.Services.MemberService, member, display, applicationContext.Services.TextService)); //FROM IMember TO MemberBasic @@ -112,7 +113,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Trashed, expression => expression.Ignore()) .ForMember(x => x.Published, expression => expression.Ignore()) .ForMember(x => x.Updater, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()); + .ForMember(x => x.Alias, expression => expression.Ignore()) + .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()); //FROM MembershipUser TO MemberBasic config.CreateMap() @@ -143,7 +145,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.Updater, expression => expression.Ignore()) .ForMember(dto => dto.Trashed, expression => expression.Ignore()) .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(x => x.ContentTypeAlias, expression => expression.Ignore()); + .ForMember(x => x.ContentTypeAlias, expression => expression.Ignore()) + .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()); //FROM IMember TO ContentItemDto config.CreateMap>() @@ -154,6 +157,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.Updater, expression => expression.Ignore()) .ForMember(x => x.Icon, expression => expression.Ignore()) .ForMember(x => x.Alias, expression => expression.Ignore()) + .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()) //do no map the custom member properties (currently anyways, they were never there in 6.x) .ForMember(dto => dto.Properties, expression => expression.ResolveUsing()); } From c61a9ee4335144dfd9aa8efedbb161966a2948ba Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 4 Aug 2016 14:46:07 +0200 Subject: [PATCH 124/413] AppendCacheExtension needs to be AppendHeader --- src/Umbraco.Web/UmbracoModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 517fd112c9..912183c2dd 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -330,7 +330,7 @@ namespace Umbraco.Web response.Cache.AppendCacheExtension(cacheExtension); foreach (var header in pcr.Headers) - response.Cache.AppendCacheExtension(string.Format("{0}, {1}", header.Key, header.Value)); + response.Cache.AppendHeader(string.Format("{0}, {1}", header.Key, header.Value)); if (pcr.IsRedirect) { From 6495077568fc5b692ab5c0a70aa26fe7bda3a58f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 4 Aug 2016 14:50:55 +0200 Subject: [PATCH 125/413] Whoops --- src/Umbraco.Web/UmbracoModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 912183c2dd..504b4826c2 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -330,7 +330,7 @@ namespace Umbraco.Web response.Cache.AppendCacheExtension(cacheExtension); foreach (var header in pcr.Headers) - response.Cache.AppendHeader(string.Format("{0}, {1}", header.Key, header.Value)); + response.AppendHeader(string.Format("{0}, {1}", header.Key, header.Value)); if (pcr.IsRedirect) { From 43737e78fb9d8ee361531c1d6c233ae26cf47486 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 4 Aug 2016 15:05:49 +0200 Subject: [PATCH 126/413] Sorry about that! --- src/Umbraco.Web/UmbracoModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 504b4826c2..d613596f56 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -330,7 +330,7 @@ namespace Umbraco.Web response.Cache.AppendCacheExtension(cacheExtension); foreach (var header in pcr.Headers) - response.AppendHeader(string.Format("{0}, {1}", header.Key, header.Value)); + response.AppendHeader(header.Key, header.Value); if (pcr.IsRedirect) { From 423588d0187f3979d31f40fbd604adf9900e7e07 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 4 Aug 2016 15:20:57 +0200 Subject: [PATCH 127/413] code formatting --- .../developer/Packages/editPackage.aspx.cs | 83 ++++++++++++------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs index 05989ba28e..132928bbec 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs @@ -193,27 +193,35 @@ namespace umbraco.presentation.developer.packages } } - protected void validateActions(object sender, ServerValidateEventArgs e) { + protected void validateActions(object sender, ServerValidateEventArgs e) + { string actions = tb_actions.Text; - if (!string.IsNullOrEmpty(actions)) { + if (!string.IsNullOrEmpty(actions)) + { actions = "" + actions + ""; - - try { + + try + { //we try to load an xml document with the potential malformed xml to ensure that this is actual action xml... XmlDocument xd = new XmlDocument(); xd.LoadXml(actions); e.IsValid = true; - } catch { + } + catch + { e.IsValid = false; } - }else + } + else e.IsValid = true; } - protected void saveOrPublish(object sender, CommandEventArgs e) { + protected void saveOrPublish(object sender, CommandEventArgs e) + { - if (!Page.IsValid) { + if (!Page.IsValid) + { this.ClientTools.ShowSpeechBubble(BasePages.BasePage.speechBubbleIcon.error, "Saved failed.", "Some fields have not been filled-out correctly"); } else @@ -221,30 +229,36 @@ namespace umbraco.presentation.developer.packages if (e.CommandName == "save") SavePackage(true); - if (e.CommandName == "publish") { + if (e.CommandName == "publish") + { SavePackage(false); int packageID = int.Parse(Request.QueryString["id"]); //string packFileName = cms.businesslogic.packager. Publish.publishPackage(packageID); createdPackage.Publish(); - - if (!string.IsNullOrEmpty(pack.PackagePath)) { + + if (!string.IsNullOrEmpty(pack.PackagePath)) + { packageUmbFile.Text = "   Download"; this.ClientTools.ShowSpeechBubble(BasePages.BasePage.speechBubbleIcon.success, "Package saved and published", ""); - } else { + } + else + { this.ClientTools.ShowSpeechBubble(BasePages.BasePage.speechBubbleIcon.error, "Save failed", "check your umbraco log."); } } } } - protected void generateXML(object sender, EventArgs e) { + protected void generateXML(object sender, EventArgs e) + { } - private void SavePackage(bool showNotification) { + private void SavePackage(bool showNotification) + { pack.Author = packageAuthorName.Text; pack.AuthorUrl = packageAuthorUrl.Text; @@ -269,7 +283,8 @@ namespace umbraco.presentation.developer.packages string tmpStylesheets = ""; - foreach (ListItem li in stylesheets.Items) { + foreach (ListItem li in stylesheets.Items) + { if (li.Selected) tmpStylesheets += li.Value + ","; } @@ -277,7 +292,8 @@ namespace umbraco.presentation.developer.packages string tmpDoctypes = ""; - foreach (ListItem li in documentTypes.Items) { + foreach (ListItem li in documentTypes.Items) + { if (li.Selected) tmpDoctypes += li.Value + ","; } @@ -285,7 +301,8 @@ namespace umbraco.presentation.developer.packages string tmpMacros = ""; - foreach (ListItem li in macros.Items) { + foreach (ListItem li in macros.Items) + { if (li.Selected) tmpMacros += li.Value + ","; } @@ -293,40 +310,44 @@ namespace umbraco.presentation.developer.packages string tmpLanguages = ""; - foreach (ListItem li in languages.Items) { + foreach (ListItem li in languages.Items) + { if (li.Selected) tmpLanguages += li.Value + ","; } pack.Languages = new List(tmpLanguages.Trim(',').Split(',')); string tmpDictionaries = ""; - foreach (ListItem li in dictionary.Items) { + foreach (ListItem li in dictionary.Items) + { if (li.Selected) tmpDictionaries += li.Value + ","; } pack.DictionaryItems = new List(tmpDictionaries.Trim(',').Split(',')); - + string tmpTemplates = ""; - foreach (ListItem li in templates.Items) { + foreach (ListItem li in templates.Items) + { if (li.Selected) tmpTemplates += li.Value + ","; } pack.Templates = new List(tmpTemplates.Trim(',').Split(',')); string tmpDataTypes = ""; - foreach (ListItem li in cbl_datatypes.Items) { + foreach (ListItem li in cbl_datatypes.Items) + { if (li.Selected) tmpDataTypes += li.Value + ","; } pack.DataTypes = new List(tmpDataTypes.Trim(',').Split(',')); - + pack.LoadControl = packageControlPath.Text; - + createdPackage.Save(); - if(showNotification) + if (showNotification) this.ClientTools.ShowSpeechBubble(BasePages.BasePage.speechBubbleIcon.save, "Package Saved", ""); } @@ -355,7 +376,7 @@ namespace umbraco.presentation.developer.packages TextBox filePathControl = (TextBox)((Control)sender).Parent.FindControl("packageFilePath"); filePathControl.Text = ""; - string tmpFilePathString = ""; + string tmpFilePathString = ""; foreach (RepeaterItem rItem in packageFilesRepeater.Items) { string tmpFFFF = ((TextBox)rItem.FindControl("packageFilePath")).Text; @@ -365,7 +386,7 @@ namespace umbraco.presentation.developer.packages cms.businesslogic.packager.CreatedPackage createdPackage = cms.businesslogic.packager.CreatedPackage.GetById(int.Parse(Request.QueryString["id"])); cms.businesslogic.packager.PackageInstance pack = createdPackage.Data; - + pack.Files = new List(tmpFilePathString.Trim('').Split('')); pack.Files.TrimExcess(); @@ -384,7 +405,7 @@ namespace umbraco.presentation.developer.packages packageInfo.Controls.Add(Pane1_1); packageInfo.Controls.Add(Pane1_2); packageInfo.Controls.Add(Pane1_3); - + packageContents = TabView1.NewTabPage("Package Contents"); packageContents.Controls.Add(Pane2); @@ -417,11 +438,11 @@ namespace umbraco.presentation.developer.packages saves.ButtonType = uicontrols.MenuButtonType.Primary; saves.ID = "save"; - - + + base.OnInit(e); } - + } } From 0ab145e4aca1687c6cc03c1899f2542d07677fa5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 4 Aug 2016 15:33:26 +0200 Subject: [PATCH 128/413] Ensures that type="strict" is added to the package creation, does some null checking on the stylesheets packaging so ysod doesn't occur, some code formatting. --- .../developer/Packages/editPackage.aspx.cs | 5 +- .../businesslogic/Packager/Installer.cs | 6 - .../PackageInstance/CreatedPackage.cs | 6 +- .../Packager/PackageInstance/utill.cs | 159 +++++++++++------- .../Packager/RequirementsType.cs | 8 + src/umbraco.cms/umbraco.cms.csproj | 1 + 6 files changed, 113 insertions(+), 72 deletions(-) create mode 100644 src/umbraco.cms/businesslogic/Packager/RequirementsType.cs diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs index 132928bbec..091996f7f0 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Web.UI; using System.Web.UI.WebControls; @@ -253,6 +254,8 @@ namespace umbraco.presentation.developer.packages } } + [Obsolete("This is not used")] + [EditorBrowsable(EditorBrowsableState.Never)] protected void generateXML(object sender, EventArgs e) { } @@ -276,7 +279,7 @@ namespace umbraco.presentation.developer.packages pack.ContentLoadChildNodes = packageContentSubdirs.Checked; - if (!String.IsNullOrEmpty(cp.Value)) + if (string.IsNullOrEmpty(cp.Value) == false) pack.ContentNodeId = cp.Value; else pack.ContentNodeId = ""; diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index 71e2248e86..6b4f9edde2 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -22,12 +22,6 @@ using umbraco.interfaces; namespace umbraco.cms.businesslogic.packager { - public enum RequirementsType - { - Strict, - Legacy - } - /// /// The packager is a component which enables sharing of both data and functionality components between different umbraco installations. /// diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs index 3d9ef6a827..7e5becf61a 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs @@ -248,8 +248,12 @@ namespace umbraco.cms.businesslogic.packager var stylesheets = _packageManifest.CreateElement("Stylesheets"); foreach (var stylesheetName in pack.Stylesheets) { + if (stylesheetName.IsNullOrWhiteSpace()) continue; var stylesheetXmlNode = utill.Stylesheet(stylesheetName, true, _packageManifest); - stylesheets.AppendChild(stylesheetXmlNode); + if (stylesheetXmlNode != null) + { + stylesheets.AppendChild(stylesheetXmlNode); + } } AppendElement(stylesheets); diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/utill.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/utill.cs index f31862b842..2780bb4423 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/utill.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/utill.cs @@ -21,50 +21,57 @@ using ICSharpCode.SharpZipLib.Zip; using Umbraco.Core; using Umbraco.Core.IO; -namespace umbraco.cms.businesslogic.packager { +namespace umbraco.cms.businesslogic.packager +{ /// /// A utillity class for working with packager data. /// It provides basic methods for adding new items to a package manifest, moving files and other misc. /// - public class utill { - + public class utill + { + /// /// Creates a package manifest containing name, license, version and other meta data. /// /// The packinstance. /// The xml document. /// - public static XmlNode PackageInfo(PackageInstance pack, XmlDocument doc) { + public static XmlNode PackageInfo(PackageInstance pack, XmlDocument doc) + { XmlNode info = doc.CreateElement("info"); //Package info XmlNode package = doc.CreateElement("package"); - package.AppendChild(_node("name", pack.Name, doc)); - package.AppendChild(_node("version", pack.Version, doc)); - package.AppendChild(_node("iconUrl", pack.IconUrl, doc)); + package.AppendChild(CreateNode("name", pack.Name, doc)); + package.AppendChild(CreateNode("version", pack.Version, doc)); + package.AppendChild(CreateNode("iconUrl", pack.IconUrl, doc)); - XmlNode license = _node("license", pack.License, doc); - license.Attributes.Append(_attribute("url", pack.LicenseUrl, doc)); + XmlNode license = CreateNode("license", pack.License, doc); + license.Attributes.Append(CreateAttribute("url", pack.LicenseUrl, doc)); package.AppendChild(license); - - package.AppendChild(_node("url", pack.Url, doc)); - XmlNode Requirements = doc.CreateElement("requirements"); + package.AppendChild(CreateNode("url", pack.Url, doc)); + + XmlNode requirements = doc.CreateElement("requirements"); //NOTE: The defaults are 3.0.0 - I'm just leaving that here since that's the way it's been //SD - Requirements.AppendChild(_node("major", pack.UmbracoVersion == null ? "3" : pack.UmbracoVersion.Major.ToInvariantString(), doc)); - Requirements.AppendChild(_node("minor", pack.UmbracoVersion == null ? "0" : pack.UmbracoVersion.Minor.ToInvariantString(), doc)); - Requirements.AppendChild(_node("patch", pack.UmbracoVersion == null ? "0" : pack.UmbracoVersion.Build.ToInvariantString(), doc)); - package.AppendChild(Requirements); + requirements.AppendChild(CreateNode("major", pack.UmbracoVersion == null ? "3" : pack.UmbracoVersion.Major.ToInvariantString(), doc)); + requirements.AppendChild(CreateNode("minor", pack.UmbracoVersion == null ? "0" : pack.UmbracoVersion.Minor.ToInvariantString(), doc)); + requirements.AppendChild(CreateNode("patch", pack.UmbracoVersion == null ? "0" : pack.UmbracoVersion.Build.ToInvariantString(), doc)); + if (pack.UmbracoVersion != null) + { + requirements.Attributes.Append(CreateAttribute("type", "strict", doc)); + } + package.AppendChild(requirements); info.AppendChild(package); //Author - XmlNode author = _node("author", "", doc); - author.AppendChild(_node("name", pack.Author, doc)); - author.AppendChild(_node("website", pack.AuthorUrl, doc)); + XmlNode author = CreateNode("author", "", doc); + author.AppendChild(CreateNode("name", pack.Author, doc)); + author.AppendChild(CreateNode("website", pack.AuthorUrl, doc)); info.AppendChild(author); - info.AppendChild(_node("readme", "", doc)); + info.AppendChild(CreateNode("readme", "", doc)); return info; } @@ -76,18 +83,20 @@ namespace umbraco.cms.businesslogic.packager { /// The template id. /// The xml doc. /// - public static XmlNode Template(int templateId, XmlDocument doc) { + public static XmlNode Template(int templateId, XmlDocument doc) + { Template tmpl = new Template(templateId); XmlNode template = doc.CreateElement("Template"); - template.AppendChild(_node("Name", tmpl.Text, doc)); - template.AppendChild(_node("Alias", tmpl.Alias, doc)); + template.AppendChild(CreateNode("Name", tmpl.Text, doc)); + template.AppendChild(CreateNode("Alias", tmpl.Alias, doc)); - if (tmpl.MasterTemplate != 0) { - template.AppendChild(_node("Master", new Template(tmpl.MasterTemplate).Alias, doc)); + if (tmpl.MasterTemplate != 0) + { + template.AppendChild(CreateNode("Master", new Template(tmpl.MasterTemplate).Alias, doc)); } - template.AppendChild(_node("Design", "", doc)); + template.AppendChild(CreateNode("Design", "", doc)); return template; } @@ -101,24 +110,27 @@ namespace umbraco.cms.businesslogic.packager { /// public static XmlNode Stylesheet(string name, bool includeProperties, XmlDocument doc) { + if (doc == null) throw new ArgumentNullException("doc"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + if (ApplicationContext.Current == null) throw new NullReferenceException("ApplicationContext is null"); var fileService = ApplicationContext.Current.Services.FileService; - + var sts = fileService.GetStylesheetByName(name); var stylesheet = doc.CreateElement("Stylesheet"); - stylesheet.AppendChild(_node("Name", sts.Alias, doc)); - stylesheet.AppendChild(_node("FileName", sts.Name, doc)); - stylesheet.AppendChild(_node("Content", "", doc)); + stylesheet.AppendChild(CreateNode("Name", sts.Alias, doc)); + stylesheet.AppendChild(CreateNode("FileName", sts.Name, doc)); + stylesheet.AppendChild(CreateNode("Content", "", doc)); if (includeProperties) { var properties = doc.CreateElement("Properties"); foreach (var ssP in sts.Properties) { var property = doc.CreateElement("Property"); - property.AppendChild(_node("Name", ssP.Name, doc)); - property.AppendChild(_node("Alias", ssP.Alias, doc)); - property.AppendChild(_node("Value", ssP.Value, doc)); + property.AppendChild(CreateNode("Name", ssP.Name, doc)); + property.AppendChild(CreateNode("Alias", ssP.Alias, doc)); + property.AppendChild(CreateNode("Value", ssP.Value, doc)); } stylesheet.AppendChild(properties); } @@ -133,11 +145,13 @@ namespace umbraco.cms.businesslogic.packager { /// The package directory. /// The doc. /// - public static XmlNode Macro(int macroId, bool appendFile, string packageDirectory, XmlDocument doc) { + public static XmlNode Macro(int macroId, bool appendFile, string packageDirectory, XmlDocument doc) + { Macro mcr = new Macro(macroId); - - if (appendFile) { + + if (appendFile) + { if (!string.IsNullOrEmpty(mcr.Xslt)) AppendFileToManifest(IOHelper.ResolveUrl(SystemDirectories.Xslt) + "/" + mcr.Xslt, packageDirectory, doc); if (!string.IsNullOrEmpty(mcr.ScriptingFile)) @@ -157,43 +171,52 @@ namespace umbraco.cms.businesslogic.packager { /// The path. /// The package directory. /// The doc. - public static void AppendFileToManifest(string path, string packageDirectory, XmlDocument doc) { + public static void AppendFileToManifest(string path, string packageDirectory, XmlDocument doc) + { if (!path.StartsWith("~/") && !path.StartsWith("/")) path = "~/" + path; - + string serverPath = IOHelper.MapPath(path); - if (System.IO.File.Exists(serverPath)) { + if (System.IO.File.Exists(serverPath)) + { AppendFileXml(path, packageDirectory, doc); - - } else if(System.IO.Directory.Exists(serverPath)){ + + } + else if (System.IO.Directory.Exists(serverPath)) + { ProcessDirectory(path, packageDirectory, doc); - } + } } - + //Process files in directory and add them to package - private static void ProcessDirectory(string path, string packageDirectory, XmlDocument doc) { + private static void ProcessDirectory(string path, string packageDirectory, XmlDocument doc) + { string serverPath = IOHelper.MapPath(path); - if (System.IO.Directory.Exists(serverPath)) { - + if (System.IO.Directory.Exists(serverPath)) + { + System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(serverPath); - - foreach (System.IO.FileInfo file in di.GetFiles()) { + + foreach (System.IO.FileInfo file in di.GetFiles()) + { AppendFileXml(path + "/" + file.Name, packageDirectory, doc); } - foreach (System.IO.DirectoryInfo dir in di.GetDirectories()) { + foreach (System.IO.DirectoryInfo dir in di.GetDirectories()) + { ProcessDirectory(path + "/" + dir.Name, packageDirectory, doc); } } } - private static void AppendFileXml(string path, string packageDirectory, XmlDocument doc) { + private static void AppendFileXml(string path, string packageDirectory, XmlDocument doc) + { string serverPath = IOHelper.MapPath(path); @@ -201,7 +224,8 @@ namespace umbraco.cms.businesslogic.packager { string orgName = path.Substring((path.LastIndexOf('/') + 1)); string newFileName = orgName; - if (System.IO.File.Exists(packageDirectory + "/" + orgName)) { + if (System.IO.File.Exists(packageDirectory + "/" + orgName)) + { string fileGuid = System.Guid.NewGuid().ToString(); newFileName = fileGuid + "_" + newFileName; } @@ -213,9 +237,9 @@ namespace umbraco.cms.businesslogic.packager { XmlNode files = doc.SelectSingleNode("/umbPackage/files"); XmlNode file = doc.CreateElement("file"); - file.AppendChild(_node("guid", newFileName, doc)); - file.AppendChild(_node("orgPath", orgPath == "" ? "/" : orgPath, doc)); - file.AppendChild(_node("orgName", orgName, doc)); + file.AppendChild(CreateNode("guid", newFileName, doc)); + file.AppendChild(CreateNode("orgPath", orgPath == "" ? "/" : orgPath, doc)); + file.AppendChild(CreateNode("orgName", orgName, doc)); files.AppendChild(file); } @@ -228,18 +252,21 @@ namespace umbraco.cms.businesslogic.packager { /// /// true if [is file in manifest]; otherwise, false. /// - public static bool IsFileInManifest(string guid, XmlDocument doc) { + public static bool IsFileInManifest(string guid, XmlDocument doc) + { return false; } - - private static XmlNode _node(string name, string value, XmlDocument doc) { - XmlNode node = doc.CreateElement(name); + + private static XmlNode CreateNode(string name, string value, XmlDocument doc) + { + var node = doc.CreateElement(name); node.InnerXml = value; return node; } - private static XmlAttribute _attribute(string name, string value, XmlDocument doc) { - XmlAttribute attribute = doc.CreateAttribute(name); + private static XmlAttribute CreateAttribute(string name, string value, XmlDocument doc) + { + var attribute = doc.CreateAttribute(name); attribute.Value = value; return attribute; } @@ -249,7 +276,8 @@ namespace umbraco.cms.businesslogic.packager { /// /// The path. /// The save path. - public static void ZipPackage(string Path, string savePath) { + public static void ZipPackage(string Path, string savePath) + { string OutPath = savePath; ArrayList ar = GenerateFileList(Path); @@ -304,7 +332,8 @@ namespace umbraco.cms.businesslogic.packager { ar = null; } - private static ArrayList GenerateFileList(string Dir) { + private static ArrayList GenerateFileList(string Dir) + { ArrayList mid = new ArrayList(); bool Empty = true; @@ -315,7 +344,8 @@ namespace umbraco.cms.businesslogic.packager { Empty = false; } - if (Empty) { + if (Empty) + { if (Directory.GetDirectories(Dir).Length == 0) // if directory is completely empty, add it { mid.Add(Dir + @"/"); @@ -324,7 +354,8 @@ namespace umbraco.cms.businesslogic.packager { foreach (string dirs in Directory.GetDirectories(Dir)) // do this recursively { - foreach (object obj in GenerateFileList(dirs)) { + foreach (object obj in GenerateFileList(dirs)) + { mid.Add(obj); } } diff --git a/src/umbraco.cms/businesslogic/Packager/RequirementsType.cs b/src/umbraco.cms/businesslogic/Packager/RequirementsType.cs new file mode 100644 index 0000000000..73022cda50 --- /dev/null +++ b/src/umbraco.cms/businesslogic/Packager/RequirementsType.cs @@ -0,0 +1,8 @@ +namespace umbraco.cms.businesslogic.packager +{ + public enum RequirementsType + { + Strict, + Legacy + } +} \ No newline at end of file diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index 95b619b961..bdd6d1e5c0 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -259,6 +259,7 @@ + From 59c3d6a2069e315afea163ee0ebbba346f12ec3d Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 4 Aug 2016 13:46:39 +0200 Subject: [PATCH 129/413] cleaning up migrations. we're deleting the table so no need to do a bunch of migration stuff instead of just compiling it into one migration deleting and creating the table again. --- .../AddRedirectUrlTable.cs | 37 +++++++---- .../AddRedirectUrlTable2.cs | 62 ------------------- .../AddRedirectUrlTable3.cs | 57 ----------------- .../AddRedirectUrlTable4.cs | 59 ------------------ .../Migrations/MigrationIssuesTests.cs | 5 +- 5 files changed, 26 insertions(+), 194 deletions(-) delete mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs delete mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable3.cs delete mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable4.cs diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs index d667d2d91f..983d6709bb 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs @@ -1,7 +1,6 @@ using System.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Migrations.Syntax.Create; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero @@ -21,24 +20,38 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer private string MigrationCode(Database database) { - // don't execute if the table is already there - var tables = SqlSyntax.GetTablesInSchema(database).ToArray(); - if (tables.InvariantContains("umbracoRedirectUrl")) return null; + var umbracoRedirectUrlTableName = "umbracoRedirectUrl"; var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - localContext.Create.Table("umbracoRedirectUrl") - .WithColumn("id").AsInt32().Identity().PrimaryKey("PK_umbracoRedirectUrl") - .WithColumn("contentId").AsInt32().NotNullable() + var tables = SqlSyntax.GetTablesInSchema(database).ToArray(); + + if (tables.InvariantContains(umbracoRedirectUrlTableName)) + { + localContext.Delete.Table(umbracoRedirectUrlTableName); + } + + localContext.Create.Table(umbracoRedirectUrlTableName) + .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_" + umbracoRedirectUrlTableName) .WithColumn("createDateUtc").AsDateTime().NotNullable() - .WithColumn("url").AsString(2048).NotNullable(); + .WithColumn("url").AsString(2048).NotNullable() + .WithColumn("contentKey").AsGuid().NotNullable() + .WithColumn("urlHash").AsString(16).NotNullable(); - localContext.Create.Index("IX_umbracoRedirectUrl") - .OnTable("umbracoRedirectUrl") - .OnColumn("url").Ascending() - .OnColumn("createDateUtc").Ascending() + localContext.Create.Index("IX_" + umbracoRedirectUrlTableName).OnTable(umbracoRedirectUrlTableName) + .OnColumn("urlHash") + .Ascending() + .OnColumn("contentKey") + .Ascending() + .OnColumn("createDateUtc") + .Descending() .WithOptions().NonClustered(); + localContext.Create.ForeignKey("FK_" + umbracoRedirectUrlTableName) + .FromTable(umbracoRedirectUrlTableName).ForeignColumn("contentKey") + .ToTable("umbracoNode").PrimaryColumn("uniqueID"); + + return localContext.GetSql(); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs deleted file mode 100644 index 2ea854d824..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable2.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Migrations.Syntax.Alter; -using Umbraco.Core.Persistence.Migrations.Syntax.Create; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete; -using Umbraco.Core.Persistence.Migrations.Syntax.Execute; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero -{ - [Migration("7.5.0", 101, GlobalSettings.UmbracoMigrationName)] - public class AddRedirectUrlTable2 : MigrationBase - { - public AddRedirectUrlTable2(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - // defer, because we are making decisions based upon what's in the database - Execute.Code(MigrationCode); - } - - private string MigrationCode(Database database) - { - var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoRedirectUrl") && x.ColumnName.InvariantEquals("contentKey"))) - return null; - - var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - - localContext.Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field - - var keyConstraints = SqlSyntax.GetConstraintsPerColumn(database).Distinct(); - var fk= keyConstraints - .SingleOrDefault(x => x.Item1 == "umbracoRedirectUrl" && x.Item2 == "contentId" && x.Item3.InvariantStartsWith("PK_") == false); - if (fk != null) - localContext.Delete.ForeignKey(fk.Item3).OnTable("umbracoRedirectUrl"); - localContext.Delete.Column("contentId").FromTable("umbracoRedirectUrl"); - - // SQL CE does not want to alter-add non-nullable columns ;-( - // but it's OK to create as nullable then alter, go figure - //localContext.Alter.Table("umbracoRedirectUrl") - // .AddColumn("contentKey").AsGuid().NotNullable(); - localContext.Alter.Table("umbracoRedirectUrl") - .AddColumn("contentKey").AsGuid().Nullable(); - localContext.Alter.Table("umbracoRedirectUrl") - .AlterColumn("contentKey").AsGuid().NotNullable(); - - localContext.Create.ForeignKey("FK_umbracoRedirectUrl") - .FromTable("umbracoRedirectUrl").ForeignColumn("contentKey") - .ToTable("umbracoNode").PrimaryColumn("uniqueID"); - - return localContext.GetSql(); - } - - public override void Down() - { } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable3.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable3.cs deleted file mode 100644 index a9550301b2..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable3.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero -{ - [Migration("7.5.0", 102, GlobalSettings.UmbracoMigrationName)] - public class AddRedirectUrlTable3 : MigrationBase - { - public AddRedirectUrlTable3(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - // defer, because we are making decisions based upon what's in the database - Execute.Code(MigrationCode); - } - private string MigrationCode(Database database) - { - var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoRedirectUrl") && x.ColumnName.InvariantEquals("hurl"))) - return null; - - var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - - localContext.Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field - - localContext.Delete.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl"); - - // SQL CE does not want to alter-add non-nullable columns ;-( - // but it's OK to create as nullable then alter, go figure - //localContext.Alter.Table("umbracoRedirectUrl") - // .AddColumn("urlHash").AsString(16).NotNullable(); - localContext.Alter.Table("umbracoRedirectUrl") - .AddColumn("hurl").AsString(16).Nullable(); - localContext.Alter.Table("umbracoRedirectUrl") - .AlterColumn("hurl").AsString(16).NotNullable(); - - localContext.Create.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl") - .OnColumn("hurl") - .Ascending() - .OnColumn("contentKey") - .Ascending() - .OnColumn("createDateUtc") - .Descending() - .WithOptions().NonClustered(); - - return localContext.GetSql(); - } - - public override void Down() - { } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable4.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable4.cs deleted file mode 100644 index e4724fc48f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable4.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero -{ - [Migration("7.5.0", 103, GlobalSettings.UmbracoMigrationName)] - public class AddRedirectUrlTable4 : MigrationBase - { - public AddRedirectUrlTable4(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - // defer, because we are making decisions based upon what's in the database - Execute.Code(MigrationCode); - } - private string MigrationCode(Database database) - { - var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoRedirectUrl") && x.ColumnName.InvariantEquals("urlHash"))) - return null; - - var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - - localContext.Execute.Sql("DELETE FROM umbracoRedirectUrl"); // else cannot add non-nullable field - - localContext.Delete.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl"); - - localContext.Delete.Column("hurl").FromTable("umbracoRedirectUrl"); - - // SQL CE does not want to alter-add non-nullable columns ;-( - // but it's OK to create as nullable then alter, go figure - //localContext.Alter.Table("umbracoRedirectUrl") - // .AddColumn("urlHash").AsString(16).NotNullable(); - localContext.Alter.Table("umbracoRedirectUrl") - .AddColumn("urlHash").AsString(16).Nullable(); - localContext.Alter.Table("umbracoRedirectUrl") - .AlterColumn("urlHash").AsString(16).NotNullable(); - - localContext.Create.Index("IX_umbracoRedirectUrl").OnTable("umbracoRedirectUrl") - .OnColumn("urlHash") - .Ascending() - .OnColumn("contentKey") - .Ascending() - .OnColumn("createDateUtc") - .Descending() - .WithOptions().NonClustered(); - - return localContext.GetSql(); - } - - public override void Down() - { } - } -} diff --git a/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs b/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs index d122a3cd25..26db7ef58c 100644 --- a/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs +++ b/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs @@ -119,10 +119,7 @@ namespace Umbraco.Tests.Migrations //pass in explicit migrations new DeleteRedirectUrlTable(SqlSyntax, logger), - new AddRedirectUrlTable(SqlSyntax, logger), - new AddRedirectUrlTable2(SqlSyntax, logger), - new AddRedirectUrlTable3(SqlSyntax, logger), - new AddRedirectUrlTable4(SqlSyntax, logger) + new AddRedirectUrlTable(SqlSyntax, logger) ); var db = new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Logger); From 66c0d9dc1228f58f5affb6d815da1bd98c2bb087 Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 4 Aug 2016 13:47:27 +0200 Subject: [PATCH 130/413] removing migrations from solution --- src/Umbraco.Core/Umbraco.Core.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5faa31a241..d6b9d4e570 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -427,9 +427,6 @@ - - - From ccaa198e257914d8e2e30894d3d36c94a1321128 Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 4 Aug 2016 13:51:17 +0200 Subject: [PATCH 131/413] changing integer IDs to guid for RedirectUrls. --- src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs | 6 +++--- .../Interfaces/IRedirectUrlRepository.cs | 4 ++-- .../Repositories/RedirectUrlRepository.cs | 16 ++++++++-------- src/Umbraco.Core/Services/IRedirectUrlService.cs | 2 +- src/Umbraco.Core/Services/RedirectUrlService.cs | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs index b23c75aa4b..ed720e8bc1 100644 --- a/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("umbracoRedirectUrl")] - [PrimaryKey("id")] + [PrimaryKey("id", autoIncrement = false)] [ExplicitColumns] class RedirectUrlDto { @@ -22,8 +22,8 @@ namespace Umbraco.Core.Models.Rdbms // inserts, and much faster on reads, so... we have an index on a hash. [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 1, Name = "PK_umbracoRedirectUrl")] - public int Id { get; set; } + [PrimaryKeyColumn(Name = "PK_umbracoRedirectUrl", AutoIncrement = false)] + public Guid Id { get; set; } [ResultColumn] public int ContentId { get; set; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs index 82f2e0e516..963128b7da 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Defines the repository. /// - public interface IRedirectUrlRepository : IRepositoryQueryable + public interface IRedirectUrlRepository : IRepositoryQueryable { /// /// Gets a redirect url. @@ -21,7 +21,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Deletes a redirect url. /// /// The redirect url identifier. - void Delete(int id); + void Delete(Guid id); /// /// Deletes all redirect urls. diff --git a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs index d66a08c129..eee1ae7a3e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs @@ -12,7 +12,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class RedirectUrlRepository : PetaPocoRepositoryBase, IRedirectUrlRepository + internal class RedirectUrlRepository : PetaPocoRepositoryBase, IRedirectUrlRepository { public RedirectUrlRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) @@ -23,19 +23,19 @@ namespace Umbraco.Core.Persistence.Repositories throw new NotSupportedException("This repository does not support this method."); } - protected override bool PerformExists(int id) + protected override bool PerformExists(Guid id) { return PerformGet(id) != null; } - protected override IRedirectUrl PerformGet(int id) + protected override IRedirectUrl PerformGet(Guid id) { var sql = GetBaseQuery(false).Where(x => x.Id == id); var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return dto == null ? null : Map(dto); } - protected override IEnumerable PerformGetAll(params int[] ids) + protected override IEnumerable PerformGetAll(params Guid[] ids) { if (ids.Length > 2000) throw new NotSupportedException("This repository does not support more than 2000 ids."); @@ -86,7 +86,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); { var dto = Map(entity); Database.Insert(dto); - entity.Id = dto.Id; + entity.Id = entity.Key.GetHashCode(); } protected override void PersistUpdatedItem(IRedirectUrl entity) @@ -101,7 +101,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); return new RedirectUrlDto { - Id = redirectUrl.Id, + Id = redirectUrl.Key, ContentKey = redirectUrl.ContentKey, CreateDateUtc = redirectUrl.CreateDateUtc, Url = redirectUrl.Url, @@ -117,7 +117,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); try { url.DisableChangeTracking(); - url.Id = dto.Id; + url.Id = dto.Id.GetHashCode(); url.ContentId = dto.ContentId; url.ContentKey = dto.ContentKey; url.CreateDateUtc = dto.CreateDateUtc; @@ -148,7 +148,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); Database.Execute("DELETE FROM umbracoRedirectUrl WHERE contentKey=@contentKey", new { contentKey }); } - public void Delete(int id) + public void Delete(Guid id) { Database.Delete(id); } diff --git a/src/Umbraco.Core/Services/IRedirectUrlService.cs b/src/Umbraco.Core/Services/IRedirectUrlService.cs index 7b21fa9bb7..d359958d63 100644 --- a/src/Umbraco.Core/Services/IRedirectUrlService.cs +++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Services /// Deletes a redirect url. /// /// The redirect url identifier. - void Delete(int id); + void Delete(Guid id); /// /// Deletes all redirect urls. diff --git a/src/Umbraco.Core/Services/RedirectUrlService.cs b/src/Umbraco.Core/Services/RedirectUrlService.cs index de698509d0..723532665a 100644 --- a/src/Umbraco.Core/Services/RedirectUrlService.cs +++ b/src/Umbraco.Core/Services/RedirectUrlService.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Services if (redir != null) redir.CreateDateUtc = DateTime.UtcNow; else - redir = new RedirectUrl { Url = url, ContentKey = contentKey }; + redir = new RedirectUrl { Key = Guid.NewGuid(), Url = url, ContentKey = contentKey }; repo.AddOrUpdate(redir); uow.Commit(); } @@ -39,7 +39,7 @@ namespace Umbraco.Core.Services } } - public void Delete(int id) + public void Delete(Guid id) { using (var uow = UowProvider.GetUnitOfWork()) using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) From ba11de51e329f55c0856238aa22f4dacd6a4b2fb Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 4 Aug 2016 15:18:38 +0200 Subject: [PATCH 132/413] deleting through the recycle bin also deletes redirect urls. --- .../Persistence/Repositories/RecycleBinRepository.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs index be1f03a73f..f17a0dd0d2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs @@ -49,6 +49,10 @@ namespace Umbraco.Core.Persistence.Repositories INNER JOIN umbracoNode as TB2 ON TB1.nodeId = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)", FormatDeleteStatement("umbracoAccess", "nodeId"), + @"DELETE FROM umbracoRedirectUrl WHERE umbracoRedirectUrl.id IN( + SELECT TB1.id FROM umbracoRedirectUrl as TB1 + INNER JOIN umbracoNode as TB2 ON TB1.contentKey = TB2.uniqueId + WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)", FormatDeleteStatement("umbracoRelation", "parentId"), FormatDeleteStatement("umbracoRelation", "childId"), FormatDeleteStatement("cmsTagRelationship", "nodeId"), From ae0ad2becfbcaf171d3c0ad153b02684dd4968cd Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 4 Aug 2016 15:23:57 +0200 Subject: [PATCH 133/413] switching to sha1 instead of md5. --- .../Persistence/Repositories/RedirectUrlRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs index eee1ae7a3e..2c608592b8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs @@ -196,7 +196,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); private static string HashUrl(string url) { - using (var crypto = new MD5CryptoServiceProvider()) + using (var crypto = new SHA1CryptoServiceProvider()) { var inputBytes = Encoding.UTF8.GetBytes(url); var hashedBytes = crypto.ComputeHash(inputBytes); From 8038016b551a43f6efe62e9c2b567fb8a706fb83 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 4 Aug 2016 15:57:28 +0200 Subject: [PATCH 134/413] U4-8778 The "Link your xx account" button doesn't work because it's a form wrapped in a form --- .../src/views/common/dialogs/user.html | 22 +++++++++---------- .../src/views/common/overlays/user/user.html | 22 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html index 83be1a7782..2b2d3bd1cf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html @@ -46,18 +46,18 @@
      -
      + + + - + + Link your {{login.caption}} account + + - + + Link your {{login.caption}} account + +
      + /// + /// By default this is true but by using events developers can toggle this off for certain documents if there is nothing to preview + /// + [DataMember( Name = "allowPreview" )] + public bool AllowPreview { get; set; } /// /// The allowed 'actions' based on the user's permissions - Create, Update, Publish, Send to publish diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 7f50b618d4..e2cdd1f7e4 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -66,6 +66,7 @@ namespace Umbraco.Web.Models.Mapping ? new[] {"Cannot generate urls without a current Umbraco Context"} : content.GetContentUrls(UmbracoContext.Current))) .ForMember(display => display.Properties, expression => expression.Ignore()) + .ForMember(display => display.AllowPreview, expression => expression.Ignore()) .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) .ForMember(display => display.Notifications, expression => expression.Ignore()) .ForMember(display => display.Errors, expression => expression.Ignore()) From b2e7c7a8d4289f24f36633bd32d2345b6f8f018f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 4 Aug 2016 19:13:47 +0200 Subject: [PATCH 141/413] More specific exception when a conversion fails --- src/Umbraco.Core/Models/Property.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index e5471217a5..dff6a712c7 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -135,7 +135,7 @@ namespace Umbraco.Core.Models private static void ThrowTypeException(object value, Type expected, string alias) { - throw new Exception(string.Format("Value \"{0}\" of type \"{1}\" could not be converted" + throw new InvalidOperationException(string.Format("Value \"{0}\" of type \"{1}\" could not be converted" + " to type \"{2}\" which is expected by property type \"{3}\".", value, value.GetType(), expected, alias)); } From e8e440516ffd406e54a4f4d9a43bc6d02c2ceefe Mon Sep 17 00:00:00 2001 From: bjarnef Date: Thu, 4 Aug 2016 20:30:54 +0200 Subject: [PATCH 142/413] Ensure editing area use full height when it only has a little or no content --- .../src/less/components/editor.less | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index dba5e29e74..00dd8592ff 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -2,7 +2,7 @@ contains styling for all main editor directives */ -.umb-editor-wrapper{ +.umb-editor-wrapper { background: white; position: absolute; top: 0; @@ -11,8 +11,13 @@ bottom: 0; display: flex; flex-direction: column; -} + > form { + display: flex; + flex-direction: column; + height: 100%; + } +} .umb-editor-header { background: @grayLighter; From a136235c72523f365f3844d74536cd8df3a0d7e3 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Thu, 4 Aug 2016 21:10:31 +0200 Subject: [PATCH 143/413] Show only empty recycle bin text, when filter doesn't has a value --- .../src/views/propertyeditors/listview/layouts/grid/grid.html | 4 ++-- .../src/views/propertyeditors/listview/layouts/list/list.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html index a2c0ff534e..ce2e2d836e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html @@ -18,7 +18,7 @@ @@ -68,7 +68,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html index 96bebb7abf..66a7e5a3f2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html @@ -58,7 +58,7 @@ From e1daa79babe4dfb64e32ce1c81f81e364cd2290e Mon Sep 17 00:00:00 2001 From: bjarnef Date: Thu, 4 Aug 2016 22:35:55 +0200 Subject: [PATCH 144/413] Open documentation link in new browser window/tab --- .../src/views/dashboard/developer/healthcheck.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html index ba355c22a2..66068183d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html @@ -5,7 +5,7 @@

      Health Check

      The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button. - You can add your own health checks, have a look at the documentation for more information about custom health checks.

      + You can add your own health checks, have a look at the documentation for more information about custom health checks.

      From 6c271d92f98bfc5a9250a8abf878e0d286f69885 Mon Sep 17 00:00:00 2001 From: Claus Date: Fri, 5 Aug 2016 08:25:58 +0200 Subject: [PATCH 145/413] migration should only delete table if it is using a integer id, otherwise leave it alone. --- .../TargetVersionSevenFiveZero/AddRedirectUrlTable.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs index 983d6709bb..d600cc30a4 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs @@ -28,9 +28,12 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer if (tables.InvariantContains(umbracoRedirectUrlTableName)) { + var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); + if (columns.Any(x => x.TableName.InvariantEquals(umbracoRedirectUrlTableName) && x.ColumnName.InvariantEquals("id") && x.DataType == "uniqueidentifier")) + return null; localContext.Delete.Table(umbracoRedirectUrlTableName); } - + localContext.Create.Table(umbracoRedirectUrlTableName) .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_" + umbracoRedirectUrlTableName) .WithColumn("createDateUtc").AsDateTime().NotNullable() @@ -51,7 +54,6 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer .FromTable(umbracoRedirectUrlTableName).ForeignColumn("contentKey") .ToTable("umbracoNode").PrimaryColumn("uniqueID"); - return localContext.GetSql(); } From 2f0ba3ac076cf19bfda9284f89e5bfe4c698fa66 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 5 Aug 2016 10:51:32 +0200 Subject: [PATCH 146/413] First part of html + js cleanup - use angular components in dashboard view + make js fit. --- .../redirecturlsearch.controller.js | 216 ++++++++---------- .../redirecturlsearch.html | 151 +++++++----- 2 files changed, 180 insertions(+), 187 deletions(-) diff --git a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js index 5dc8f901b2..51d50eebd6 100644 --- a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js +++ b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js @@ -1,166 +1,134 @@ -angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($scope, $http, angularHelper, notificationsService, entityResource, $routeParams) { +angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($scope, $http, angularHelper, notificationsService, entityResource, $routeParams, $q) { //...todo //search by url or url part //search by domain //display domain in dashboard results? - $scope.isSearch = false; - $scope.hasResults = false; + //used to cancel any request in progress if another one needs to take it's place + var canceler = null; - $scope.pagination = []; - $scope.isNew = false; - $scope.actionInProgress = false; - $scope.listViewResultSet = { - totalPages: 0, - items: [] + $scope.dashboard = { + searchTerm: "", + loading: false, + UrlTrackerDisabled: false }; - $scope.options = { - pageSize: 20, - pageNumber: ($routeParams.page && Number($routeParams.page) !== NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1 + $scope.pagination = { + pageIndex: 0, + pageNumber: 1, + totalPages: 1, + pageSize: 20 }; - $scope.next = function () { - if ($scope.options.pageNumber < $scope.pageCount) { - $scope.options.pageNumber++; - $scope.currentPage++; - $scope.load(); - } - }; + function activate() { + $scope.search(); + } - $scope.goToPage = function (pageNumber) { - $scope.options.pageNumber = pageNumber + 1; - $scope.currentPage = pageNumber; - $scope.load(); - }; - - $scope.prev = function () { - if ($scope.options.pageNumber > 1) { - $scope.options.pageNumber--; - $scope.currentPage--; - $scope.load(); - } + $scope.goToPage = function(pageNumber) { + $scope.pagination.pageIndex = pageNumber - 1; + $scope.pagination.pageNumber = pageNumber; + $scope.search(); }; $scope.search = function () { - var searchTerm = $scope.searchTerm; + + $scope.dashboard.loading = true; + + var searchTerm = $scope.dashboard.searchTerm; if (searchTerm === undefined) { searchTerm = ""; } - $http.get("backoffice/api/RedirectUrlManagement/SearchRedirectUrls/?searchTerm=" + searchTerm + "&page=" + $scope.currentPage + "&pageSize=" + $scope.options.pageSize).then(function (response) { - var matchingItems = response.data; - $scope.isSearch = true; - $scope.StatusMessage = matchingItems.StatusMessage; - $scope.hasResults = matchingItems.HasSearchResults; - $scope.redirectUrls = matchingItems.SearchResults; + $http.get("backoffice/api/RedirectUrlManagement/SearchRedirectUrls/?searchTerm=" + searchTerm + "&page=" + $scope.pagination.pageIndex + "&pageSize=" + $scope.pagination.pageSize).then(function (response) { - $scope.urlTrackerDisabled = matchingItems.UrlTrackerDisabled; - $scope.action = ""; - if ($scope.urlTrackerDisabled !== true) { - $scope.action = "Disable"; - } else { - $scope.action = "Enable"; - } + console.log(response); - $scope.pageCount = matchingItems.PageCount; - $scope.totalCount = matchingItems.TotalCount; - $scope.currentPage = matchingItems.CurrentPage; - $scope.options.pageNumber = matchingItems.CurrentPage + 1; - - if ($scope.options.pageNumber > $scope.pageCount) { - $scope.options.pageNumber = $scope.pageCount; - } + $scope.redirectUrls = response.data.SearchResults; - $scope.pagination = []; + // update pagination + $scope.pagination.pageIndex = response.data.CurrentPage; + $scope.pagination.pageNumber = response.data.CurrentPage + 1; + $scope.pagination.totalPages = response.data.PageCount; - var i; - if ($scope.pageCount <= 10) { - for (i = 0; i < $scope.pageCount; i++) { - $scope.pagination.push({ - val: (i + 1), - isActive: $scope.options.pageNumber === (i + 1) - }); - } - } else { - //if there is more than 10 pages, we need to do some fancy bits + // Set enable/disable state for url tracker + $scope.dashboard.UrlTrackerDisabled = response.data.UrlTrackerDisabled; - //get the max index to start - var maxIndex = $scope.pageCount - 10; - //set the start, but it can't be below zero - var start = Math.max($scope.options.pageNumber - 5, 0); - //ensure that it's not too far either - start = Math.min(maxIndex, start); - - for (i = start; i < (10 + start) ; i++) { - $scope.pagination.push({ - val: (i + 1), - isActive: $scope.options.pageNumber === (i + 1) - }); - } - - //now, if the start is greater than 0 then '1' will not be displayed, so do the elipses thing - if (start > 0) { - $scope.pagination.unshift({ name: "First", val: 1, isActive: false }, { val: "...", isActive: false }); - } - - //same for the end - if (start < maxIndex) { - $scope.pagination.push({ val: "...", isActive: false }, { name: "Last", val: $scope.pageCount, isActive: false }); - } - } - - angular.forEach($scope.redirectUrls, function (item) { + angular.forEach($scope.redirectUrls, function (item) { $http.get("backoffice/api/RedirectUrlManagement/GetPublishedUrl/?id=" + item.ContentId).then(function (response) { item.ContentUrl = response.data; - }); + }); }); - }); - }; - $scope.load = function () { - // $scope.searchTerm = ""; - $scope.search(); + $scope.dashboard.loading = false; + + }); }; $scope.removeRedirect = function (redirectToDelete) { $http.post("backoffice/api/RedirectUrlManagement/DeleteRedirectUrl/" + redirectToDelete.Id).then(function (response) { if (response.status === 200) { - notificationsService.success("Redirect Url Removed!", "Redirect Url " + redirectToDelete.Url + " has been deleted"); - // now remove from table client sides - var index = -1; - var urlArr = eval($scope.redirectUrls); - for (var i = 0; i < urlArr.length; i++) { - if (urlArr[i].Id === redirectToDelete.Id) { - index = i; - break; - } - } - if (index === -1) { - notificationsService.warning("Redirect Url Removal Error!", "Redirect Url " + redirectToDelete.Url + " may have already been removed"); - } + + var index = $scope.redirectUrls.indexOf(redirectToDelete); $scope.redirectUrls.splice(index, 1); + + notificationsService.success("Redirect Url Removed!", "Redirect Url " + redirectToDelete.Url + " has been deleted"); } else { notificationsService.warning("Redirect Url Error!", "Redirect Url " + redirectToDelete.Url + " was not deleted"); - } - }); + } + }); }; - $scope.toggleUrlTracker = function () { - var toggleConfirm = confirm("Are you sure you want to " + $scope.action.toLowerCase() + " the URL tracker?"); + $scope.disableUrlTracker = function () { + var toggleConfirm = confirm("Are you sure you want to disable the URL tracker?"); if (toggleConfirm) { - $http.post("backoffice/api/RedirectUrlManagement/ToggleUrlTracker/?disable=" + (!$scope.urlTrackerDisabled)).then(function (response) { - if (response.status === 200) { - notificationsService.success("URL Tracker has now been " + $scope.action.toLowerCase() + "d."); - - $scope.load(); - } else { - notificationsService.warning("Error " + $scope.action.toLowerCase() + "ing the URL Tracker, more information can be found in your log file."); - } - }); + $http.post("backoffice/api/RedirectUrlManagement/ToggleUrlTracker/?disable=true").then(function (response) { + if (response.status === 200) { + notificationsService.success("URL Tracker has now been disabled"); + activate(); + } else { + notificationsService.warning("Error disabling the URL Tracker, more information can be found in your log file."); + } + }); } }; - $scope.load(); -}); \ No newline at end of file + $scope.enableUrlTracker = function () { + if (toggleConfirm) { + $http.post("backoffice/api/RedirectUrlManagement/ToggleUrlTracker/?disable=false").then(function (response) { + if (response.status === 200) { + notificationsService.success("URL Tracker has now been enabled"); + activate(); + } else { + notificationsService.warning("Error enabling the URL Tracker, more information can be found in your log file."); + } + }); + } + }; + + var filterDebounced = _.debounce(function(e) { + + $scope.$apply(function () { + + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } + else { + canceler = $q.defer(); + } + + $scope.search(); + + }); + + }, 200); + + $scope.filter = function() { + filterDebounced(); + }; + + activate(); + +}); diff --git a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html index dcc3c01b90..8c4a342aaa 100644 --- a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html +++ b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html @@ -1,73 +1,98 @@
      -
      - -
      + -

      Redirect Url Management

      -

      When you move and rename pages in Umbraco, 301 permanent redirects are automatically created

      -

      Here is a list of the redirects you

      + -
      -
      - -
      - + + + + + + + + + + + + + + + + + + + + + +
      + +
      +
      +
      +
      Original Url
      +
      Redirected To
      +
      Date Created
      +
      + +
      + +
      + +
      + +
      + + + + + +
      + {{redirectUrl.CreateDateUtc | date:'medium'}} +
      + +
      + Edit + +
      + +
      + +
      +
      -
      -
      - - - +
      + + +
      - - - - - - - - - - - - - - - -
      Original UrlRedirected ToDate CreatedRemove
      {{redirectUrl.Url}}{{redirectUrl.ContentUrl}} {{redirectUrl.CreateDateUtc | date:'medium'}}
      -
      -
      - - -
      - From 36a191464d2241f6e4cd4e961bf4d53ac63f3f24 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 5 Aug 2016 11:26:20 +0200 Subject: [PATCH 147/413] ensures table name is quoted properly --- src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 3b18477caa..97981849bc 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -454,7 +454,9 @@ namespace Umbraco.Core.Persistence.Repositories if (filter.IsNullOrWhiteSpace() == false) { sbWhere - .Append("AND (umbracoNode.") + .Append("AND (") + .Append(SqlSyntax.GetQuotedTableName("umbracoNode")) + .Append(".") .Append(SqlSyntax.GetQuotedColumnName("text")) .Append(" LIKE @") .Append(args.Count) From 3ece30bd8ba3f9ee05bc1de5865a2e3082c8957f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 5 Aug 2016 11:41:08 +0200 Subject: [PATCH 148/413] Don't let the event handlers kick in if Redirect Tracking is turned off in the config --- src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs index 82c06ca7d5..39b3351aac 100644 --- a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs +++ b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs @@ -61,6 +61,9 @@ namespace Umbraco.Web.Redirects /// protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { + // don't let the event handlers kick in if Redirect Tracking is turned off in the config + if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableRedirectUrlTracking) return; + // events are weird // on 'published' we 'could' get the old or the new route depending on event handlers order // so it is not reliable. getting the old route in 'publishing' to be sure and storing in http From 3dcba7a6b0bbcb4effa4dc56880c2d4e8f902cba Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 5 Aug 2016 11:51:08 +0200 Subject: [PATCH 149/413] urlHash column has to be 20 chars because SHA1 uses more bytes than MD5 --- .../Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs index d600cc30a4..ce6af9186c 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs @@ -39,7 +39,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer .WithColumn("createDateUtc").AsDateTime().NotNullable() .WithColumn("url").AsString(2048).NotNullable() .WithColumn("contentKey").AsGuid().NotNullable() - .WithColumn("urlHash").AsString(16).NotNullable(); + .WithColumn("urlHash").AsString(20).NotNullable(); localContext.Create.Index("IX_" + umbracoRedirectUrlTableName).OnTable(umbracoRedirectUrlTableName) .OnColumn("urlHash") From 774f00d4f500c66efd546fc41a8c38b47fc69a56 Mon Sep 17 00:00:00 2001 From: David Peck Date: Fri, 5 Aug 2016 11:14:01 +0100 Subject: [PATCH 150/413] Children added to HtmlTagWrapper twice By calling the overload on line 884 the children are added. To do it again results in the children being added twice. --- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index c161e32ec6..3062613b6b 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -883,10 +883,6 @@ namespace Umbraco.Web { var item = html.Wrap(tag, innerText, anonymousAttributes, children); item.Visible = visible; - foreach (var child in children) - { - item.AddChild(child); - } return item; } From 6a3f146da1f96aef67d88127087c0f65a7149b37 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 5 Aug 2016 12:29:05 +0200 Subject: [PATCH 151/413] Fixes health check index & throbber while fixing, fixes xml data integrity check to check for invalid XML that doesn't contain a GUID --- .../developer/healthcheck.controller.js | 2 + .../dashboard/developer/healthcheck.html | 12 +-- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 +- .../umbraco/config/lang/en_us.xml | 4 +- .../XmlDataIntegrityHealthCheck.cs | 87 ++++++++++++++++--- 5 files changed, 86 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js index ea25195ffa..8631b09a45 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js @@ -73,9 +73,11 @@ } function executeAction(check, index, action) { + check.loading = true; healthCheckResource.executeAction(action) .then(function(response) { check.status[index] = response; + check.loading = false; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html index ba355c22a2..a50d70211a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html @@ -82,10 +82,10 @@
      - - - - + + + +
      @@ -101,7 +101,9 @@
      - + + +
      diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 439cc1aaab..2b5ce44d79 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1352,8 +1352,8 @@ To manage your website, simply open the Umbraco back office and start adding con There was an error, check log for full error: %0%. Total XML: %0%, Total: %1% - Total XML: %0%, Total: %1% - Total XML: %0%, Total published: %1% + Total XML: %0%, Total: %1%, Total invalid %2% + Total XML: %0%, Total published: %1%, Total invalid %2% Certificate validation error: '%0%' Error pinging the URL %0% - '%1%' diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index c2c3417e66..05015116ee 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1356,8 +1356,8 @@ To manage your website, simply open the Umbraco back office and start adding con There was an error, check log for full error: %0%. Total XML: %0%, Total: %1% - Total XML: %0%, Total: %1% - Total XML: %0%, Total published: %1% + Total XML: %0%, Total: %1%, Total invalid %2% + Total XML: %0%, Total published: %1%, Total invalid %2% Certificate validation error: '%0%' Error pinging the URL %0% - '%1%' diff --git a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs index 5ba16305de..8895e18af9 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs @@ -92,46 +92,105 @@ namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity }; } + /// + /// Checks the cmsContentXml table to see if the number of entries for media matches the number of media entities + /// + /// + /// + /// Further to just counting the data integrity, this also checks if the XML stored in the cmsContentXml table contains the correct + /// GUID identifier. In older versions of Umbraco, the GUID is not persisted to the content cache, that will cause problems in newer + /// versions of Umbraco, so the xml table would need a rebuild. + /// private HealthCheckStatus CheckMedia() { var total = _services.MediaService.Count(); var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); - var subQuery = new Sql() + + //count entries + var countTotalSubQuery = new Sql() .Select("Count(*)") .From(_sqlSyntax) .InnerJoin(_sqlSyntax) .On(_sqlSyntax, left => left.NodeId, right => right.NodeId) .Where(dto => dto.NodeObjectType == mediaObjectType); - var totalXml = _database.ExecuteScalar(subQuery); + var totalXml = _database.ExecuteScalar(countTotalSubQuery); + //count invalid entries + var countNonGuidQuery = new Sql() + .Select("Count(*)") + .From(_sqlSyntax) + .InnerJoin(_sqlSyntax) + .On(_sqlSyntax, left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == mediaObjectType) + .Where(string.Format("cmsContentXml.{0} NOT LIKE '% key=\"%'", _sqlSyntax.GetQuotedColumnName("xml"))); + var totalNonGuidXml = _database.ExecuteScalar(countNonGuidQuery); + + var hasError = false; var actions = new List(); - if (totalXml != total) - actions.Add(new HealthCheckAction(CheckMediaXmlTableAction, Id)); - - return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckMedia", new[] { totalXml.ToString(), total.ToString() })) + if (totalXml != total || totalNonGuidXml > 0) { - ResultType = totalXml == total ? StatusResultType.Success : StatusResultType.Error, + //if the counts don't match + actions.Add(new HealthCheckAction(CheckMediaXmlTableAction, Id)); + hasError = true; + } + + return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckMedia", + new[] { totalXml.ToString(), total.ToString(), totalNonGuidXml.ToString() })) + { + ResultType = hasError == false + ? StatusResultType.Success + : StatusResultType.Error, Actions = actions }; } + /// + /// Checks the cmsContentXml table to see if the number of entries for content matches the number of published content entities + /// + /// + /// + /// Further to just counting the data integrity, this also checks if the XML stored in the cmsContentXml table contains the correct + /// GUID identifier. In older versions of Umbraco, the GUID is not persisted to the content cache, that will cause problems in newer + /// versions of Umbraco, so the xml table would need a rebuild. + /// private HealthCheckStatus CheckContent() { var total = _services.ContentService.CountPublished(); - var subQuery = new Sql() + var documentObjectType = Guid.Parse(Constants.ObjectTypes.Document); + + //count entires + var countTotalSubQuery = new Sql() .Select("DISTINCT cmsContentXml.nodeId") .From(_sqlSyntax) .InnerJoin(_sqlSyntax) .On(_sqlSyntax, left => left.NodeId, right => right.NodeId); - var totalXml = _database.ExecuteScalar("SELECT COUNT(*) FROM (" + subQuery.SQL + ") as tmp"); + var totalXml = _database.ExecuteScalar("SELECT COUNT(*) FROM (" + countTotalSubQuery.SQL + ") as tmp"); + + //count invalid entries + var countNonGuidQuery = new Sql() + .Select("Count(*)") + .From(_sqlSyntax) + .InnerJoin(_sqlSyntax) + .On(_sqlSyntax, left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == documentObjectType) + .Where(string.Format("cmsContentXml.{0} NOT LIKE '% key=\"%'", _sqlSyntax.GetQuotedColumnName("xml"))); + var totalNonGuidXml = _database.ExecuteScalar(countNonGuidQuery); + var hasError = false; var actions = new List(); - if (totalXml != total) - actions.Add(new HealthCheckAction(CheckContentXmlTableAction, Id)); - - return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckContent", new[] { totalXml.ToString(), total.ToString() })) + if (totalXml != total || totalNonGuidXml > 0) { - ResultType = totalXml == total ? StatusResultType.Success : StatusResultType.Error, + //if the counts don't match + actions.Add(new HealthCheckAction(CheckContentXmlTableAction, Id)); + hasError = true; + } + + return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckContent", + new[] { totalXml.ToString(), total.ToString(), totalNonGuidXml.ToString() })) + { + ResultType = hasError == false + ? StatusResultType.Success + : StatusResultType.Error, Actions = actions }; } From 1f8dc9a42463e04be9ecd4f63d5b11601c97696f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 5 Aug 2016 12:36:10 +0200 Subject: [PATCH 152/413] move dashboard from App_Plugins to dashboards folder --- .../developer/redirecturls.controller.js} | 30 ++++++++---------- .../dashboard/developer/redirecturls.html} | 2 +- .../RedirectUrlDashboard/package.manifest | Bin 336 -> 0 bytes src/Umbraco.Web.UI/config/Dashboard.config | 2 +- 4 files changed, 16 insertions(+), 18 deletions(-) rename src/{Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js => Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js} (82%) rename src/{Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html => Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html} (97%) delete mode 100644 src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/package.manifest diff --git a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js similarity index 82% rename from src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js rename to src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js index 51d50eebd6..0bf6c60427 100644 --- a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js @@ -1,4 +1,4 @@ -angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($scope, $http, angularHelper, notificationsService, entityResource, $routeParams, $q) { +angular.module("umbraco").controller("Umbraco.Dashboard.RedirectUrlsController", function($scope, $http, angularHelper, notificationsService, entityResource, $routeParams, $q) { //...todo //search by url or url part //search by domain @@ -30,7 +30,7 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco $scope.search(); }; - $scope.search = function () { + $scope.search = function() { $scope.dashboard.loading = true; @@ -39,7 +39,7 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco searchTerm = ""; } - $http.get("backoffice/api/RedirectUrlManagement/SearchRedirectUrls/?searchTerm=" + searchTerm + "&page=" + $scope.pagination.pageIndex + "&pageSize=" + $scope.pagination.pageSize).then(function (response) { + $http.get("backoffice/api/RedirectUrlManagement/SearchRedirectUrls/?searchTerm=" + searchTerm + "&page=" + $scope.pagination.pageIndex + "&pageSize=" + $scope.pagination.pageSize).then(function(response) { console.log(response); @@ -53,8 +53,8 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco // Set enable/disable state for url tracker $scope.dashboard.UrlTrackerDisabled = response.data.UrlTrackerDisabled; - angular.forEach($scope.redirectUrls, function (item) { - $http.get("backoffice/api/RedirectUrlManagement/GetPublishedUrl/?id=" + item.ContentId).then(function (response) { + angular.forEach($scope.redirectUrls, function(item) { + $http.get("backoffice/api/RedirectUrlManagement/GetPublishedUrl/?id=" + item.ContentId).then(function(response) { item.ContentUrl = response.data; }); }); @@ -64,25 +64,24 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco }); }; - $scope.removeRedirect = function (redirectToDelete) { - $http.post("backoffice/api/RedirectUrlManagement/DeleteRedirectUrl/" + redirectToDelete.Id).then(function (response) { + $scope.removeRedirect = function(redirectToDelete) { + $http.post("backoffice/api/RedirectUrlManagement/DeleteRedirectUrl/" + redirectToDelete.Id).then(function(response) { if (response.status === 200) { var index = $scope.redirectUrls.indexOf(redirectToDelete); $scope.redirectUrls.splice(index, 1); notificationsService.success("Redirect Url Removed!", "Redirect Url " + redirectToDelete.Url + " has been deleted"); - } - else { + } else { notificationsService.warning("Redirect Url Error!", "Redirect Url " + redirectToDelete.Url + " was not deleted"); } }); }; - $scope.disableUrlTracker = function () { + $scope.disableUrlTracker = function() { var toggleConfirm = confirm("Are you sure you want to disable the URL tracker?"); if (toggleConfirm) { - $http.post("backoffice/api/RedirectUrlManagement/ToggleUrlTracker/?disable=true").then(function (response) { + $http.post("backoffice/api/RedirectUrlManagement/ToggleUrlTracker/?disable=true").then(function(response) { if (response.status === 200) { notificationsService.success("URL Tracker has now been disabled"); activate(); @@ -93,9 +92,9 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco } }; - $scope.enableUrlTracker = function () { + $scope.enableUrlTracker = function() { if (toggleConfirm) { - $http.post("backoffice/api/RedirectUrlManagement/ToggleUrlTracker/?disable=false").then(function (response) { + $http.post("backoffice/api/RedirectUrlManagement/ToggleUrlTracker/?disable=false").then(function(response) { if (response.status === 200) { notificationsService.success("URL Tracker has now been enabled"); activate(); @@ -108,14 +107,13 @@ angular.module("umbraco").controller("Umbraco.RedirectUrlSearch", function ($sco var filterDebounced = _.debounce(function(e) { - $scope.$apply(function () { + $scope.$apply(function() { //a canceler exists, so perform the cancelation operation and reset if (canceler) { canceler.resolve(); canceler = $q.defer(); - } - else { + } else { canceler = $q.defer(); } diff --git a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html similarity index 97% rename from src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html rename to src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html index 8c4a342aaa..26c2ad7e2d 100644 --- a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/redirecturlsearch.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html @@ -1,4 +1,4 @@ -
      +
      diff --git a/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/package.manifest b/src/Umbraco.Web.UI/App_Plugins/RedirectUrlDashboard/package.manifest deleted file mode 100644 index 996d847a970ac78a86a2bf8587df2fe0e4afc821..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 336 zcmaKo%?g505QV?3LGK`!mQnk5K@Si`tB^3QEKEhSP1K`XXD$Vgp-;26=6ka6Hjta_%tW6GRL*N!DQEho+tBB!1zR2`*gxUuf3CtTDkMLNdb!yGQ! zd)I9~wUm`q=Cl10#=QPGuH=+TSL>ZfyU|z9!f(vj%P&?Z)dnN_Wb4UBT_fx-Cwf}G io9a?qa?NUWEw}S|>|_F>Uj9Tgn}+^ueyJl*KluP=xHyXd diff --git a/src/Umbraco.Web.UI/config/Dashboard.config b/src/Umbraco.Web.UI/config/Dashboard.config index cc20f70654..9eb808c67c 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.config +++ b/src/Umbraco.Web.UI/config/Dashboard.config @@ -115,7 +115,7 @@ - /App_Plugins/RedirectUrlDashboard/redirecturlsearch.html + views/dashboard/developer/redirecturls.html From 3e932ecf3fdd527795b805cc319dd569e8a3bea6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 5 Aug 2016 13:06:35 +0200 Subject: [PATCH 153/413] fixes icon and author urls for local package install --- .../src/views/packager/views/install-local.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html index ec8c1e5476..07fb21d00b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html @@ -69,8 +69,8 @@
      - - + +
      @@ -79,7 +79,7 @@
      From 69b6044bc640ad27a65c9412eaf32f4800b40db5 Mon Sep 17 00:00:00 2001 From: Claus Date: Fri, 5 Aug 2016 14:01:25 +0200 Subject: [PATCH 154/413] fixing null check on UmbracoContext.Current in RedirectTrackingEventHandler's PageCacheRefresher_CacheUpdated. --- src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs index 39b3351aac..417238bdbe 100644 --- a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs +++ b/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs @@ -93,6 +93,8 @@ namespace Umbraco.Web.Redirects { get { + if (UmbracoContext.Current == null) + return null; var oldRoutes = (Dictionary>) UmbracoContext.Current.HttpContext.Items[ContextKey3]; if (oldRoutes == null) UmbracoContext.Current.HttpContext.Items[ContextKey3] = oldRoutes = new Dictionary>(); @@ -165,8 +167,11 @@ namespace Umbraco.Web.Redirects private void PageCacheRefresher_CacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs cacheRefresherEventArgs) { - var removeKeys = new List(); + if (OldRoutes == null) + return; + var removeKeys = new List(); + foreach (var oldRoute in OldRoutes) { // assuming we cannot have 'CacheUpdated' for only part of the infos else we'd need From b8b30bdc6ed97d18363c13ddb1beec5b895e79ca Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 5 Aug 2016 14:35:58 +0200 Subject: [PATCH 155/413] make resource for all redirect url end points --- .../common/resources/redirecturls.resource.js | 127 ++++++++++++++++++ .../developer/redirecturls.controller.js | 72 +++++----- 2 files changed, 162 insertions(+), 37 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js new file mode 100644 index 0000000000..f79c773eea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js @@ -0,0 +1,127 @@ +/** + * @ngdoc service + * @name umbraco.resources.redirectUrlResource + * @function + * + * @description + * Used by the redirect url dashboard to get urls and send requests to remove redirects. + */ +(function() { + 'use strict'; + + function redirectUrlsResource($http, umbRequestHelper) { + + var redirectBaseUrl = "backoffice/api/RedirectUrlManagement/"; + + /** + * @ngdoc function + * @name umbraco.resources.redirectUrlResource#searchRedirectUrls + * @methodOf umbraco.resources.redirectUrlResource + * @function + * + * @description + * Called to search redirects + * ##usage + *
      +         * redirectUrlsResource.searchRedirectUrls("", 0, 20)
      +         *    .then(function(response) {
      +         *
      +         *    });
      +         * 
      + * @param {String} searchTerm Searh term + * @param {Int} pageIndex index of the page to retrive items from + * @param {Int} pageSize The number of items on a page + */ + function searchRedirectUrls(searchTerm, pageIndex, pageSize) { + return umbRequestHelper.resourcePromise( + $http.get(redirectBaseUrl + "SearchRedirectUrls/?searchTerm=" + searchTerm + "&page=" + pageIndex + "&pageSize=" + pageSize), + "Failed to retrieve redirects" + ); + } + + /** + * @ngdoc function + * @name umbraco.resources.redirectUrlResource#deleteRedirectUrl + * @methodOf umbraco.resources.redirectUrlResource + * @function + * + * @description + * Called to delete a redirect + * ##usage + *
      +         * redirectUrlsResource.deleteRedirectUrl(1234)
      +         *    .then(function() {
      +         *
      +         *    });
      +         * 
      + * @param {Int} id Id of the redirect + */ + function deleteRedirectUrl(id) { + return umbRequestHelper.resourcePromise( + $http.post(redirectBaseUrl + "DeleteRedirectUrl/" + id), + "Failed to remove redirect" + ); + } + + /** + * @ngdoc function + * @name umbraco.resources.redirectUrlResource#toggleUrlTracker + * @methodOf umbraco.resources.redirectUrlResource + * @function + * + * @description + * Called to enable or disable redirect url tracker + * ##usage + *
      +         * redirectUrlsResource.toggleUrlTracker(true)
      +         *    .then(function() {
      +         *
      +         *    });
      +         * 
      + * @param {Bool} disable true/false to disable/enable the url tracker + */ + function toggleUrlTracker(disable) { + return umbRequestHelper.resourcePromise( + $http.post(redirectBaseUrl + "ToggleUrlTracker/?disable=" + disable), + "Failed to toggle redirect url tracker" + ); + } + + /** + * @ngdoc function + * @name umbraco.resources.redirectUrlResource#getPublishedUrl + * @methodOf umbraco.resources.redirectUrlResource + * @function + * + * @description + * Called to get the published url for a content item + * ##usage + *
      +         * redirectUrlsResource.getPublishedUrl(1234)
      +         *    .then(function() {
      +         *
      +         *    });
      +         * 
      + * @param {Int} contentId The content id of the item to get the published url + */ + function getPublishedUrl(contentId) { + return umbRequestHelper.resourcePromise( + $http.get(redirectBaseUrl + "GetPublishedUrl/?id=" + contentId), + "Failed to get published url" + ); + } + + var resource = { + searchRedirectUrls: searchRedirectUrls, + deleteRedirectUrl: deleteRedirectUrl, + toggleUrlTracker: toggleUrlTracker, + getPublishedUrl: getPublishedUrl + }; + + return resource; + + } + + angular.module('umbraco.resources').factory('redirectUrlsResource', redirectUrlsResource); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js index 0bf6c60427..439ecb08bd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js @@ -1,4 +1,4 @@ -angular.module("umbraco").controller("Umbraco.Dashboard.RedirectUrlsController", function($scope, $http, angularHelper, notificationsService, entityResource, $routeParams, $q) { +angular.module("umbraco").controller("Umbraco.Dashboard.RedirectUrlsController", function($scope, redirectUrlsResource, notificationsService, $q) { //...todo //search by url or url part //search by domain @@ -39,23 +39,23 @@ angular.module("umbraco").controller("Umbraco.Dashboard.RedirectUrlsController", searchTerm = ""; } - $http.get("backoffice/api/RedirectUrlManagement/SearchRedirectUrls/?searchTerm=" + searchTerm + "&page=" + $scope.pagination.pageIndex + "&pageSize=" + $scope.pagination.pageSize).then(function(response) { + redirectUrlsResource.searchRedirectUrls(searchTerm, $scope.pagination.pageIndex, $scope.pagination.pageSize).then(function(response) { - console.log(response); - - $scope.redirectUrls = response.data.SearchResults; + $scope.redirectUrls = response.SearchResults; // update pagination - $scope.pagination.pageIndex = response.data.CurrentPage; - $scope.pagination.pageNumber = response.data.CurrentPage + 1; - $scope.pagination.totalPages = response.data.PageCount; + $scope.pagination.pageIndex = response.CurrentPage; + $scope.pagination.pageNumber = response.CurrentPage + 1; + $scope.pagination.totalPages = response.PageCount; // Set enable/disable state for url tracker - $scope.dashboard.UrlTrackerDisabled = response.data.UrlTrackerDisabled; + $scope.dashboard.UrlTrackerDisabled = response.UrlTrackerDisabled; - angular.forEach($scope.redirectUrls, function(item) { - $http.get("backoffice/api/RedirectUrlManagement/GetPublishedUrl/?id=" + item.ContentId).then(function(response) { - item.ContentUrl = response.data; + angular.forEach($scope.redirectUrls, function(redirect) { + redirectUrlsResource.getPublishedUrl(redirect.ContentId).then(function(response) { + redirect.ContentUrl = response; + }, function(error) { + notificationsService.error("Redirect Url Error!", "Failed to get published url for " + redirect.Url); }); }); @@ -65,44 +65,42 @@ angular.module("umbraco").controller("Umbraco.Dashboard.RedirectUrlsController", }; $scope.removeRedirect = function(redirectToDelete) { - $http.post("backoffice/api/RedirectUrlManagement/DeleteRedirectUrl/" + redirectToDelete.Id).then(function(response) { - if (response.status === 200) { - var index = $scope.redirectUrls.indexOf(redirectToDelete); - $scope.redirectUrls.splice(index, 1); + redirectUrlsResource.deleteRedirectUrl(redirectToDelete.Id).then(function() { + + var index = $scope.redirectUrls.indexOf(redirectToDelete); + $scope.redirectUrls.splice(index, 1); + notificationsService.success("Redirect Url Removed!", "Redirect Url " + redirectToDelete.Url + " has been deleted"); + + }, function(error) { + + notificationsService.error("Redirect Url Error!", "Redirect Url " + redirectToDelete.Url + " was not deleted"); - notificationsService.success("Redirect Url Removed!", "Redirect Url " + redirectToDelete.Url + " has been deleted"); - } else { - notificationsService.warning("Redirect Url Error!", "Redirect Url " + redirectToDelete.Url + " was not deleted"); - } }); + }; $scope.disableUrlTracker = function() { var toggleConfirm = confirm("Are you sure you want to disable the URL tracker?"); if (toggleConfirm) { - $http.post("backoffice/api/RedirectUrlManagement/ToggleUrlTracker/?disable=true").then(function(response) { - if (response.status === 200) { - notificationsService.success("URL Tracker has now been disabled"); - activate(); - } else { - notificationsService.warning("Error disabling the URL Tracker, more information can be found in your log file."); - } + + redirectUrlsResource.toggleUrlTracker(true).then(function() { + activate(); + notificationsService.success("URL Tracker has now been disabled"); + }, function(error) { + notificationsService.warning("Error disabling the URL Tracker, more information can be found in your log file."); }); + } }; $scope.enableUrlTracker = function() { - if (toggleConfirm) { - $http.post("backoffice/api/RedirectUrlManagement/ToggleUrlTracker/?disable=false").then(function(response) { - if (response.status === 200) { - notificationsService.success("URL Tracker has now been enabled"); - activate(); - } else { - notificationsService.warning("Error enabling the URL Tracker, more information can be found in your log file."); - } - }); - } + redirectUrlsResource.toggleUrlTracker(false).then(function() { + activate(); + notificationsService.success("URL Tracker has now been enabled"); + }, function(error) { + notificationsService.warning("Error enabling the URL Tracker, more information can be found in your log file."); + }); }; var filterDebounced = _.debounce(function(e) { From 19325ac52230cfd132ff666bc290274bfe2131c6 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 5 Aug 2016 14:44:37 +0200 Subject: [PATCH 156/413] Fixes the build --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 7ec05f3265..51c3f4b053 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -578,8 +578,6 @@ - - @@ -639,7 +637,6 @@ - 404handlers.config From aa66fd6e335b9e71e4116880e5fe020f4da33e67 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 5 Aug 2016 16:59:03 +0200 Subject: [PATCH 157/413] rewrite controller as vm + refactor controller to follow angular style guide --- .../developer/redirecturls.controller.js | 236 ++++++++++-------- .../dashboard/developer/redirecturls.html | 32 +-- 2 files changed, 141 insertions(+), 127 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js index 439ecb08bd..7cc3baca00 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js @@ -1,130 +1,144 @@ -angular.module("umbraco").controller("Umbraco.Dashboard.RedirectUrlsController", function($scope, redirectUrlsResource, notificationsService, $q) { - //...todo - //search by url or url part - //search by domain - //display domain in dashboard results? +(function() { + "use strict"; - //used to cancel any request in progress if another one needs to take it's place - var canceler = null; + function RedirectUrlsController($scope, redirectUrlsResource, notificationsService, $q) { + //...todo + //search by url or url part + //search by domain + //display domain in dashboard results? - $scope.dashboard = { - searchTerm: "", - loading: false, - UrlTrackerDisabled: false - }; + //used to cancel any request in progress if another one needs to take it's place + var vm = this; + var canceler = null; - $scope.pagination = { - pageIndex: 0, - pageNumber: 1, - totalPages: 1, - pageSize: 20 - }; + vm.dashboard = { + searchTerm: "", + loading: false, + UrlTrackerDisabled: false + }; - function activate() { - $scope.search(); - } + vm.pagination = { + pageIndex: 0, + pageNumber: 1, + totalPages: 1, + pageSize: 20 + }; - $scope.goToPage = function(pageNumber) { - $scope.pagination.pageIndex = pageNumber - 1; - $scope.pagination.pageNumber = pageNumber; - $scope.search(); - }; + vm.goToPage = goToPage; + vm.search = search; + vm.removeRedirect = removeRedirect; + vm.disableUrlTracker = disableUrlTracker; + vm.enableUrlTracker = enableUrlTracker; + vm.filter = filter; - $scope.search = function() { - - $scope.dashboard.loading = true; - - var searchTerm = $scope.dashboard.searchTerm; - if (searchTerm === undefined) { - searchTerm = ""; + function activate() { + vm.search(); } - redirectUrlsResource.searchRedirectUrls(searchTerm, $scope.pagination.pageIndex, $scope.pagination.pageSize).then(function(response) { - - $scope.redirectUrls = response.SearchResults; - - // update pagination - $scope.pagination.pageIndex = response.CurrentPage; - $scope.pagination.pageNumber = response.CurrentPage + 1; - $scope.pagination.totalPages = response.PageCount; - - // Set enable/disable state for url tracker - $scope.dashboard.UrlTrackerDisabled = response.UrlTrackerDisabled; - - angular.forEach($scope.redirectUrls, function(redirect) { - redirectUrlsResource.getPublishedUrl(redirect.ContentId).then(function(response) { - redirect.ContentUrl = response; - }, function(error) { - notificationsService.error("Redirect Url Error!", "Failed to get published url for " + redirect.Url); - }); - }); - - $scope.dashboard.loading = false; - - }); - }; - - $scope.removeRedirect = function(redirectToDelete) { - - redirectUrlsResource.deleteRedirectUrl(redirectToDelete.Id).then(function() { - - var index = $scope.redirectUrls.indexOf(redirectToDelete); - $scope.redirectUrls.splice(index, 1); - notificationsService.success("Redirect Url Removed!", "Redirect Url " + redirectToDelete.Url + " has been deleted"); - - }, function(error) { - - notificationsService.error("Redirect Url Error!", "Redirect Url " + redirectToDelete.Url + " was not deleted"); - - }); - - }; - - $scope.disableUrlTracker = function() { - var toggleConfirm = confirm("Are you sure you want to disable the URL tracker?"); - if (toggleConfirm) { - - redirectUrlsResource.toggleUrlTracker(true).then(function() { - activate(); - notificationsService.success("URL Tracker has now been disabled"); - }, function(error) { - notificationsService.warning("Error disabling the URL Tracker, more information can be found in your log file."); - }); - + function goToPage(pageNumber) { + vm.pagination.pageIndex = pageNumber - 1; + vm.pagination.pageNumber = pageNumber; + vm.search(); } - }; - $scope.enableUrlTracker = function() { - redirectUrlsResource.toggleUrlTracker(false).then(function() { - activate(); - notificationsService.success("URL Tracker has now been enabled"); - }, function(error) { - notificationsService.warning("Error enabling the URL Tracker, more information can be found in your log file."); - }); - }; + function search() { - var filterDebounced = _.debounce(function(e) { + vm.dashboard.loading = true; - $scope.$apply(function() { - - //a canceler exists, so perform the cancelation operation and reset - if (canceler) { - canceler.resolve(); - canceler = $q.defer(); - } else { - canceler = $q.defer(); + var searchTerm = vm.dashboard.searchTerm; + if (searchTerm === undefined) { + searchTerm = ""; } - $scope.search(); + redirectUrlsResource.searchRedirectUrls(searchTerm, vm.pagination.pageIndex, vm.pagination.pageSize).then(function(response) { - }); + vm.redirectUrls = response.SearchResults; - }, 200); + // update pagination + vm.pagination.pageIndex = response.CurrentPage; + vm.pagination.pageNumber = response.CurrentPage + 1; + vm.pagination.totalPages = response.PageCount; - $scope.filter = function() { - filterDebounced(); - }; + // Set enable/disable state for url tracker + vm.dashboard.UrlTrackerDisabled = response.UrlTrackerDisabled; - activate(); + angular.forEach(vm.redirectUrls, function(redirect) { + redirectUrlsResource.getPublishedUrl(redirect.ContentId).then(function(response) { + redirect.ContentUrl = response; + }, function(error) { + notificationsService.error("Redirect Url Error!", "Failed to get published url for " + redirect.Url); + }); + }); -}); + vm.dashboard.loading = false; + + }); + } + + function removeRedirect(redirectToDelete) { + + redirectUrlsResource.deleteRedirectUrl(redirectToDelete.Id).then(function() { + + var index = vm.redirectUrls.indexOf(redirectToDelete); + vm.redirectUrls.splice(index, 1); + notificationsService.success("Redirect Url Removed!", "Redirect Url " + redirectToDelete.Url + " has been deleted"); + + }, function(error) { + + notificationsService.error("Redirect Url Error!", "Redirect Url " + redirectToDelete.Url + " was not deleted"); + + }); + + } + + function disableUrlTracker() { + var toggleConfirm = confirm("Are you sure you want to disable the URL tracker?"); + if (toggleConfirm) { + + redirectUrlsResource.toggleUrlTracker(true).then(function() { + activate(); + notificationsService.success("URL Tracker has now been disabled"); + }, function(error) { + notificationsService.warning("Error disabling the URL Tracker, more information can be found in your log file."); + }); + + } + } + + function enableUrlTracker() { + redirectUrlsResource.toggleUrlTracker(false).then(function() { + activate(); + notificationsService.success("URL Tracker has now been enabled"); + }, function(error) { + notificationsService.warning("Error enabling the URL Tracker, more information can be found in your log file."); + }); + } + + var filterDebounced = _.debounce(function(e) { + + $scope.$apply(function() { + + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } else { + canceler = $q.defer(); + } + + vm.search(); + + }); + + }, 200); + + function filter() { + filterDebounced(); + } + + activate(); + + } + + angular.module("umbraco").controller("Umbraco.Dashboard.RedirectUrlsController", RedirectUrlsController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html index 26c2ad7e2d..ebd007cb1a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html @@ -1,6 +1,6 @@ -
      +
      - + @@ -9,18 +9,18 @@ @@ -30,8 +30,8 @@ @@ -55,7 +55,7 @@
      -
      +
      @@ -75,7 +75,7 @@
      Edit - +
      @@ -86,12 +86,12 @@
      + ng-if="vm.pagination.totalPages > 1 && !vm.dashboard.loading" + page-number="vm.pagination.pageNumber" + total-pages="vm.pagination.totalPages" + on-next="vm.goToPage" + on-prev="vm.goToPage" + on-go-to-page="vm.goToPage">
      From 42666403a6ee289c9702d9b8d17f9fd0aeb28e17 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 5 Aug 2016 17:41:00 +0200 Subject: [PATCH 158/413] Fixes regression issue with building the xml cache file, moves the logic to query and build the xml document for the cache to the repository level. --- .../Repositories/ContentRepository.cs | 72 +++++++++++++++++ .../Interfaces/IContentRepository.cs | 7 ++ src/Umbraco.Core/Services/ContentService.cs | 17 +++- src/Umbraco.Core/Services/IContentService.cs | 7 ++ .../umbraco.presentation/content.cs | 80 ++----------------- 5 files changed, 108 insertions(+), 75 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 90442c5e7f..9222769247 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Linq.Expressions; using System.Net.Http.Headers; using System.Text; +using System.Xml; using System.Xml.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Dynamics; @@ -688,6 +689,77 @@ namespace Umbraco.Core.Persistence.Repositories } } + + /// + /// This builds the Xml document used for the XML cache + /// + /// + public XmlDocument BuildXmlCache() + { + //TODO: This is what we should do , but converting to use XDocument would be breaking unless we convert + // to XmlDocument at the end of this, but again, this would be bad for memory... though still not nearly as + // bad as what is happening before! + // We'll keep using XmlDocument for now though, but XDocument xml generation is much faster: + // https://blogs.msdn.microsoft.com/codejunkie/2008/10/08/xmldocument-vs-xelement-performance/ + // I think we already have code in here to convert XDocument to XmlDocument but in case we don't here + // it is: https://blogs.msdn.microsoft.com/marcelolr/2009/03/13/fast-way-to-convert-xmldocument-into-xdocument/ + + //// Prepare an XmlDocument with an appropriate inline DTD to match + //// the expected content + //var parent = new XElement("root", new XAttribute("id", "-1")); + //var xmlDoc = new XDocument( + // new XDocumentType("root", null, null, DocumentType.GenerateDtd()), + // parent); + + var xmlDoc = new XmlDocument(); + var doctype = xmlDoc.CreateDocumentType("root", null, null, + ApplicationContext.Current.Services.ContentTypeService.GetContentTypesDtd()); + xmlDoc.AppendChild(doctype); + var parent = xmlDoc.CreateElement("root"); + var pIdAtt = xmlDoc.CreateAttribute("id"); + pIdAtt.Value = "-1"; + parent.Attributes.Append(pIdAtt); + xmlDoc.AppendChild(parent); + + const string sql = @"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.xml, umbracoNode.level from umbracoNode +inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type +where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1) +order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; + + XmlElement last = null; + + //NOTE: Query creates a reader - does not load all into memory + foreach (var row in Database.Query(sql, new { type = new Guid(Constants.ObjectTypes.Document) })) + { + string parentId = ((int)row.parentID).ToInvariantString(); + string xml = row.xml; + int sortOrder = row.sortOrder; + + //if the parentid is changing + if (last != null && last.GetAttribute("parentID") != parentId) + { + parent = xmlDoc.GetElementById(parentId); + if (parent == null) + { + //Need to short circuit here, if the parent is not there it means that the parent is unpublished + // and therefore the child is not published either so cannot be included in the xml cache + continue; + } + } + + var xmlDocFragment = xmlDoc.CreateDocumentFragment(); + xmlDocFragment.InnerXml = xml; + + last = (XmlElement)parent.AppendChild(xmlDocFragment); + + // fix sortOrder - see notes in UpdateSortOrder + last.Attributes["sortOrder"].Value = sortOrder.ToInvariantString(); + } + + return xmlDoc; + + } + public int CountPublished() { var sql = GetBaseQuery(true).Where(x => x.Trashed == false) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index c18765239b..e2555995d2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using System.Xml; using System.Xml.Linq; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -11,6 +12,12 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository { + /// + /// This builds the Xml document used for the XML cache + /// + /// + XmlDocument BuildXmlCache(); + /// /// Get the count of published items /// diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 7fcb915b30..cbcd5ecc05 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Globalization; using System.Linq; using System.Threading; +using System.Xml; using System.Xml.Linq; using Umbraco.Core.Auditing; using Umbraco.Core.Configuration; @@ -636,7 +637,7 @@ namespace Umbraco.Core.Services query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); - + return contents; } } @@ -1714,6 +1715,20 @@ namespace Umbraco.Core.Services return true; } + /// + /// This builds the Xml document used for the XML cache + /// + /// + public XmlDocument BuildXmlCache() + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + var result = repository.BuildXmlCache(); + return result; + } + } + /// /// Rebuilds all xml content in the cmsContentXml table for all documents /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 5e90559233..fa4130f8c4 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Xml; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -94,6 +95,12 @@ namespace Umbraco.Core.Services /// public interface IContentService : IService { + /// + /// This builds the Xml document used for the XML cache + /// + /// + XmlDocument BuildXmlCache(); + /// /// Rebuilds all xml content in the cmsContentXml table for all documents /// diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 6c4eef7862..777fe80782 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -482,7 +482,7 @@ namespace umbraco if (UmbracoContext.Current != null && UmbracoContext.Current.HttpContext != null && UmbracoContext.Current.HttpContext.Items.Contains(XmlContextContentItemKey)) UmbracoContext.Current.HttpContext.Items.Remove(XmlContextContentItemKey); } - + /// /// Load content from database /// @@ -490,81 +490,13 @@ namespace umbraco { try { - // Try to log to the DB LogHelper.Info("Loading content from database..."); - - try + + lock (DbReadSyncLock) { - LogHelper.Debug("Republishing starting"); - - lock (DbReadSyncLock) - { - //TODO: This is what we should do , but converting to use XDocument would be breaking unless we convert - // to XmlDocument at the end of this, but again, this would be bad for memory... though still not nearly as - // bad as what is happening before! - // We'll keep using XmlDocument for now though, but XDocument xml generation is much faster: - // https://blogs.msdn.microsoft.com/codejunkie/2008/10/08/xmldocument-vs-xelement-performance/ - // I think we already have code in here to convert XDocument to XmlDocument but in case we don't here - // it is: https://blogs.msdn.microsoft.com/marcelolr/2009/03/13/fast-way-to-convert-xmldocument-into-xdocument/ - - //// Prepare an XmlDocument with an appropriate inline DTD to match - //// the expected content - //var parent = new XElement("root", new XAttribute("id", "-1")); - //var xmlDoc = new XDocument( - // new XDocumentType("root", null, null, DocumentType.GenerateDtd()), - // parent); - - var xmlDoc = new XmlDocument(); - var doctype = xmlDoc.CreateDocumentType("root", null, null, - ApplicationContext.Current.Services.ContentTypeService.GetContentTypesDtd()); - xmlDoc.AppendChild(doctype); - var parent = xmlDoc.CreateElement("root"); - var pIdAtt = xmlDoc.CreateAttribute("id"); - pIdAtt.Value = "-1"; - parent.Attributes.Append(pIdAtt); - xmlDoc.AppendChild(parent); - - // Esben Carlsen: At some point we really need to put all data access into to a tier of its own. - // CLN - added checks that document xml is for a document that is actually published. - const string sql = @"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.xml, umbracoNode.level from umbracoNode -inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type -where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1) -order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; - - XmlElement last = null; - - var db = ApplicationContext.Current.DatabaseContext.Database; - //NOTE: Query creates a reader - does not load all into memory - foreach (var row in db.Query(sql, new { type = new Guid(Constants.ObjectTypes.Document)})) - { - string parentId = ((int)row.parentID).ToInvariantString(); - string xml = row.xml; - int sortOrder = row.sortOrder; - - //if the parentid is changing - if (last != null && last.GetAttribute("parentID") != parentId) - { - parent = xmlDoc.GetElementById(parentId); - if (parent == null) throw new InvalidOperationException("No parent node found in xml doc with id " + parentId); - } - - var xmlDocFragment = xmlDoc.CreateDocumentFragment(); - xmlDocFragment.InnerXml = xml; - - last = (XmlElement)parent.AppendChild(xmlDocFragment); - - // fix sortOrder - see notes in UpdateSortOrder - last.Attributes["sortOrder"].Value = sortOrder.ToInvariantString(); - } - - LogHelper.Debug("Done republishing Xml Index"); - - return xmlDoc; - } - } - catch (Exception ee) - { - LogHelper.Error("Error Republishing", ee); + var xmlDoc = ApplicationContext.Current.Services.ContentService.BuildXmlCache(); + LogHelper.Debug("Done republishing Xml Index"); + return xmlDoc; } } catch (Exception ee) From aedba40b546701ab6dcb02c22328ba69e7f3b506 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 5 Aug 2016 14:42:29 +0200 Subject: [PATCH 159/413] Updates language file to specify which integrity check we're looking at. Quotes all table names correctly. Updates description for the check. --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 6 +++--- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 6 +++--- .../DataIntegrity/XmlDataIntegrityHealthCheck.cs | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 2b5ce44d79..2f9871add8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1351,9 +1351,9 @@ To manage your website, simply open the Umbraco back office and start adding con '%0%' in config file '%1%'.]]> There was an error, check log for full error: %0%. - Total XML: %0%, Total: %1% - Total XML: %0%, Total: %1%, Total invalid %2% - Total XML: %0%, Total published: %1%, Total invalid %2% + Members - Total XML: %0%, Total: %1%, Total invalid: %2% + Media - Total XML: %0%, Total: %1%, Total invalid %2% + Content - Total XML: %0%, Total published: %1%, Total invalid %2% Certificate validation error: '%0%' Error pinging the URL %0% - '%1%' diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 05015116ee..e0022eb2c0 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1355,9 +1355,9 @@ To manage your website, simply open the Umbraco back office and start adding con '%0%' in config file '%1%'.]]> There was an error, check log for full error: %0%. - Total XML: %0%, Total: %1% - Total XML: %0%, Total: %1%, Total invalid %2% - Total XML: %0%, Total published: %1%, Total invalid %2% + Members - Total XML: %0%, Total: %1%, Total invalid: %2% + Media - Total XML: %0%, Total: %1%, Total invalid: %2% + Content - Total XML: %0%, Total published: %1%, Total invalid: %2% Certificate validation error: '%0%' Error pinging the URL %0% - '%1%' diff --git a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs index 8895e18af9..5d888ef117 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity [HealthCheck( "D999EB2B-64C2-400F-B50C-334D41F8589A", "XML Data Integrity", - Description = "Checks the integrity of the XML data in Umbraco", + Description = "This checks the data integrity for the xml structures for content, media and members that are stored in the cmsContentXml table. This does not check the data integrity of the xml cache file, only the xml structures stored in the database used to create the xml cache file.", Group = "Data Integrity")] public class XmlDataIntegrityHealthCheck : HealthCheck { @@ -85,7 +85,7 @@ namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity if (totalXml != total) actions.Add(new HealthCheckAction(CheckMembersXmlTableAction, Id)); - return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckMembers", new[] { totalXml.ToString(), total.ToString() })) + return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckMembers", new[] { totalXml.ToString(), total.ToString(), (total - totalXml).ToString() })) { ResultType = totalXml == total ? StatusResultType.Success : StatusResultType.Error, Actions = actions @@ -122,7 +122,7 @@ namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity .InnerJoin(_sqlSyntax) .On(_sqlSyntax, left => left.NodeId, right => right.NodeId) .Where(dto => dto.NodeObjectType == mediaObjectType) - .Where(string.Format("cmsContentXml.{0} NOT LIKE '% key=\"%'", _sqlSyntax.GetQuotedColumnName("xml"))); + .Where(string.Format("{0}.{1} NOT LIKE '% key=\"%'", _sqlSyntax.GetQuotedTableName("cmsContentXml"), _sqlSyntax.GetQuotedColumnName("xml"))); var totalNonGuidXml = _database.ExecuteScalar(countNonGuidQuery); var hasError = false; @@ -160,7 +160,7 @@ namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity //count entires var countTotalSubQuery = new Sql() - .Select("DISTINCT cmsContentXml.nodeId") + .Select(string.Format("DISTINCT {0}.{1}", _sqlSyntax.GetQuotedTableName("cmsContentXml"), _sqlSyntax.GetQuotedColumnName("nodeId"))) .From(_sqlSyntax) .InnerJoin(_sqlSyntax) .On(_sqlSyntax, left => left.NodeId, right => right.NodeId); @@ -173,7 +173,7 @@ namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity .InnerJoin(_sqlSyntax) .On(_sqlSyntax, left => left.NodeId, right => right.NodeId) .Where(dto => dto.NodeObjectType == documentObjectType) - .Where(string.Format("cmsContentXml.{0} NOT LIKE '% key=\"%'", _sqlSyntax.GetQuotedColumnName("xml"))); + .Where(string.Format("{0}.{1} NOT LIKE '% key=\"%'", _sqlSyntax.GetQuotedTableName("cmsContentXml"), _sqlSyntax.GetQuotedColumnName("xml"))); var totalNonGuidXml = _database.ExecuteScalar(countNonGuidQuery); var hasError = false; From 816107a348215b19eac85cbe3397da62b92a23b9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 5 Aug 2016 18:55:48 +0200 Subject: [PATCH 160/413] U4-8813 RedirectTrackingEventhandler always iterates over all descendants fixes SHA1 hashing (moves to a ext method), fixes col length for new db for the redirect table, updates migration len to 40 to store the correct hash format, don't iterate descendents if there's nothing changed, fixes issue with RedirectTrackingEventHandler with regards to operating outside of a web request (i.e. console app) --- .../Models/Rdbms/RedirectUrlDto.cs | 2 +- .../AddRedirectUrlTable.cs | 2 +- .../Repositories/RedirectUrlRepository.cs | 18 +-- src/Umbraco.Core/StringExtensions.cs | 30 ++++ .../RedirectTrackingEventHandler.cs | 153 ++++++++++++------ src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 6 files changed, 145 insertions(+), 62 deletions(-) rename src/Umbraco.Web/{Redirects => Routing}/RedirectTrackingEventHandler.cs (59%) diff --git a/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs index ed720e8bc1..ca65e4b9a1 100644 --- a/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs @@ -39,12 +39,12 @@ namespace Umbraco.Core.Models.Rdbms [Column("url")] [NullSetting(NullSetting = NullSettings.NotNull)] - //[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "url, createDateUtc")] public string Url { get; set; } [Column("urlHash")] [NullSetting(NullSetting = NullSettings.NotNull)] [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "urlHash, contentKey, createDateUtc")] + [Length(40)] public string UrlHash { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs index ce6af9186c..508c0f284b 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs @@ -39,7 +39,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer .WithColumn("createDateUtc").AsDateTime().NotNullable() .WithColumn("url").AsString(2048).NotNullable() .WithColumn("contentKey").AsGuid().NotNullable() - .WithColumn("urlHash").AsString(20).NotNullable(); + .WithColumn("urlHash").AsString(40).NotNullable(); localContext.Create.Index("IX_" + umbracoRedirectUrlTableName).OnTable(umbracoRedirectUrlTableName) .OnColumn("urlHash") diff --git a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs index 2c608592b8..7073aae560 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs @@ -105,7 +105,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); ContentKey = redirectUrl.ContentKey, CreateDateUtc = redirectUrl.CreateDateUtc, Url = redirectUrl.Url, - UrlHash = HashUrl(redirectUrl.Url) + UrlHash = redirectUrl.Url.ToSHA1() }; } @@ -132,7 +132,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); public IRedirectUrl Get(string url, Guid contentKey) { - var urlHash = HashUrl(url); + var urlHash = url.ToSHA1(); var sql = GetBaseQuery(false).Where(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey); var dto = Database.Fetch(sql).FirstOrDefault(); return dto == null ? null : Map(dto); @@ -155,7 +155,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); public IRedirectUrl GetMostRecentUrl(string url) { - var urlHash = HashUrl(url); + var urlHash = url.ToSHA1(); var sql = GetBaseQuery(false) .Where(x => x.Url == url && x.UrlHash == urlHash) .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); @@ -192,16 +192,6 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); var rules = result.Items.Select(Map); return rules; - } - - private static string HashUrl(string url) - { - using (var crypto = new SHA1CryptoServiceProvider()) - { - var inputBytes = Encoding.UTF8.GetBytes(url); - var hashedBytes = crypto.ComputeHash(inputBytes); - return Encoding.UTF8.GetString(hashedBytes); - } - } + } } } diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index b92df5f1cf..6600092421 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -750,6 +750,36 @@ namespace Umbraco.Core return stringBuilder.ToString(); } + /// + /// Converts the string to SHA1 + /// + /// referrs to itself + /// the md5 hashed string + public static string ToSHA1(this string stringToConvert) + { + //create an instance of the SHA1CryptoServiceProvider + var md5Provider = new SHA1CryptoServiceProvider(); + + //convert our string into byte array + var byteArray = Encoding.UTF8.GetBytes(stringToConvert); + + //get the hashed values created by our SHA1CryptoServiceProvider + var hashedByteArray = md5Provider.ComputeHash(byteArray); + + //create a StringBuilder object + var stringBuilder = new StringBuilder(); + + //loop to each each byte + foreach (var b in hashedByteArray) + { + //append it to our StringBuilder + stringBuilder.Append(b.ToString("x2").ToLower()); + } + + //return the hashed value + return stringBuilder.ToString(); + } + /// /// Decodes a string that was encoded with UrlTokenEncode diff --git a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs similarity index 59% rename from src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs rename to src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs index 417238bdbe..65cad094dc 100644 --- a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs +++ b/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs @@ -1,18 +1,19 @@ using System; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Core.Publishing; -using Umbraco.Core.Events; -using Umbraco.Web.Routing; using System.Collections.Generic; using System.Linq; +using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; +using Umbraco.Core.Events; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; +using Umbraco.Core.Sync; using Umbraco.Web.Cache; +using Umbraco.Web.PublishedCache; -namespace Umbraco.Web.Redirects +namespace Umbraco.Web.Routing { /// /// Implements an Application Event Handler for managing redirect urls tracking. @@ -24,9 +25,9 @@ namespace Umbraco.Web.Redirects /// public class RedirectTrackingEventHandler : ApplicationEventHandler { - private const string ContextKey1 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.1"; - private const string ContextKey2 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.2"; - private const string ContextKey3 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.3"; + private const string ContextKey1 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.1"; + private const string ContextKey2 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.2"; + private const string ContextKey3 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.3"; /// protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) @@ -89,62 +90,92 @@ namespace Umbraco.Web.Redirects // rolled back items have to be published, so publishing will take care of that } + /// + /// Tracks a documents URLs during publishing in the current request + /// private static Dictionary> OldRoutes { get { - if (UmbracoContext.Current == null) - return null; - var oldRoutes = (Dictionary>) UmbracoContext.Current.HttpContext.Items[ContextKey3]; - if (oldRoutes == null) - UmbracoContext.Current.HttpContext.Items[ContextKey3] = oldRoutes = new Dictionary>(); + var oldRoutes = RequestCache.GetCacheItem>>( + ContextKey3, + () => new Dictionary>()); return oldRoutes; } } private static bool LockedEvents { - get { return Moving && UmbracoContext.Current.HttpContext.Items[ContextKey2] != null; } + get + { + return Moving && RequestCache.GetCacheItem(ContextKey2) != null; + } set { if (Moving && value) - UmbracoContext.Current.HttpContext.Items[ContextKey2] = true; + { + //this forces true into the cache + RequestCache.GetCacheItem(ContextKey2, () => true); + } else - UmbracoContext.Current.HttpContext.Items.Remove(ContextKey2); + { + RequestCache.ClearCacheItem(ContextKey2); + } } } private static bool Moving { - get { return UmbracoContext.Current.HttpContext.Items[ContextKey1] != null; } + get { return RequestCache.GetCacheItem(ContextKey1) != null; } set { if (value) - UmbracoContext.Current.HttpContext.Items[ContextKey1] = true; + { + //this forces true into the cache + RequestCache.GetCacheItem(ContextKey1, () => true); + } else { - UmbracoContext.Current.HttpContext.Items.Remove(ContextKey1); - UmbracoContext.Current.HttpContext.Items.Remove(ContextKey2); + RequestCache.ClearCacheItem(ContextKey1); + RequestCache.ClearCacheItem(ContextKey2); } } } + /// + /// Before the items are published, we need to get it's current URL before it changes + /// + /// + /// private static void ContentService_Publishing(IPublishingStrategy sender, PublishEventArgs args) { if (LockedEvents) return; - var contentCache = UmbracoContext.Current.ContentCache; + var contentCache = GetPublishedCache(); + if (contentCache == null) return; + foreach (var entity in args.PublishedEntities) { - var entityContent = contentCache.GetById(entity.Id); - if (entityContent == null) continue; - foreach (var x in entityContent.DescendantsOrSelf()) + //don't continue if this entity hasn't changed with regards to anything + // that might change it's URLs + if (entity.IsDirty() == false) continue; + + if (entity.IsPropertyDirty("Name") + || entity.IsPropertyDirty(Constants.Conventions.Content.UrlName) + || entity.IsPropertyDirty(Constants.Conventions.Content.UrlAlias) + || Moving) { - var route = contentCache.GetRouteById(x.Id); - if (IsNotRoute(route)) continue; - var wk = UnwrapToKey(x); - if (wk == null) continue; - OldRoutes[x.Id] = Tuple.Create(wk.Key, route); + var entityContent = contentCache.GetById(entity.Id); + if (entityContent == null) continue; + foreach (var x in entityContent.DescendantsOrSelf()) + { + var route = contentCache.GetRouteById(x.Id); + if (IsNotRoute(route)) continue; + var wk = UnwrapToKey(x); + if (wk == null) continue; + + OldRoutes[x.Id] = Tuple.Create(wk.Key, route); + } } } @@ -165,23 +196,36 @@ namespace Umbraco.Web.Redirects return withKey; } + /// + /// Executed when the cache updates, which means we can know what the new URL is for a given document + /// + /// + /// private void PageCacheRefresher_CacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs cacheRefresherEventArgs) { - if (OldRoutes == null) - return; - - var removeKeys = new List(); - - foreach (var oldRoute in OldRoutes) + //This should only ever occur on the Master server when in load balancing since this will fire on all + // servers taking part in load balancing + var serverRole = ApplicationContext.Current.GetCurrentServerRole(); + if (serverRole == ServerRole.Master || serverRole == ServerRole.Single) { - // assuming we cannot have 'CacheUpdated' for only part of the infos else we'd need - // to set a flag in 'Published' to indicate which entities have been refreshed ok - CreateRedirect(oldRoute.Key, oldRoute.Value.Item1, oldRoute.Value.Item2); - removeKeys.Add(oldRoute.Key); - } + if (RequestCache.GetCacheItem(ContextKey3) == null) + return; - foreach (var k in removeKeys) - OldRoutes.Remove(k); + try + { + foreach (var oldRoute in OldRoutes) + { + // assuming we cannot have 'CacheUpdated' for only part of the infos else we'd need + // to set a flag in 'Published' to indicate which entities have been refreshed ok + CreateRedirect(oldRoute.Key, oldRoute.Value.Item1, oldRoute.Value.Item2); + } + } + finally + { + OldRoutes.Clear(); + RequestCache.ClearCacheItem(ContextKey3); + } + } } private static void ContentService_Published(IPublishingStrategy sender, PublishEventArgs e) @@ -203,7 +247,10 @@ namespace Umbraco.Web.Redirects private static void CreateRedirect(int contentId, Guid contentKey, string oldRoute) { - var contentCache = UmbracoContext.Current.ContentCache; + + var contentCache = GetPublishedCache(); + if (contentCache == null) return; + var newRoute = contentCache.GetRouteById(contentId); if (IsNotRoute(newRoute) || oldRoute == newRoute) return; var redirectUrlService = ApplicationContext.Current.Services.RedirectUrlService; @@ -216,5 +263,21 @@ namespace Umbraco.Web.Redirects // err/- if collision or anomaly or ... return route == null || route.StartsWith("err/"); } + + /// + /// Gets the current request cache to persist the values between handlers + /// + private static ContextualPublishedContentCache GetPublishedCache() + { + return UmbracoContext.Current == null ? null : UmbracoContext.Current.ContentCache; + } + + /// + /// Gets the current request cache to persist the values between handlers + /// + private static ICacheProvider RequestCache + { + get { return ApplicationContext.Current.ApplicationCache.RequestCache; } + } } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d1cab1b52d..41f0256cab 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -390,7 +390,7 @@ - + From 4022c3cec9e0321dd1e1fade55a8cfe90bbcc182 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 5 Aug 2016 19:15:11 +0200 Subject: [PATCH 161/413] U4-8816 Update to jQuery 2.2.4 --- src/Umbraco.Web.UI.Client/bower.json | 2 +- src/Umbraco.Web.UI.Client/gruntFile.js | 3 +- .../umbraco_client/ui/jquery.js | 8 +- .../umbraco_client/ui/jqueryui.js | 138 ++---------------- 4 files changed, 20 insertions(+), 131 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index d1397b5d4e..09cfa16609 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -20,7 +20,7 @@ "underscore": "~1.7.0", "rgrove-lazyload": "*", "bootstrap-social": "~4.8.0", - "jquery": "2.0.3", + "jquery": "2.2.4", "jquery-ui": "1.11.4", "angular-dynamic-locale": "0.1.28", "ng-file-upload": "~7.3.8", diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js index d5b785c54c..946966d358 100644 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ b/src/Umbraco.Web.UI.Client/gruntFile.js @@ -484,7 +484,8 @@ module.exports = function (grunt) { files: ['css/font-awesome.min.css', 'fonts/*'] }, "jquery": { - files: ['jquery.min.js', 'jquery.min.map'] + keepExpandedHierarchy: false, + files: ['dist/jquery.min.js', 'dist/jquery.min.map'] }, 'jquery-ui': { keepExpandedHierarchy: false, diff --git a/src/Umbraco.Web.UI/umbraco_client/ui/jquery.js b/src/Umbraco.Web.UI/umbraco_client/ui/jquery.js index 1775c9c031..bb70005267 100644 --- a/src/Umbraco.Web.UI/umbraco_client/ui/jquery.js +++ b/src/Umbraco.Web.UI/umbraco_client/ui/jquery.js @@ -1,4 +1,4 @@ -/*! jQuery v1.7.2 jquery.com | jquery.org/license */ -(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
      a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
      "+""+"
      ",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
      t
      ",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
      ",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( -a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

      ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
      ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
      ","
      "],thead:[1,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
      ","
      "]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f -.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
      ").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file +/*! jQuery v2.2.4 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c; +}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,"
      ","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("' : ""), a._keyEvent = !1, K }, _generateMonthYearHeader: function (a, b, c, d, e, f, g, h) { var i = this._get(a, "changeMonth"), j = this._get(a, "changeYear"), k = this._get(a, "showMonthAfterYear"), l = '
      ', m = ""; if (f || !i) m += '' + g[b] + ""; else { var n = d && d.getFullYear() == c, o = e && e.getFullYear() == c; m += '" } k || (l += m + (f || !i || !j ? " " : "")); if (!a.yearshtml) { a.yearshtml = ""; if (f || !j) l += '' + c + ""; else { var q = this._get(a, "yearRange").split(":"), r = (new Date).getFullYear(), s = function (a) { var b = a.match(/c[+-].*/) ? c + parseInt(a.substring(1), 10) : a.match(/[+-].*/) ? r + parseInt(a, 10) : parseInt(a, 10); return isNaN(b) ? r : b }, t = s(q[0]), u = Math.max(t, s(q[1] || "")); t = d ? Math.max(t, d.getFullYear()) : t, u = e ? Math.min(u, e.getFullYear()) : u, a.yearshtml += '", l += a.yearshtml, a.yearshtml = null } } return l += this._get(a, "yearSuffix"), k && (l += (f || !i || !j ? " " : "") + m), l += "
      ", l }, _adjustInstDate: function (a, b, c) { var d = a.drawYear + (c == "Y" ? b : 0), e = a.drawMonth + (c == "M" ? b : 0), f = Math.min(a.selectedDay, this._getDaysInMonth(d, e)) + (c == "D" ? b : 0), g = this._restrictMinMax(a, this._daylightSavingAdjust(new Date(d, e, f))); a.selectedDay = g.getDate(), a.drawMonth = a.selectedMonth = g.getMonth(), a.drawYear = a.selectedYear = g.getFullYear(), (c == "M" || c == "Y") && this._notifyChange(a) }, _restrictMinMax: function (a, b) { var c = this._getMinMaxDate(a, "min"), d = this._getMinMaxDate(a, "max"), e = c && b < c ? c : b; return e = d && e > d ? d : e, e }, _notifyChange: function (a) { var b = this._get(a, "onChangeMonthYear"); b && b.apply(a.input ? a.input[0] : null, [a.selectedYear, a.selectedMonth + 1, a]) }, _getNumberOfMonths: function (a) { var b = this._get(a, "numberOfMonths"); return b == null ? [1, 1] : typeof b == "number" ? [1, b] : b }, _getMinMaxDate: function (a, b) { return this._determineDate(a, this._get(a, b + "Date"), null) }, _getDaysInMonth: function (a, b) { return 32 - this._daylightSavingAdjust(new Date(a, b, 32)).getDate() }, _getFirstDayOfMonth: function (a, b) { return (new Date(a, b, 1)).getDay() }, _canAdjustMonth: function (a, b, c, d) { var e = this._getNumberOfMonths(a), f = this._daylightSavingAdjust(new Date(c, d + (b < 0 ? b : e[0] * e[1]), 1)); return b < 0 && f.setDate(this._getDaysInMonth(f.getFullYear(), f.getMonth())), this._isInRange(a, f) }, _isInRange: function (a, b) { var c = this._getMinMaxDate(a, "min"), d = this._getMinMaxDate(a, "max"); return (!c || b.getTime() >= c.getTime()) && (!d || b.getTime() <= d.getTime()) }, _getFormatConfig: function (a) { var b = this._get(a, "shortYearCutoff"); return b = typeof b != "string" ? b : (new Date).getFullYear() % 100 + parseInt(b, 10), { shortYearCutoff: b, dayNamesShort: this._get(a, "dayNamesShort"), dayNames: this._get(a, "dayNames"), monthNamesShort: this._get(a, "monthNamesShort"), monthNames: this._get(a, "monthNames")} }, _formatDate: function (a, b, c, d) { b || (a.currentDay = a.selectedDay, a.currentMonth = a.selectedMonth, a.currentYear = a.selectedYear); var e = b ? typeof b == "object" ? b : this._daylightSavingAdjust(new Date(d, c, b)) : this._daylightSavingAdjust(new Date(a.currentYear, a.currentMonth, a.currentDay)); return this.formatDate(this._get(a, "dateFormat"), e, this._getFormatConfig(a)) } }), $.fn.datepicker = function (a) { if (!this.length) return this; $.datepicker.initialized || ($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv), $.datepicker.initialized = !0); var b = Array.prototype.slice.call(arguments, 1); return typeof a != "string" || a != "isDisabled" && a != "getDate" && a != "widget" ? a == "option" && arguments.length == 2 && typeof arguments[1] == "string" ? $.datepicker["_" + a + "Datepicker"].apply($.datepicker, [this[0]].concat(b)) : this.each(function () { typeof a == "string" ? $.datepicker["_" + a + "Datepicker"].apply($.datepicker, [this].concat(b)) : $.datepicker._attachDatepicker(this, a) }) : $.datepicker["_" + a + "Datepicker"].apply($.datepicker, [this[0]].concat(b)) }, $.datepicker = new Datepicker, $.datepicker.initialized = !1, $.datepicker.uuid = (new Date).getTime(), $.datepicker.version = "1.8.21", window["DP_jQuery_" + dpuuid] = $ })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.progressbar.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.widget("ui.progressbar", { options: { value: 0, max: 100 }, min: 0, _create: function () { this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({ role: "progressbar", "aria-valuemin": this.min, "aria-valuemax": this.options.max, "aria-valuenow": this._value() }), this.valueDiv = a("
      ").appendTo(this.element), this.oldValue = this._value(), this._refreshValue() }, destroy: function () { this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"), this.valueDiv.remove(), a.Widget.prototype.destroy.apply(this, arguments) }, value: function (a) { return a === b ? this._value() : (this._setOption("value", a), this) }, _setOption: function (b, c) { b === "value" && (this.options.value = c, this._refreshValue(), this._value() === this.options.max && this._trigger("complete")), a.Widget.prototype._setOption.apply(this, arguments) }, _value: function () { var a = this.options.value; return typeof a != "number" && (a = 0), Math.min(this.options.max, Math.max(this.min, a)) }, _percentage: function () { return 100 * this._value() / this.options.max }, _refreshValue: function () { var a = this.value(), b = this._percentage(); this.oldValue !== a && (this.oldValue = a, this._trigger("change")), this.valueDiv.toggle(a > this.min).toggleClass("ui-corner-right", a === this.options.max).width(b.toFixed(0) + "%"), this.element.attr("aria-valuenow", a) } }), a.extend(a.ui.progressbar, { version: "1.8.21" }) })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.core.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -jQuery.effects || function (a, b) { function c(b) { var c; return b && b.constructor == Array && b.length == 3 ? b : (c = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b)) ? [parseInt(c[1], 10), parseInt(c[2], 10), parseInt(c[3], 10)] : (c = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b)) ? [parseFloat(c[1]) * 2.55, parseFloat(c[2]) * 2.55, parseFloat(c[3]) * 2.55] : (c = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b)) ? [parseInt(c[1], 16), parseInt(c[2], 16), parseInt(c[3], 16)] : (c = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b)) ? [parseInt(c[1] + c[1], 16), parseInt(c[2] + c[2], 16), parseInt(c[3] + c[3], 16)] : (c = /rgba\(0, 0, 0, 0\)/.exec(b)) ? e.transparent : e[a.trim(b).toLowerCase()] } function d(b, d) { var e; do { e = a.curCSS(b, d); if (e != "" && e != "transparent" || a.nodeName(b, "body")) break; d = "backgroundColor" } while (b = b.parentNode); return c(e) } function h() { var a = document.defaultView ? document.defaultView.getComputedStyle(this, null) : this.currentStyle, b = {}, c, d; if (a && a.length && a[0] && a[a[0]]) { var e = a.length; while (e--) c = a[e], typeof a[c] == "string" && (d = c.replace(/\-(\w)/g, function (a, b) { return b.toUpperCase() }), b[d] = a[c]) } else for (c in a) typeof a[c] == "string" && (b[c] = a[c]); return b } function i(b) { var c, d; for (c in b) d = b[c], (d == null || a.isFunction(d) || c in g || /scrollbar/.test(c) || !/color/i.test(c) && isNaN(parseFloat(d))) && delete b[c]; return b } function j(a, b) { var c = { _: 0 }, d; for (d in b) a[d] != b[d] && (c[d] = b[d]); return c } function k(b, c, d, e) { typeof b == "object" && (e = c, d = null, c = b, b = c.effect), a.isFunction(c) && (e = c, d = null, c = {}); if (typeof c == "number" || a.fx.speeds[c]) e = d, d = c, c = {}; return a.isFunction(d) && (e = d, d = null), c = c || {}, d = d || c.duration, d = a.fx.off ? 0 : typeof d == "number" ? d : d in a.fx.speeds ? a.fx.speeds[d] : a.fx.speeds._default, e = e || c.complete, [b, c, d, e] } function l(b) { return !b || typeof b == "number" || a.fx.speeds[b] ? !0 : typeof b == "string" && !a.effects[b] ? !0 : !1 } a.effects = {}, a.each(["backgroundColor", "borderBottomColor", "borderLeftColor", "borderRightColor", "borderTopColor", "borderColor", "color", "outlineColor"], function (b, e) { a.fx.step[e] = function (a) { a.colorInit || (a.start = d(a.elem, e), a.end = c(a.end), a.colorInit = !0), a.elem.style[e] = "rgb(" + Math.max(Math.min(parseInt(a.pos * (a.end[0] - a.start[0]) + a.start[0], 10), 255), 0) + "," + Math.max(Math.min(parseInt(a.pos * (a.end[1] - a.start[1]) + a.start[1], 10), 255), 0) + "," + Math.max(Math.min(parseInt(a.pos * (a.end[2] - a.start[2]) + a.start[2], 10), 255), 0) + ")" } }); var e = { aqua: [0, 255, 255], azure: [240, 255, 255], beige: [245, 245, 220], black: [0, 0, 0], blue: [0, 0, 255], brown: [165, 42, 42], cyan: [0, 255, 255], darkblue: [0, 0, 139], darkcyan: [0, 139, 139], darkgrey: [169, 169, 169], darkgreen: [0, 100, 0], darkkhaki: [189, 183, 107], darkmagenta: [139, 0, 139], darkolivegreen: [85, 107, 47], darkorange: [255, 140, 0], darkorchid: [153, 50, 204], darkred: [139, 0, 0], darksalmon: [233, 150, 122], darkviolet: [148, 0, 211], fuchsia: [255, 0, 255], gold: [255, 215, 0], green: [0, 128, 0], indigo: [75, 0, 130], khaki: [240, 230, 140], lightblue: [173, 216, 230], lightcyan: [224, 255, 255], lightgreen: [144, 238, 144], lightgrey: [211, 211, 211], lightpink: [255, 182, 193], lightyellow: [255, 255, 224], lime: [0, 255, 0], magenta: [255, 0, 255], maroon: [128, 0, 0], navy: [0, 0, 128], olive: [128, 128, 0], orange: [255, 165, 0], pink: [255, 192, 203], purple: [128, 0, 128], violet: [128, 0, 128], red: [255, 0, 0], silver: [192, 192, 192], white: [255, 255, 255], yellow: [255, 255, 0], transparent: [255, 255, 255] }, f = ["add", "remove", "toggle"], g = { border: 1, borderBottom: 1, borderColor: 1, borderLeft: 1, borderRight: 1, borderTop: 1, borderWidth: 1, margin: 1, padding: 1 }; a.effects.animateClass = function (b, c, d, e) { return a.isFunction(d) && (e = d, d = null), this.queue(function () { var g = a(this), k = g.attr("style") || " ", l = i(h.call(this)), m, n = g.attr("class") || ""; a.each(f, function (a, c) { b[c] && g[c + "Class"](b[c]) }), m = i(h.call(this)), g.attr("class", n), g.animate(j(l, m), { queue: !1, duration: c, easing: d, complete: function () { a.each(f, function (a, c) { b[c] && g[c + "Class"](b[c]) }), typeof g.attr("style") == "object" ? (g.attr("style").cssText = "", g.attr("style").cssText = k) : g.attr("style", k), e && e.apply(this, arguments), a.dequeue(this) } }) }) }, a.fn.extend({ _addClass: a.fn.addClass, addClass: function (b, c, d, e) { return c ? a.effects.animateClass.apply(this, [{ add: b }, c, d, e]) : this._addClass(b) }, _removeClass: a.fn.removeClass, removeClass: function (b, c, d, e) { return c ? a.effects.animateClass.apply(this, [{ remove: b }, c, d, e]) : this._removeClass(b) }, _toggleClass: a.fn.toggleClass, toggleClass: function (c, d, e, f, g) { return typeof d == "boolean" || d === b ? e ? a.effects.animateClass.apply(this, [d ? { add: c} : { remove: c }, e, f, g]) : this._toggleClass(c, d) : a.effects.animateClass.apply(this, [{ toggle: c }, d, e, f]) }, switchClass: function (b, c, d, e, f) { return a.effects.animateClass.apply(this, [{ add: c, remove: b }, d, e, f]) } }), a.extend(a.effects, { version: "1.8.21", save: function (a, b) { for (var c = 0; c < b.length; c++) b[c] !== null && a.data("ec.storage." + b[c], a[0].style[b[c]]) }, restore: function (a, b) { for (var c = 0; c < b.length; c++) b[c] !== null && a.css(b[c], a.data("ec.storage." + b[c])) }, setMode: function (a, b) { return b == "toggle" && (b = a.is(":hidden") ? "show" : "hide"), b }, getBaseline: function (a, b) { var c, d; switch (a[0]) { case "top": c = 0; break; case "middle": c = .5; break; case "bottom": c = 1; break; default: c = a[0] / b.height } switch (a[1]) { case "left": d = 0; break; case "center": d = .5; break; case "right": d = 1; break; default: d = a[1] / b.width } return { x: d, y: c} }, createWrapper: function (b) { if (b.parent().is(".ui-effects-wrapper")) return b.parent(); var c = { width: b.outerWidth(!0), height: b.outerHeight(!0), "float": b.css("float") }, d = a("
      ").addClass("ui-effects-wrapper").css({ fontSize: "100%", background: "transparent", border: "none", margin: 0, padding: 0 }), e = document.activeElement; try { e.id } catch (f) { e = document.body } return b.wrap(d), (b[0] === e || a.contains(b[0], e)) && a(e).focus(), d = b.parent(), b.css("position") == "static" ? (d.css({ position: "relative" }), b.css({ position: "relative" })) : (a.extend(c, { position: b.css("position"), zIndex: b.css("z-index") }), a.each(["top", "left", "bottom", "right"], function (a, d) { c[d] = b.css(d), isNaN(parseInt(c[d], 10)) && (c[d] = "auto") }), b.css({ position: "relative", top: 0, left: 0, right: "auto", bottom: "auto" })), d.css(c).show() }, removeWrapper: function (b) { var c, d = document.activeElement; return b.parent().is(".ui-effects-wrapper") ? (c = b.parent().replaceWith(b), (b[0] === d || a.contains(b[0], d)) && a(d).focus(), c) : b }, setTransition: function (b, c, d, e) { return e = e || {}, a.each(c, function (a, c) { var f = b.cssUnit(c); f[0] > 0 && (e[c] = f[0] * d + f[1]) }), e } }), a.fn.extend({ effect: function (b, c, d, e) { var f = k.apply(this, arguments), g = { options: f[1], duration: f[2], callback: f[3] }, h = g.options.mode, i = a.effects[b]; return a.fx.off || !i ? h ? this[h](g.duration, g.callback) : this.each(function () { g.callback && g.callback.call(this) }) : i.call(this, g) }, _show: a.fn.show, show: function (a) { if (l(a)) return this._show.apply(this, arguments); var b = k.apply(this, arguments); return b[1].mode = "show", this.effect.apply(this, b) }, _hide: a.fn.hide, hide: function (a) { if (l(a)) return this._hide.apply(this, arguments); var b = k.apply(this, arguments); return b[1].mode = "hide", this.effect.apply(this, b) }, __toggle: a.fn.toggle, toggle: function (b) { if (l(b) || typeof b == "boolean" || a.isFunction(b)) return this.__toggle.apply(this, arguments); var c = k.apply(this, arguments); return c[1].mode = "toggle", this.effect.apply(this, c) }, cssUnit: function (b) { var c = this.css(b), d = []; return a.each(["em", "px", "%", "pt"], function (a, b) { c.indexOf(b) > 0 && (d = [parseFloat(c), b]) }), d } }), a.easing.jswing = a.easing.swing, a.extend(a.easing, { def: "easeOutQuad", swing: function (b, c, d, e, f) { return a.easing[a.easing.def](b, c, d, e, f) }, easeInQuad: function (a, b, c, d, e) { return d * (b /= e) * b + c }, easeOutQuad: function (a, b, c, d, e) { return -d * (b /= e) * (b - 2) + c }, easeInOutQuad: function (a, b, c, d, e) { return (b /= e / 2) < 1 ? d / 2 * b * b + c : -d / 2 * (--b * (b - 2) - 1) + c }, easeInCubic: function (a, b, c, d, e) { return d * (b /= e) * b * b + c }, easeOutCubic: function (a, b, c, d, e) { return d * ((b = b / e - 1) * b * b + 1) + c }, easeInOutCubic: function (a, b, c, d, e) { return (b /= e / 2) < 1 ? d / 2 * b * b * b + c : d / 2 * ((b -= 2) * b * b + 2) + c }, easeInQuart: function (a, b, c, d, e) { return d * (b /= e) * b * b * b + c }, easeOutQuart: function (a, b, c, d, e) { return -d * ((b = b / e - 1) * b * b * b - 1) + c }, easeInOutQuart: function (a, b, c, d, e) { return (b /= e / 2) < 1 ? d / 2 * b * b * b * b + c : -d / 2 * ((b -= 2) * b * b * b - 2) + c }, easeInQuint: function (a, b, c, d, e) { return d * (b /= e) * b * b * b * b + c }, easeOutQuint: function (a, b, c, d, e) { return d * ((b = b / e - 1) * b * b * b * b + 1) + c }, easeInOutQuint: function (a, b, c, d, e) { return (b /= e / 2) < 1 ? d / 2 * b * b * b * b * b + c : d / 2 * ((b -= 2) * b * b * b * b + 2) + c }, easeInSine: function (a, b, c, d, e) { return -d * Math.cos(b / e * (Math.PI / 2)) + d + c }, easeOutSine: function (a, b, c, d, e) { return d * Math.sin(b / e * (Math.PI / 2)) + c }, easeInOutSine: function (a, b, c, d, e) { return -d / 2 * (Math.cos(Math.PI * b / e) - 1) + c }, easeInExpo: function (a, b, c, d, e) { return b == 0 ? c : d * Math.pow(2, 10 * (b / e - 1)) + c }, easeOutExpo: function (a, b, c, d, e) { return b == e ? c + d : d * (-Math.pow(2, -10 * b / e) + 1) + c }, easeInOutExpo: function (a, b, c, d, e) { return b == 0 ? c : b == e ? c + d : (b /= e / 2) < 1 ? d / 2 * Math.pow(2, 10 * (b - 1)) + c : d / 2 * (-Math.pow(2, -10 * --b) + 2) + c }, easeInCirc: function (a, b, c, d, e) { return -d * (Math.sqrt(1 - (b /= e) * b) - 1) + c }, easeOutCirc: function (a, b, c, d, e) { return d * Math.sqrt(1 - (b = b / e - 1) * b) + c }, easeInOutCirc: function (a, b, c, d, e) { return (b /= e / 2) < 1 ? -d / 2 * (Math.sqrt(1 - b * b) - 1) + c : d / 2 * (Math.sqrt(1 - (b -= 2) * b) + 1) + c }, easeInElastic: function (a, b, c, d, e) { var f = 1.70158, g = 0, h = d; if (b == 0) return c; if ((b /= e) == 1) return c + d; g || (g = e * .3); if (h < Math.abs(d)) { h = d; var f = g / 4 } else var f = g / (2 * Math.PI) * Math.asin(d / h); return -(h * Math.pow(2, 10 * (b -= 1)) * Math.sin((b * e - f) * 2 * Math.PI / g)) + c }, easeOutElastic: function (a, b, c, d, e) { var f = 1.70158, g = 0, h = d; if (b == 0) return c; if ((b /= e) == 1) return c + d; g || (g = e * .3); if (h < Math.abs(d)) { h = d; var f = g / 4 } else var f = g / (2 * Math.PI) * Math.asin(d / h); return h * Math.pow(2, -10 * b) * Math.sin((b * e - f) * 2 * Math.PI / g) + d + c }, easeInOutElastic: function (a, b, c, d, e) { var f = 1.70158, g = 0, h = d; if (b == 0) return c; if ((b /= e / 2) == 2) return c + d; g || (g = e * .3 * 1.5); if (h < Math.abs(d)) { h = d; var f = g / 4 } else var f = g / (2 * Math.PI) * Math.asin(d / h); return b < 1 ? -0.5 * h * Math.pow(2, 10 * (b -= 1)) * Math.sin((b * e - f) * 2 * Math.PI / g) + c : h * Math.pow(2, -10 * (b -= 1)) * Math.sin((b * e - f) * 2 * Math.PI / g) * .5 + d + c }, easeInBack: function (a, c, d, e, f, g) { return g == b && (g = 1.70158), e * (c /= f) * c * ((g + 1) * c - g) + d }, easeOutBack: function (a, c, d, e, f, g) { return g == b && (g = 1.70158), e * ((c = c / f - 1) * c * ((g + 1) * c + g) + 1) + d }, easeInOutBack: function (a, c, d, e, f, g) { return g == b && (g = 1.70158), (c /= f / 2) < 1 ? e / 2 * c * c * (((g *= 1.525) + 1) * c - g) + d : e / 2 * ((c -= 2) * c * (((g *= 1.525) + 1) * c + g) + 2) + d }, easeInBounce: function (b, c, d, e, f) { return e - a.easing.easeOutBounce(b, f - c, 0, e, f) + d }, easeOutBounce: function (a, b, c, d, e) { return (b /= e) < 1 / 2.75 ? d * 7.5625 * b * b + c : b < 2 / 2.75 ? d * (7.5625 * (b -= 1.5 / 2.75) * b + .75) + c : b < 2.5 / 2.75 ? d * (7.5625 * (b -= 2.25 / 2.75) * b + .9375) + c : d * (7.5625 * (b -= 2.625 / 2.75) * b + .984375) + c }, easeInOutBounce: function (b, c, d, e, f) { return c < f / 2 ? a.easing.easeInBounce(b, c * 2, 0, e, f) * .5 + d : a.easing.easeOutBounce(b, c * 2 - f, 0, e, f) * .5 + e * .5 + d } }) } (jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.blind.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.blind = function (b) { return this.queue(function () { var c = a(this), d = ["position", "top", "bottom", "left", "right"], e = a.effects.setMode(c, b.options.mode || "hide"), f = b.options.direction || "vertical"; a.effects.save(c, d), c.show(); var g = a.effects.createWrapper(c).css({ overflow: "hidden" }), h = f == "vertical" ? "height" : "width", i = f == "vertical" ? g.height() : g.width(); e == "show" && g.css(h, 0); var j = {}; j[h] = e == "show" ? i : 0, g.animate(j, b.duration, b.options.easing, function () { e == "hide" && c.hide(), a.effects.restore(c, d), a.effects.removeWrapper(c), b.callback && b.callback.apply(c[0], arguments), c.dequeue() }) }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.bounce.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.bounce = function (b) { return this.queue(function () { var c = a(this), d = ["position", "top", "bottom", "left", "right"], e = a.effects.setMode(c, b.options.mode || "effect"), f = b.options.direction || "up", g = b.options.distance || 20, h = b.options.times || 5, i = b.duration || 250; /show|hide/.test(e) && d.push("opacity"), a.effects.save(c, d), c.show(), a.effects.createWrapper(c); var j = f == "up" || f == "down" ? "top" : "left", k = f == "up" || f == "left" ? "pos" : "neg", g = b.options.distance || (j == "top" ? c.outerHeight({ margin: !0 }) / 3 : c.outerWidth({ margin: !0 }) / 3); e == "show" && c.css("opacity", 0).css(j, k == "pos" ? -g : g), e == "hide" && (g = g / (h * 2)), e != "hide" && h--; if (e == "show") { var l = { opacity: 1 }; l[j] = (k == "pos" ? "+=" : "-=") + g, c.animate(l, i / 2, b.options.easing), g = g / 2, h-- } for (var m = 0; m < h; m++) { var n = {}, p = {}; n[j] = (k == "pos" ? "-=" : "+=") + g, p[j] = (k == "pos" ? "+=" : "-=") + g, c.animate(n, i / 2, b.options.easing).animate(p, i / 2, b.options.easing), g = e == "hide" ? g * 2 : g / 2 } if (e == "hide") { var l = { opacity: 0 }; l[j] = (k == "pos" ? "-=" : "+=") + g, c.animate(l, i / 2, b.options.easing, function () { c.hide(), a.effects.restore(c, d), a.effects.removeWrapper(c), b.callback && b.callback.apply(this, arguments) }) } else { var n = {}, p = {}; n[j] = (k == "pos" ? "-=" : "+=") + g, p[j] = (k == "pos" ? "+=" : "-=") + g, c.animate(n, i / 2, b.options.easing).animate(p, i / 2, b.options.easing, function () { a.effects.restore(c, d), a.effects.removeWrapper(c), b.callback && b.callback.apply(this, arguments) }) } c.queue("fx", function () { c.dequeue() }), c.dequeue() }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.clip.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.clip = function (b) { return this.queue(function () { var c = a(this), d = ["position", "top", "bottom", "left", "right", "height", "width"], e = a.effects.setMode(c, b.options.mode || "hide"), f = b.options.direction || "vertical"; a.effects.save(c, d), c.show(); var g = a.effects.createWrapper(c).css({ overflow: "hidden" }), h = c[0].tagName == "IMG" ? g : c, i = { size: f == "vertical" ? "height" : "width", position: f == "vertical" ? "top" : "left" }, j = f == "vertical" ? h.height() : h.width(); e == "show" && (h.css(i.size, 0), h.css(i.position, j / 2)); var k = {}; k[i.size] = e == "show" ? j : 0, k[i.position] = e == "show" ? 0 : j / 2, h.animate(k, { queue: !1, duration: b.duration, easing: b.options.easing, complete: function () { e == "hide" && c.hide(), a.effects.restore(c, d), a.effects.removeWrapper(c), b.callback && b.callback.apply(c[0], arguments), c.dequeue() } }) }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.drop.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.drop = function (b) { return this.queue(function () { var c = a(this), d = ["position", "top", "bottom", "left", "right", "opacity"], e = a.effects.setMode(c, b.options.mode || "hide"), f = b.options.direction || "left"; a.effects.save(c, d), c.show(), a.effects.createWrapper(c); var g = f == "up" || f == "down" ? "top" : "left", h = f == "up" || f == "left" ? "pos" : "neg", i = b.options.distance || (g == "top" ? c.outerHeight({ margin: !0 }) / 2 : c.outerWidth({ margin: !0 }) / 2); e == "show" && c.css("opacity", 0).css(g, h == "pos" ? -i : i); var j = { opacity: e == "show" ? 1 : 0 }; j[g] = (e == "show" ? h == "pos" ? "+=" : "-=" : h == "pos" ? "-=" : "+=") + i, c.animate(j, { queue: !1, duration: b.duration, easing: b.options.easing, complete: function () { e == "hide" && c.hide(), a.effects.restore(c, d), a.effects.removeWrapper(c), b.callback && b.callback.apply(this, arguments), c.dequeue() } }) }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.explode.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.explode = function (b) { return this.queue(function () { var c = b.options.pieces ? Math.round(Math.sqrt(b.options.pieces)) : 3, d = b.options.pieces ? Math.round(Math.sqrt(b.options.pieces)) : 3; b.options.mode = b.options.mode == "toggle" ? a(this).is(":visible") ? "hide" : "show" : b.options.mode; var e = a(this).show().css("visibility", "hidden"), f = e.offset(); f.top -= parseInt(e.css("marginTop"), 10) || 0, f.left -= parseInt(e.css("marginLeft"), 10) || 0; var g = e.outerWidth(!0), h = e.outerHeight(!0); for (var i = 0; i < c; i++) for (var j = 0; j < d; j++) e.clone().appendTo("body").wrap("
      ").css({ position: "absolute", visibility: "visible", left: -j * (g / d), top: -i * (h / c) }).parent().addClass("ui-effects-explode").css({ position: "absolute", overflow: "hidden", width: g / d, height: h / c, left: f.left + j * (g / d) + (b.options.mode == "show" ? (j - Math.floor(d / 2)) * (g / d) : 0), top: f.top + i * (h / c) + (b.options.mode == "show" ? (i - Math.floor(c / 2)) * (h / c) : 0), opacity: b.options.mode == "show" ? 0 : 1 }).animate({ left: f.left + j * (g / d) + (b.options.mode == "show" ? 0 : (j - Math.floor(d / 2)) * (g / d)), top: f.top + i * (h / c) + (b.options.mode == "show" ? 0 : (i - Math.floor(c / 2)) * (h / c)), opacity: b.options.mode == "show" ? 1 : 0 }, b.duration || 500); setTimeout(function () { b.options.mode == "show" ? e.css({ visibility: "visible" }) : e.css({ visibility: "visible" }).hide(), b.callback && b.callback.apply(e[0]), e.dequeue(), a("div.ui-effects-explode").remove() }, b.duration || 500) }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.fade.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.fade = function (b) { return this.queue(function () { var c = a(this), d = a.effects.setMode(c, b.options.mode || "hide"); c.animate({ opacity: d }, { queue: !1, duration: b.duration, easing: b.options.easing, complete: function () { b.callback && b.callback.apply(this, arguments), c.dequeue() } }) }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.fold.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.fold = function (b) { return this.queue(function () { var c = a(this), d = ["position", "top", "bottom", "left", "right"], e = a.effects.setMode(c, b.options.mode || "hide"), f = b.options.size || 15, g = !!b.options.horizFirst, h = b.duration ? b.duration / 2 : a.fx.speeds._default / 2; a.effects.save(c, d), c.show(); var i = a.effects.createWrapper(c).css({ overflow: "hidden" }), j = e == "show" != g, k = j ? ["width", "height"] : ["height", "width"], l = j ? [i.width(), i.height()] : [i.height(), i.width()], m = /([0-9]+)%/.exec(f); m && (f = parseInt(m[1], 10) / 100 * l[e == "hide" ? 0 : 1]), e == "show" && i.css(g ? { height: 0, width: f} : { height: f, width: 0 }); var n = {}, p = {}; n[k[0]] = e == "show" ? l[0] : f, p[k[1]] = e == "show" ? l[1] : 0, i.animate(n, h, b.options.easing).animate(p, h, b.options.easing, function () { e == "hide" && c.hide(), a.effects.restore(c, d), a.effects.removeWrapper(c), b.callback && b.callback.apply(c[0], arguments), c.dequeue() }) }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.highlight.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.highlight = function (b) { return this.queue(function () { var c = a(this), d = ["backgroundImage", "backgroundColor", "opacity"], e = a.effects.setMode(c, b.options.mode || "show"), f = { backgroundColor: c.css("backgroundColor") }; e == "hide" && (f.opacity = 0), a.effects.save(c, d), c.show().css({ backgroundImage: "none", backgroundColor: b.options.color || "#ffff99" }).animate(f, { queue: !1, duration: b.duration, easing: b.options.easing, complete: function () { e == "hide" && c.hide(), a.effects.restore(c, d), e == "show" && !a.support.opacity && this.style.removeAttribute("filter"), b.callback && b.callback.apply(this, arguments), c.dequeue() } }) }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.pulsate.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.pulsate = function (b) { return this.queue(function () { var c = a(this), d = a.effects.setMode(c, b.options.mode || "show"), e = (b.options.times || 5) * 2 - 1, f = b.duration ? b.duration / 2 : a.fx.speeds._default / 2, g = c.is(":visible"), h = 0; g || (c.css("opacity", 0).show(), h = 1), (d == "hide" && g || d == "show" && !g) && e--; for (var i = 0; i < e; i++) c.animate({ opacity: h }, f, b.options.easing), h = (h + 1) % 2; c.animate({ opacity: h }, f, b.options.easing, function () { h == 0 && c.hide(), b.callback && b.callback.apply(this, arguments) }), c.queue("fx", function () { c.dequeue() }).dequeue() }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.scale.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.puff = function (b) { return this.queue(function () { var c = a(this), d = a.effects.setMode(c, b.options.mode || "hide"), e = parseInt(b.options.percent, 10) || 150, f = e / 100, g = { height: c.height(), width: c.width() }; a.extend(b.options, { fade: !0, mode: d, percent: d == "hide" ? e : 100, from: d == "hide" ? g : { height: g.height * f, width: g.width * f} }), c.effect("scale", b.options, b.duration, b.callback), c.dequeue() }) }, a.effects.scale = function (b) { return this.queue(function () { var c = a(this), d = a.extend(!0, {}, b.options), e = a.effects.setMode(c, b.options.mode || "effect"), f = parseInt(b.options.percent, 10) || (parseInt(b.options.percent, 10) == 0 ? 0 : e == "hide" ? 0 : 100), g = b.options.direction || "both", h = b.options.origin; e != "effect" && (d.origin = h || ["middle", "center"], d.restore = !0); var i = { height: c.height(), width: c.width() }; c.from = b.options.from || (e == "show" ? { height: 0, width: 0} : i); var j = { y: g != "horizontal" ? f / 100 : 1, x: g != "vertical" ? f / 100 : 1 }; c.to = { height: i.height * j.y, width: i.width * j.x }, b.options.fade && (e == "show" && (c.from.opacity = 0, c.to.opacity = 1), e == "hide" && (c.from.opacity = 1, c.to.opacity = 0)), d.from = c.from, d.to = c.to, d.mode = e, c.effect("size", d, b.duration, b.callback), c.dequeue() }) }, a.effects.size = function (b) { return this.queue(function () { var c = a(this), d = ["position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity"], e = ["position", "top", "bottom", "left", "right", "overflow", "opacity"], f = ["width", "height", "overflow"], g = ["fontSize"], h = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"], i = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"], j = a.effects.setMode(c, b.options.mode || "effect"), k = b.options.restore || !1, l = b.options.scale || "both", m = b.options.origin, n = { height: c.height(), width: c.width() }; c.from = b.options.from || n, c.to = b.options.to || n; if (m) { var p = a.effects.getBaseline(m, n); c.from.top = (n.height - c.from.height) * p.y, c.from.left = (n.width - c.from.width) * p.x, c.to.top = (n.height - c.to.height) * p.y, c.to.left = (n.width - c.to.width) * p.x } var q = { from: { y: c.from.height / n.height, x: c.from.width / n.width }, to: { y: c.to.height / n.height, x: c.to.width / n.width} }; if (l == "box" || l == "both") q.from.y != q.to.y && (d = d.concat(h), c.from = a.effects.setTransition(c, h, q.from.y, c.from), c.to = a.effects.setTransition(c, h, q.to.y, c.to)), q.from.x != q.to.x && (d = d.concat(i), c.from = a.effects.setTransition(c, i, q.from.x, c.from), c.to = a.effects.setTransition(c, i, q.to.x, c.to)); (l == "content" || l == "both") && q.from.y != q.to.y && (d = d.concat(g), c.from = a.effects.setTransition(c, g, q.from.y, c.from), c.to = a.effects.setTransition(c, g, q.to.y, c.to)), a.effects.save(c, k ? d : e), c.show(), a.effects.createWrapper(c), c.css("overflow", "hidden").css(c.from); if (l == "content" || l == "both") h = h.concat(["marginTop", "marginBottom"]).concat(g), i = i.concat(["marginLeft", "marginRight"]), f = d.concat(h).concat(i), c.find("*[width]").each(function () { var c = a(this); k && a.effects.save(c, f); var d = { height: c.height(), width: c.width() }; c.from = { height: d.height * q.from.y, width: d.width * q.from.x }, c.to = { height: d.height * q.to.y, width: d.width * q.to.x }, q.from.y != q.to.y && (c.from = a.effects.setTransition(c, h, q.from.y, c.from), c.to = a.effects.setTransition(c, h, q.to.y, c.to)), q.from.x != q.to.x && (c.from = a.effects.setTransition(c, i, q.from.x, c.from), c.to = a.effects.setTransition(c, i, q.to.x, c.to)), c.css(c.from), c.animate(c.to, b.duration, b.options.easing, function () { k && a.effects.restore(c, f) }) }); c.animate(c.to, { queue: !1, duration: b.duration, easing: b.options.easing, complete: function () { c.to.opacity === 0 && c.css("opacity", c.from.opacity), j == "hide" && c.hide(), a.effects.restore(c, k ? d : e), a.effects.removeWrapper(c), b.callback && b.callback.apply(this, arguments), c.dequeue() } }) }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.shake.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.shake = function (b) { return this.queue(function () { var c = a(this), d = ["position", "top", "bottom", "left", "right"], e = a.effects.setMode(c, b.options.mode || "effect"), f = b.options.direction || "left", g = b.options.distance || 20, h = b.options.times || 3, i = b.duration || b.options.duration || 140; a.effects.save(c, d), c.show(), a.effects.createWrapper(c); var j = f == "up" || f == "down" ? "top" : "left", k = f == "up" || f == "left" ? "pos" : "neg", l = {}, m = {}, n = {}; l[j] = (k == "pos" ? "-=" : "+=") + g, m[j] = (k == "pos" ? "+=" : "-=") + g * 2, n[j] = (k == "pos" ? "-=" : "+=") + g * 2, c.animate(l, i, b.options.easing); for (var p = 1; p < h; p++) c.animate(m, i, b.options.easing).animate(n, i, b.options.easing); c.animate(m, i, b.options.easing).animate(l, i / 2, b.options.easing, function () { a.effects.restore(c, d), a.effects.removeWrapper(c), b.callback && b.callback.apply(this, arguments) }), c.queue("fx", function () { c.dequeue() }), c.dequeue() }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.slide.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.slide = function (b) { return this.queue(function () { var c = a(this), d = ["position", "top", "bottom", "left", "right"], e = a.effects.setMode(c, b.options.mode || "show"), f = b.options.direction || "left"; a.effects.save(c, d), c.show(), a.effects.createWrapper(c).css({ overflow: "hidden" }); var g = f == "up" || f == "down" ? "top" : "left", h = f == "up" || f == "left" ? "pos" : "neg", i = b.options.distance || (g == "top" ? c.outerHeight({ margin: !0 }) : c.outerWidth({ margin: !0 })); e == "show" && c.css(g, h == "pos" ? isNaN(i) ? "-" + i : -i : i); var j = {}; j[g] = (e == "show" ? h == "pos" ? "+=" : "-=" : h == "pos" ? "-=" : "+=") + i, c.animate(j, { queue: !1, duration: b.duration, easing: b.options.easing, complete: function () { e == "hide" && c.hide(), a.effects.restore(c, d), a.effects.removeWrapper(c), b.callback && b.callback.apply(this, arguments), c.dequeue() } }) }) } })(jQuery); ; /*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.transfer.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function (a, b) { a.effects.transfer = function (b) { return this.queue(function () { var c = a(this), d = a(b.options.to), e = d.offset(), f = { top: e.top, left: e.left, height: d.innerHeight(), width: d.innerWidth() }, g = c.offset(), h = a('
      ').appendTo(document.body).addClass(b.options.className).css({ top: g.top, left: g.left, height: c.innerHeight(), width: c.innerWidth(), position: "absolute" }).animate(f, b.duration, b.options.easing, function () { h.remove(), b.callback && b.callback.apply(c[0], arguments), c.dequeue() }) }) } })(jQuery); ; \ No newline at end of file +/*! jQuery UI - v1.11.4 - 2015-03-11 +* http://jqueryui.com +* Includes: core.js, widget.js, mouse.js, position.js, accordion.js, autocomplete.js, button.js, datepicker.js, dialog.js, draggable.js, droppable.js, effect.js, effect-blind.js, effect-bounce.js, effect-clip.js, effect-drop.js, effect-explode.js, effect-fade.js, effect-fold.js, effect-highlight.js, effect-puff.js, effect-pulsate.js, effect-scale.js, effect-shake.js, effect-size.js, effect-slide.js, effect-transfer.js, menu.js, progressbar.js, resizable.js, selectable.js, selectmenu.js, slider.js, sortable.js, spinner.js, tabs.js, tooltip.js +* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ + +(function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e(jQuery)})(function(e){function t(t,s){var n,a,o,r=t.nodeName.toLowerCase();return"area"===r?(n=t.parentNode,a=n.name,t.href&&a&&"map"===n.nodeName.toLowerCase()?(o=e("img[usemap='#"+a+"']")[0],!!o&&i(o)):!1):(/^(input|select|textarea|button|object)$/.test(r)?!t.disabled:"a"===r?t.href||s:s)&&i(t)}function i(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}function s(e){for(var t,i;e.length&&e[0]!==document;){if(t=e.css("position"),("absolute"===t||"relative"===t||"fixed"===t)&&(i=parseInt(e.css("zIndex"),10),!isNaN(i)&&0!==i))return i;e=e.parent()}return 0}function n(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},e.extend(this._defaults,this.regional[""]),this.regional.en=e.extend(!0,{},this.regional[""]),this.regional["en-US"]=e.extend(!0,{},this.regional.en),this.dpDiv=a(e("
      "))}function a(t){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return t.delegate(i,"mouseout",function(){e(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&e(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&e(this).removeClass("ui-datepicker-next-hover")}).delegate(i,"mouseover",o)}function o(){e.datepicker._isDisabledDatepicker(v.inline?v.dpDiv.parent()[0]:v.input[0])||(e(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),e(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&e(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&e(this).addClass("ui-datepicker-next-hover"))}function r(t,i){e.extend(t,i);for(var s in i)null==i[s]&&(t[s]=i[s]);return t}function h(e){return function(){var t=this.element.val();e.apply(this,arguments),this._refresh(),t!==this.element.val()&&this._trigger("change")}}e.ui=e.ui||{},e.extend(e.ui,{version:"1.11.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({scrollParent:function(t){var i=this.css("position"),s="absolute"===i,n=t?/(auto|scroll|hidden)/:/(auto|scroll)/,a=this.parents().filter(function(){var t=e(this);return s&&"static"===t.css("position")?!1:n.test(t.css("overflow")+t.css("overflow-y")+t.css("overflow-x"))}).eq(0);return"fixed"!==i&&a.length?a:e(this[0].ownerDocument||document)},uniqueId:function(){var e=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++e)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,s){return!!e.data(t,s[3])},focusable:function(i){return t(i,!isNaN(e.attr(i,"tabindex")))},tabbable:function(i){var s=e.attr(i,"tabindex"),n=isNaN(s);return(n||s>=0)&&t(i,!n)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(t,i){function s(t,i,s,a){return e.each(n,function(){i-=parseFloat(e.css(t,"padding"+this))||0,s&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),a&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],a=i.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+i]=function(t){return void 0===t?o["inner"+i].call(this):this.each(function(){e(this).css(a,s(this,t)+"px")})},e.fn["outer"+i]=function(t,n){return"number"!=typeof t?o["outer"+i].call(this,t):this.each(function(){e(this).css(a,s(this,t,!0,n)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.fn.extend({focus:function(t){return function(i,s){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),s&&s.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),disableSelection:function(){var e="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.bind(e+".ui-disableSelection",function(e){e.preventDefault()})}}(),enableSelection:function(){return this.unbind(".ui-disableSelection")},zIndex:function(t){if(void 0!==t)return this.css("zIndex",t);if(this.length)for(var i,s,n=e(this[0]);n.length&&n[0]!==document;){if(i=n.css("position"),("absolute"===i||"relative"===i||"fixed"===i)&&(s=parseInt(n.css("zIndex"),10),!isNaN(s)&&0!==s))return s;n=n.parent()}return 0}}),e.ui.plugin={add:function(t,i,s){var n,a=e.ui[t].prototype;for(n in s)a.plugins[n]=a.plugins[n]||[],a.plugins[n].push([i,s[n]])},call:function(e,t,i,s){var n,a=e.plugins[t];if(a&&(s||e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType))for(n=0;a.length>n;n++)e.options[a[n][0]]&&a[n][1].apply(e.element,i)}};var l=0,u=Array.prototype.slice;e.cleanData=function(t){return function(i){var s,n,a;for(a=0;null!=(n=i[a]);a++)try{s=e._data(n,"events"),s&&s.remove&&e(n).triggerHandler("remove")}catch(o){}t(i)}}(e.cleanData),e.widget=function(t,i,s){var n,a,o,r,h={},l=t.split(".")[0];return t=t.split(".")[1],n=l+"-"+t,s||(s=i,i=e.Widget),e.expr[":"][n.toLowerCase()]=function(t){return!!e.data(t,n)},e[l]=e[l]||{},a=e[l][t],o=e[l][t]=function(e,t){return this._createWidget?(arguments.length&&this._createWidget(e,t),void 0):new o(e,t)},e.extend(o,a,{version:s.version,_proto:e.extend({},s),_childConstructors:[]}),r=new i,r.options=e.widget.extend({},r.options),e.each(s,function(t,s){return e.isFunction(s)?(h[t]=function(){var e=function(){return i.prototype[t].apply(this,arguments)},n=function(e){return i.prototype[t].apply(this,e)};return function(){var t,i=this._super,a=this._superApply;return this._super=e,this._superApply=n,t=s.apply(this,arguments),this._super=i,this._superApply=a,t}}(),void 0):(h[t]=s,void 0)}),o.prototype=e.widget.extend(r,{widgetEventPrefix:a?r.widgetEventPrefix||t:t},h,{constructor:o,namespace:l,widgetName:t,widgetFullName:n}),a?(e.each(a._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete a._childConstructors):i._childConstructors.push(o),e.widget.bridge(t,o),o},e.widget.extend=function(t){for(var i,s,n=u.call(arguments,1),a=0,o=n.length;o>a;a++)for(i in n[a])s=n[a][i],n[a].hasOwnProperty(i)&&void 0!==s&&(t[i]=e.isPlainObject(s)?e.isPlainObject(t[i])?e.widget.extend({},t[i],s):e.widget.extend({},s):s);return t},e.widget.bridge=function(t,i){var s=i.prototype.widgetFullName||t;e.fn[t]=function(n){var a="string"==typeof n,o=u.call(arguments,1),r=this;return a?this.each(function(){var i,a=e.data(this,s);return"instance"===n?(r=a,!1):a?e.isFunction(a[n])&&"_"!==n.charAt(0)?(i=a[n].apply(a,o),i!==a&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):e.error("no such method '"+n+"' for "+t+" widget instance"):e.error("cannot call methods on "+t+" prior to initialization; "+"attempted to call method '"+n+"'")}):(o.length&&(n=e.widget.extend.apply(null,[n].concat(o))),this.each(function(){var t=e.data(this,s);t?(t.option(n||{}),t._init&&t._init()):e.data(this,s,new i(n,this))})),r}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
      ",options:{disabled:!1,create:null},_createWidget:function(t,i){i=e(i||this.defaultElement||this)[0],this.element=e(i),this.uuid=l++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=e(),this.hoverable=e(),this.focusable=e(),i!==this&&(e.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===i&&this.destroy()}}),this.document=e(i.style?i.ownerDocument:i.document||i),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(t,i){var s,n,a,o=t;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof t)if(o={},s=t.split("."),t=s.shift(),s.length){for(n=o[t]=e.widget.extend({},this.options[t]),a=0;s.length-1>a;a++)n[s[a]]=n[s[a]]||{},n=n[s[a]];if(t=s.pop(),1===arguments.length)return void 0===n[t]?null:n[t];n[t]=i}else{if(1===arguments.length)return void 0===this.options[t]?null:this.options[t];o[t]=i}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled",!!t),t&&(this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus"))),this},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_on:function(t,i,s){var n,a=this;"boolean"!=typeof t&&(s=i,i=t,t=!1),s?(i=n=e(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),e.each(s,function(s,o){function r(){return t||a.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?a[o]:o).apply(a,arguments):void 0}"string"!=typeof o&&(r.guid=o.guid=o.guid||r.guid||e.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+a.eventNamespace,u=h[2];u?n.delegate(u,l,r):i.bind(l,r)})},_off:function(t,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(i).undelegate(i),this.bindings=e(this.bindings.not(t).get()),this.focusable=e(this.focusable.not(t).get()),this.hoverable=e(this.hoverable.not(t).get())},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var n,a,o=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],a=i.originalEvent)for(n in a)n in i||(i[n]=a[n]);return this.element.trigger(i,s),!(e.isFunction(o)&&o.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,n,a){"string"==typeof n&&(n={effect:n});var o,r=n?n===!0||"number"==typeof n?i:n.effect||i:t;n=n||{},"number"==typeof n&&(n={duration:n}),o=!e.isEmptyObject(n),n.complete=a,n.delay&&s.delay(n.delay),o&&e.effects&&e.effects.effect[r]?s[t](n):r!==t&&s[r]?s[r](n.duration,n.easing,a):s.queue(function(i){e(this)[t](),a&&a.call(s[0]),i()})}}),e.widget;var d=!1;e(document).mouseup(function(){d=!1}),e.widget("ui.mouse",{version:"1.11.4",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(i){return!0===e.data(i.target,t.widgetName+".preventClickEvent")?(e.removeData(i.target,t.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(!d){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var i=this,s=1===t.which,n="string"==typeof this.options.cancel&&t.target.nodeName?e(t.target).closest(this.options.cancel).length:!1;return s&&!n&&this._mouseCapture(t)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(t)!==!1,!this._mouseStarted)?(t.preventDefault(),!0):(!0===e.data(t.target,this.widgetName+".preventClickEvent")&&e.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return i._mouseMove(e)},this._mouseUpDelegate=function(e){return i._mouseUp(e)},this.document.bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),d=!0,!0)):!0}},_mouseMove:function(t){if(this._mouseMoved){if(e.ui.ie&&(!document.documentMode||9>document.documentMode)&&!t.button)return this._mouseUp(t);if(!t.which)return this._mouseUp(t)}return(t.which||t.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),d=!1,!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),function(){function t(e,t,i){return[parseFloat(e[0])*(p.test(e[0])?t/100:1),parseFloat(e[1])*(p.test(e[1])?i/100:1)]}function i(t,i){return parseInt(e.css(t,i),10)||0}function s(t){var i=t[0];return 9===i.nodeType?{width:t.width(),height:t.height(),offset:{top:0,left:0}}:e.isWindow(i)?{width:t.width(),height:t.height(),offset:{top:t.scrollTop(),left:t.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:t.outerWidth(),height:t.outerHeight(),offset:t.offset()}}e.ui=e.ui||{};var n,a,o=Math.max,r=Math.abs,h=Math.round,l=/left|center|right/,u=/top|center|bottom/,d=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,p=/%$/,f=e.fn.position;e.position={scrollbarWidth:function(){if(void 0!==n)return n;var t,i,s=e("
      "),a=s.children()[0];return e("body").append(s),t=a.offsetWidth,s.css("overflow","scroll"),i=a.offsetWidth,t===i&&(i=s[0].clientWidth),s.remove(),n=t-i},getScrollInfo:function(t){var i=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),s=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),n="scroll"===i||"auto"===i&&t.widthi?"left":t>0?"right":"center",vertical:0>a?"top":s>0?"bottom":"middle"};d>m&&m>r(t+i)&&(h.horizontal="center"),c>g&&g>r(s+a)&&(h.vertical="middle"),h.important=o(r(t),r(i))>o(r(s),r(a))?"horizontal":"vertical",n.using.call(this,e,h)}),u.offset(e.extend(M,{using:l}))})},e.ui.position={fit:{left:function(e,t){var i,s=t.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=e.left-t.collisionPosition.marginLeft,h=n-r,l=r+t.collisionWidth-a-n;t.collisionWidth>a?h>0&&0>=l?(i=e.left+h+t.collisionWidth-a-n,e.left+=h-i):e.left=l>0&&0>=h?n:h>l?n+a-t.collisionWidth:n:h>0?e.left+=h:l>0?e.left-=l:e.left=o(e.left-r,e.left)},top:function(e,t){var i,s=t.within,n=s.isWindow?s.scrollTop:s.offset.top,a=t.within.height,r=e.top-t.collisionPosition.marginTop,h=n-r,l=r+t.collisionHeight-a-n;t.collisionHeight>a?h>0&&0>=l?(i=e.top+h+t.collisionHeight-a-n,e.top+=h-i):e.top=l>0&&0>=h?n:h>l?n+a-t.collisionHeight:n:h>0?e.top+=h:l>0?e.top-=l:e.top=o(e.top-r,e.top)}},flip:{left:function(e,t){var i,s,n=t.within,a=n.offset.left+n.scrollLeft,o=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=e.left-t.collisionPosition.marginLeft,u=l-h,d=l+t.collisionWidth-o-h,c="left"===t.my[0]?-t.elemWidth:"right"===t.my[0]?t.elemWidth:0,p="left"===t.at[0]?t.targetWidth:"right"===t.at[0]?-t.targetWidth:0,f=-2*t.offset[0];0>u?(i=e.left+c+p+f+t.collisionWidth-o-a,(0>i||r(u)>i)&&(e.left+=c+p+f)):d>0&&(s=e.left-t.collisionPosition.marginLeft+c+p+f-h,(s>0||d>r(s))&&(e.left+=c+p+f))},top:function(e,t){var i,s,n=t.within,a=n.offset.top+n.scrollTop,o=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=e.top-t.collisionPosition.marginTop,u=l-h,d=l+t.collisionHeight-o-h,c="top"===t.my[1],p=c?-t.elemHeight:"bottom"===t.my[1]?t.elemHeight:0,f="top"===t.at[1]?t.targetHeight:"bottom"===t.at[1]?-t.targetHeight:0,m=-2*t.offset[1];0>u?(s=e.top+p+f+m+t.collisionHeight-o-a,(0>s||r(u)>s)&&(e.top+=p+f+m)):d>0&&(i=e.top-t.collisionPosition.marginTop+p+f+m-h,(i>0||d>r(i))&&(e.top+=p+f+m))}},flipfit:{left:function(){e.ui.position.flip.left.apply(this,arguments),e.ui.position.fit.left.apply(this,arguments)},top:function(){e.ui.position.flip.top.apply(this,arguments),e.ui.position.fit.top.apply(this,arguments)}}},function(){var t,i,s,n,o,r=document.getElementsByTagName("body")[0],h=document.createElement("div");t=document.createElement(r?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},r&&e.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(o in s)t.style[o]=s[o];t.appendChild(h),i=r||document.documentElement,i.insertBefore(t,i.firstChild),h.style.cssText="position: absolute; left: 10.7432222px;",n=e(h).offset().left,a=n>10&&11>n,t.innerHTML="",i.removeChild(t)}()}(),e.ui.position,e.widget("ui.accordion",{version:"1.11.4",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var t=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset").attr("role","tablist"),t.collapsible||t.active!==!1&&null!=t.active||(t.active=0),this._processPanels(),0>t.active&&(t.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").removeUniqueId(),this._destroyIcons(),e=this.headers.next().removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").css("display","").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&e.css("height","")},_setOption:function(e,t){return"active"===e?(this._activate(t),void 0):("event"===e&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),"collapsible"!==e||t||this.options.active!==!1||this._activate(0),"icons"===e&&(this._destroyIcons(),t&&this._createIcons()),"disabled"===e&&(this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t)),void 0)},_keydown:function(t){if(!t.altKey&&!t.ctrlKey){var i=e.ui.keyCode,s=this.headers.length,n=this.headers.index(t.target),a=!1;switch(t.keyCode){case i.RIGHT:case i.DOWN:a=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:a=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(t);break;case i.HOME:a=this.headers[0];break;case i.END:a=this.headers[s-1]}a&&(e(t.target).attr("tabIndex",-1),e(a).attr("tabIndex",0),a.focus(),t.preventDefault())}},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t=this.options;this._processPanels(),t.active===!1&&t.collapsible===!0||!this.headers.length?(t.active=!1,this.active=e()):t.active===!1?this._activate(0):this.active.length&&!e.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(t.active=!1,this.active=e()):this._activate(Math.max(0,t.active-1)):t.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var e=this.headers,t=this.panels;this.headers=this.element.find(this.options.header).addClass("ui-accordion-header ui-state-default ui-corner-all"),this.panels=this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").filter(":not(.ui-accordion-content-active)").hide(),t&&(this._off(e.not(this.headers)),this._off(t.not(this.panels)))},_refresh:function(){var t,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active).addClass("ui-accordion-header-active ui-state-active ui-corner-top").removeClass("ui-corner-all"),this.active.next().addClass("ui-accordion-content-active").show(),this.headers.attr("role","tab").each(function(){var t=e(this),i=t.uniqueId().attr("id"),s=t.next(),n=s.uniqueId().attr("id");t.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(t=n.height(),this.element.siblings(":visible").each(function(){var i=e(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(t-=i.outerHeight(!0))}),this.headers.each(function(){t-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,t-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===s&&(t=0,this.headers.next().each(function(){t=Math.max(t,e(this).css("height","").height())}).height(t))},_activate:function(t){var i=this._findActive(t)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:e.noop}))},_findActive:function(t){return"number"==typeof t?this.headers.eq(t):e()},_setupEvents:function(t){var i={keydown:"_keydown"};t&&e.each(t.split(" "),function(e,t){i[t]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(t){var i=this.options,s=this.active,n=e(t.currentTarget),a=n[0]===s[0],o=a&&i.collapsible,r=o?e():n.next(),h=s.next(),l={oldHeader:s,oldPanel:h,newHeader:o?e():n,newPanel:r};t.preventDefault(),a&&!i.collapsible||this._trigger("beforeActivate",t,l)===!1||(i.active=o?!1:this.headers.index(n),this.active=a?e():n,this._toggle(l),s.removeClass("ui-accordion-header-active ui-state-active"),i.icons&&s.children(".ui-accordion-header-icon").removeClass(i.icons.activeHeader).addClass(i.icons.header),a||(n.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),i.icons&&n.children(".ui-accordion-header-icon").removeClass(i.icons.header).addClass(i.icons.activeHeader),n.next().addClass("ui-accordion-content-active")))},_toggle:function(t){var i=t.newPanel,s=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,t):(s.hide(),i.show(),this._toggleComplete(t)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(e(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(e,t,i){var s,n,a,o=this,r=0,h=e.css("box-sizing"),l=e.length&&(!t.length||e.index()",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},items:"> *",menus:"ul",position:{my:"left-1 top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item":function(e){e.preventDefault()},"click .ui-menu-item":function(t){var i=e(t.target);!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(t),t.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(t):!this.element.is(":focus")&&e(this.document[0].activeElement).closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(t){if(!this.previousFilter){var i=e(t.currentTarget); +i.siblings(".ui-state-active").removeClass("ui-state-active"),this.focus(t,i)}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(e,t){var i=this.active||this.element.find(this.options.items).eq(0);t||this.focus(e,i)},blur:function(t){this._delay(function(){e.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(e){this._closeOnDocumentClick(e)&&this.collapseAll(e),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-menu-icons ui-front").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").removeUniqueId().removeClass("ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var t=e(this);t.data("ui-menu-submenu-carat")&&t.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(t){var i,s,n,a,o=!0;switch(t.keyCode){case e.ui.keyCode.PAGE_UP:this.previousPage(t);break;case e.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case e.ui.keyCode.HOME:this._move("first","first",t);break;case e.ui.keyCode.END:this._move("last","last",t);break;case e.ui.keyCode.UP:this.previous(t);break;case e.ui.keyCode.DOWN:this.next(t);break;case e.ui.keyCode.LEFT:this.collapse(t);break;case e.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case e.ui.keyCode.ENTER:case e.ui.keyCode.SPACE:this._activate(t);break;case e.ui.keyCode.ESCAPE:this.collapse(t);break;default:o=!1,s=this.previousFilter||"",n=String.fromCharCode(t.keyCode),a=!1,clearTimeout(this.filterTimer),n===s?a=!0:n=s+n,i=this._filterMenuItems(n),i=a&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(t.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(t,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}o&&t.preventDefault()},_activate:function(e){this.active.is(".ui-state-disabled")||(this.active.is("[aria-haspopup='true']")?this.expand(e):this.select(e))},refresh:function(){var t,i,s=this,n=this.options.icons.submenu,a=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),a.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-front").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=e(this),i=t.parent(),s=e("").addClass("ui-menu-icon ui-icon "+n).data("ui-menu-submenu-carat",!0);i.attr("aria-haspopup","true").prepend(s),t.attr("aria-labelledby",i.attr("id"))}),t=a.add(this.element),i=t.find(this.options.items),i.not(".ui-menu-item").each(function(){var t=e(this);s._isDivider(t)&&t.addClass("ui-widget-content ui-menu-divider")}),i.not(".ui-menu-item, .ui-menu-divider").addClass("ui-menu-item").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!e.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(e,t){"icons"===e&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(t.submenu),"disabled"===e&&this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this._super(e,t)},focus:function(e,t){var i,s;this.blur(e,e&&"focus"===e.type),this._scrollIntoView(t),this.active=t.first(),s=this.active.addClass("ui-state-focus").removeClass("ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),this.active.parent().closest(".ui-menu-item").addClass("ui-state-active"),e&&"keydown"===e.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=t.children(".ui-menu"),i.length&&e&&/^mouse/.test(e.type)&&this._startOpening(i),this.activeMenu=t.parent(),this._trigger("focus",e,{item:t})},_scrollIntoView:function(t){var i,s,n,a,o,r;this._hasScroll()&&(i=parseFloat(e.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(e.css(this.activeMenu[0],"paddingTop"))||0,n=t.offset().top-this.activeMenu.offset().top-i-s,a=this.activeMenu.scrollTop(),o=this.activeMenu.height(),r=t.outerHeight(),0>n?this.activeMenu.scrollTop(a+n):n+r>o&&this.activeMenu.scrollTop(a+n-o+r))},blur:function(e,t){t||clearTimeout(this.timer),this.active&&(this.active.removeClass("ui-state-focus"),this.active=null,this._trigger("blur",e,{item:this.active}))},_startOpening:function(e){clearTimeout(this.timer),"true"===e.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(e)},this.delay))},_open:function(t){var i=e.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(t.parents(".ui-menu")).hide().attr("aria-hidden","true"),t.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(t,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:e(t&&t.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(t),this.activeMenu=s},this.delay)},_close:function(e){e||(e=this.active?this.active.parent():this.element),e.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find(".ui-state-active").not(".ui-state-focus").removeClass("ui-state-active")},_closeOnDocumentClick:function(t){return!e(t.target).closest(".ui-menu").length},_isDivider:function(e){return!/[^\-\u2014\u2013\s]/.test(e.text())},collapse:function(e){var t=this.active&&this.active.parent().closest(".ui-menu-item",this.element);t&&t.length&&(this._close(),this.focus(e,t))},expand:function(e){var t=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();t&&t.length&&(this._open(t.parent()),this._delay(function(){this.focus(e,t)}))},next:function(e){this._move("next","first",e)},previous:function(e){this._move("prev","last",e)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(e,t,i){var s;this.active&&(s="first"===e||"last"===e?this.active["first"===e?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[e+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[t]()),this.focus(i,s)},nextPage:function(t){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=e(this),0>i.offset().top-s-n}),this.focus(t,i)):this.focus(t,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(t),void 0)},previousPage:function(t){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=e(this),i.offset().top-s+n>0}),this.focus(t,i)):this.focus(t,this.activeMenu.find(this.options.items).first())),void 0):(this.next(t),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var t,i,s,n=this.element[0].nodeName.toLowerCase(),a="textarea"===n,o="input"===n;this.isMultiLine=a?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[a||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return t=!0,s=!0,i=!0,void 0;t=!1,s=!1,i=!1;var a=e.ui.keyCode;switch(n.keyCode){case a.PAGE_UP:t=!0,this._move("previousPage",n);break;case a.PAGE_DOWN:t=!0,this._move("nextPage",n);break;case a.UP:t=!0,this._keyEvent("previous",n);break;case a.DOWN:t=!0,this._keyEvent("next",n);break;case a.ENTER:this.menu.active&&(t=!0,n.preventDefault(),this.menu.select(n));break;case a.TAB:this.menu.active&&this.menu.select(n);break;case a.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(t)return t=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=e.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(e){return s?(s=!1,e.preventDefault(),void 0):(this._searchTimeout(e),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(e),this._change(e),void 0)}}),this._initSource(),this.menu=e("
        ").addClass("ui-autocomplete ui-front").appendTo(this._appendTo()).menu({role:null}).hide().menu("instance"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var i=this.menu.element[0];e(t.target).closest(".ui-menu-item").length||this._delay(function(){var t=this;this.document.one("mousedown",function(s){s.target===t.element[0]||s.target===i||e.contains(i,s.target)||t.close()})})},menufocus:function(t,i){var s,n;return this.isNewMenu&&(this.isNewMenu=!1,t.originalEvent&&/^mouse/.test(t.originalEvent.type))?(this.menu.blur(),this.document.one("mousemove",function(){e(t.target).trigger(t.originalEvent)}),void 0):(n=i.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",t,{item:n})&&t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(n.value),s=i.item.attr("aria-label")||n.value,s&&e.trim(s).length&&(this.liveRegion.children().hide(),e("
        ").text(s).appendTo(this.liveRegion)),void 0)},menuselect:function(e,t){var i=t.item.data("ui-autocomplete-item"),s=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s,this.selectedItem=i})),!1!==this._trigger("select",e,{item:i})&&this._value(i.value),this.term=this._value(),this.close(e),this.selectedItem=i}}),this.liveRegion=e("",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).addClass("ui-helper-hidden-accessible").appendTo(this.document[0].body),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),"source"===e&&this._initSource(),"appendTo"===e&&this.menu.element.appendTo(this._appendTo()),"disabled"===e&&t&&this.xhr&&this.xhr.abort()},_appendTo:function(){var t=this.options.appendTo;return t&&(t=t.jquery||t.nodeType?e(t):this.document.find(t).eq(0)),t&&t[0]||(t=this.element.closest(".ui-front")),t.length||(t=this.document[0].body),t},_initSource:function(){var t,i,s=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(i,s){s(e.ui.autocomplete.filter(t,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(t,n){s.xhr&&s.xhr.abort(),s.xhr=e.ajax({url:i,data:t,dataType:"json",success:function(e){n(e)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){var t=this.term===this._value(),i=this.menu.element.is(":visible"),s=e.altKey||e.ctrlKey||e.metaKey||e.shiftKey;(!t||t&&!i&&!s)&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){return e=null!=e?e:this._value(),this.term=this._value(),e.length").text(i.label).appendTo(t)},_move:function(e,t){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)?(this.isMultiLine||this._value(this.term),this.menu.blur(),void 0):(this.menu[e](t),void 0):(this.search(null,t),void 0)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(e,t),t.preventDefault())}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(t,i){var s=RegExp(e.ui.autocomplete.escapeRegex(i),"i");return e.grep(t,function(e){return s.test(e.label||e.value||e)})}}),e.widget("ui.autocomplete",e.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(e){return e+(e>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(t){var i;this._superApply(arguments),this.options.disabled||this.cancelSearch||(i=t&&t.length?this.options.messages.results(t.length):this.options.messages.noResults,this.liveRegion.children().hide(),e("
        ").text(i).appendTo(this.liveRegion))}}),e.ui.autocomplete;var c,p="ui-button ui-widget ui-state-default ui-corner-all",f="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",m=function(){var t=e(this);setTimeout(function(){t.find(":ui-button").button("refresh")},1)},g=function(t){var i=t.name,s=t.form,n=e([]);return i&&(i=i.replace(/'/g,"\\'"),n=s?e(s).find("[name='"+i+"'][type=radio]"):e("[name='"+i+"'][type=radio]",t.ownerDocument).filter(function(){return!this.form})),n};e.widget("ui.button",{version:"1.11.4",defaultElement:"").addClass(this._triggerClass).html(a?e("").attr({src:a,alt:n,title:n}):n)),t[r?"before":"after"](i.trigger),i.trigger.click(function(){return e.datepicker._datepickerShowing&&e.datepicker._lastInput===t[0]?e.datepicker._hideDatepicker():e.datepicker._datepickerShowing&&e.datepicker._lastInput!==t[0]?(e.datepicker._hideDatepicker(),e.datepicker._showDatepicker(t[0])):e.datepicker._showDatepicker(t[0]),!1}))},_autoSize:function(e){if(this._get(e,"autoSize")&&!e.inline){var t,i,s,n,a=new Date(2009,11,20),o=this._get(e,"dateFormat");o.match(/[DM]/)&&(t=function(e){for(i=0,s=0,n=0;e.length>n;n++)e[n].length>i&&(i=e[n].length,s=n);return s},a.setMonth(t(this._get(e,o.match(/MM/)?"monthNames":"monthNamesShort"))),a.setDate(t(this._get(e,o.match(/DD/)?"dayNames":"dayNamesShort"))+20-a.getDay())),e.input.attr("size",this._formatDate(e,a).length)}},_inlineDatepicker:function(t,i){var s=e(t);s.hasClass(this.markerClassName)||(s.addClass(this.markerClassName).append(i.dpDiv),e.data(t,"datepicker",i),this._setDate(i,this._getDefaultDate(i),!0),this._updateDatepicker(i),this._updateAlternate(i),i.settings.disabled&&this._disableDatepicker(t),i.dpDiv.css("display","block"))},_dialogDatepicker:function(t,i,s,n,a){var o,h,l,u,d,c=this._dialogInst;return c||(this.uuid+=1,o="dp"+this.uuid,this._dialogInput=e(""),this._dialogInput.keydown(this._doKeyDown),e("body").append(this._dialogInput),c=this._dialogInst=this._newInst(this._dialogInput,!1),c.settings={},e.data(this._dialogInput[0],"datepicker",c)),r(c.settings,n||{}),i=i&&i.constructor===Date?this._formatDate(c,i):i,this._dialogInput.val(i),this._pos=a?a.length?a:[a.pageX,a.pageY]:null,this._pos||(h=document.documentElement.clientWidth,l=document.documentElement.clientHeight,u=document.documentElement.scrollLeft||document.body.scrollLeft,d=document.documentElement.scrollTop||document.body.scrollTop,this._pos=[h/2-100+u,l/2-150+d]),this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),c.settings.onSelect=s,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),e.blockUI&&e.blockUI(this.dpDiv),e.data(this._dialogInput[0],"datepicker",c),this},_destroyDatepicker:function(t){var i,s=e(t),n=e.data(t,"datepicker");s.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),e.removeData(t,"datepicker"),"input"===i?(n.append.remove(),n.trigger.remove(),s.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):("div"===i||"span"===i)&&s.removeClass(this.markerClassName).empty(),v===n&&(v=null))},_enableDatepicker:function(t){var i,s,n=e(t),a=e.data(t,"datepicker");n.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!1,a.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().removeClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}))},_disableDatepicker:function(t){var i,s,n=e(t),a=e.data(t,"datepicker");n.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!0,a.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().addClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}),this._disabledInputs[this._disabledInputs.length]=t)},_isDisabledDatepicker:function(e){if(!e)return!1;for(var t=0;this._disabledInputs.length>t;t++)if(this._disabledInputs[t]===e)return!0;return!1},_getInst:function(t){try{return e.data(t,"datepicker")}catch(i){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(t,i,s){var n,a,o,h,l=this._getInst(t);return 2===arguments.length&&"string"==typeof i?"defaults"===i?e.extend({},e.datepicker._defaults):l?"all"===i?e.extend({},l.settings):this._get(l,i):null:(n=i||{},"string"==typeof i&&(n={},n[i]=s),l&&(this._curInst===l&&this._hideDatepicker(),a=this._getDateDatepicker(t,!0),o=this._getMinMaxDate(l,"min"),h=this._getMinMaxDate(l,"max"),r(l.settings,n),null!==o&&void 0!==n.dateFormat&&void 0===n.minDate&&(l.settings.minDate=this._formatDate(l,o)),null!==h&&void 0!==n.dateFormat&&void 0===n.maxDate&&(l.settings.maxDate=this._formatDate(l,h)),"disabled"in n&&(n.disabled?this._disableDatepicker(t):this._enableDatepicker(t)),this._attachments(e(t),l),this._autoSize(l),this._setDate(l,a),this._updateAlternate(l),this._updateDatepicker(l)),void 0)},_changeDatepicker:function(e,t,i){this._optionDatepicker(e,t,i)},_refreshDatepicker:function(e){var t=this._getInst(e);t&&this._updateDatepicker(t)},_setDateDatepicker:function(e,t){var i=this._getInst(e);i&&(this._setDate(i,t),this._updateDatepicker(i),this._updateAlternate(i))},_getDateDatepicker:function(e,t){var i=this._getInst(e);return i&&!i.inline&&this._setDateFromField(i,t),i?this._getDate(i):null},_doKeyDown:function(t){var i,s,n,a=e.datepicker._getInst(t.target),o=!0,r=a.dpDiv.is(".ui-datepicker-rtl");if(a._keyEvent=!0,e.datepicker._datepickerShowing)switch(t.keyCode){case 9:e.datepicker._hideDatepicker(),o=!1;break;case 13:return n=e("td."+e.datepicker._dayOverClass+":not(."+e.datepicker._currentClass+")",a.dpDiv),n[0]&&e.datepicker._selectDay(t.target,a.selectedMonth,a.selectedYear,n[0]),i=e.datepicker._get(a,"onSelect"),i?(s=e.datepicker._formatDate(a),i.apply(a.input?a.input[0]:null,[s,a])):e.datepicker._hideDatepicker(),!1;case 27:e.datepicker._hideDatepicker();break;case 33:e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(a,"stepBigMonths"):-e.datepicker._get(a,"stepMonths"),"M");break;case 34:e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(a,"stepBigMonths"):+e.datepicker._get(a,"stepMonths"),"M");break;case 35:(t.ctrlKey||t.metaKey)&&e.datepicker._clearDate(t.target),o=t.ctrlKey||t.metaKey;break;case 36:(t.ctrlKey||t.metaKey)&&e.datepicker._gotoToday(t.target),o=t.ctrlKey||t.metaKey;break;case 37:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,r?1:-1,"D"),o=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(a,"stepBigMonths"):-e.datepicker._get(a,"stepMonths"),"M");break;case 38:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,-7,"D"),o=t.ctrlKey||t.metaKey;break;case 39:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,r?-1:1,"D"),o=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(a,"stepBigMonths"):+e.datepicker._get(a,"stepMonths"),"M");break;case 40:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,7,"D"),o=t.ctrlKey||t.metaKey;break;default:o=!1}else 36===t.keyCode&&t.ctrlKey?e.datepicker._showDatepicker(this):o=!1;o&&(t.preventDefault(),t.stopPropagation())},_doKeyPress:function(t){var i,s,n=e.datepicker._getInst(t.target); +return e.datepicker._get(n,"constrainInput")?(i=e.datepicker._possibleChars(e.datepicker._get(n,"dateFormat")),s=String.fromCharCode(null==t.charCode?t.keyCode:t.charCode),t.ctrlKey||t.metaKey||" ">s||!i||i.indexOf(s)>-1):void 0},_doKeyUp:function(t){var i,s=e.datepicker._getInst(t.target);if(s.input.val()!==s.lastVal)try{i=e.datepicker.parseDate(e.datepicker._get(s,"dateFormat"),s.input?s.input.val():null,e.datepicker._getFormatConfig(s)),i&&(e.datepicker._setDateFromField(s),e.datepicker._updateAlternate(s),e.datepicker._updateDatepicker(s))}catch(n){}return!0},_showDatepicker:function(t){if(t=t.target||t,"input"!==t.nodeName.toLowerCase()&&(t=e("input",t.parentNode)[0]),!e.datepicker._isDisabledDatepicker(t)&&e.datepicker._lastInput!==t){var i,n,a,o,h,l,u;i=e.datepicker._getInst(t),e.datepicker._curInst&&e.datepicker._curInst!==i&&(e.datepicker._curInst.dpDiv.stop(!0,!0),i&&e.datepicker._datepickerShowing&&e.datepicker._hideDatepicker(e.datepicker._curInst.input[0])),n=e.datepicker._get(i,"beforeShow"),a=n?n.apply(t,[t,i]):{},a!==!1&&(r(i.settings,a),i.lastVal=null,e.datepicker._lastInput=t,e.datepicker._setDateFromField(i),e.datepicker._inDialog&&(t.value=""),e.datepicker._pos||(e.datepicker._pos=e.datepicker._findPos(t),e.datepicker._pos[1]+=t.offsetHeight),o=!1,e(t).parents().each(function(){return o|="fixed"===e(this).css("position"),!o}),h={left:e.datepicker._pos[0],top:e.datepicker._pos[1]},e.datepicker._pos=null,i.dpDiv.empty(),i.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),e.datepicker._updateDatepicker(i),h=e.datepicker._checkOffset(i,h,o),i.dpDiv.css({position:e.datepicker._inDialog&&e.blockUI?"static":o?"fixed":"absolute",display:"none",left:h.left+"px",top:h.top+"px"}),i.inline||(l=e.datepicker._get(i,"showAnim"),u=e.datepicker._get(i,"duration"),i.dpDiv.css("z-index",s(e(t))+1),e.datepicker._datepickerShowing=!0,e.effects&&e.effects.effect[l]?i.dpDiv.show(l,e.datepicker._get(i,"showOptions"),u):i.dpDiv[l||"show"](l?u:null),e.datepicker._shouldFocusInput(i)&&i.input.focus(),e.datepicker._curInst=i))}},_updateDatepicker:function(t){this.maxRows=4,v=t,t.dpDiv.empty().append(this._generateHTML(t)),this._attachHandlers(t);var i,s=this._getNumberOfMonths(t),n=s[1],a=17,r=t.dpDiv.find("."+this._dayOverClass+" a");r.length>0&&o.apply(r.get(0)),t.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),n>1&&t.dpDiv.addClass("ui-datepicker-multi-"+n).css("width",a*n+"em"),t.dpDiv[(1!==s[0]||1!==s[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),t.dpDiv[(this._get(t,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),t===e.datepicker._curInst&&e.datepicker._datepickerShowing&&e.datepicker._shouldFocusInput(t)&&t.input.focus(),t.yearshtml&&(i=t.yearshtml,setTimeout(function(){i===t.yearshtml&&t.yearshtml&&t.dpDiv.find("select.ui-datepicker-year:first").replaceWith(t.yearshtml),i=t.yearshtml=null},0))},_shouldFocusInput:function(e){return e.input&&e.input.is(":visible")&&!e.input.is(":disabled")&&!e.input.is(":focus")},_checkOffset:function(t,i,s){var n=t.dpDiv.outerWidth(),a=t.dpDiv.outerHeight(),o=t.input?t.input.outerWidth():0,r=t.input?t.input.outerHeight():0,h=document.documentElement.clientWidth+(s?0:e(document).scrollLeft()),l=document.documentElement.clientHeight+(s?0:e(document).scrollTop());return i.left-=this._get(t,"isRTL")?n-o:0,i.left-=s&&i.left===t.input.offset().left?e(document).scrollLeft():0,i.top-=s&&i.top===t.input.offset().top+r?e(document).scrollTop():0,i.left-=Math.min(i.left,i.left+n>h&&h>n?Math.abs(i.left+n-h):0),i.top-=Math.min(i.top,i.top+a>l&&l>a?Math.abs(a+r):0),i},_findPos:function(t){for(var i,s=this._getInst(t),n=this._get(s,"isRTL");t&&("hidden"===t.type||1!==t.nodeType||e.expr.filters.hidden(t));)t=t[n?"previousSibling":"nextSibling"];return i=e(t).offset(),[i.left,i.top]},_hideDatepicker:function(t){var i,s,n,a,o=this._curInst;!o||t&&o!==e.data(t,"datepicker")||this._datepickerShowing&&(i=this._get(o,"showAnim"),s=this._get(o,"duration"),n=function(){e.datepicker._tidyDialog(o)},e.effects&&(e.effects.effect[i]||e.effects[i])?o.dpDiv.hide(i,e.datepicker._get(o,"showOptions"),s,n):o.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?s:null,n),i||n(),this._datepickerShowing=!1,a=this._get(o,"onClose"),a&&a.apply(o.input?o.input[0]:null,[o.input?o.input.val():"",o]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),e.blockUI&&(e.unblockUI(),e("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(e){e.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(t){if(e.datepicker._curInst){var i=e(t.target),s=e.datepicker._getInst(i[0]);(i[0].id!==e.datepicker._mainDivId&&0===i.parents("#"+e.datepicker._mainDivId).length&&!i.hasClass(e.datepicker.markerClassName)&&!i.closest("."+e.datepicker._triggerClass).length&&e.datepicker._datepickerShowing&&(!e.datepicker._inDialog||!e.blockUI)||i.hasClass(e.datepicker.markerClassName)&&e.datepicker._curInst!==s)&&e.datepicker._hideDatepicker()}},_adjustDate:function(t,i,s){var n=e(t),a=this._getInst(n[0]);this._isDisabledDatepicker(n[0])||(this._adjustInstDate(a,i+("M"===s?this._get(a,"showCurrentAtPos"):0),s),this._updateDatepicker(a))},_gotoToday:function(t){var i,s=e(t),n=this._getInst(s[0]);this._get(n,"gotoCurrent")&&n.currentDay?(n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear):(i=new Date,n.selectedDay=i.getDate(),n.drawMonth=n.selectedMonth=i.getMonth(),n.drawYear=n.selectedYear=i.getFullYear()),this._notifyChange(n),this._adjustDate(s)},_selectMonthYear:function(t,i,s){var n=e(t),a=this._getInst(n[0]);a["selected"+("M"===s?"Month":"Year")]=a["draw"+("M"===s?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(a),this._adjustDate(n)},_selectDay:function(t,i,s,n){var a,o=e(t);e(n).hasClass(this._unselectableClass)||this._isDisabledDatepicker(o[0])||(a=this._getInst(o[0]),a.selectedDay=a.currentDay=e("a",n).html(),a.selectedMonth=a.currentMonth=i,a.selectedYear=a.currentYear=s,this._selectDate(t,this._formatDate(a,a.currentDay,a.currentMonth,a.currentYear)))},_clearDate:function(t){var i=e(t);this._selectDate(i,"")},_selectDate:function(t,i){var s,n=e(t),a=this._getInst(n[0]);i=null!=i?i:this._formatDate(a),a.input&&a.input.val(i),this._updateAlternate(a),s=this._get(a,"onSelect"),s?s.apply(a.input?a.input[0]:null,[i,a]):a.input&&a.input.trigger("change"),a.inline?this._updateDatepicker(a):(this._hideDatepicker(),this._lastInput=a.input[0],"object"!=typeof a.input[0]&&a.input.focus(),this._lastInput=null)},_updateAlternate:function(t){var i,s,n,a=this._get(t,"altField");a&&(i=this._get(t,"altFormat")||this._get(t,"dateFormat"),s=this._getDate(t),n=this.formatDate(i,s,this._getFormatConfig(t)),e(a).each(function(){e(this).val(n)}))},noWeekends:function(e){var t=e.getDay();return[t>0&&6>t,""]},iso8601Week:function(e){var t,i=new Date(e.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7)),t=i.getTime(),i.setMonth(0),i.setDate(1),Math.floor(Math.round((t-i)/864e5)/7)+1},parseDate:function(t,i,s){if(null==t||null==i)throw"Invalid arguments";if(i="object"==typeof i?""+i:i+"",""===i)return null;var n,a,o,r,h=0,l=(s?s.shortYearCutoff:null)||this._defaults.shortYearCutoff,u="string"!=typeof l?l:(new Date).getFullYear()%100+parseInt(l,10),d=(s?s.dayNamesShort:null)||this._defaults.dayNamesShort,c=(s?s.dayNames:null)||this._defaults.dayNames,p=(s?s.monthNamesShort:null)||this._defaults.monthNamesShort,f=(s?s.monthNames:null)||this._defaults.monthNames,m=-1,g=-1,v=-1,y=-1,b=!1,_=function(e){var i=t.length>n+1&&t.charAt(n+1)===e;return i&&n++,i},x=function(e){var t=_(e),s="@"===e?14:"!"===e?20:"y"===e&&t?4:"o"===e?3:2,n="y"===e?s:1,a=RegExp("^\\d{"+n+","+s+"}"),o=i.substring(h).match(a);if(!o)throw"Missing number at position "+h;return h+=o[0].length,parseInt(o[0],10)},w=function(t,s,n){var a=-1,o=e.map(_(t)?n:s,function(e,t){return[[t,e]]}).sort(function(e,t){return-(e[1].length-t[1].length)});if(e.each(o,function(e,t){var s=t[1];return i.substr(h,s.length).toLowerCase()===s.toLowerCase()?(a=t[0],h+=s.length,!1):void 0}),-1!==a)return a+1;throw"Unknown name at position "+h},k=function(){if(i.charAt(h)!==t.charAt(n))throw"Unexpected literal at position "+h;h++};for(n=0;t.length>n;n++)if(b)"'"!==t.charAt(n)||_("'")?k():b=!1;else switch(t.charAt(n)){case"d":v=x("d");break;case"D":w("D",d,c);break;case"o":y=x("o");break;case"m":g=x("m");break;case"M":g=w("M",p,f);break;case"y":m=x("y");break;case"@":r=new Date(x("@")),m=r.getFullYear(),g=r.getMonth()+1,v=r.getDate();break;case"!":r=new Date((x("!")-this._ticksTo1970)/1e4),m=r.getFullYear(),g=r.getMonth()+1,v=r.getDate();break;case"'":_("'")?k():b=!0;break;default:k()}if(i.length>h&&(o=i.substr(h),!/^\s+/.test(o)))throw"Extra/unparsed characters found in date: "+o;if(-1===m?m=(new Date).getFullYear():100>m&&(m+=(new Date).getFullYear()-(new Date).getFullYear()%100+(u>=m?0:-100)),y>-1)for(g=1,v=y;;){if(a=this._getDaysInMonth(m,g-1),a>=v)break;g++,v-=a}if(r=this._daylightSavingAdjust(new Date(m,g-1,v)),r.getFullYear()!==m||r.getMonth()+1!==g||r.getDate()!==v)throw"Invalid date";return r},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:1e7*60*60*24*(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925)),formatDate:function(e,t,i){if(!t)return"";var s,n=(i?i.dayNamesShort:null)||this._defaults.dayNamesShort,a=(i?i.dayNames:null)||this._defaults.dayNames,o=(i?i.monthNamesShort:null)||this._defaults.monthNamesShort,r=(i?i.monthNames:null)||this._defaults.monthNames,h=function(t){var i=e.length>s+1&&e.charAt(s+1)===t;return i&&s++,i},l=function(e,t,i){var s=""+t;if(h(e))for(;i>s.length;)s="0"+s;return s},u=function(e,t,i,s){return h(e)?s[t]:i[t]},d="",c=!1;if(t)for(s=0;e.length>s;s++)if(c)"'"!==e.charAt(s)||h("'")?d+=e.charAt(s):c=!1;else switch(e.charAt(s)){case"d":d+=l("d",t.getDate(),2);break;case"D":d+=u("D",t.getDay(),n,a);break;case"o":d+=l("o",Math.round((new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime()-new Date(t.getFullYear(),0,0).getTime())/864e5),3);break;case"m":d+=l("m",t.getMonth()+1,2);break;case"M":d+=u("M",t.getMonth(),o,r);break;case"y":d+=h("y")?t.getFullYear():(10>t.getYear()%100?"0":"")+t.getYear()%100;break;case"@":d+=t.getTime();break;case"!":d+=1e4*t.getTime()+this._ticksTo1970;break;case"'":h("'")?d+="'":c=!0;break;default:d+=e.charAt(s)}return d},_possibleChars:function(e){var t,i="",s=!1,n=function(i){var s=e.length>t+1&&e.charAt(t+1)===i;return s&&t++,s};for(t=0;e.length>t;t++)if(s)"'"!==e.charAt(t)||n("'")?i+=e.charAt(t):s=!1;else switch(e.charAt(t)){case"d":case"m":case"y":case"@":i+="0123456789";break;case"D":case"M":return null;case"'":n("'")?i+="'":s=!0;break;default:i+=e.charAt(t)}return i},_get:function(e,t){return void 0!==e.settings[t]?e.settings[t]:this._defaults[t]},_setDateFromField:function(e,t){if(e.input.val()!==e.lastVal){var i=this._get(e,"dateFormat"),s=e.lastVal=e.input?e.input.val():null,n=this._getDefaultDate(e),a=n,o=this._getFormatConfig(e);try{a=this.parseDate(i,s,o)||n}catch(r){s=t?"":s}e.selectedDay=a.getDate(),e.drawMonth=e.selectedMonth=a.getMonth(),e.drawYear=e.selectedYear=a.getFullYear(),e.currentDay=s?a.getDate():0,e.currentMonth=s?a.getMonth():0,e.currentYear=s?a.getFullYear():0,this._adjustInstDate(e)}},_getDefaultDate:function(e){return this._restrictMinMax(e,this._determineDate(e,this._get(e,"defaultDate"),new Date))},_determineDate:function(t,i,s){var n=function(e){var t=new Date;return t.setDate(t.getDate()+e),t},a=function(i){try{return e.datepicker.parseDate(e.datepicker._get(t,"dateFormat"),i,e.datepicker._getFormatConfig(t))}catch(s){}for(var n=(i.toLowerCase().match(/^c/)?e.datepicker._getDate(t):null)||new Date,a=n.getFullYear(),o=n.getMonth(),r=n.getDate(),h=/([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,l=h.exec(i);l;){switch(l[2]||"d"){case"d":case"D":r+=parseInt(l[1],10);break;case"w":case"W":r+=7*parseInt(l[1],10);break;case"m":case"M":o+=parseInt(l[1],10),r=Math.min(r,e.datepicker._getDaysInMonth(a,o));break;case"y":case"Y":a+=parseInt(l[1],10),r=Math.min(r,e.datepicker._getDaysInMonth(a,o))}l=h.exec(i)}return new Date(a,o,r)},o=null==i||""===i?s:"string"==typeof i?a(i):"number"==typeof i?isNaN(i)?s:n(i):new Date(i.getTime());return o=o&&"Invalid Date"==""+o?s:o,o&&(o.setHours(0),o.setMinutes(0),o.setSeconds(0),o.setMilliseconds(0)),this._daylightSavingAdjust(o)},_daylightSavingAdjust:function(e){return e?(e.setHours(e.getHours()>12?e.getHours()+2:0),e):null},_setDate:function(e,t,i){var s=!t,n=e.selectedMonth,a=e.selectedYear,o=this._restrictMinMax(e,this._determineDate(e,t,new Date));e.selectedDay=e.currentDay=o.getDate(),e.drawMonth=e.selectedMonth=e.currentMonth=o.getMonth(),e.drawYear=e.selectedYear=e.currentYear=o.getFullYear(),n===e.selectedMonth&&a===e.selectedYear||i||this._notifyChange(e),this._adjustInstDate(e),e.input&&e.input.val(s?"":this._formatDate(e))},_getDate:function(e){var t=!e.currentYear||e.input&&""===e.input.val()?null:this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return t},_attachHandlers:function(t){var i=this._get(t,"stepMonths"),s="#"+t.id.replace(/\\\\/g,"\\");t.dpDiv.find("[data-handler]").map(function(){var t={prev:function(){e.datepicker._adjustDate(s,-i,"M")},next:function(){e.datepicker._adjustDate(s,+i,"M")},hide:function(){e.datepicker._hideDatepicker()},today:function(){e.datepicker._gotoToday(s)},selectDay:function(){return e.datepicker._selectDay(s,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return e.datepicker._selectMonthYear(s,this,"M"),!1},selectYear:function(){return e.datepicker._selectMonthYear(s,this,"Y"),!1}};e(this).bind(this.getAttribute("data-event"),t[this.getAttribute("data-handler")])})},_generateHTML:function(e){var t,i,s,n,a,o,r,h,l,u,d,c,p,f,m,g,v,y,b,_,x,w,k,T,D,S,M,C,N,A,P,I,H,z,F,E,O,j,W,L=new Date,R=this._daylightSavingAdjust(new Date(L.getFullYear(),L.getMonth(),L.getDate())),Y=this._get(e,"isRTL"),B=this._get(e,"showButtonPanel"),J=this._get(e,"hideIfNoPrevNext"),q=this._get(e,"navigationAsDateFormat"),K=this._getNumberOfMonths(e),V=this._get(e,"showCurrentAtPos"),U=this._get(e,"stepMonths"),Q=1!==K[0]||1!==K[1],G=this._daylightSavingAdjust(e.currentDay?new Date(e.currentYear,e.currentMonth,e.currentDay):new Date(9999,9,9)),X=this._getMinMaxDate(e,"min"),$=this._getMinMaxDate(e,"max"),Z=e.drawMonth-V,et=e.drawYear;if(0>Z&&(Z+=12,et--),$)for(t=this._daylightSavingAdjust(new Date($.getFullYear(),$.getMonth()-K[0]*K[1]+1,$.getDate())),t=X&&X>t?X:t;this._daylightSavingAdjust(new Date(et,Z,1))>t;)Z--,0>Z&&(Z=11,et--);for(e.drawMonth=Z,e.drawYear=et,i=this._get(e,"prevText"),i=q?this.formatDate(i,this._daylightSavingAdjust(new Date(et,Z-U,1)),this._getFormatConfig(e)):i,s=this._canAdjustMonth(e,-1,et,Z)?""+i+"":J?"":""+i+"",n=this._get(e,"nextText"),n=q?this.formatDate(n,this._daylightSavingAdjust(new Date(et,Z+U,1)),this._getFormatConfig(e)):n,a=this._canAdjustMonth(e,1,et,Z)?""+n+"":J?"":""+n+"",o=this._get(e,"currentText"),r=this._get(e,"gotoCurrent")&&e.currentDay?G:R,o=q?this.formatDate(o,r,this._getFormatConfig(e)):o,h=e.inline?"":"",l=B?"
        "+(Y?h:"")+(this._isInRange(e,r)?"":"")+(Y?"":h)+"
        ":"",u=parseInt(this._get(e,"firstDay"),10),u=isNaN(u)?0:u,d=this._get(e,"showWeek"),c=this._get(e,"dayNames"),p=this._get(e,"dayNamesMin"),f=this._get(e,"monthNames"),m=this._get(e,"monthNamesShort"),g=this._get(e,"beforeShowDay"),v=this._get(e,"showOtherMonths"),y=this._get(e,"selectOtherMonths"),b=this._getDefaultDate(e),_="",w=0;K[0]>w;w++){for(k="",this.maxRows=4,T=0;K[1]>T;T++){if(D=this._daylightSavingAdjust(new Date(et,Z,e.selectedDay)),S=" ui-corner-all",M="",Q){if(M+="
        "}for(M+="
        "+(/all|left/.test(S)&&0===w?Y?a:s:"")+(/all|right/.test(S)&&0===w?Y?s:a:"")+this._generateMonthYearHeader(e,Z,et,X,$,w>0||T>0,f,m)+"
        "+"",C=d?"":"",x=0;7>x;x++)N=(x+u)%7,C+="";for(M+=C+"",A=this._getDaysInMonth(et,Z),et===e.selectedYear&&Z===e.selectedMonth&&(e.selectedDay=Math.min(e.selectedDay,A)),P=(this._getFirstDayOfMonth(et,Z)-u+7)%7,I=Math.ceil((P+A)/7),H=Q?this.maxRows>I?this.maxRows:I:I,this.maxRows=H,z=this._daylightSavingAdjust(new Date(et,Z,1-P)),F=0;H>F;F++){for(M+="",E=d?"":"",x=0;7>x;x++)O=g?g.apply(e.input?e.input[0]:null,[z]):[!0,""],j=z.getMonth()!==Z,W=j&&!y||!O[0]||X&&X>z||$&&z>$,E+="",z.setDate(z.getDate()+1),z=this._daylightSavingAdjust(z);M+=E+""}Z++,Z>11&&(Z=0,et++),M+="
        "+this._get(e,"weekHeader")+"=5?" class='ui-datepicker-week-end'":"")+">"+""+p[N]+"
        "+this._get(e,"calculateWeek")(z)+""+(j&&!v?" ":W?""+z.getDate()+"":""+z.getDate()+"")+"
        "+(Q?"
        "+(K[0]>0&&T===K[1]-1?"
        ":""):""),k+=M}_+=k}return _+=l,e._keyEvent=!1,_},_generateMonthYearHeader:function(e,t,i,s,n,a,o,r){var h,l,u,d,c,p,f,m,g=this._get(e,"changeMonth"),v=this._get(e,"changeYear"),y=this._get(e,"showMonthAfterYear"),b="
        ",_="";if(a||!g)_+=""+o[t]+"";else{for(h=s&&s.getFullYear()===i,l=n&&n.getFullYear()===i,_+=""}if(y||(b+=_+(!a&&g&&v?"":" ")),!e.yearshtml)if(e.yearshtml="",a||!v)b+=""+i+"";else{for(d=this._get(e,"yearRange").split(":"),c=(new Date).getFullYear(),p=function(e){var t=e.match(/c[+\-].*/)?i+parseInt(e.substring(1),10):e.match(/[+\-].*/)?c+parseInt(e,10):parseInt(e,10);return isNaN(t)?c:t},f=p(d[0]),m=Math.max(f,p(d[1]||"")),f=s?Math.max(f,s.getFullYear()):f,m=n?Math.min(m,n.getFullYear()):m,e.yearshtml+="",b+=e.yearshtml,e.yearshtml=null}return b+=this._get(e,"yearSuffix"),y&&(b+=(!a&&g&&v?"":" ")+_),b+="
        "},_adjustInstDate:function(e,t,i){var s=e.drawYear+("Y"===i?t:0),n=e.drawMonth+("M"===i?t:0),a=Math.min(e.selectedDay,this._getDaysInMonth(s,n))+("D"===i?t:0),o=this._restrictMinMax(e,this._daylightSavingAdjust(new Date(s,n,a)));e.selectedDay=o.getDate(),e.drawMonth=e.selectedMonth=o.getMonth(),e.drawYear=e.selectedYear=o.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(e)},_restrictMinMax:function(e,t){var i=this._getMinMaxDate(e,"min"),s=this._getMinMaxDate(e,"max"),n=i&&i>t?i:t;return s&&n>s?s:n},_notifyChange:function(e){var t=this._get(e,"onChangeMonthYear");t&&t.apply(e.input?e.input[0]:null,[e.selectedYear,e.selectedMonth+1,e])},_getNumberOfMonths:function(e){var t=this._get(e,"numberOfMonths");return null==t?[1,1]:"number"==typeof t?[1,t]:t},_getMinMaxDate:function(e,t){return this._determineDate(e,this._get(e,t+"Date"),null)},_getDaysInMonth:function(e,t){return 32-this._daylightSavingAdjust(new Date(e,t,32)).getDate()},_getFirstDayOfMonth:function(e,t){return new Date(e,t,1).getDay()},_canAdjustMonth:function(e,t,i,s){var n=this._getNumberOfMonths(e),a=this._daylightSavingAdjust(new Date(i,s+(0>t?t:n[0]*n[1]),1));return 0>t&&a.setDate(this._getDaysInMonth(a.getFullYear(),a.getMonth())),this._isInRange(e,a)},_isInRange:function(e,t){var i,s,n=this._getMinMaxDate(e,"min"),a=this._getMinMaxDate(e,"max"),o=null,r=null,h=this._get(e,"yearRange");return h&&(i=h.split(":"),s=(new Date).getFullYear(),o=parseInt(i[0],10),r=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(o+=s),i[1].match(/[+\-].*/)&&(r+=s)),(!n||t.getTime()>=n.getTime())&&(!a||t.getTime()<=a.getTime())&&(!o||t.getFullYear()>=o)&&(!r||r>=t.getFullYear())},_getFormatConfig:function(e){var t=this._get(e,"shortYearCutoff");return t="string"!=typeof t?t:(new Date).getFullYear()%100+parseInt(t,10),{shortYearCutoff:t,dayNamesShort:this._get(e,"dayNamesShort"),dayNames:this._get(e,"dayNames"),monthNamesShort:this._get(e,"monthNamesShort"),monthNames:this._get(e,"monthNames")}},_formatDate:function(e,t,i,s){t||(e.currentDay=e.selectedDay,e.currentMonth=e.selectedMonth,e.currentYear=e.selectedYear);var n=t?"object"==typeof t?t:this._daylightSavingAdjust(new Date(s,i,t)):this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return this.formatDate(this._get(e,"dateFormat"),n,this._getFormatConfig(e))}}),e.fn.datepicker=function(t){if(!this.length)return this;e.datepicker.initialized||(e(document).mousedown(e.datepicker._checkExternalClick),e.datepicker.initialized=!0),0===e("#"+e.datepicker._mainDivId).length&&e("body").append(e.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof t||"isDisabled"!==t&&"getDate"!==t&&"widget"!==t?"option"===t&&2===arguments.length&&"string"==typeof arguments[1]?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof t?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this].concat(i)):e.datepicker._attachDatepicker(this,t)}):e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i))},e.datepicker=new n,e.datepicker.initialized=!1,e.datepicker.uuid=(new Date).getTime(),e.datepicker.version="1.11.4",e.datepicker,e.widget("ui.draggable",e.ui.mouse,{version:"1.11.4",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"===this.options.helper&&this._setPositionRelative(),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._setHandleClassName(),this._mouseInit()},_setOption:function(e,t){this._super(e,t),"handle"===e&&(this._removeHandleClassName(),this._setHandleClassName())},_destroy:function(){return(this.helper||this.element).is(".ui-draggable-dragging")?(this.destroyOnClear=!0,void 0):(this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._removeHandleClassName(),this._mouseDestroy(),void 0)},_mouseCapture:function(t){var i=this.options;return this._blurActiveElement(t),this.helper||i.disabled||e(t.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(t),this.handle?(this._blockFrames(i.iframeFix===!0?"iframe":i.iframeFix),!0):!1)},_blockFrames:function(t){this.iframeBlocks=this.document.find(t).map(function(){var t=e(this);return e("
        ").css("position","absolute").appendTo(t.parent()).outerWidth(t.outerWidth()).outerHeight(t.outerHeight()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(t){var i=this.document[0];if(this.handleElement.is(t.target))try{i.activeElement&&"body"!==i.activeElement.nodeName.toLowerCase()&&e(i.activeElement).blur()}catch(s){}},_mouseStart:function(t){var i=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=this.helper.parents().filter(function(){return"fixed"===e(this).css("position")}).length>0,this.positionAbs=this.element.offset(),this._refreshOffsets(t),this.originalPosition=this.position=this._generatePosition(t,!1),this.originalPageX=t.pageX,this.originalPageY=t.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!i.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._normalizeRightBottom(),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_refreshOffsets:function(e){this.offset={top:this.positionAbs.top-this.margins.top,left:this.positionAbs.left-this.margins.left,scroll:!1,parent:this._getParentOffset(),relative:this._getRelativeOffset()},this.offset.click={left:e.pageX-this.offset.left,top:e.pageY-this.offset.top}},_mouseDrag:function(t,i){if(this.hasFixedAncestor&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(t,!0),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",t,s)===!1)return this._mouseUp({}),!1;this.position=s.position}return this.helper[0].style.left=this.position.left+"px",this.helper[0].style.top=this.position.top+"px",e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var i=this,s=!1;return e.ui.ddmanager&&!this.options.dropBehaviour&&(s=e.ui.ddmanager.drop(this,t)),this.dropped&&(s=this.dropped,this.dropped=!1),"invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",t)!==!1&&i._clear()}):this._trigger("stop",t)!==!1&&this._clear(),!1},_mouseUp:function(t){return this._unblockFrames(),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),this.handleElement.is(t.target)&&this.element.focus(),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){return this.options.handle?!!e(t.target).closest(this.element.find(this.options.handle)).length:!0},_setHandleClassName:function(){this.handleElement=this.options.handle?this.element.find(this.options.handle):this.element,this.handleElement.addClass("ui-draggable-handle")},_removeHandleClassName:function(){this.handleElement.removeClass("ui-draggable-handle")},_createHelper:function(t){var i=this.options,s=e.isFunction(i.helper),n=s?e(i.helper.apply(this.element[0],[t])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return n.parents("body").length||n.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s&&n[0]===this.element[0]&&this._setPositionRelative(),n[0]===this.element[0]||/(fixed|absolute)/.test(n.css("position"))||n.css("position","absolute"),n},_setPositionRelative:function(){/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative")},_adjustOffsetFromHelper:function(t){"string"==typeof t&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_isRootNode:function(e){return/(html|body)/i.test(e.tagName)||e===this.document[0]},_getParentOffset:function(){var t=this.offsetParent.offset(),i=this.document[0];return"absolute"===this.cssPosition&&this.scrollParent[0]!==i&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop()),this._isRootNode(this.offsetParent[0])&&(t={top:0,left:0}),{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"!==this.cssPosition)return{top:0,left:0};var e=this.element.position(),t=this._isRootNode(this.scrollParent[0]);return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+(t?0:this.scrollParent.scrollTop()),left:e.left-(parseInt(this.helper.css("left"),10)||0)+(t?0:this.scrollParent.scrollLeft())}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,i,s,n=this.options,a=this.document[0];return this.relativeContainer=null,n.containment?"window"===n.containment?(this.containment=[e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,e(window).scrollLeft()+e(window).width()-this.helperProportions.width-this.margins.left,e(window).scrollTop()+(e(window).height()||a.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):"document"===n.containment?(this.containment=[0,0,e(a).width()-this.helperProportions.width-this.margins.left,(e(a).height()||a.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):n.containment.constructor===Array?(this.containment=n.containment,void 0):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=e(n.containment),s=i[0],s&&(t=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(t?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(t?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i),void 0):(this.containment=null,void 0) +},_convertPositionTo:function(e,t){t||(t=this.position);var i="absolute"===e?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:t.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:t.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(e,t){var i,s,n,a,o=this.options,r=this._isRootNode(this.scrollParent[0]),h=e.pageX,l=e.pageY;return r&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),t&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,e.pageX-this.offset.click.lefti[2]&&(h=i[2]+this.offset.click.left),e.pageY-this.offset.click.top>i[3]&&(l=i[3]+this.offset.click.top)),o.grid&&(n=o.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/o.grid[1])*o.grid[1]:this.originalPageY,l=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-o.grid[1]:n+o.grid[1]:n,a=o.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/o.grid[0])*o.grid[0]:this.originalPageX,h=i?a-this.offset.click.left>=i[0]||a-this.offset.click.left>i[2]?a:a-this.offset.click.left>=i[0]?a-o.grid[0]:a+o.grid[0]:a),"y"===o.axis&&(h=this.originalPageX),"x"===o.axis&&(l=this.originalPageY)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:r?0:this.offset.scroll.top),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:r?0:this.offset.scroll.left)}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_normalizeRightBottom:function(){"y"!==this.options.axis&&"auto"!==this.helper.css("right")&&(this.helper.width(this.helper.width()),this.helper.css("right","auto")),"x"!==this.options.axis&&"auto"!==this.helper.css("bottom")&&(this.helper.height(this.helper.height()),this.helper.css("bottom","auto"))},_trigger:function(t,i,s){return s=s||this._uiHash(),e.ui.plugin.call(this,t,[i,s,this],!0),/^(drag|start|stop)/.test(t)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),e.Widget.prototype._trigger.call(this,t,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),e.ui.plugin.add("draggable","connectToSortable",{start:function(t,i,s){var n=e.extend({},i,{item:s.element});s.sortables=[],e(s.options.connectToSortable).each(function(){var i=e(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",t,n))})},stop:function(t,i,s){var n=e.extend({},i,{item:s.element});s.cancelHelperRemoval=!1,e.each(s.sortables,function(){var e=this;e.isOver?(e.isOver=0,s.cancelHelperRemoval=!0,e.cancelHelperRemoval=!1,e._storedCSS={position:e.placeholder.css("position"),top:e.placeholder.css("top"),left:e.placeholder.css("left")},e._mouseStop(t),e.options.helper=e.options._helper):(e.cancelHelperRemoval=!0,e._trigger("deactivate",t,n))})},drag:function(t,i,s){e.each(s.sortables,function(){var n=!1,a=this;a.positionAbs=s.positionAbs,a.helperProportions=s.helperProportions,a.offset.click=s.offset.click,a._intersectsWith(a.containerCache)&&(n=!0,e.each(s.sortables,function(){return this.positionAbs=s.positionAbs,this.helperProportions=s.helperProportions,this.offset.click=s.offset.click,this!==a&&this._intersectsWith(this.containerCache)&&e.contains(a.element[0],this.element[0])&&(n=!1),n})),n?(a.isOver||(a.isOver=1,s._parent=i.helper.parent(),a.currentItem=i.helper.appendTo(a.element).data("ui-sortable-item",!0),a.options._helper=a.options.helper,a.options.helper=function(){return i.helper[0]},t.target=a.currentItem[0],a._mouseCapture(t,!0),a._mouseStart(t,!0,!0),a.offset.click.top=s.offset.click.top,a.offset.click.left=s.offset.click.left,a.offset.parent.left-=s.offset.parent.left-a.offset.parent.left,a.offset.parent.top-=s.offset.parent.top-a.offset.parent.top,s._trigger("toSortable",t),s.dropped=a.element,e.each(s.sortables,function(){this.refreshPositions()}),s.currentItem=s.element,a.fromOutside=s),a.currentItem&&(a._mouseDrag(t),i.position=a.position)):a.isOver&&(a.isOver=0,a.cancelHelperRemoval=!0,a.options._revert=a.options.revert,a.options.revert=!1,a._trigger("out",t,a._uiHash(a)),a._mouseStop(t,!0),a.options.revert=a.options._revert,a.options.helper=a.options._helper,a.placeholder&&a.placeholder.remove(),i.helper.appendTo(s._parent),s._refreshOffsets(t),i.position=s._generatePosition(t,!0),s._trigger("fromSortable",t),s.dropped=!1,e.each(s.sortables,function(){this.refreshPositions()}))})}}),e.ui.plugin.add("draggable","cursor",{start:function(t,i,s){var n=e("body"),a=s.options;n.css("cursor")&&(a._cursor=n.css("cursor")),n.css("cursor",a.cursor)},stop:function(t,i,s){var n=s.options;n._cursor&&e("body").css("cursor",n._cursor)}}),e.ui.plugin.add("draggable","opacity",{start:function(t,i,s){var n=e(i.helper),a=s.options;n.css("opacity")&&(a._opacity=n.css("opacity")),n.css("opacity",a.opacity)},stop:function(t,i,s){var n=s.options;n._opacity&&e(i.helper).css("opacity",n._opacity)}}),e.ui.plugin.add("draggable","scroll",{start:function(e,t,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(t,i,s){var n=s.options,a=!1,o=s.scrollParentNotHidden[0],r=s.document[0];o!==r&&"HTML"!==o.tagName?(n.axis&&"x"===n.axis||(s.overflowOffset.top+o.offsetHeight-t.pageY=0;c--)h=s.snapElements[c].left-s.margins.left,l=h+s.snapElements[c].width,u=s.snapElements[c].top-s.margins.top,d=u+s.snapElements[c].height,h-m>v||g>l+m||u-m>b||y>d+m||!e.contains(s.snapElements[c].item.ownerDocument,s.snapElements[c].item)?(s.snapElements[c].snapping&&s.options.snap.release&&s.options.snap.release.call(s.element,t,e.extend(s._uiHash(),{snapItem:s.snapElements[c].item})),s.snapElements[c].snapping=!1):("inner"!==f.snapMode&&(n=m>=Math.abs(u-b),a=m>=Math.abs(d-y),o=m>=Math.abs(h-v),r=m>=Math.abs(l-g),n&&(i.position.top=s._convertPositionTo("relative",{top:u-s.helperProportions.height,left:0}).top),a&&(i.position.top=s._convertPositionTo("relative",{top:d,left:0}).top),o&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h-s.helperProportions.width}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l}).left)),p=n||a||o||r,"outer"!==f.snapMode&&(n=m>=Math.abs(u-y),a=m>=Math.abs(d-b),o=m>=Math.abs(h-g),r=m>=Math.abs(l-v),n&&(i.position.top=s._convertPositionTo("relative",{top:u,left:0}).top),a&&(i.position.top=s._convertPositionTo("relative",{top:d-s.helperProportions.height,left:0}).top),o&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l-s.helperProportions.width}).left)),!s.snapElements[c].snapping&&(n||a||o||r||p)&&s.options.snap.snap&&s.options.snap.snap.call(s.element,t,e.extend(s._uiHash(),{snapItem:s.snapElements[c].item})),s.snapElements[c].snapping=n||a||o||r||p)}}),e.ui.plugin.add("draggable","stack",{start:function(t,i,s){var n,a=s.options,o=e.makeArray(e(a.stack)).sort(function(t,i){return(parseInt(e(t).css("zIndex"),10)||0)-(parseInt(e(i).css("zIndex"),10)||0)});o.length&&(n=parseInt(e(o[0]).css("zIndex"),10)||0,e(o).each(function(t){e(this).css("zIndex",n+t)}),this.css("zIndex",n+o.length))}}),e.ui.plugin.add("draggable","zIndex",{start:function(t,i,s){var n=e(i.helper),a=s.options;n.css("zIndex")&&(a._zIndex=n.css("zIndex")),n.css("zIndex",a.zIndex)},stop:function(t,i,s){var n=s.options;n._zIndex&&e(i.helper).css("zIndex",n._zIndex)}}),e.ui.draggable,e.widget("ui.resizable",e.ui.mouse,{version:"1.11.4",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(e){return parseInt(e,10)||0},_isNumber:function(e){return!isNaN(parseInt(e,10))},_hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return t[s]>0?!0:(t[s]=1,n=t[s]>0,t[s]=0,n)},_create:function(){var t,i,s,n,a,o=this,r=this.options;if(this.element.addClass("ui-resizable"),e.extend(this,{_aspectRatio:!!r.aspectRatio,aspectRatio:r.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:r.helper||r.ghost||r.animate?r.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(e("
        ").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=r.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=e(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),t=this.handles.split(","),this.handles={},i=0;t.length>i;i++)s=e.trim(t[i]),a="ui-resizable-"+s,n=e("
        "),n.css({zIndex:r.zIndex}),"se"===s&&n.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(n);this._renderAxis=function(t){var i,s,n,a;t=t||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=e(this.handles[i]),this._on(this.handles[i],{mousedown:o._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=e(this.handles[i],this.element),a=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),t.css(n,a),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.mouseover(function(){o.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),o.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){r.disabled||(e(this).removeClass("ui-resizable-autohide"),o._handles.show())}).mouseleave(function(){r.disabled||o.resizing||(e(this).addClass("ui-resizable-autohide"),o._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t,i=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),t=this.element,this.originalElement.css({position:t.css("position"),width:t.outerWidth(),height:t.outerHeight(),top:t.css("top"),left:t.css("left")}).insertAfter(t),t.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_mouseCapture:function(t){var i,s,n=!1;for(i in this.handles)s=e(this.handles[i])[0],(s===t.target||e.contains(s,t.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(t){var i,s,n,a=this.options,o=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),a.containment&&(i+=e(a.containment).scrollLeft()||0,s+=e(a.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:o.width(),height:o.height()},this.originalSize=this._helper?{width:o.outerWidth(),height:o.outerHeight()}:{width:o.width(),height:o.height()},this.sizeDiff={width:o.outerWidth()-o.width(),height:o.outerHeight()-o.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio="number"==typeof a.aspectRatio?a.aspectRatio:this.originalSize.width/this.originalSize.height||1,n=e(".ui-resizable-"+this.axis).css("cursor"),e("body").css("cursor","auto"===n?this.axis+"-resize":n),o.addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(t){var i,s,n=this.originalMousePosition,a=this.axis,o=t.pageX-n.left||0,r=t.pageY-n.top||0,h=this._change[a];return this._updatePrevProperties(),h?(i=h.apply(this,[t,o,r]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(i=this._updateRatio(i,t)),i=this._respectSize(i,t),this._updateCache(i),this._propagate("resize",t),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),e.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",t,this.ui()),this._applyChanges()),!1):!1},_mouseStop:function(t){this.resizing=!1;var i,s,n,a,o,r,h,l=this.options,u=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&this._hasScroll(i[0],"left")?0:u.sizeDiff.height,a=s?0:u.sizeDiff.width,o={width:u.helper.width()-a,height:u.helper.height()-n},r=parseInt(u.element.css("left"),10)+(u.position.left-u.originalPosition.left)||null,h=parseInt(u.element.css("top"),10)+(u.position.top-u.originalPosition.top)||null,l.animate||this.element.css(e.extend(o,{top:h,left:r})),u.helper.height(u.size.height),u.helper.width(u.size.width),this._helper&&!l.animate&&this._proportionallyResize()),e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var e={};return this.position.top!==this.prevPosition.top&&(e.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(e.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(e.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(e.height=this.size.height+"px"),this.helper.css(e),e},_updateVirtualBoundaries:function(e){var t,i,s,n,a,o=this.options;a={minWidth:this._isNumber(o.minWidth)?o.minWidth:0,maxWidth:this._isNumber(o.maxWidth)?o.maxWidth:1/0,minHeight:this._isNumber(o.minHeight)?o.minHeight:0,maxHeight:this._isNumber(o.maxHeight)?o.maxHeight:1/0},(this._aspectRatio||e)&&(t=a.minHeight*this.aspectRatio,s=a.minWidth/this.aspectRatio,i=a.maxHeight*this.aspectRatio,n=a.maxWidth/this.aspectRatio,t>a.minWidth&&(a.minWidth=t),s>a.minHeight&&(a.minHeight=s),a.maxWidth>i&&(a.maxWidth=i),a.maxHeight>n&&(a.maxHeight=n)),this._vBoundaries=a},_updateCache:function(e){this.offset=this.helper.offset(),this._isNumber(e.left)&&(this.position.left=e.left),this._isNumber(e.top)&&(this.position.top=e.top),this._isNumber(e.height)&&(this.size.height=e.height),this._isNumber(e.width)&&(this.size.width=e.width)},_updateRatio:function(e){var t=this.position,i=this.size,s=this.axis;return this._isNumber(e.height)?e.width=e.height*this.aspectRatio:this._isNumber(e.width)&&(e.height=e.width/this.aspectRatio),"sw"===s&&(e.left=t.left+(i.width-e.width),e.top=null),"nw"===s&&(e.top=t.top+(i.height-e.height),e.left=t.left+(i.width-e.width)),e},_respectSize:function(e){var t=this._vBoundaries,i=this.axis,s=this._isNumber(e.width)&&t.maxWidth&&t.maxWidthe.width,o=this._isNumber(e.height)&&t.minHeight&&t.minHeight>e.height,r=this.originalPosition.left+this.originalSize.width,h=this.position.top+this.size.height,l=/sw|nw|w/.test(i),u=/nw|ne|n/.test(i);return a&&(e.width=t.minWidth),o&&(e.height=t.minHeight),s&&(e.width=t.maxWidth),n&&(e.height=t.maxHeight),a&&l&&(e.left=r-t.minWidth),s&&l&&(e.left=r-t.maxWidth),o&&u&&(e.top=h-t.minHeight),n&&u&&(e.top=h-t.maxHeight),e.width||e.height||e.left||!e.top?e.width||e.height||e.top||!e.left||(e.left=null):e.top=null,e},_getPaddingPlusBorderDimensions:function(e){for(var t=0,i=[],s=[e.css("borderTopWidth"),e.css("borderRightWidth"),e.css("borderBottomWidth"),e.css("borderLeftWidth")],n=[e.css("paddingTop"),e.css("paddingRight"),e.css("paddingBottom"),e.css("paddingLeft")];4>t;t++)i[t]=parseInt(s[t],10)||0,i[t]+=parseInt(n[t],10)||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var e,t=0,i=this.helper||this.element;this._proportionallyResizeElements.length>t;t++)e=this._proportionallyResizeElements[t],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(e)),e.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var t=this.element,i=this.options;this.elementOffset=t.offset(),this._helper?(this.helper=this.helper||e("
        "),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(e,t){return{width:this.originalSize.width+t}},w:function(e,t){var i=this.originalSize,s=this.originalPosition;return{left:s.left+t,width:i.width-t}},n:function(e,t,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(e,t,i){return{height:this.originalSize.height+i}},se:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},sw:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,i,s]))},ne:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},nw:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,i,s]))}},_propagate:function(t,i){e.ui.plugin.call(this,t,[i,this.ui()]),"resize"!==t&&this._trigger(t,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","animate",{stop:function(t){var i=e(this).resizable("instance"),s=i.options,n=i._proportionallyResizeElements,a=n.length&&/textarea/i.test(n[0].nodeName),o=a&&i._hasScroll(n[0],"left")?0:i.sizeDiff.height,r=a?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-o},l=parseInt(i.element.css("left"),10)+(i.position.left-i.originalPosition.left)||null,u=parseInt(i.element.css("top"),10)+(i.position.top-i.originalPosition.top)||null;i.element.animate(e.extend(h,u&&l?{top:u,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseInt(i.element.css("width"),10),height:parseInt(i.element.css("height"),10),top:parseInt(i.element.css("top"),10),left:parseInt(i.element.css("left"),10)};n&&n.length&&e(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(){var t,i,s,n,a,o,r,h=e(this).resizable("instance"),l=h.options,u=h.element,d=l.containment,c=d instanceof e?d.get(0):/parent/.test(d)?u.parent().get(0):d;c&&(h.containerElement=e(c),/document/.test(d)||d===document?(h.containerOffset={left:0,top:0},h.containerPosition={left:0,top:0},h.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}):(t=e(c),i=[],e(["Top","Right","Left","Bottom"]).each(function(e,s){i[e]=h._num(t.css("padding"+s))}),h.containerOffset=t.offset(),h.containerPosition=t.position(),h.containerSize={height:t.innerHeight()-i[3],width:t.innerWidth()-i[1]},s=h.containerOffset,n=h.containerSize.height,a=h.containerSize.width,o=h._hasScroll(c,"left")?c.scrollWidth:a,r=h._hasScroll(c)?c.scrollHeight:n,h.parentData={element:c,left:s.left,top:s.top,width:o,height:r}))},resize:function(t){var i,s,n,a,o=e(this).resizable("instance"),r=o.options,h=o.containerOffset,l=o.position,u=o._aspectRatio||t.shiftKey,d={top:0,left:0},c=o.containerElement,p=!0;c[0]!==document&&/static/.test(c.css("position"))&&(d=h),l.left<(o._helper?h.left:0)&&(o.size.width=o.size.width+(o._helper?o.position.left-h.left:o.position.left-d.left),u&&(o.size.height=o.size.width/o.aspectRatio,p=!1),o.position.left=r.helper?h.left:0),l.top<(o._helper?h.top:0)&&(o.size.height=o.size.height+(o._helper?o.position.top-h.top:o.position.top),u&&(o.size.width=o.size.height*o.aspectRatio,p=!1),o.position.top=o._helper?h.top:0),n=o.containerElement.get(0)===o.element.parent().get(0),a=/relative|absolute/.test(o.containerElement.css("position")),n&&a?(o.offset.left=o.parentData.left+o.position.left,o.offset.top=o.parentData.top+o.position.top):(o.offset.left=o.element.offset().left,o.offset.top=o.element.offset().top),i=Math.abs(o.sizeDiff.width+(o._helper?o.offset.left-d.left:o.offset.left-h.left)),s=Math.abs(o.sizeDiff.height+(o._helper?o.offset.top-d.top:o.offset.top-h.top)),i+o.size.width>=o.parentData.width&&(o.size.width=o.parentData.width-i,u&&(o.size.height=o.size.width/o.aspectRatio,p=!1)),s+o.size.height>=o.parentData.height&&(o.size.height=o.parentData.height-s,u&&(o.size.width=o.size.height*o.aspectRatio,p=!1)),p||(o.position.left=o.prevPosition.left,o.position.top=o.prevPosition.top,o.size.width=o.prevSize.width,o.size.height=o.prevSize.height)},stop:function(){var t=e(this).resizable("instance"),i=t.options,s=t.containerOffset,n=t.containerPosition,a=t.containerElement,o=e(t.helper),r=o.offset(),h=o.outerWidth()-t.sizeDiff.width,l=o.outerHeight()-t.sizeDiff.height;t._helper&&!i.animate&&/relative/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l}),t._helper&&!i.animate&&/static/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),e.ui.plugin.add("resizable","alsoResize",{start:function(){var t=e(this).resizable("instance"),i=t.options;e(i.alsoResize).each(function(){var t=e(this);t.data("ui-resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})},resize:function(t,i){var s=e(this).resizable("instance"),n=s.options,a=s.originalSize,o=s.originalPosition,r={height:s.size.height-a.height||0,width:s.size.width-a.width||0,top:s.position.top-o.top||0,left:s.position.left-o.left||0};e(n.alsoResize).each(function(){var t=e(this),s=e(this).data("ui-resizable-alsoresize"),n={},a=t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(a,function(e,t){var i=(s[t]||0)+(r[t]||0);i&&i>=0&&(n[t]=i||null)}),t.css(n)})},stop:function(){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","ghost",{start:function(){var t=e(this).resizable("instance"),i=t.options,s=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof i.ghost?i.ghost:""),t.ghost.appendTo(t.helper)},resize:function(){var t=e(this).resizable("instance");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=e(this).resizable("instance");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(){var t,i=e(this).resizable("instance"),s=i.options,n=i.size,a=i.originalSize,o=i.originalPosition,r=i.axis,h="number"==typeof s.grid?[s.grid,s.grid]:s.grid,l=h[0]||1,u=h[1]||1,d=Math.round((n.width-a.width)/l)*l,c=Math.round((n.height-a.height)/u)*u,p=a.width+d,f=a.height+c,m=s.maxWidth&&p>s.maxWidth,g=s.maxHeight&&f>s.maxHeight,v=s.minWidth&&s.minWidth>p,y=s.minHeight&&s.minHeight>f;s.grid=h,v&&(p+=l),y&&(f+=u),m&&(p-=l),g&&(f-=u),/^(se|s|e)$/.test(r)?(i.size.width=p,i.size.height=f):/^(ne)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.top=o.top-c):/^(sw)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.left=o.left-d):((0>=f-u||0>=p-l)&&(t=i._getPaddingPlusBorderDimensions(this)),f-u>0?(i.size.height=f,i.position.top=o.top-c):(f=u-t.height,i.size.height=f,i.position.top=o.top+a.height-f),p-l>0?(i.size.width=p,i.position.left=o.left-d):(p=l-t.width,i.size.width=p,i.position.left=o.left+a.width-p))}}),e.ui.resizable,e.widget("ui.dialog",{version:"1.11.4",options:{appendTo:"body",autoOpen:!0,buttons:[],closeOnEscape:!0,closeText:"Close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(t){var i=e(this).css(t).offset().top;0>i&&e(this).css("top",t.top-i)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},sizeRelatedOptions:{buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},resizableRelatedOptions:{maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),this.options.title=this.options.title||this.originalTitle,this._createWrapper(),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(this.uiDialog),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&e.fn.draggable&&this._makeDraggable(),this.options.resizable&&e.fn.resizable&&this._makeResizable(),this._isOpen=!1,this._trackFocus()},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var t=this.options.appendTo;return t&&(t.jquery||t.nodeType)?e(t):this.document.find(t||"body").eq(0)},_destroy:function(){var e,t=this.originalPosition;this._untrackInstance(),this._destroyOverlay(),this.element.removeUniqueId().removeClass("ui-dialog-content ui-widget-content").css(this.originalCss).detach(),this.uiDialog.stop(!0,!0).remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),e=t.parent.children().eq(t.index),e.length&&e[0]!==this.element[0]?e.before(this.element):t.parent.append(this.element)},widget:function(){return this.uiDialog},disable:e.noop,enable:e.noop,close:function(t){var i,s=this;if(this._isOpen&&this._trigger("beforeClose",t)!==!1){if(this._isOpen=!1,this._focusedElement=null,this._destroyOverlay(),this._untrackInstance(),!this.opener.filter(":focusable").focus().length)try{i=this.document[0].activeElement,i&&"body"!==i.nodeName.toLowerCase()&&e(i).blur()}catch(n){}this._hide(this.uiDialog,this.options.hide,function(){s._trigger("close",t)})}},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(t,i){var s=!1,n=this.uiDialog.siblings(".ui-front:visible").map(function(){return+e(this).css("z-index")}).get(),a=Math.max.apply(null,n);return a>=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",a+1),s=!0),s&&!i&&this._trigger("focus",t),s},open:function(){var t=this;return this._isOpen?(this._moveToTop()&&this._focusTabbable(),void 0):(this._isOpen=!0,this.opener=e(this.document[0].activeElement),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){t._focusTabbable(),t._trigger("focus")}),this._makeFocusTarget(),this._trigger("open"),void 0)},_focusTabbable:function(){var e=this._focusedElement;e||(e=this.element.find("[autofocus]")),e.length||(e=this.element.find(":tabbable")),e.length||(e=this.uiDialogButtonPane.find(":tabbable")),e.length||(e=this.uiDialogTitlebarClose.filter(":tabbable")),e.length||(e=this.uiDialog),e.eq(0).focus()},_keepFocus:function(t){function i(){var t=this.document[0].activeElement,i=this.uiDialog[0]===t||e.contains(this.uiDialog[0],t);i||this._focusTabbable()}t.preventDefault(),i.call(this),this._delay(i)},_createWrapper:function(){this.uiDialog=e("
        ").addClass("ui-dialog ui-widget ui-widget-content ui-corner-all ui-front "+this.options.dialogClass).hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._on(this.uiDialog,{keydown:function(t){if(this.options.closeOnEscape&&!t.isDefaultPrevented()&&t.keyCode&&t.keyCode===e.ui.keyCode.ESCAPE)return t.preventDefault(),this.close(t),void 0; +if(t.keyCode===e.ui.keyCode.TAB&&!t.isDefaultPrevented()){var i=this.uiDialog.find(":tabbable"),s=i.filter(":first"),n=i.filter(":last");t.target!==n[0]&&t.target!==this.uiDialog[0]||t.shiftKey?t.target!==s[0]&&t.target!==this.uiDialog[0]||!t.shiftKey||(this._delay(function(){n.focus()}),t.preventDefault()):(this._delay(function(){s.focus()}),t.preventDefault())}},mousedown:function(e){this._moveToTop(e)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var t;this.uiDialogTitlebar=e("
        ").addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(this.uiDialog),this._on(this.uiDialogTitlebar,{mousedown:function(t){e(t.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.focus()}}),this.uiDialogTitlebarClose=e("").button({label:this.options.closeText,icons:{primary:"ui-icon-closethick"},text:!1}).addClass("ui-dialog-titlebar-close").appendTo(this.uiDialogTitlebar),this._on(this.uiDialogTitlebarClose,{click:function(e){e.preventDefault(),this.close(e)}}),t=e("").uniqueId().addClass("ui-dialog-title").prependTo(this.uiDialogTitlebar),this._title(t),this.uiDialog.attr({"aria-labelledby":t.attr("id")})},_title:function(e){this.options.title||e.html(" "),e.text(this.options.title)},_createButtonPane:function(){this.uiDialogButtonPane=e("
        ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),this.uiButtonSet=e("
        ").addClass("ui-dialog-buttonset").appendTo(this.uiDialogButtonPane),this._createButtons()},_createButtons:function(){var t=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),e.isEmptyObject(i)||e.isArray(i)&&!i.length?(this.uiDialog.removeClass("ui-dialog-buttons"),void 0):(e.each(i,function(i,s){var n,a;s=e.isFunction(s)?{click:s,text:i}:s,s=e.extend({type:"button"},s),n=s.click,s.click=function(){n.apply(t.element[0],arguments)},a={icons:s.icons,text:s.showText},delete s.icons,delete s.showText,e("",s).button(a).appendTo(t.uiButtonSet)}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),void 0)},_makeDraggable:function(){function t(e){return{position:e.position,offset:e.offset}}var i=this,s=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(s,n){e(this).addClass("ui-dialog-dragging"),i._blockFrames(),i._trigger("dragStart",s,t(n))},drag:function(e,s){i._trigger("drag",e,t(s))},stop:function(n,a){var o=a.offset.left-i.document.scrollLeft(),r=a.offset.top-i.document.scrollTop();s.position={my:"left top",at:"left"+(o>=0?"+":"")+o+" "+"top"+(r>=0?"+":"")+r,of:i.window},e(this).removeClass("ui-dialog-dragging"),i._unblockFrames(),i._trigger("dragStop",n,t(a))}})},_makeResizable:function(){function t(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}var i=this,s=this.options,n=s.resizable,a=this.uiDialog.css("position"),o="string"==typeof n?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:s.maxWidth,maxHeight:s.maxHeight,minWidth:s.minWidth,minHeight:this._minHeight(),handles:o,start:function(s,n){e(this).addClass("ui-dialog-resizing"),i._blockFrames(),i._trigger("resizeStart",s,t(n))},resize:function(e,s){i._trigger("resize",e,t(s))},stop:function(n,a){var o=i.uiDialog.offset(),r=o.left-i.document.scrollLeft(),h=o.top-i.document.scrollTop();s.height=i.uiDialog.height(),s.width=i.uiDialog.width(),s.position={my:"left top",at:"left"+(r>=0?"+":"")+r+" "+"top"+(h>=0?"+":"")+h,of:i.window},e(this).removeClass("ui-dialog-resizing"),i._unblockFrames(),i._trigger("resizeStop",n,t(a))}}).css("position",a)},_trackFocus:function(){this._on(this.widget(),{focusin:function(t){this._makeFocusTarget(),this._focusedElement=e(t.target)}})},_makeFocusTarget:function(){this._untrackInstance(),this._trackingInstances().unshift(this)},_untrackInstance:function(){var t=this._trackingInstances(),i=e.inArray(this,t);-1!==i&&t.splice(i,1)},_trackingInstances:function(){var e=this.document.data("ui-dialog-instances");return e||(e=[],this.document.data("ui-dialog-instances",e)),e},_minHeight:function(){var e=this.options;return"auto"===e.height?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(){var e=this.uiDialog.is(":visible");e||this.uiDialog.show(),this.uiDialog.position(this.options.position),e||this.uiDialog.hide()},_setOptions:function(t){var i=this,s=!1,n={};e.each(t,function(e,t){i._setOption(e,t),e in i.sizeRelatedOptions&&(s=!0),e in i.resizableRelatedOptions&&(n[e]=t)}),s&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",n)},_setOption:function(e,t){var i,s,n=this.uiDialog;"dialogClass"===e&&n.removeClass(this.options.dialogClass).addClass(t),"disabled"!==e&&(this._super(e,t),"appendTo"===e&&this.uiDialog.appendTo(this._appendTo()),"buttons"===e&&this._createButtons(),"closeText"===e&&this.uiDialogTitlebarClose.button({label:""+t}),"draggable"===e&&(i=n.is(":data(ui-draggable)"),i&&!t&&n.draggable("destroy"),!i&&t&&this._makeDraggable()),"position"===e&&this._position(),"resizable"===e&&(s=n.is(":data(ui-resizable)"),s&&!t&&n.resizable("destroy"),s&&"string"==typeof t&&n.resizable("option","handles",t),s||t===!1||this._makeResizable()),"title"===e&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var e,t,i,s=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),s.minWidth>s.width&&(s.width=s.minWidth),e=this.uiDialog.css({height:"auto",width:s.width}).outerHeight(),t=Math.max(0,s.minHeight-e),i="number"==typeof s.maxHeight?Math.max(0,s.maxHeight-e):"none","auto"===s.height?this.element.css({minHeight:t,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,s.height-e)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var t=e(this);return e("
        ").css({position:"absolute",width:t.outerWidth(),height:t.outerHeight()}).appendTo(t.parent()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(t){return e(t.target).closest(".ui-dialog").length?!0:!!e(t.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var t=!0;this._delay(function(){t=!1}),this.document.data("ui-dialog-overlays")||this._on(this.document,{focusin:function(e){t||this._allowInteraction(e)||(e.preventDefault(),this._trackingInstances()[0]._focusTabbable())}}),this.overlay=e("
        ").addClass("ui-widget-overlay ui-front").appendTo(this._appendTo()),this._on(this.overlay,{mousedown:"_keepFocus"}),this.document.data("ui-dialog-overlays",(this.document.data("ui-dialog-overlays")||0)+1)}},_destroyOverlay:function(){if(this.options.modal&&this.overlay){var e=this.document.data("ui-dialog-overlays")-1;e?this.document.data("ui-dialog-overlays",e):this.document.unbind("focusin").removeData("ui-dialog-overlays"),this.overlay.remove(),this.overlay=null}}}),e.widget("ui.droppable",{version:"1.11.4",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var t,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=e.isFunction(s)?s:function(e){return e.is(s)},this.proportions=function(){return arguments.length?(t=arguments[0],void 0):t?t:t={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},this._addToManager(i.scope),i.addClasses&&this.element.addClass("ui-droppable")},_addToManager:function(t){e.ui.ddmanager.droppables[t]=e.ui.ddmanager.droppables[t]||[],e.ui.ddmanager.droppables[t].push(this)},_splice:function(e){for(var t=0;e.length>t;t++)e[t]===this&&e.splice(t,1)},_destroy:function(){var t=e.ui.ddmanager.droppables[this.options.scope];this._splice(t),this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(t,i){if("accept"===t)this.accept=e.isFunction(i)?i:function(e){return e.is(i)};else if("scope"===t){var s=e.ui.ddmanager.droppables[this.options.scope];this._splice(s),this._addToManager(i)}this._super(t,i)},_activate:function(t){var i=e.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),i&&this._trigger("activate",t,this.ui(i))},_deactivate:function(t){var i=e.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),i&&this._trigger("deactivate",t,this.ui(i))},_over:function(t){var i=e.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",t,this.ui(i)))},_out:function(t){var i=e.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",t,this.ui(i)))},_drop:function(t,i){var s=i||e.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var i=e(this).droppable("instance");return i.options.greedy&&!i.options.disabled&&i.options.scope===s.options.scope&&i.accept.call(i.element[0],s.currentItem||s.element)&&e.ui.intersect(s,e.extend(i,{offset:i.element.offset()}),i.options.tolerance,t)?(n=!0,!1):void 0}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",t,this.ui(s)),this.element):!1):!1},ui:function(e){return{draggable:e.currentItem||e.element,helper:e.helper,position:e.position,offset:e.positionAbs}}}),e.ui.intersect=function(){function e(e,t,i){return e>=t&&t+i>e}return function(t,i,s,n){if(!i.offset)return!1;var a=(t.positionAbs||t.position.absolute).left+t.margins.left,o=(t.positionAbs||t.position.absolute).top+t.margins.top,r=a+t.helperProportions.width,h=o+t.helperProportions.height,l=i.offset.left,u=i.offset.top,d=l+i.proportions().width,c=u+i.proportions().height;switch(s){case"fit":return a>=l&&d>=r&&o>=u&&c>=h;case"intersect":return a+t.helperProportions.width/2>l&&d>r-t.helperProportions.width/2&&o+t.helperProportions.height/2>u&&c>h-t.helperProportions.height/2;case"pointer":return e(n.pageY,u,i.proportions().height)&&e(n.pageX,l,i.proportions().width);case"touch":return(o>=u&&c>=o||h>=u&&c>=h||u>o&&h>c)&&(a>=l&&d>=a||r>=l&&d>=r||l>a&&r>d);default:return!1}}}(),e.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(t,i){var s,n,a=e.ui.ddmanager.droppables[t.options.scope]||[],o=i?i.type:null,r=(t.currentItem||t.element).find(":data(ui-droppable)").addBack();e:for(s=0;a.length>s;s++)if(!(a[s].options.disabled||t&&!a[s].accept.call(a[s].element[0],t.currentItem||t.element))){for(n=0;r.length>n;n++)if(r[n]===a[s].element[0]){a[s].proportions().height=0;continue e}a[s].visible="none"!==a[s].element.css("display"),a[s].visible&&("mousedown"===o&&a[s]._activate.call(a[s],i),a[s].offset=a[s].element.offset(),a[s].proportions({width:a[s].element[0].offsetWidth,height:a[s].element[0].offsetHeight}))}},drop:function(t,i){var s=!1;return e.each((e.ui.ddmanager.droppables[t.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&e.ui.intersect(t,this,this.options.tolerance,i)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],t.currentItem||t.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(t,i){t.element.parentsUntil("body").bind("scroll.droppable",function(){t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,i)})},drag:function(t,i){t.options.refreshPositions&&e.ui.ddmanager.prepareOffsets(t,i),e.each(e.ui.ddmanager.droppables[t.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,a,o=e.ui.intersect(t,this,this.options.tolerance,i),r=!o&&this.isover?"isout":o&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,a=this.element.parents(":data(ui-droppable)").filter(function(){return e(this).droppable("instance").options.scope===n}),a.length&&(s=e(a[0]).droppable("instance"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(t,i){t.element.parentsUntil("body").unbind("scroll.droppable"),t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,i)}},e.ui.droppable;var y="ui-effects-",b=e;e.effects={effect:{}},function(e,t){function i(e,t,i){var s=d[t.type]||{};return null==e?i||!t.def?null:t.def:(e=s.floor?~~e:parseFloat(e),isNaN(e)?t.def:s.mod?(e+s.mod)%s.mod:0>e?0:e>s.max?s.max:e)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(e,a){var o,r=a.re.exec(i),h=r&&a.parse(r),l=a.space||"rgba";return h?(o=s[l](h),s[u[l].cache]=o[u[l].cache],n=s._rgba=o._rgba,!1):t}),n.length?("0,0,0,0"===n.join()&&e.extend(n,a.transparent),s):a[i]}function n(e,t,i){return i=(i+1)%1,1>6*i?e+6*(t-e)*i:1>2*i?t:2>3*i?e+6*(t-e)*(2/3-i):e}var a,o="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1],e[2],e[3],e[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[2.55*e[1],2.55*e[2],2.55*e[3],e[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(e){return[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(e){return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(e){return[e[1],e[2]/100,e[3]/100,e[4]]}}],l=e.Color=function(t,i,s,n){return new e.Color.fn.parse(t,i,s,n)},u={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},d={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},c=l.support={},p=e("

        ")[0],f=e.each;p.style.cssText="background-color:rgba(1,1,1,.5)",c.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(u,function(e,t){t.cache="_"+e,t.props.alpha={idx:3,type:"percent",def:1}}),l.fn=e.extend(l.prototype,{parse:function(n,o,r,h){if(n===t)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=e(n).css(o),o=t);var d=this,c=e.type(n),p=this._rgba=[];return o!==t&&(n=[n,o,r,h],c="array"),"string"===c?this.parse(s(n)||a._default):"array"===c?(f(u.rgba.props,function(e,t){p[t.idx]=i(n[t.idx],t)}),this):"object"===c?(n instanceof l?f(u,function(e,t){n[t.cache]&&(d[t.cache]=n[t.cache].slice())}):f(u,function(t,s){var a=s.cache;f(s.props,function(e,t){if(!d[a]&&s.to){if("alpha"===e||null==n[e])return;d[a]=s.to(d._rgba)}d[a][t.idx]=i(n[e],t,!0)}),d[a]&&0>e.inArray(null,d[a].slice(0,3))&&(d[a][3]=1,s.from&&(d._rgba=s.from(d[a])))}),this):t},is:function(e){var i=l(e),s=!0,n=this;return f(u,function(e,a){var o,r=i[a.cache];return r&&(o=n[a.cache]||a.to&&a.to(n._rgba)||[],f(a.props,function(e,i){return null!=r[i.idx]?s=r[i.idx]===o[i.idx]:t})),s}),s},_space:function(){var e=[],t=this;return f(u,function(i,s){t[s.cache]&&e.push(i)}),e.pop()},transition:function(e,t){var s=l(e),n=s._space(),a=u[n],o=0===this.alpha()?l("transparent"):this,r=o[a.cache]||a.to(o._rgba),h=r.slice();return s=s[a.cache],f(a.props,function(e,n){var a=n.idx,o=r[a],l=s[a],u=d[n.type]||{};null!==l&&(null===o?h[a]=l:(u.mod&&(l-o>u.mod/2?o+=u.mod:o-l>u.mod/2&&(o-=u.mod)),h[a]=i((l-o)*t+o,n)))}),this[n](h)},blend:function(t){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(t)._rgba;return l(e.map(i,function(e,t){return(1-s)*n[t]+s*e}))},toRgbaString:function(){var t="rgba(",i=e.map(this._rgba,function(e,t){return null==e?t>2?1:0:e});return 1===i[3]&&(i.pop(),t="rgb("),t+i.join()+")"},toHslaString:function(){var t="hsla(",i=e.map(this.hsla(),function(e,t){return null==e&&(e=t>2?1:0),t&&3>t&&(e=Math.round(100*e)+"%"),e});return 1===i[3]&&(i.pop(),t="hsl("),t+i.join()+")"},toHexString:function(t){var i=this._rgba.slice(),s=i.pop();return t&&i.push(~~(255*s)),"#"+e.map(i,function(e){return e=(e||0).toString(16),1===e.length?"0"+e:e}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,u.hsla.to=function(e){if(null==e[0]||null==e[1]||null==e[2])return[null,null,null,e[3]];var t,i,s=e[0]/255,n=e[1]/255,a=e[2]/255,o=e[3],r=Math.max(s,n,a),h=Math.min(s,n,a),l=r-h,u=r+h,d=.5*u;return t=h===r?0:s===r?60*(n-a)/l+360:n===r?60*(a-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=d?l/u:l/(2-u),[Math.round(t)%360,i,d,null==o?1:o]},u.hsla.from=function(e){if(null==e[0]||null==e[1]||null==e[2])return[null,null,null,e[3]];var t=e[0]/360,i=e[1],s=e[2],a=e[3],o=.5>=s?s*(1+i):s+i-s*i,r=2*s-o;return[Math.round(255*n(r,o,t+1/3)),Math.round(255*n(r,o,t)),Math.round(255*n(r,o,t-1/3)),a]},f(u,function(s,n){var a=n.props,o=n.cache,h=n.to,u=n.from;l.fn[s]=function(s){if(h&&!this[o]&&(this[o]=h(this._rgba)),s===t)return this[o].slice();var n,r=e.type(s),d="array"===r||"object"===r?s:arguments,c=this[o].slice();return f(a,function(e,t){var s=d["object"===r?e:t.idx];null==s&&(s=c[t.idx]),c[t.idx]=i(s,t)}),u?(n=l(u(c)),n[o]=c,n):l(c)},f(a,function(t,i){l.fn[t]||(l.fn[t]=function(n){var a,o=e.type(n),h="alpha"===t?this._hsla?"hsla":"rgba":s,l=this[h](),u=l[i.idx];return"undefined"===o?u:("function"===o&&(n=n.call(this,u),o=e.type(n)),null==n&&i.empty?this:("string"===o&&(a=r.exec(n),a&&(n=u+parseFloat(a[2])*("+"===a[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(t){var i=t.split(" ");f(i,function(t,i){e.cssHooks[i]={set:function(t,n){var a,o,r="";if("transparent"!==n&&("string"!==e.type(n)||(a=s(n)))){if(n=l(a||n),!c.rgba&&1!==n._rgba[3]){for(o="backgroundColor"===i?t.parentNode:t;(""===r||"transparent"===r)&&o&&o.style;)try{r=e.css(o,"backgroundColor"),o=o.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{t.style[i]=n}catch(h){}}},e.fx.step[i]=function(t){t.colorInit||(t.start=l(t.elem,i),t.end=l(t.end),t.colorInit=!0),e.cssHooks[i].set(t.elem,t.start.transition(t.end,t.pos))}})},l.hook(o),e.cssHooks.borderColor={expand:function(e){var t={};return f(["Top","Right","Bottom","Left"],function(i,s){t["border"+s+"Color"]=e}),t}},a=e.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(b),function(){function t(t){var i,s,n=t.ownerDocument.defaultView?t.ownerDocument.defaultView.getComputedStyle(t,null):t.currentStyle,a={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(a[e.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(a[i]=n[i]);return a}function i(t,i){var s,a,o={};for(s in i)a=i[s],t[s]!==a&&(n[s]||(e.fx.step[s]||!isNaN(parseFloat(a)))&&(o[s]=a));return o}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};e.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(t,i){e.fx.step[i]=function(e){("none"!==e.end&&!e.setAttr||1===e.pos&&!e.setAttr)&&(b.style(e.elem,i,e.end),e.setAttr=!0)}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e.effects.animateClass=function(n,a,o,r){var h=e.speed(a,o,r);return this.queue(function(){var a,o=e(this),r=o.attr("class")||"",l=h.children?o.find("*").addBack():o;l=l.map(function(){var i=e(this);return{el:i,start:t(this)}}),a=function(){e.each(s,function(e,t){n[t]&&o[t+"Class"](n[t])})},a(),l=l.map(function(){return this.end=t(this.el[0]),this.diff=i(this.start,this.end),this}),o.attr("class",r),l=l.map(function(){var t=this,i=e.Deferred(),s=e.extend({},h,{queue:!1,complete:function(){i.resolve(t)}});return this.el.animate(this.diff,s),i.promise()}),e.when.apply(e,l.get()).done(function(){a(),e.each(arguments,function(){var t=this.el;e.each(this.diff,function(e){t.css(e,"")})}),h.complete.call(o[0])})})},e.fn.extend({addClass:function(t){return function(i,s,n,a){return s?e.effects.animateClass.call(this,{add:i},s,n,a):t.apply(this,arguments)}}(e.fn.addClass),removeClass:function(t){return function(i,s,n,a){return arguments.length>1?e.effects.animateClass.call(this,{remove:i},s,n,a):t.apply(this,arguments)}}(e.fn.removeClass),toggleClass:function(t){return function(i,s,n,a,o){return"boolean"==typeof s||void 0===s?n?e.effects.animateClass.call(this,s?{add:i}:{remove:i},n,a,o):t.apply(this,arguments):e.effects.animateClass.call(this,{toggle:i},s,n,a)}}(e.fn.toggleClass),switchClass:function(t,i,s,n,a){return e.effects.animateClass.call(this,{add:i,remove:t},s,n,a)}})}(),function(){function t(t,i,s,n){return e.isPlainObject(t)&&(i=t,t=t.effect),t={effect:t},null==i&&(i={}),e.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||e.fx.speeds[i])&&(n=s,s=i,i={}),e.isFunction(s)&&(n=s,s=null),i&&e.extend(t,i),s=s||i.duration,t.duration=e.fx.off?0:"number"==typeof s?s:s in e.fx.speeds?e.fx.speeds[s]:e.fx.speeds._default,t.complete=n||i.complete,t}function i(t){return!t||"number"==typeof t||e.fx.speeds[t]?!0:"string"!=typeof t||e.effects.effect[t]?e.isFunction(t)?!0:"object"!=typeof t||t.effect?!1:!0:!0}e.extend(e.effects,{version:"1.11.4",save:function(e,t){for(var i=0;t.length>i;i++)null!==t[i]&&e.data(y+t[i],e[0].style[t[i]])},restore:function(e,t){var i,s;for(s=0;t.length>s;s++)null!==t[s]&&(i=e.data(y+t[s]),void 0===i&&(i=""),e.css(t[s],i))},setMode:function(e,t){return"toggle"===t&&(t=e.is(":hidden")?"show":"hide"),t},getBaseline:function(e,t){var i,s;switch(e[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=e[0]/t.height}switch(e[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=e[1]/t.width}return{x:s,y:i}},createWrapper:function(t){if(t.parent().is(".ui-effects-wrapper"))return t.parent();var i={width:t.outerWidth(!0),height:t.outerHeight(!0),"float":t.css("float")},s=e("

        ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:t.width(),height:t.height()},a=document.activeElement;try{a.id}catch(o){a=document.body}return t.wrap(s),(t[0]===a||e.contains(t[0],a))&&e(a).focus(),s=t.parent(),"static"===t.css("position")?(s.css({position:"relative"}),t.css({position:"relative"})):(e.extend(i,{position:t.css("position"),zIndex:t.css("z-index")}),e.each(["top","left","bottom","right"],function(e,s){i[s]=t.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),t.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),t.css(n),s.css(i).show()},removeWrapper:function(t){var i=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),(t[0]===i||e.contains(t[0],i))&&e(i).focus()),t},setTransition:function(t,i,s,n){return n=n||{},e.each(i,function(e,i){var a=t.cssUnit(i);a[0]>0&&(n[i]=a[0]*s+a[1])}),n}}),e.fn.extend({effect:function(){function i(t){function i(){e.isFunction(a)&&a.call(n[0]),e.isFunction(t)&&t()}var n=e(this),a=s.complete,r=s.mode;(n.is(":hidden")?"hide"===r:"show"===r)?(n[r](),i()):o.call(n[0],s,i)}var s=t.apply(this,arguments),n=s.mode,a=s.queue,o=e.effects.effect[s.effect];return e.fx.off||!o?n?this[n](s.duration,s.complete):this.each(function(){s.complete&&s.complete.call(this)}):a===!1?this.each(i):this.queue(a||"fx",i)},show:function(e){return function(s){if(i(s))return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="show",this.effect.call(this,n)}}(e.fn.show),hide:function(e){return function(s){if(i(s))return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(e.fn.hide),toggle:function(e){return function(s){if(i(s)||"boolean"==typeof s)return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(e.fn.toggle),cssUnit:function(t){var i=this.css(t),s=[];return e.each(["em","px","%","pt"],function(e,t){i.indexOf(t)>0&&(s=[parseFloat(i),t])}),s}})}(),function(){var t={};e.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,i){t[i]=function(t){return Math.pow(t,e+2)}}),e.extend(t,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return 0===e||1===e?e:-Math.pow(2,8*(e-1))*Math.sin((80*(e-1)-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(e){for(var t,i=4;((t=Math.pow(2,--i))-1)/11>e;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*t-2)/22-e,2)}}),e.each(t,function(t,i){e.easing["easeIn"+t]=i,e.easing["easeOut"+t]=function(e){return 1-i(1-e)},e.easing["easeInOut"+t]=function(e){return.5>e?i(2*e)/2:1-i(-2*e+2)/2}})}(),e.effects,e.effects.effect.blind=function(t,i){var s,n,a,o=e(this),r=/up|down|vertical/,h=/up|left|vertical|horizontal/,l=["position","top","bottom","left","right","height","width"],u=e.effects.setMode(o,t.mode||"hide"),d=t.direction||"up",c=r.test(d),p=c?"height":"width",f=c?"top":"left",m=h.test(d),g={},v="show"===u;o.parent().is(".ui-effects-wrapper")?e.effects.save(o.parent(),l):e.effects.save(o,l),o.show(),s=e.effects.createWrapper(o).css({overflow:"hidden"}),n=s[p](),a=parseFloat(s.css(f))||0,g[p]=v?n:0,m||(o.css(c?"bottom":"right",0).css(c?"top":"left","auto").css({position:"absolute"}),g[f]=v?a:n+a),v&&(s.css(p,0),m||s.css(f,a+n)),s.animate(g,{duration:t.duration,easing:t.easing,queue:!1,complete:function(){"hide"===u&&o.hide(),e.effects.restore(o,l),e.effects.removeWrapper(o),i()}})},e.effects.effect.bounce=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","height","width"],h=e.effects.setMode(o,t.mode||"effect"),l="hide"===h,u="show"===h,d=t.direction||"up",c=t.distance,p=t.times||5,f=2*p+(u||l?1:0),m=t.duration/f,g=t.easing,v="up"===d||"down"===d?"top":"left",y="up"===d||"left"===d,b=o.queue(),_=b.length;for((u||l)&&r.push("opacity"),e.effects.save(o,r),o.show(),e.effects.createWrapper(o),c||(c=o["top"===v?"outerHeight":"outerWidth"]()/3),u&&(a={opacity:1},a[v]=0,o.css("opacity",0).css(v,y?2*-c:2*c).animate(a,m,g)),l&&(c/=Math.pow(2,p-1)),a={},a[v]=0,s=0;p>s;s++)n={},n[v]=(y?"-=":"+=")+c,o.animate(n,m,g).animate(a,m,g),c=l?2*c:c/2;l&&(n={opacity:0},n[v]=(y?"-=":"+=")+c,o.animate(n,m,g)),o.queue(function(){l&&o.hide(),e.effects.restore(o,r),e.effects.removeWrapper(o),i()}),_>1&&b.splice.apply(b,[1,0].concat(b.splice(_,f+1))),o.dequeue()},e.effects.effect.clip=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","height","width"],h=e.effects.setMode(o,t.mode||"hide"),l="show"===h,u=t.direction||"vertical",d="vertical"===u,c=d?"height":"width",p=d?"top":"left",f={};e.effects.save(o,r),o.show(),s=e.effects.createWrapper(o).css({overflow:"hidden"}),n="IMG"===o[0].tagName?s:o,a=n[c](),l&&(n.css(c,0),n.css(p,a/2)),f[c]=l?a:0,f[p]=l?0:a/2,n.animate(f,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){l||o.hide(),e.effects.restore(o,r),e.effects.removeWrapper(o),i()}})},e.effects.effect.drop=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","opacity","height","width"],o=e.effects.setMode(n,t.mode||"hide"),r="show"===o,h=t.direction||"left",l="up"===h||"down"===h?"top":"left",u="up"===h||"left"===h?"pos":"neg",d={opacity:r?1:0};e.effects.save(n,a),n.show(),e.effects.createWrapper(n),s=t.distance||n["top"===l?"outerHeight":"outerWidth"](!0)/2,r&&n.css("opacity",0).css(l,"pos"===u?-s:s),d[l]=(r?"pos"===u?"+=":"-=":"pos"===u?"-=":"+=")+s,n.animate(d,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}})},e.effects.effect.explode=function(t,i){function s(){b.push(this),b.length===d*c&&n()}function n(){p.css({visibility:"visible"}),e(b).remove(),m||p.hide(),i()}var a,o,r,h,l,u,d=t.pieces?Math.round(Math.sqrt(t.pieces)):3,c=d,p=e(this),f=e.effects.setMode(p,t.mode||"hide"),m="show"===f,g=p.show().css("visibility","hidden").offset(),v=Math.ceil(p.outerWidth()/c),y=Math.ceil(p.outerHeight()/d),b=[];for(a=0;d>a;a++)for(h=g.top+a*y,u=a-(d-1)/2,o=0;c>o;o++)r=g.left+o*v,l=o-(c-1)/2,p.clone().appendTo("body").wrap("
        ").css({position:"absolute",visibility:"visible",left:-o*v,top:-a*y}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:v,height:y,left:r+(m?l*v:0),top:h+(m?u*y:0),opacity:m?0:1}).animate({left:r+(m?0:l*v),top:h+(m?0:u*y),opacity:m?1:0},t.duration||500,t.easing,s)},e.effects.effect.fade=function(t,i){var s=e(this),n=e.effects.setMode(s,t.mode||"toggle");s.animate({opacity:n},{queue:!1,duration:t.duration,easing:t.easing,complete:i})},e.effects.effect.fold=function(t,i){var s,n,a=e(this),o=["position","top","bottom","left","right","height","width"],r=e.effects.setMode(a,t.mode||"hide"),h="show"===r,l="hide"===r,u=t.size||15,d=/([0-9]+)%/.exec(u),c=!!t.horizFirst,p=h!==c,f=p?["width","height"]:["height","width"],m=t.duration/2,g={},v={};e.effects.save(a,o),a.show(),s=e.effects.createWrapper(a).css({overflow:"hidden"}),n=p?[s.width(),s.height()]:[s.height(),s.width()],d&&(u=parseInt(d[1],10)/100*n[l?0:1]),h&&s.css(c?{height:0,width:u}:{height:u,width:0}),g[f[0]]=h?n[0]:u,v[f[1]]=h?n[1]:0,s.animate(g,m,t.easing).animate(v,m,t.easing,function(){l&&a.hide(),e.effects.restore(a,o),e.effects.removeWrapper(a),i()})},e.effects.effect.highlight=function(t,i){var s=e(this),n=["backgroundImage","backgroundColor","opacity"],a=e.effects.setMode(s,t.mode||"show"),o={backgroundColor:s.css("backgroundColor")};"hide"===a&&(o.opacity=0),e.effects.save(s,n),s.show().css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(o,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===a&&s.hide(),e.effects.restore(s,n),i()}})},e.effects.effect.size=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","width","height","overflow","opacity"],h=["position","top","bottom","left","right","overflow","opacity"],l=["width","height","overflow"],u=["fontSize"],d=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],c=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=e.effects.setMode(o,t.mode||"effect"),f=t.restore||"effect"!==p,m=t.scale||"both",g=t.origin||["middle","center"],v=o.css("position"),y=f?r:h,b={height:0,width:0,outerHeight:0,outerWidth:0};"show"===p&&o.show(),s={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},"toggle"===t.mode&&"show"===p?(o.from=t.to||b,o.to=t.from||s):(o.from=t.from||("show"===p?b:s),o.to=t.to||("hide"===p?b:s)),a={from:{y:o.from.height/s.height,x:o.from.width/s.width},to:{y:o.to.height/s.height,x:o.to.width/s.width}},("box"===m||"both"===m)&&(a.from.y!==a.to.y&&(y=y.concat(d),o.from=e.effects.setTransition(o,d,a.from.y,o.from),o.to=e.effects.setTransition(o,d,a.to.y,o.to)),a.from.x!==a.to.x&&(y=y.concat(c),o.from=e.effects.setTransition(o,c,a.from.x,o.from),o.to=e.effects.setTransition(o,c,a.to.x,o.to))),("content"===m||"both"===m)&&a.from.y!==a.to.y&&(y=y.concat(u).concat(l),o.from=e.effects.setTransition(o,u,a.from.y,o.from),o.to=e.effects.setTransition(o,u,a.to.y,o.to)),e.effects.save(o,y),o.show(),e.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),g&&(n=e.effects.getBaseline(g,s),o.from.top=(s.outerHeight-o.outerHeight())*n.y,o.from.left=(s.outerWidth-o.outerWidth())*n.x,o.to.top=(s.outerHeight-o.to.outerHeight)*n.y,o.to.left=(s.outerWidth-o.to.outerWidth)*n.x),o.css(o.from),("content"===m||"both"===m)&&(d=d.concat(["marginTop","marginBottom"]).concat(u),c=c.concat(["marginLeft","marginRight"]),l=r.concat(d).concat(c),o.find("*[width]").each(function(){var i=e(this),s={height:i.height(),width:i.width(),outerHeight:i.outerHeight(),outerWidth:i.outerWidth()}; +f&&e.effects.save(i,l),i.from={height:s.height*a.from.y,width:s.width*a.from.x,outerHeight:s.outerHeight*a.from.y,outerWidth:s.outerWidth*a.from.x},i.to={height:s.height*a.to.y,width:s.width*a.to.x,outerHeight:s.height*a.to.y,outerWidth:s.width*a.to.x},a.from.y!==a.to.y&&(i.from=e.effects.setTransition(i,d,a.from.y,i.from),i.to=e.effects.setTransition(i,d,a.to.y,i.to)),a.from.x!==a.to.x&&(i.from=e.effects.setTransition(i,c,a.from.x,i.from),i.to=e.effects.setTransition(i,c,a.to.x,i.to)),i.css(i.from),i.animate(i.to,t.duration,t.easing,function(){f&&e.effects.restore(i,l)})})),o.animate(o.to,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){0===o.to.opacity&&o.css("opacity",o.from.opacity),"hide"===p&&o.hide(),e.effects.restore(o,y),f||("static"===v?o.css({position:"relative",top:o.to.top,left:o.to.left}):e.each(["top","left"],function(e,t){o.css(t,function(t,i){var s=parseInt(i,10),n=e?o.to.left:o.to.top;return"auto"===i?n+"px":s+n+"px"})})),e.effects.removeWrapper(o),i()}})},e.effects.effect.scale=function(t,i){var s=e(this),n=e.extend(!0,{},t),a=e.effects.setMode(s,t.mode||"effect"),o=parseInt(t.percent,10)||(0===parseInt(t.percent,10)?0:"hide"===a?0:100),r=t.direction||"both",h=t.origin,l={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()},u={y:"horizontal"!==r?o/100:1,x:"vertical"!==r?o/100:1};n.effect="size",n.queue=!1,n.complete=i,"effect"!==a&&(n.origin=h||["middle","center"],n.restore=!0),n.from=t.from||("show"===a?{height:0,width:0,outerHeight:0,outerWidth:0}:l),n.to={height:l.height*u.y,width:l.width*u.x,outerHeight:l.outerHeight*u.y,outerWidth:l.outerWidth*u.x},n.fade&&("show"===a&&(n.from.opacity=0,n.to.opacity=1),"hide"===a&&(n.from.opacity=1,n.to.opacity=0)),s.effect(n)},e.effects.effect.puff=function(t,i){var s=e(this),n=e.effects.setMode(s,t.mode||"hide"),a="hide"===n,o=parseInt(t.percent,10)||150,r=o/100,h={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()};e.extend(t,{effect:"scale",queue:!1,fade:!0,mode:n,complete:i,percent:a?o:100,from:a?h:{height:h.height*r,width:h.width*r,outerHeight:h.outerHeight*r,outerWidth:h.outerWidth*r}}),s.effect(t)},e.effects.effect.pulsate=function(t,i){var s,n=e(this),a=e.effects.setMode(n,t.mode||"show"),o="show"===a,r="hide"===a,h=o||"hide"===a,l=2*(t.times||5)+(h?1:0),u=t.duration/l,d=0,c=n.queue(),p=c.length;for((o||!n.is(":visible"))&&(n.css("opacity",0).show(),d=1),s=1;l>s;s++)n.animate({opacity:d},u,t.easing),d=1-d;n.animate({opacity:d},u,t.easing),n.queue(function(){r&&n.hide(),i()}),p>1&&c.splice.apply(c,[1,0].concat(c.splice(p,l+1))),n.dequeue()},e.effects.effect.shake=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","height","width"],o=e.effects.setMode(n,t.mode||"effect"),r=t.direction||"left",h=t.distance||20,l=t.times||3,u=2*l+1,d=Math.round(t.duration/u),c="up"===r||"down"===r?"top":"left",p="up"===r||"left"===r,f={},m={},g={},v=n.queue(),y=v.length;for(e.effects.save(n,a),n.show(),e.effects.createWrapper(n),f[c]=(p?"-=":"+=")+h,m[c]=(p?"+=":"-=")+2*h,g[c]=(p?"-=":"+=")+2*h,n.animate(f,d,t.easing),s=1;l>s;s++)n.animate(m,d,t.easing).animate(g,d,t.easing);n.animate(m,d,t.easing).animate(f,d/2,t.easing).queue(function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}),y>1&&v.splice.apply(v,[1,0].concat(v.splice(y,u+1))),n.dequeue()},e.effects.effect.slide=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","width","height"],o=e.effects.setMode(n,t.mode||"show"),r="show"===o,h=t.direction||"left",l="up"===h||"down"===h?"top":"left",u="up"===h||"left"===h,d={};e.effects.save(n,a),n.show(),s=t.distance||n["top"===l?"outerHeight":"outerWidth"](!0),e.effects.createWrapper(n).css({overflow:"hidden"}),r&&n.css(l,u?isNaN(s)?"-"+s:-s:s),d[l]=(r?u?"+=":"-=":u?"-=":"+=")+s,n.animate(d,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}})},e.effects.effect.transfer=function(t,i){var s=e(this),n=e(t.to),a="fixed"===n.css("position"),o=e("body"),r=a?o.scrollTop():0,h=a?o.scrollLeft():0,l=n.offset(),u={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},d=s.offset(),c=e("
        ").appendTo(document.body).addClass(t.className).css({top:d.top-r,left:d.left-h,height:s.innerHeight(),width:s.innerWidth(),position:a?"fixed":"absolute"}).animate(u,t.duration,t.easing,function(){c.remove(),i()})},e.widget("ui.progressbar",{version:"1.11.4",options:{max:100,value:0,change:null,complete:null},min:0,_create:function(){this.oldValue=this.options.value=this._constrainedValue(),this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min}),this.valueDiv=e("
        ").appendTo(this.element),this._refreshValue()},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove()},value:function(e){return void 0===e?this.options.value:(this.options.value=this._constrainedValue(e),this._refreshValue(),void 0)},_constrainedValue:function(e){return void 0===e&&(e=this.options.value),this.indeterminate=e===!1,"number"!=typeof e&&(e=0),this.indeterminate?!1:Math.min(this.options.max,Math.max(this.min,e))},_setOptions:function(e){var t=e.value;delete e.value,this._super(e),this.options.value=this._constrainedValue(t),this._refreshValue()},_setOption:function(e,t){"max"===e&&(t=Math.max(this.min,t)),"disabled"===e&&this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this._super(e,t)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var t=this.options.value,i=this._percentage();this.valueDiv.toggle(this.indeterminate||t>this.min).toggleClass("ui-corner-right",t===this.options.max).width(i.toFixed(0)+"%"),this.element.toggleClass("ui-progressbar-indeterminate",this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=e("
        ").appendTo(this.valueDiv))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":t}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==t&&(this.oldValue=t,this._trigger("change")),t===this.options.max&&this._trigger("complete")}}),e.widget("ui.selectable",e.ui.mouse,{version:"1.11.4",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var t,i=this;this.element.addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){t=e(i.options.filter,i.element[0]),t.addClass("ui-selectee"),t.each(function(){var t=e(this),i=t.offset();e.data(this,"selectable-item",{element:this,$element:t,left:i.left,top:i.top,right:i.left+t.outerWidth(),bottom:i.top+t.outerHeight(),startselected:!1,selected:t.hasClass("ui-selected"),selecting:t.hasClass("ui-selecting"),unselecting:t.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=t.addClass("ui-selectee"),this._mouseInit(),this.helper=e("
        ")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(t){var i=this,s=this.options;this.opos=[t.pageX,t.pageY],this.options.disabled||(this.selectees=e(s.filter,this.element[0]),this._trigger("start",t),e(s.appendTo).append(this.helper),this.helper.css({left:t.pageX,top:t.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=e.data(this,"selectable-item");s.startselected=!0,t.metaKey||t.ctrlKey||(s.$element.removeClass("ui-selected"),s.selected=!1,s.$element.addClass("ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",t,{unselecting:s.element}))}),e(t.target).parents().addBack().each(function(){var s,n=e.data(this,"selectable-item");return n?(s=!t.metaKey&&!t.ctrlKey||!n.$element.hasClass("ui-selected"),n.$element.removeClass(s?"ui-unselecting":"ui-selected").addClass(s?"ui-selecting":"ui-unselecting"),n.unselecting=!s,n.selecting=s,n.selected=s,s?i._trigger("selecting",t,{selecting:n.element}):i._trigger("unselecting",t,{unselecting:n.element}),!1):void 0}))},_mouseDrag:function(t){if(this.dragged=!0,!this.options.disabled){var i,s=this,n=this.options,a=this.opos[0],o=this.opos[1],r=t.pageX,h=t.pageY;return a>r&&(i=r,r=a,a=i),o>h&&(i=h,h=o,o=i),this.helper.css({left:a,top:o,width:r-a,height:h-o}),this.selectees.each(function(){var i=e.data(this,"selectable-item"),l=!1;i&&i.element!==s.element[0]&&("touch"===n.tolerance?l=!(i.left>r||a>i.right||i.top>h||o>i.bottom):"fit"===n.tolerance&&(l=i.left>a&&r>i.right&&i.top>o&&h>i.bottom),l?(i.selected&&(i.$element.removeClass("ui-selected"),i.selected=!1),i.unselecting&&(i.$element.removeClass("ui-unselecting"),i.unselecting=!1),i.selecting||(i.$element.addClass("ui-selecting"),i.selecting=!0,s._trigger("selecting",t,{selecting:i.element}))):(i.selecting&&((t.metaKey||t.ctrlKey)&&i.startselected?(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.$element.addClass("ui-selected"),i.selected=!0):(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.startselected&&(i.$element.addClass("ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",t,{unselecting:i.element}))),i.selected&&(t.metaKey||t.ctrlKey||i.startselected||(i.$element.removeClass("ui-selected"),i.selected=!1,i.$element.addClass("ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",t,{unselecting:i.element})))))}),!1}},_mouseStop:function(t){var i=this;return this.dragged=!1,e(".ui-unselecting",this.element[0]).each(function(){var s=e.data(this,"selectable-item");s.$element.removeClass("ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",t,{unselected:s.element})}),e(".ui-selecting",this.element[0]).each(function(){var s=e.data(this,"selectable-item");s.$element.removeClass("ui-selecting").addClass("ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",t,{selected:s.element})}),this._trigger("stop",t),this.helper.remove(),!1}}),e.widget("ui.selectmenu",{version:"1.11.4",defaultElement:"",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var t={},i=this.element;return e.each(["min","max","step"],function(e,s){var n=i.attr(s);void 0!==n&&n.length&&(t[s]=n)}),t},_events:{keydown:function(e){this._start(e)&&this._keydown(e)&&e.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",e),void 0)},mousewheel:function(e,t){if(t){if(!this.spinning&&!this._start(e))return!1;this._spin((t>0?1:-1)*this.options.step,e),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(e)},100),e.preventDefault()}},"mousedown .ui-spinner-button":function(t){function i(){var e=this.element[0]===this.document[0].activeElement;e||(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),t.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(t)!==!1&&this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(t){return e(t.currentTarget).hasClass("ui-state-active")?this._start(t)===!1?!1:(this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var e=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=e.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(.5*e.height())&&e.height()>0&&e.height(e.height()),this.options.disabled&&this.disable()},_keydown:function(t){var i=this.options,s=e.ui.keyCode;switch(t.keyCode){case s.UP:return this._repeat(null,1,t),!0;case s.DOWN:return this._repeat(null,-1,t),!0;case s.PAGE_UP:return this._repeat(null,i.page,t),!0;case s.PAGE_DOWN:return this._repeat(null,-i.page,t),!0}return!1},_uiSpinnerHtml:function(){return""},_buttonHtml:function(){return""+""+""+""+""},_start:function(e){return this.spinning||this._trigger("start",e)!==!1?(this.counter||(this.counter=1),this.spinning=!0,!0):!1},_repeat:function(e,t,i){e=e||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,t,i)},e),this._spin(t*this.options.step,i)},_spin:function(e,t){var i=this.value()||0;this.counter||(this.counter=1),i=this._adjustValue(i+e*this._increment(this.counter)),this.spinning&&this._trigger("spin",t,{value:i})===!1||(this._value(i),this.counter++)},_increment:function(t){var i=this.options.incremental;return i?e.isFunction(i)?i(t):Math.floor(t*t*t/5e4-t*t/500+17*t/200+1):1},_precision:function(){var e=this._precisionOf(this.options.step);return null!==this.options.min&&(e=Math.max(e,this._precisionOf(this.options.min))),e},_precisionOf:function(e){var t=""+e,i=t.indexOf(".");return-1===i?0:t.length-i-1},_adjustValue:function(e){var t,i,s=this.options;return t=null!==s.min?s.min:0,i=e-t,i=Math.round(i/s.step)*s.step,e=t+i,e=parseFloat(e.toFixed(this._precision())),null!==s.max&&e>s.max?s.max:null!==s.min&&s.min>e?s.min:e},_stop:function(e){this.spinning&&(clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",e))},_setOption:function(e,t){if("culture"===e||"numberFormat"===e){var i=this._parse(this.element.val());return this.options[e]=t,this.element.val(this._format(i)),void 0}("max"===e||"min"===e||"step"===e)&&"string"==typeof t&&(t=this._parse(t)),"icons"===e&&(this.buttons.first().find(".ui-icon").removeClass(this.options.icons.up).addClass(t.up),this.buttons.last().find(".ui-icon").removeClass(this.options.icons.down).addClass(t.down)),this._super(e,t),"disabled"===e&&(this.widget().toggleClass("ui-state-disabled",!!t),this.element.prop("disabled",!!t),this.buttons.button(t?"disable":"enable"))},_setOptions:h(function(e){this._super(e)}),_parse:function(e){return"string"==typeof e&&""!==e&&(e=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(e,10,this.options.culture):+e),""===e||isNaN(e)?null:e},_format:function(e){return""===e?"":window.Globalize&&this.options.numberFormat?Globalize.format(e,this.options.numberFormat,this.options.culture):e},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},isValid:function(){var e=this.value();return null===e?!1:e===this._adjustValue(e)},_value:function(e,t){var i;""!==e&&(i=this._parse(e),null!==i&&(t||(i=this._adjustValue(i)),e=this._format(i))),this.element.val(e),this._refresh()},_destroy:function(){this.element.removeClass("ui-spinner-input").prop("disabled",!1).removeAttr("autocomplete").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:h(function(e){this._stepUp(e)}),_stepUp:function(e){this._start()&&(this._spin((e||1)*this.options.step),this._stop())},stepDown:h(function(e){this._stepDown(e)}),_stepDown:function(e){this._start()&&(this._spin((e||1)*-this.options.step),this._stop())},pageUp:h(function(e){this._stepUp((e||1)*this.options.page)}),pageDown:h(function(e){this._stepDown((e||1)*this.options.page)}),value:function(e){return arguments.length?(h(this._value).call(this,e),void 0):this._parse(this.element.val())},widget:function(){return this.uiSpinner}}),e.widget("ui.tabs",{version:"1.11.4",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_isLocal:function(){var e=/#.*$/;return function(t){var i,s;t=t.cloneNode(!1),i=t.href.replace(e,""),s=location.href.replace(e,"");try{i=decodeURIComponent(i)}catch(n){}try{s=decodeURIComponent(s)}catch(n){}return t.hash.length>1&&i===s}}(),_create:function(){var t=this,i=this.options;this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",i.collapsible),this._processTabs(),i.active=this._initialActive(),e.isArray(i.disabled)&&(i.disabled=e.unique(i.disabled.concat(e.map(this.tabs.filter(".ui-state-disabled"),function(e){return t.tabs.index(e)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):e(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var t=this.options.active,i=this.options.collapsible,s=location.hash.substring(1);return null===t&&(s&&this.tabs.each(function(i,n){return e(n).attr("aria-controls")===s?(t=i,!1):void 0}),null===t&&(t=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===t||-1===t)&&(t=this.tabs.length?0:!1)),t!==!1&&(t=this.tabs.index(this.tabs.eq(t)),-1===t&&(t=i?!1:0)),!i&&t===!1&&this.anchors.length&&(t=0),t},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):e()}},_tabKeydown:function(t){var i=e(this.document[0].activeElement).closest("li"),s=this.tabs.index(i),n=!0;if(!this._handlePageNav(t)){switch(t.keyCode){case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:s++;break;case e.ui.keyCode.UP:case e.ui.keyCode.LEFT:n=!1,s--;break;case e.ui.keyCode.END:s=this.anchors.length-1;break;case e.ui.keyCode.HOME:s=0;break;case e.ui.keyCode.SPACE:return t.preventDefault(),clearTimeout(this.activating),this._activate(s),void 0;case e.ui.keyCode.ENTER:return t.preventDefault(),clearTimeout(this.activating),this._activate(s===this.options.active?!1:s),void 0;default:return}t.preventDefault(),clearTimeout(this.activating),s=this._focusNextTab(s,n),t.ctrlKey||t.metaKey||(i.attr("aria-selected","false"),this.tabs.eq(s).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",s)},this.delay))}},_panelKeydown:function(t){this._handlePageNav(t)||t.ctrlKey&&t.keyCode===e.ui.keyCode.UP&&(t.preventDefault(),this.active.focus())},_handlePageNav:function(t){return t.altKey&&t.keyCode===e.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):t.altKey&&t.keyCode===e.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):void 0},_findNextTab:function(t,i){function s(){return t>n&&(t=0),0>t&&(t=n),t}for(var n=this.tabs.length-1;-1!==e.inArray(s(),this.options.disabled);)t=i?t+1:t-1;return t},_focusNextTab:function(e,t){return e=this._findNextTab(e,t),this.tabs.eq(e).focus(),e},_setOption:function(e,t){return"active"===e?(this._activate(t),void 0):"disabled"===e?(this._setupDisabled(t),void 0):(this._super(e,t),"collapsible"===e&&(this.element.toggleClass("ui-tabs-collapsible",t),t||this.options.active!==!1||this._activate(0)),"event"===e&&this._setupEvents(t),"heightStyle"===e&&this._setupHeightStyle(t),void 0)},_sanitizeSelector:function(e){return e?e.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var t=this.options,i=this.tablist.children(":has(a[href])");t.disabled=e.map(i.filter(".ui-state-disabled"),function(e){return i.index(e)}),this._processTabs(),t.active!==!1&&this.anchors.length?this.active.length&&!e.contains(this.tablist[0],this.active[0])?this.tabs.length===t.disabled.length?(t.active=!1,this.active=e()):this._activate(this._findNextTab(Math.max(0,t.active-1),!1)):t.active=this.tabs.index(this.active):(t.active=!1,this.active=e()),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var t=this,i=this.tabs,s=this.anchors,n=this.panels; +this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist").delegate("> li","mousedown"+this.eventNamespace,function(t){e(this).is(".ui-state-disabled")&&t.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){e(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return e("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=e(),this.anchors.each(function(i,s){var n,a,o,r=e(s).uniqueId().attr("id"),h=e(s).closest("li"),l=h.attr("aria-controls");t._isLocal(s)?(n=s.hash,o=n.substring(1),a=t.element.find(t._sanitizeSelector(n))):(o=h.attr("aria-controls")||e({}).uniqueId()[0].id,n="#"+o,a=t.element.find(n),a.length||(a=t._createPanel(o),a.insertAfter(t.panels[i-1]||t.tablist)),a.attr("aria-live","polite")),a.length&&(t.panels=t.panels.add(a)),l&&h.data("ui-tabs-aria-controls",l),h.attr({"aria-controls":o,"aria-labelledby":r}),a.attr("aria-labelledby",r)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel"),i&&(this._off(i.not(this.tabs)),this._off(s.not(this.anchors)),this._off(n.not(this.panels)))},_getList:function(){return this.tablist||this.element.find("ol,ul").eq(0)},_createPanel:function(t){return e("
        ").attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(t){e.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1);for(var i,s=0;i=this.tabs[s];s++)t===!0||-1!==e.inArray(s,t)?e(i).addClass("ui-state-disabled").attr("aria-disabled","true"):e(i).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=t},_setupEvents:function(t){var i={};t&&e.each(t.split(" "),function(e,t){i[t]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(!0,this.anchors,{click:function(e){e.preventDefault()}}),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var i,s=this.element.parent();"fill"===t?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var t=e(this),s=t.css("position");"absolute"!==s&&"fixed"!==s&&(i-=t.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=e(this).outerHeight(!0)}),this.panels.each(function(){e(this).height(Math.max(0,i-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===t&&(i=0,this.panels.each(function(){i=Math.max(i,e(this).height("").height())}).height(i))},_eventHandler:function(t){var i=this.options,s=this.active,n=e(t.currentTarget),a=n.closest("li"),o=a[0]===s[0],r=o&&i.collapsible,h=r?e():this._getPanelForTab(a),l=s.length?this._getPanelForTab(s):e(),u={oldTab:s,oldPanel:l,newTab:r?e():a,newPanel:h};t.preventDefault(),a.hasClass("ui-state-disabled")||a.hasClass("ui-tabs-loading")||this.running||o&&!i.collapsible||this._trigger("beforeActivate",t,u)===!1||(i.active=r?!1:this.tabs.index(a),this.active=o?e():a,this.xhr&&this.xhr.abort(),l.length||h.length||e.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(a),t),this._toggle(t,u))},_toggle:function(t,i){function s(){a.running=!1,a._trigger("activate",t,i)}function n(){i.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),o.length&&a.options.show?a._show(o,a.options.show,s):(o.show(),s())}var a=this,o=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),n()}):(i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),r.hide(),n()),r.attr("aria-hidden","true"),i.oldTab.attr({"aria-selected":"false","aria-expanded":"false"}),o.length&&r.length?i.oldTab.attr("tabIndex",-1):o.length&&this.tabs.filter(function(){return 0===e(this).attr("tabIndex")}).attr("tabIndex",-1),o.attr("aria-hidden","false"),i.newTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_activate:function(t){var i,s=this._findActive(t);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:e.noop}))},_findActive:function(t){return t===!1?e():this.tabs.eq(t)},_getIndex:function(e){return"string"==typeof e&&(e=this.anchors.index(this.anchors.filter("[href$='"+e+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeUniqueId(),this.tablist.unbind(this.eventNamespace),this.tabs.add(this.panels).each(function(){e.data(this,"ui-tabs-destroy")?e(this).remove():e(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var t=e(this),i=t.data("ui-tabs-aria-controls");i?t.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):t.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(t){var i=this.options.disabled;i!==!1&&(void 0===t?i=!1:(t=this._getIndex(t),i=e.isArray(i)?e.map(i,function(e){return e!==t?e:null}):e.map(this.tabs,function(e,i){return i!==t?i:null})),this._setupDisabled(i))},disable:function(t){var i=this.options.disabled;if(i!==!0){if(void 0===t)i=!0;else{if(t=this._getIndex(t),-1!==e.inArray(t,i))return;i=e.isArray(i)?e.merge([t],i).sort():[t]}this._setupDisabled(i)}},load:function(t,i){t=this._getIndex(t);var s=this,n=this.tabs.eq(t),a=n.find(".ui-tabs-anchor"),o=this._getPanelForTab(n),r={tab:n,panel:o},h=function(e,t){"abort"===t&&s.panels.stop(!1,!0),n.removeClass("ui-tabs-loading"),o.removeAttr("aria-busy"),e===s.xhr&&delete s.xhr};this._isLocal(a[0])||(this.xhr=e.ajax(this._ajaxSettings(a,i,r)),this.xhr&&"canceled"!==this.xhr.statusText&&(n.addClass("ui-tabs-loading"),o.attr("aria-busy","true"),this.xhr.done(function(e,t,n){setTimeout(function(){o.html(e),s._trigger("load",i,r),h(n,t)},1)}).fail(function(e,t){setTimeout(function(){h(e,t)},1)})))},_ajaxSettings:function(t,i,s){var n=this;return{url:t.attr("href"),beforeSend:function(t,a){return n._trigger("beforeLoad",i,e.extend({jqXHR:t,ajaxSettings:a},s))}}},_getPanelForTab:function(t){var i=e(t).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}}),e.widget("ui.tooltip",{version:"1.11.4",options:{content:function(){var t=e(this).attr("title")||"";return e("").text(t).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_addDescribedBy:function(t,i){var s=(t.attr("aria-describedby")||"").split(/\s+/);s.push(i),t.data("ui-tooltip-id",i).attr("aria-describedby",e.trim(s.join(" ")))},_removeDescribedBy:function(t){var i=t.data("ui-tooltip-id"),s=(t.attr("aria-describedby")||"").split(/\s+/),n=e.inArray(i,s);-1!==n&&s.splice(n,1),t.removeData("ui-tooltip-id"),s=e.trim(s.join(" ")),s?t.attr("aria-describedby",s):t.removeAttr("aria-describedby")},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable(),this.liveRegion=e("
        ").attr({role:"log","aria-live":"assertive","aria-relevant":"additions"}).addClass("ui-helper-hidden-accessible").appendTo(this.document[0].body)},_setOption:function(t,i){var s=this;return"disabled"===t?(this[i?"_disable":"_enable"](),this.options[t]=i,void 0):(this._super(t,i),"content"===t&&e.each(this.tooltips,function(e,t){s._updateContent(t.element)}),void 0)},_disable:function(){var t=this;e.each(this.tooltips,function(i,s){var n=e.Event("blur");n.target=n.currentTarget=s.element[0],t.close(n,!0)}),this.element.find(this.options.items).addBack().each(function(){var t=e(this);t.is("[title]")&&t.data("ui-tooltip-title",t.attr("title")).removeAttr("title")})},_enable:function(){this.element.find(this.options.items).addBack().each(function(){var t=e(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))})},open:function(t){var i=this,s=e(t?t.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),t&&"mouseover"===t.type&&s.parents().each(function(){var t,s=e(this);s.data("ui-tooltip-open")&&(t=e.Event("blur"),t.target=t.currentTarget=this,i.close(t,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._registerCloseHandlers(t,s),this._updateContent(s,t))},_updateContent:function(e,t){var i,s=this.options.content,n=this,a=t?t.type:null;return"string"==typeof s?this._open(t,e,s):(i=s.call(e[0],function(i){n._delay(function(){e.data("ui-tooltip-open")&&(t&&(t.type=a),this._open(t,e,i))})}),i&&this._open(t,e,i),void 0)},_open:function(t,i,s){function n(e){l.of=e,o.is(":hidden")||o.position(l)}var a,o,r,h,l=e.extend({},this.options.position);if(s){if(a=this._find(i))return a.tooltip.find(".ui-tooltip-content").html(s),void 0;i.is("[title]")&&(t&&"mouseover"===t.type?i.attr("title",""):i.removeAttr("title")),a=this._tooltip(i),o=a.tooltip,this._addDescribedBy(i,o.attr("id")),o.find(".ui-tooltip-content").html(s),this.liveRegion.children().hide(),s.clone?(h=s.clone(),h.removeAttr("id").find("[id]").removeAttr("id")):h=s,e("
        ").html(h).appendTo(this.liveRegion),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:n}),n(t)):o.position(e.extend({of:i},this.options.position)),o.hide(),this._show(o,this.options.show),this.options.show&&this.options.show.delay&&(r=this.delayedShow=setInterval(function(){o.is(":visible")&&(n(l.of),clearInterval(r))},e.fx.interval)),this._trigger("open",t,{tooltip:o})}},_registerCloseHandlers:function(t,i){var s={keyup:function(t){if(t.keyCode===e.ui.keyCode.ESCAPE){var s=e.Event(t);s.currentTarget=i[0],this.close(s,!0)}}};i[0]!==this.element[0]&&(s.remove=function(){this._removeTooltip(this._find(i).tooltip)}),t&&"mouseover"!==t.type||(s.mouseleave="close"),t&&"focusin"!==t.type||(s.focusout="close"),this._on(!0,i,s)},close:function(t){var i,s=this,n=e(t?t.currentTarget:this.element),a=this._find(n);return a?(i=a.tooltip,a.closing||(clearInterval(this.delayedShow),n.data("ui-tooltip-title")&&!n.attr("title")&&n.attr("title",n.data("ui-tooltip-title")),this._removeDescribedBy(n),a.hiding=!0,i.stop(!0),this._hide(i,this.options.hide,function(){s._removeTooltip(e(this))}),n.removeData("ui-tooltip-open"),this._off(n,"mouseleave focusout keyup"),n[0]!==this.element[0]&&this._off(n,"remove"),this._off(this.document,"mousemove"),t&&"mouseleave"===t.type&&e.each(this.parents,function(t,i){e(i.element).attr("title",i.title),delete s.parents[t]}),a.closing=!0,this._trigger("close",t,{tooltip:i}),a.hiding||(a.closing=!1)),void 0):(n.removeData("ui-tooltip-open"),void 0)},_tooltip:function(t){var i=e("
        ").attr("role","tooltip").addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||"")),s=i.uniqueId().attr("id");return e("
        ").addClass("ui-tooltip-content").appendTo(i),i.appendTo(this.document[0].body),this.tooltips[s]={element:t,tooltip:i}},_find:function(e){var t=e.data("ui-tooltip-id");return t?this.tooltips[t]:null},_removeTooltip:function(e){e.remove(),delete this.tooltips[e.attr("id")]},_destroy:function(){var t=this;e.each(this.tooltips,function(i,s){var n=e.Event("blur"),a=s.element;n.target=n.currentTarget=a[0],t.close(n,!0),e("#"+i).remove(),a.data("ui-tooltip-title")&&(a.attr("title")||a.attr("title",a.data("ui-tooltip-title")),a.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}})}); \ No newline at end of file From 87934e52dc41b72230e2711f5537d54dcfa1aa33 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 5 Aug 2016 19:28:46 +0200 Subject: [PATCH 162/413] obsoletes strange IsGuid and changes logic to use what .Net does instead of regex in case others are using this. --- src/Umbraco.Core/StringExtensions.cs | 29 +++++----------------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index b92df5f1cf..766e038588 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -666,31 +666,12 @@ namespace Umbraco.Core return compare.Contains(compareTo, StringComparer.InvariantCultureIgnoreCase); } - /// - /// Determines if the string is a Guid - /// - /// - /// - /// + [Obsolete("Use Guid.TryParse instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static bool IsGuid(this string str, bool withHyphens) { - var isGuid = false; - - if (!String.IsNullOrEmpty(str)) - { - Regex guidRegEx; - if (withHyphens) - { - guidRegEx = new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$"); - } - else - { - guidRegEx = new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}([0-9a-fA-F]){4}([0-9a-fA-F]){4}([0-9a-fA-F]){4}([0-9a-fA-F]){12}\}{0,1})$"); - } - isGuid = guidRegEx.IsMatch(str); - } - - return isGuid; + Guid g; + return Guid.TryParse(str, out g); } /// @@ -712,7 +693,7 @@ namespace Umbraco.Core /// public static object ParseInto(this string val, Type type) { - if (!String.IsNullOrEmpty(val)) + if (string.IsNullOrEmpty(val) == false) { TypeConverter tc = TypeDescriptor.GetConverter(type); return tc.ConvertFrom(val); From 809970bc2e2076c1ae8e53b5da37e7d8ca5f0447 Mon Sep 17 00:00:00 2001 From: Alexander Bryukhov Date: Sun, 7 Aug 2016 14:06:16 +0700 Subject: [PATCH 163/413] Update UI language ru-RU.xml New 'bulk' area and few more corrections --- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 237 ++++++++++++++---- 1 file changed, 187 insertions(+), 50 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 84b87b872f..856eb1c4aa 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -64,8 +64,37 @@ Наблюдать за + + Завершено + + Удален %0% элемент + Удалено %0% элементов + Удален %0% из %1% элементов + Удалено %0% из %1% элементов + + Опубликован %0% элемент + Опубликовано %0% элементов + Опубликован %0% из %1% элементов + Опубликовано %0% из %1% элементов + + Скрыт %0% элемент + Скрыто %0% элементов + Скрыт %0% из %1% элементов + Скрыто %0% из %1% элементов + + Перенесен %0% элемент + Перенесено %0% элементов + Перенесен %0% из %1% элементов + Перенесено %0% из %1% элементов + + Скопирован %0% элемент + Скопировано %0% элементов + Скопирован %0% из %1% элементов + Скопировано %0% из %1% элементов + Полужирный + Снять выбор Уменьшить отступ Вставить поле формы Сгенерировать модели @@ -91,9 +120,10 @@ Сохранить список Выбрать Выбрать текущую папку + выбранные Предварительный просмотр Предварительный просмотр запрещен, так как документу не сопоставлен шаблон - Сделать что-нибудь еще + Другие действия Выбрать стиль Показать стили Вставить таблицу @@ -145,6 +175,7 @@ Тип документа Редактирование Скрыть + ВНИМАНИЕ: невозможно получить URL документа (внутренняя ошибка - подробности в системном журнале) Опубликовано Этот документ был изменен после публикации Этот документ не опубликован @@ -162,11 +193,14 @@ Не является членом групп(ы) Свойства Этот документ опубликован, но скрыт, потому что его родительский документ '%0%' не опубликован - ВНИМАНИЕ: этот документ опубликован, но его нет в глобальном кэше (внутренняя ошибка) + ВНИМАНИЕ: этот документ опубликован, но его нет в глобальном кэше (внутренняя ошибка - подробности в системном журнале) Опубликовать Состояние публикации Опубликовать Очистить дату + ВНИМАНИЕ: этот документ опубликован, но его URL вступает в протиаоречнеие с документом %0% + Это время будет соответствовать следующему времени на сервере: + Что это означает?]]> Порядок сортировки обновлен Для сортировки узлов просто перетаскивайте узлы или нажмите на заголовке столбца. Вы можете выбрать несколько узлов, удерживая клавиши "shift" или "ctrl" при пометке Статистика @@ -185,7 +219,7 @@ Композиции Вы не добавили ни одной вкладки Добавить вкладку - Добавитьеще вкладку + Добавить новую вкладку Унаследован от Добавить свойство Обязательная метка @@ -215,8 +249,10 @@ ДА, удалить - была перемещена внутрь + перемещены внутрь + скопированы внутрь Выбрать папку для перемещения + Выбрать папку для копирования в структуре дерева Все типы документов @@ -240,10 +276,12 @@ Где вы хотите создать новый %0% Создать в узле + Новая папка + Новый тип данных "Типы документов".]]> "Типы медиа-материалов".]]> Выберите тип и заголовок - Типу документа не сопоставлен шаблон + Тип документа без сопоставленного шаблона Обзор сайта @@ -254,40 +292,6 @@ Посетить Рады приветствовать - - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes - - - Done - - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items - - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items - - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items - - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items - - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items - Название Управление доменами @@ -362,7 +366,7 @@ Провайдеры аутентификации Подробное сообщение об ошибке Трассировка стека - Inner Exception + Внутренняя ошибка Связать Разорвать связь @@ -560,7 +564,7 @@ Список Сохранение... текущий - Переместить + выбрано Внедрить @@ -608,6 +612,108 @@ Страница + + + Для параметра установлено рекомендованное значение: '%0%'. + Значение установлено в '%1%' для пути XPath '%2%' в файле конфигурации '%3%'. + Ожидаемое значение '%1%' для параметра '%2%' в файле конфигурации '%3%', найденное значение: '%0%'. + Найдено неожиданное значение '%0%' для параметра '%2%' в файле конфигурации '%3%'. + + + Параметр 'CustomErrors' установлен в '%0%'. + Параметр 'CustomErrors' сейчас установлен в '%0%'. Рекомендуется установить в '%1%' перед размещением сайта в сети. + Параметр 'CustomErrors' успешно установлен в '%0%'. + + Параметр 'MacroErrors' установлен в '%0%'. + Параметр 'MacroErrors' установлен в '%0%', что может привести к неполной обработке части страниц или всех страниц сайта при наличии ошибок в макросах. Устранить это можно путем установки значения в '%1%'. + Параметр 'MacroErrors' теперь установлен в '%0%'. + + + Параметр 'Try Skip IIS Custom Errors' установлен в '%0%' и Вы используете IIS версии '%1%'. + Параметр 'Try Skip IIS Custom Errors' сейчас установлен в '%0%'. Рекомендуется установить в '%1%' для Вашего текущего IIS версии (%2%). + Параметр 'Try Skip IIS Custom Errors' успешно установлен в '%0%'. + + + Файл не существует: '%0%'. + '%0%' в файле конфигурации '%1%'.]]> + Обнаружена ошибка, для получения полной информации обратитесь к журналу: %0%. + + Участники - всего в XML: %0%, всего: %1%, с ошибками: %2% + Медиа - всего в XML: %0%, всего: %1%Б с ошибками: %2% + Содержимое - всего в XML: %0%, всего опубликовано: %1%, с ошибками: %2% + + Ошибка проверки сертификата: '%0%' + Ошибка проверки адреса URL %0% - '%1%' + Сейчас Вы %0% просматриваете сайт, используя протокол HTTPS. + Параметр 'umbracoUseSSL' в секции 'appSetting' установлен в 'false' в файле web.config. Если Вам необходим доступ к сайту по протоколу HTTPS, нужно установить данный параметр в 'true'. + Параметр 'umbracoUseSSL' в секции 'appSetting' в файле установлен в '%0%', значения cookies %1% маркированы как безопасные. + Невозможно обновить значение параметра 'umbracoUseSSL' в файле web.config. Ошибка: %0% + + + Разрешить HTTPS + Устанавливает значение параметра 'umbracoSSL' в 'true' в секции 'appSettings' файла web.config. + Параметр 'umbracoUseSSL' в секции 'appSetting' файлf web.config теперь установлен в 'true', значения cookies будут промаркированы как безопасные. + + Исправить + Невозможно испраление по результату проверки значения на 'ShouldNotEqual'. + Невозможно исправление по результату проверки значения на 'ShouldEqual' с предоставленным значением. + Значение для исправления не предоставлено. + + Режим компиляции с отладкой выключен. + Режим компиляции с отладкой сейчас включен. Рекомендуется выключить перед размещением сайта в сети. + Режим компиляции с отладкой усешно выключен. + + Режим трассировки выключен. + Режим трассировки сейчас включен. Рекомендуется выключить перед размещением сайта в сети. + Режим трассировки успешно выключен. + + Все папки имеют корректно установленные параметры безопасности. + + %0%.]]> + %0%. Если в них не разрешена запись, не нужно предпринимать никаких действий.]]> + + Все файлы имеют корректно установленные параметры безопасности. + + %0%.]]> + %0%. Если в них не разрешена запись, не нужно предпринимать никаких действий.]]> + + X-Frame-Options, использующийся для управления возможностью помещать сайт в IFRAME на другом сайте.]]> + X-Frame-Options, использующийся для управления возможностью помещать сайт в IFRAME на другом сайте, не обнаружен.]]> + Установить заголовок в файле конфигурации + Добавляет значение в секцию 'httpProtocol/customHeaders' файла web.config, препятствующее возможному использованию этого сайта внутри IFRAME на другои сайте. + Значение, добавляющее заголовок, препятствующий использованию этого сайта внутри IFRAME другого сайта, успешно добавлено в файл web.config. + Невозможно обновить файл web.config. Ошибка: %0% + + + %0%.]]> + Заголовки, позволяющие выяснить базовую технологию сайта, не обнаружены. + + В файле Web.config, не обнаружено параметров работы с отправкой электронной почты (секция 'system.net/mailsettings'). + В файле Web.config в секции 'system.net/mailsettings' не обнаружены настройки почтового хоста. + Параметры отправки электронной почты (SMTP) настроены корректно, серсис работает как ожидается. + Сервер SMTP сконфигурирован на использование хоста '%0%' на порту '%1%', который в настоящее время недоступен. Пожалуйста, убедитесь, что настройки SMTP в файле Web.config в секции 'system.net/mailsettings' верны. + + %0%.]]> + %0%.]]> + перейти к Разделы справки для @@ -751,6 +857,17 @@ Понедельник начинается в субботу Укажите имя пользователя и пароль Время сессии истекло + Забыли проль? + На email-адрес будет выслано письмо со ссылкой для сброса пароля + Будет выслано письмо с инструкциями по сбросу пароля на указанный email-адрес, если он совпадает с адресом пользователя + Вернуться к форме входа + Пожалуйста укажите новый пароль + Ваш пароль обновлен + Ссылка, по которой Вы попали сюда, неверна или устарела + Umbraco: сброс пароля + + Ваше имя пользователя для входа в панель администрирования Umbraco: %0%

        Перейдите по этой ссылке для того, чтобы сбросить Ваш пароль, или скопируйте текст ссылки и вставьте в адресную строку своего браузера:

        %1%

        ]]> +
        Панель управления @@ -759,6 +876,7 @@ Нажмите, чтобы загрузить + Невозможна загрузка этого файла, этот тип файлов не разрешен для загрузки Перетащите файлы сюда... Ссылка на файл или нажмите сюда, чтобы выбрать файлы @@ -866,6 +984,14 @@ Версия пакета История версий пакета Перейти на веб-сайт пакета + Этот пакет уже установлен в системе + Этот пакет не может быть установлен, он требует наличия Umbraco версии как минимум %0% + Удаление... + Загрузка... + Импорт... + Установка... + Перезапуск, подождите, пожалуйста... + Все готово, сейчас браузер перезагрузит страницу, подождите, пожалуйста... Вставить, полностью сохранив форматирование (не рекомендуется) @@ -874,15 +1000,23 @@ Вставить с очисткой форматирования (рекомендуется) + Подтвердите пароль + Укажите Ваш email + Укажите описание... Укажите имя... Укажите теги (нажимайте Enter после каждого тега)... Укажите фильтр... - Назовите %0%... - Пароль - Что искать... - Имя пользователя Метка... - Укажите описание... + Назовите %0%... + Укажите пароль + Что искать... + Укажите имя пользователя + + + Остаться + Отменить изменения + Есть несохраненные изменения + Вы уверены, что хотите уйти с этой страницы? - на ней имеются несохраненные изменения Расширенный: Защита на основе ролей (групп) @@ -904,7 +1038,7 @@ Документ %0% опубликован. Документ %0% и его дочерние документы были опубликованы Опубликовать документ %0% и все его дочерние документы - Ok для публикации документа %0%. - Тем самым Вы сделаете содержимое документа доступным для просмотра.

        Вы можете опубликовать этот документ и все его дочерние документы, отметив опцию Опубликовать все дочерние документы. + Опубликовать для публикации документа %0%. + Тем самым Вы сделаете содержимое документа доступным для просмотра.

        + Вы можете опубликовать этот документ и все его дочерние документы, отметив опцию Опубликовать все дочерние документы. + Чтобы опубликовать ранее неопубликованные документы среди дочерних, отметьте опцию Включая неопубликованные дочерние документы. ]]>
        @@ -1147,6 +1283,7 @@ Генератор уведомлений Umbraco. ]]>
        [%0%] Задание по переводу %1% + Пожалуйста, выберите язык, на который нужно перевести документ(ы) Пользователей-переводчиков не обнаружено. Пожалуйста, создайте пользователя с ролью переводчика, перед тем как отсылать содержимое на перевод Созданные Вами задания созданных Вами. @@ -1253,6 +1390,6 @@ Валидация числового значения Валидация по формату Url ...или указать свои правила валидации - Обязательно к заполению + Обязательно к заполнению From a28c52aa167c2b13a14a034d58985cd930d05a93 Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Mon, 8 Aug 2016 09:36:10 +0200 Subject: [PATCH 164/413] Added styling to url list --- .../src/less/components/umb-packages.less | 5 +++ .../src/less/components/umb-table.less | 31 +++++++++++++++++++ .../dashboard/developer/redirecturls.html | 16 +++++----- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less index cfbf425bef..f14df918c0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -331,6 +331,11 @@ width: 100%; } +.umb-era-button.umb-button--s { + height: 30px; + font-size: 13px; +} + /* CATEGORIES */ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index 874f9e9551..415ccefff0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -3,6 +3,8 @@ display: flex; flex-direction: column; + position: relative; + border: 1px solid @grayLight; flex-wrap: nowrap; @@ -11,6 +13,25 @@ min-width: 640px; } +.umb-table.umb-table-inactive { + + &:before { + content: ""; + background: rgba(255, 255, 255, 0.8); + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + z-index: 10; + + outline: 1px solid rgba(255, 255, 255, 0.8); + } + +} + .umb-table a { text-decoration: none; cursor: pointer; @@ -93,6 +114,14 @@ input.umb-table__input { } } +.umb-table-body .umb-table-row.-solid { + cursor: default; + + &:hover { + background-color: white; + } +} + .umb-table-body__link { text-decoration: none; @@ -183,6 +212,8 @@ input.umb-table__input { user-select: none; } + + .umb-table-row.-selected, .umb-table-row.-selected:hover { background-color: fade(@blueDark, 4%); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html index ebd007cb1a..a9ddeb1e88 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html @@ -11,7 +11,7 @@ @@ -19,7 +19,7 @@ @@ -55,27 +55,27 @@
        -
        +
        {{redirectUrl.CreateDateUtc | date:'medium'}}
        -
        - Edit - +
        + Edit +
        From cbf67a4399e97474352320a9e0d37dec85926d5b Mon Sep 17 00:00:00 2001 From: Claus Date: Mon, 8 Aug 2016 10:18:44 +0200 Subject: [PATCH 165/413] changing to Debug.Print. --- src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index 2d779d0eaf..4529343811 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -124,7 +124,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + Debug.Print("Model to Sql ExpressionHelper: \n" + result); Assert.AreEqual("(replace([umbracoUser].[userLogin], @1, @2) = @0)", result); Assert.AreEqual("hello@test.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); From 32d48b12d1bbe74945b2582b6301625ecc244b0a Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Mon, 8 Aug 2016 10:36:10 +0200 Subject: [PATCH 166/413] Changed button when inactive, added class to toggle when table in inactive --- .../src/less/components/umb-table.less | 6 +++--- .../src/views/dashboard/developer/redirecturls.html | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index 415ccefff0..219c15c7d5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -17,17 +17,17 @@ &:before { content: ""; - background: rgba(255, 255, 255, 0.8); + background: rgba(255, 255, 255, 0.75); position: absolute; top: 0; bottom: 0; left: 0; right: 0; - + z-index: 10; - outline: 1px solid rgba(255, 255, 255, 0.8); + outline: 1px solid rgba(255, 255, 255, 0.75); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html index a9ddeb1e88..919394ca28 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html @@ -19,7 +19,7 @@ @@ -41,7 +41,7 @@ -
        +
        From 081284bc6207cf30740115d3e4db5d510142642e Mon Sep 17 00:00:00 2001 From: Alexander Bryukhov Date: Mon, 8 Aug 2016 15:48:14 +0700 Subject: [PATCH 167/413] Update UI language ru.xml 1. Duplicated 'prompt' area removed 2. Several typos corrected --- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 60 +++++++++---------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 10a1766891..784e8f04f8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -131,7 +131,7 @@ Чтобы сменить тип документа для выбранного узла, сначала выберите тип из списка разрешенных для данного расположения. Затем подтвердите и/или исправьте сопоставление свойств текущего типа документа свойствам нового и нажмите "Сохранить". - Узел переопубликован. + Документ переопубликован. Текущее свойство Текущий тип Тип документа не может быть изменен, так как для данного расположения нет разрешенных альтернатив. @@ -198,7 +198,7 @@ Состояние публикации Опубликовать Очистить дату - ВНИМАНИЕ: этот документ опубликован, но его URL вступает в протиаоречнеие с документом %0% + ВНИМАНИЕ: этот документ опубликован, но его URL вступает в противоречие с документом %0% Это время будет соответствовать следующему времени на сервере: Что это означает?]]> Порядок сортировки обновлен @@ -442,14 +442,14 @@ Выберите тип Вы пытаетесь увеличить изображение по сравнению с его исходным размером. Уверены, что хотите сделать это? Ошибка в скрипте Python - Скрипт на языке Python не был сохранен, т.к. он содержит одну или несколько ошибок. + Скрипт на языке Python не был сохранен, так как он содержит одну или несколько ошибок. Начальный узел был удален, свяжитесь с Вашим администратором Для смены стиля отметьте фрагмент текста Не определен ни один доступный стиль Поместите курсор в левую из двух ячеек, которые хотите объединить Нельзя разделить ячейку, которая не была до этого объединена Ошибка в XSLT-документе - XSLT-документ не был сохранен, т.к. он содержит одну или несколько ошибок + XSLT-документ не был сохранен, так как он содержит одну или несколько ошибок О системе @@ -667,13 +667,13 @@ Параметр 'umbracoUseSSL' в секции 'appSetting' файлf web.config теперь установлен в 'true', значения cookies будут промаркированы как безопасные. Исправить - Невозможно испраление по результату проверки значения на 'ShouldNotEqual'. + Невозможно исправление по результату проверки значения на 'ShouldNotEqual'. Невозможно исправление по результату проверки значения на 'ShouldEqual' с предоставленным значением. Значение для исправления не предоставлено. Режим компиляции с отладкой выключен. Режим компиляции с отладкой сейчас включен. Рекомендуется выключить перед размещением сайта в сети. - Режим компиляции с отладкой усешно выключен. + Режим компиляции с отладкой успешно выключен. Режим трассировки выключен. Режим трассировки сейчас включен. Рекомендуется выключить перед размещением сайта в сети. @@ -696,7 +696,7 @@ X-Frame-Options, использующийся для управления возможностью помещать сайт в IFRAME на другом сайте.]]> X-Frame-Options, использующийся для управления возможностью помещать сайт в IFRAME на другом сайте, не обнаружен.]]> Установить заголовок в файле конфигурации - Добавляет значение в секцию 'httpProtocol/customHeaders' файла web.config, препятствующее возможному использованию этого сайта внутри IFRAME на другои сайте. + Добавляет значение в секцию 'httpProtocol/customHeaders' файла web.config, препятствующее возможному использованию этого сайта внутри IFRAME на другом сайте. Значение, добавляющее заголовок, препятствующий использованию этого сайта внутри IFRAME другого сайта, успешно добавлено в файл web.config. Невозможно обновить файл web.config. Ошибка: %0% @@ -708,7 +708,7 @@ В файле Web.config, не обнаружено параметров работы с отправкой электронной почты (секция 'system.net/mailsettings'). В файле Web.config в секции 'system.net/mailsettings' не обнаружены настройки почтового хоста. - Параметры отправки электронной почты (SMTP) настроены корректно, серсис работает как ожидается. + Параметры отправки электронной почты (SMTP) настроены корректно, сервис работает как ожидается. Сервер SMTP сконфигурирован на использование хоста '%0%' на порту '%1%', который в настоящее время недоступен. Пожалуйста, убедитесь, что настройки SMTP в файле Web.config в секции 'system.net/mailsettings' верны. %0%.]]> @@ -718,7 +718,7 @@ перейти к Разделы справки для Обучающие видео для - Лучшие обучающие видеокурсы по Umbraco + Лучшие обучающие видео-курсы по Umbraco Сбросить @@ -733,12 +733,12 @@ ]]> Далее для продолжения.]]> База данных не найдена! Пожалуйста, проверьте настройки строки подключения ("connection string") в файле конфигурации "web.config"

        -

        Для настройки откройте файл "web.config" с помошью любого текстового редактора и добавьте нужную информацию в строку подключения (параметр "UmbracoDbDSN"), +

        Для настройки откройте файл "web.config" с помощью любого текстового редактора и добавьте нужную информацию в строку подключения (параметр "UmbracoDbDSN"), затем сохраните файл.

        Нажмите кнопку "Повторить" когда все будет готово
        Более подробно о внесении изменений в файл "web.config" расскзано здесь.

        ]]>
        - Более подробно о внесении изменений в файл "web.config" рассказано здесь.

        ]]>
        + Пожалуйста, свяжитесь с Вашим хостинг-провайдером, если есть необходимость, а если устанавливаете на локальную рабочую станцию или сервер, то получите информацию у Вашего системного администратора.]]> ОЧЕНЬ ВАЖНО изменить пароль по-умолчанию на что-либо уникальное.

        ]]>
        Для начального обзора возможностей системы рекомендуем посмотреть ознакомительные видеоматериалы - Далее (или модифицируя вручную ключ "UmbracoConfigurationStatus" в файле "web.config"), Вы принимаете лицензионное соглашение для данного программного обеспечения, расположенное ниже. Пожалуйста, обратите внимание, что инсталляционный пакет Umbraco отвечает двум различным типам лицензий: лицензии MIT на программные продукты с открытым исходным кодом в части ядра системы и свободной лицензии Umbraco в части пользовательского интерфейса.]]> + Далее (или модифицируя вручную ключ "UmbracoConfigurationStatus" в файле "web.config"), Вы принимаете лицензионное соглашение для данного программного обеспечения, расположенное ниже. Пожалуйста, обратите внимание, что установочный пакет Umbraco отвечает двум различным типам лицензий: лицензии MIT на программные продукты с открытым исходным кодом в части ядра системы и свободной лицензии Umbraco в части пользовательского интерфейса.]]> Система не установлена. Затронутые файлы и папки Более подробно об установке разрешений для Umbraco рассказано здесь @@ -806,7 +806,7 @@ но Вы можете в дальнейшем свободно изменять, расширять или удалить его. Этот демонстрационный сайт не является необходимой частью, и Вы можете свободно использовать Umbraco без него. Однако, "Runway" предоставляет Вам возможность - максимально быстро познакомиться с базовыми принциапми и техникой построения сайтов + максимально быстро познакомиться с базовыми принципами и техникой построения сайтов на основе Umbraco. Если Вы выберете вариант с установкой "Runway", Вам будет предложен выбор "базовых строительных блоков" (т.н. модулей Runway) для расширения возможностей страниц сайта "Runway".

        В "Runway" входят:"Домашняя" (главная) страница, страница "Начало работы", @@ -829,7 +829,7 @@ прямо сейчас, воспользовавшись ссылкой "Начать работу с Umbraco".
        Если Вы новичок в мире Umbraco, Вы сможете найти много полезных ссылок на ресурсы на странице "Начало работы".]]>
        Начните работу с Umbraco - Для того, чтобы начать администрировать свой сайт, просто откройте бэк-офис Umbraco и начните обновлять контент, изменять шаблоны страниц и стили CSS или добавлять новую функциональность]]> + Для того, чтобы начать администрировать свой сайт, просто откройте административную панель Umbraco и начните обновлять контент, изменять шаблоны страниц и стили CSS или добавлять новую функциональность]]> Попытка соединения с базой данных потерпела неудачу. Версия Umbraco 3 Версия Umbraco 4 @@ -848,16 +848,16 @@ © 2001 - %0%
        umbraco.com

        ]]>
        - Сегодня ж выходной - Понедельник — день тяжелый - Уже вторник + Сегодня же выходной! + Понедельник — день тяжелый... + Вот уже вторник... Берегите окружающую среду Рыбный день - Слава Богу, сегодня пятница + Слава Богу, сегодня пятница! Понедельник начинается в субботу Укажите имя пользователя и пароль Время сессии истекло - Забыли проль? + Забыли пароль? На email-адрес будет выслано письмо со ссылкой для сброса пароля Будет выслано письмо с инструкциями по сбросу пароля на указанный email-адрес, если он совпадает с адресом пользователя Вернуться к форме входа @@ -995,7 +995,7 @@ Вставить, полностью сохранив форматирование (не рекомендуется) - Текст, который Вы пытаетесь вставить, содержит специальные символы и/или элемены форматирования. Это возможно при вставке текста, скопированного из Microsoft Word. Система может удалить эти элементы автоматически, чтобы сделать вставляемый текст более пригодным для веб-публикации. + Текст, который Вы пытаетесь вставить, содержит специальные символы и/или элементы форматирования. Это возможно при вставке текста, скопированного из Microsoft Word. Система может удалить эти элементы автоматически, чтобы сделать вставляемый текст более пригодным для веб-публикации. Вставить как простой текст без форматирования Вставить с очисткой форматирования (рекомендуется) @@ -1018,12 +1018,6 @@ Есть несохраненные изменения Вы уверены, что хотите уйти с этой страницы? - на ней имеются несохраненные изменения - - Остаться - Отменить изменения - Есть несохраненные изменения - Вы уверены, что хотите уйти с этой страницы? - на ней имеются несохраненные изменения - Расширенный: Защита на основе ролей (групп) с использованием групп участников Umbraco.]]> @@ -1037,14 +1031,14 @@ Используйте как страницу с формой для авторизации пользователей Снять защиту Выберите страницы авторизации и сообщений об ошибках - Выберитет роли пользователей, имеющих доступ к документу + Выберите роли пользователей, имеющих доступ к документу Установите имя пользователя и пароль для доступа к этому документу Простой: Защита по имени пользователя и паролю Применяйте, если хотите установить самый простой способ доступа к документу - явно указанные имя пользователя и пароль Задачи, назначенные Вам назначенные Вам. Для того, чтобы просмотреть подробные сведения, - включая комментарии, нажмите кнопку "Подробно" или просто кликните имя страницы. Вы ткже можете скачать XML-версию страницы нажав ссылку "Загрузить Xml".
        Чтобы закрыть задачу перевода, следует перейти к подробному просмотру и нажать там кнопку "Закрыть". + включая комментарии, нажмите кнопку "Подробно" или просто кликните имя страницы. Вы также можете скачать XML-версию страницы нажав ссылку "Загрузить Xml".
        Чтобы закрыть задачу перевода, следует перейти к подробному просмотру и нажать там кнопку "Закрыть". ]]>
        Закрыть задачу Подробности перевода @@ -1274,7 +1268,7 @@ Поля Включить дочерние документы Доступны обновления Обновление %0% готово, кликните для загрузки Нет связи с сервером - Во время проверки обновлений произошла ошибка. Пожалуйста, просмотрите жкрнал трассировки для получения дополнительной информации. + Во время проверки обновлений произошла ошибка. Пожалуйста, просмотрите журнал трассировки для получения дополнительной информации. Администратор @@ -1367,7 +1361,7 @@ Прежний пароль Пароль Ваш пароль доступа изменен! - Подвердите новый пароль + Подтвердите новый пароль Текущий пароль Укажите новый пароль Текущий пароль указан неверно From 67430392e35abbed1f18748f40455e172d00440d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 8 Aug 2016 11:22:16 +0200 Subject: [PATCH 168/413] Change object to img to make it render in Edge --- .../src/views/components/umb-media-grid.html | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html index 6d6ff0d599..b680ec9daa 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -8,12 +8,19 @@
        {{item.name}}
        +
        - {{item.name}} - + + {{item.name}} + + + {{item.name}} + + {{item.name}} +
        From 80d579891b059984e71114cfedfa60a134381acd Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Mon, 8 Aug 2016 11:33:56 +0200 Subject: [PATCH 169/413] Removes search when disabled --- .../src/views/dashboard/developer/redirecturls.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html index 919394ca28..f7fef60c8e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html @@ -26,9 +26,10 @@ - + Date: Mon, 8 Aug 2016 16:41:19 +0700 Subject: [PATCH 170/413] Update UI language ru.xml --- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 784e8f04f8..ff3c94b74d 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -863,6 +863,7 @@ Вернуться к форме входа Пожалуйста укажите новый пароль Ваш пароль обновлен + Войти с помощью Ссылка, по которой Вы попали сюда, неверна или устарела Umbraco: сброс пароля From 126c358546bbf693671a1eea48d0f5d62fd33d66 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 8 Aug 2016 11:57:48 +0200 Subject: [PATCH 171/413] Fixes build and use Guid where necessary --- .../views/dashboard/developer/redirecturls.controller.js | 2 +- .../Redirects/RedirectUrlManagementController.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js index 7cc3baca00..61dbb2a709 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js @@ -77,7 +77,7 @@ function removeRedirect(redirectToDelete) { - redirectUrlsResource.deleteRedirectUrl(redirectToDelete.Id).then(function() { + redirectUrlsResource.deleteRedirectUrl(redirectToDelete.ContentKey).then(function () { var index = vm.redirectUrls.indexOf(redirectToDelete); vm.redirectUrls.splice(index, 1); diff --git a/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs b/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs index a147d859e0..47dd8c5eff 100644 --- a/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs @@ -1,9 +1,11 @@ -using System.Net.Http; +using System; +using System.Net.Http; using System.Text; using System.Web; using System.Web.Http; using System.Xml; using Umbraco.Core.Configuration; +using Umbraco.Core.Services; using Umbraco.Web.WebApi; using File = System.IO.File; @@ -46,7 +48,7 @@ namespace Umbraco.Web.Redirects } [HttpPost] - public IHttpActionResult DeleteRedirectUrl(int id) + public IHttpActionResult DeleteRedirectUrl(Guid id) { var redirectUrlService = Services.RedirectUrlService; redirectUrlService.Delete(id); From ad2a644bdf1c6adcbd184793a0df511ec6ef4cd5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 8 Aug 2016 12:07:43 +0200 Subject: [PATCH 172/413] temp save to track history since i need to roll this back --- .../Routing/RedirectTrackingEventHandler.cs | 173 ++++++++++++++---- 1 file changed, 136 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs index 65cad094dc..65aac4658d 100644 --- a/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs +++ b/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Publishing; using Umbraco.Core.Services; +using Umbraco.Core.Strings; using Umbraco.Core.Sync; using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; @@ -25,9 +26,10 @@ namespace Umbraco.Web.Routing /// public class RedirectTrackingEventHandler : ApplicationEventHandler { - private const string ContextKey1 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.1"; - private const string ContextKey2 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.2"; - private const string ContextKey3 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.3"; + private const string ContextKey1 = "RedirectTrackingEventHandler.1"; + private const string ContextKey2 = "RedirectTrackingEventHandler.2"; + //private const string ContextKey3 = "RedirectTrackingEventHandler.3"; + private const string ContextKey4 = "RedirectTrackingEventHandler.4"; /// protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) @@ -90,20 +92,80 @@ namespace Umbraco.Web.Routing // rolled back items have to be published, so publishing will take care of that } + ///// + ///// Tracks a documents URLs during publishing in the current request + ///// + //private static Dictionary> OldRoutes + //{ + // get + // { + // var oldRoutes = RequestCache.GetCacheItem>>( + // ContextKey3, + // () => new Dictionary>()); + // return oldRoutes; + // } + //} + + private class PrePublishedContentContext + { + public static PrePublishedContentContext Empty + { + get { return new PrePublishedContentContext(null, null, null, null); } + } + /// Initializes a new instance of the class. + public PrePublishedContentContext(IContent entity, string urlSegment, ContextualPublishedContentCache contentCache, Func> descendentsDelegate) + { + if (entity == null) throw new ArgumentNullException("entity"); + if (contentCache == null) throw new ArgumentNullException("contentCache"); + if (descendentsDelegate == null) throw new ArgumentNullException("descendentsDelegate"); + if (string.IsNullOrWhiteSpace(urlSegment)) throw new ArgumentException("Value cannot be null or whitespace.", "urlSegment"); + Entity = entity; + UrlSegment = urlSegment; + ContentCache = contentCache; + DescendentsDelegate = descendentsDelegate; + } + + public IContent Entity { get; set; } + public string UrlSegment { get; set; } + public Func> DescendentsDelegate { get; set; } + public ContextualPublishedContentCache ContentCache { get; set; } + } + /// - /// Tracks a documents URLs during publishing in the current request + /// Tracks the current doc's entity, url segment and delegate to retrieve it's old descendents during publishing in the current request /// - private static Dictionary> OldRoutes + private static PrePublishedContentContext PrePublishedContent { get { - var oldRoutes = RequestCache.GetCacheItem>>( - ContextKey3, - () => new Dictionary>()); - return oldRoutes; + //return the item in the cache - otherwise initialize it to an empty instance + return RequestCache.GetCacheItem(ContextKey4, () => PrePublishedContentContext.Empty); + } + set + { + //clear it + RequestCache.ClearCacheItem(ContextKey4); + //force it into the cache + RequestCache.GetCacheItem(ContextKey4, () => value); } } + //private static Func> DescendentsOrSelfDelegate + //{ + // get + // { + // //return the item in the cache - otherwise initialize it to an empty string + // return RequestCache.GetCacheItem>>(ContextKey4, () => (() => Enumerable.Empty())); + // } + // set + // { + // //clear it + // RequestCache.ClearCacheItem(ContextKey4); + // //force it into the cache + // RequestCache.GetCacheItem>>(ContextKey4, () => value); + // } + //} + private static bool LockedEvents { get @@ -155,28 +217,26 @@ namespace Umbraco.Web.Routing if (contentCache == null) return; foreach (var entity in args.PublishedEntities) - { - //don't continue if this entity hasn't changed with regards to anything - // that might change it's URLs - if (entity.IsDirty() == false) continue; + { + var entityContent = contentCache.GetById(entity.Id); + if (entityContent == null) continue; - if (entity.IsPropertyDirty("Name") - || entity.IsPropertyDirty(Constants.Conventions.Content.UrlName) - || entity.IsPropertyDirty(Constants.Conventions.Content.UrlAlias) - || Moving) - { - var entityContent = contentCache.GetById(entity.Id); - if (entityContent == null) continue; - foreach (var x in entityContent.DescendantsOrSelf()) - { - var route = contentCache.GetRouteById(x.Id); - if (IsNotRoute(route)) continue; - var wk = UnwrapToKey(x); - if (wk == null) continue; - - OldRoutes[x.Id] = Tuple.Create(wk.Key, route); - } - } + PrePublishedContent = new PrePublishedContentContext(entity, entity.GetUrlSegment(), contentCache, () => entityContent.Descendants()); + + //if (Moving) + //{ + // var entityContent = contentCache.GetById(entity.Id); + // if (entityContent == null) continue; + // foreach (var x in entityContent.Descendants()) + // { + // var route = contentCache.GetRouteById(x.Id); + // if (IsNotRoute(route)) continue; + // var wk = UnwrapToKey(x); + // if (wk == null) continue; + + // OldRoutes[x.Id] = Tuple.Create(wk.Key, route); + // } + //} } LockedEvents = true; // we only want to see the "first batch" @@ -208,23 +268,62 @@ namespace Umbraco.Web.Routing var serverRole = ApplicationContext.Current.GetCurrentServerRole(); if (serverRole == ServerRole.Master || serverRole == ServerRole.Single) { - if (RequestCache.GetCacheItem(ContextKey3) == null) - return; + //copy local + var prePublishedContext = PrePublishedContent; + //cannot continue if this is empty + if (prePublishedContext.Entity == null) return; + + //cannot continue if there is no published cache + var contentCache = GetPublishedCache(); + if (contentCache == null) return; + + //get the entity id out of the event args to compare with the id stored during publishing + if (cacheRefresherEventArgs.MessageType != MessageType.RefreshById || cacheRefresherEventArgs.MessageType != MessageType.RefreshByInstance) return; + + var refreshedEntityId = cacheRefresherEventArgs.MessageType == MessageType.RefreshByInstance + ? ((IContent)cacheRefresherEventArgs.MessageObject).Id + : (int) cacheRefresherEventArgs.MessageObject; + + //if it's not the id that we're targeting, don't continue + if (refreshedEntityId != prePublishedContext.Entity.Id) return; + + //cannot continue if the entity is not found + var entityContent = contentCache.GetById(prePublishedContext.Entity.Id); + if (entityContent == null) return; + + //now we can check if the segment has changed + var newSegment = prePublishedContext.Entity.GetUrlSegment(); try { - foreach (var oldRoute in OldRoutes) + if (Moving || newSegment != prePublishedContext.UrlSegment) { + //it's changed! + // assuming we cannot have 'CacheUpdated' for only part of the infos else we'd need // to set a flag in 'Published' to indicate which entities have been refreshed ok - CreateRedirect(oldRoute.Key, oldRoute.Value.Item1, oldRoute.Value.Item2); + CreateRedirect(prePublishedContext.Entity.Id, prePublishedContext.Entity.Key, prePublishedContext.UrlSegment); + + //iterate the old descendents and get their old routes + foreach (var x in prePublishedContext.DescendentsDelegate()) + { + //get the old route from the old contextual cache + var oldRoute = prePublishedContext.ContentCache.GetRouteById(x.Id); + if (IsNotRoute(oldRoute)) continue; + var wk = UnwrapToKey(x); + if (wk == null) continue; + + CreateRedirect(wk.Id, wk.Key, oldRoute); + } } } finally { - OldRoutes.Clear(); - RequestCache.ClearCacheItem(ContextKey3); - } + //set all refs to null + prePublishedContext.Entity = null; + prePublishedContext.ContentCache = null; + prePublishedContext.DescendentsDelegate = null; + } } } From fee4a84c668dec8b5d020224eb3e6f0f9a58750c Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 8 Aug 2016 12:19:03 +0200 Subject: [PATCH 173/413] Revert temp commit, adds logic to check if the segment value has changed + lots of notes --- .../Strings/IUrlSegmentProvider.cs | 4 + .../Routing/RedirectTrackingEventHandler.cs | 181 +++++------------- 2 files changed, 49 insertions(+), 136 deletions(-) diff --git a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs index dc3f4243e7..c3ef596ad0 100644 --- a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs +++ b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs @@ -26,5 +26,9 @@ namespace Umbraco.Core.Strings /// per content, in 1-to-1 multilingual configurations. Then there would be one /// url per culture. string GetUrlSegment(IContentBase content, CultureInfo culture); + + //TODO: For the 301 tracking, we need to add another extended interface to this so that + // the RedirectTrackingEventHandler can ask the IUrlSegmentProvider if the URL is changing. + // Currently the way it works is very hacky, see notes in: RedirectTrackingEventHandler.ContentService_Publishing } } diff --git a/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs index 65aac4658d..b0340fa7e4 100644 --- a/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs +++ b/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs @@ -26,10 +26,9 @@ namespace Umbraco.Web.Routing /// public class RedirectTrackingEventHandler : ApplicationEventHandler { - private const string ContextKey1 = "RedirectTrackingEventHandler.1"; - private const string ContextKey2 = "RedirectTrackingEventHandler.2"; - //private const string ContextKey3 = "RedirectTrackingEventHandler.3"; - private const string ContextKey4 = "RedirectTrackingEventHandler.4"; + private const string ContextKey1 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.1"; + private const string ContextKey2 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.2"; + private const string ContextKey3 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.3"; /// protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) @@ -92,80 +91,20 @@ namespace Umbraco.Web.Routing // rolled back items have to be published, so publishing will take care of that } - ///// - ///// Tracks a documents URLs during publishing in the current request - ///// - //private static Dictionary> OldRoutes - //{ - // get - // { - // var oldRoutes = RequestCache.GetCacheItem>>( - // ContextKey3, - // () => new Dictionary>()); - // return oldRoutes; - // } - //} - - private class PrePublishedContentContext - { - public static PrePublishedContentContext Empty - { - get { return new PrePublishedContentContext(null, null, null, null); } - } - /// Initializes a new instance of the class. - public PrePublishedContentContext(IContent entity, string urlSegment, ContextualPublishedContentCache contentCache, Func> descendentsDelegate) - { - if (entity == null) throw new ArgumentNullException("entity"); - if (contentCache == null) throw new ArgumentNullException("contentCache"); - if (descendentsDelegate == null) throw new ArgumentNullException("descendentsDelegate"); - if (string.IsNullOrWhiteSpace(urlSegment)) throw new ArgumentException("Value cannot be null or whitespace.", "urlSegment"); - Entity = entity; - UrlSegment = urlSegment; - ContentCache = contentCache; - DescendentsDelegate = descendentsDelegate; - } - - public IContent Entity { get; set; } - public string UrlSegment { get; set; } - public Func> DescendentsDelegate { get; set; } - public ContextualPublishedContentCache ContentCache { get; set; } - } - /// - /// Tracks the current doc's entity, url segment and delegate to retrieve it's old descendents during publishing in the current request + /// Tracks a documents URLs during publishing in the current request /// - private static PrePublishedContentContext PrePublishedContent + private static Dictionary> OldRoutes { get { - //return the item in the cache - otherwise initialize it to an empty instance - return RequestCache.GetCacheItem(ContextKey4, () => PrePublishedContentContext.Empty); - } - set - { - //clear it - RequestCache.ClearCacheItem(ContextKey4); - //force it into the cache - RequestCache.GetCacheItem(ContextKey4, () => value); + var oldRoutes = RequestCache.GetCacheItem>>( + ContextKey3, + () => new Dictionary>()); + return oldRoutes; } } - //private static Func> DescendentsOrSelfDelegate - //{ - // get - // { - // //return the item in the cache - otherwise initialize it to an empty string - // return RequestCache.GetCacheItem>>(ContextKey4, () => (() => Enumerable.Empty())); - // } - // set - // { - // //clear it - // RequestCache.ClearCacheItem(ContextKey4); - // //force it into the cache - // RequestCache.GetCacheItem>>(ContextKey4, () => value); - // } - //} - private static bool LockedEvents { get @@ -217,26 +156,34 @@ namespace Umbraco.Web.Routing if (contentCache == null) return; foreach (var entity in args.PublishedEntities) - { - var entityContent = contentCache.GetById(entity.Id); - if (entityContent == null) continue; - - PrePublishedContent = new PrePublishedContentContext(entity, entity.GetUrlSegment(), contentCache, () => entityContent.Descendants()); - - //if (Moving) - //{ - // var entityContent = contentCache.GetById(entity.Id); - // if (entityContent == null) continue; - // foreach (var x in entityContent.Descendants()) - // { - // var route = contentCache.GetRouteById(x.Id); - // if (IsNotRoute(route)) continue; - // var wk = UnwrapToKey(x); - // if (wk == null) continue; - - // OldRoutes[x.Id] = Tuple.Create(wk.Key, route); - // } - //} + { + //TODO: This is horrible - we need to check if the url segment for this entity is changing in + // order to determine if we need to make redirects for itself and all of it's descendents. + // The way this works right now (7.5.0) is that we re-lookup the entity that is currently being published which + // returns it's existing data in the db which we use to extract it's current segment. Then we compare that with + // the segment value returned from the current entity. + // In the future this will certainly cause some problems, to fix this we'd need to change the IUrlSegmentProvider + // to support being able to determine if a segment is going to change for an entity. See notes in IUrlSegmentProvider. + var oldEntity = ApplicationContext.Current.Services.ContentService.GetById(entity.Id); + if (oldEntity == null) continue; + var oldSegmentName = oldEntity.GetUrlSegment(); + + //if the segment has changed or we are moving, then process all descendent + // Urls and schedule them for creating a rewrite when publishing is done. + if (oldSegmentName != entity.GetUrlSegment() || Moving) + { + var entityContent = contentCache.GetById(entity.Id); + if (entityContent == null) continue; + foreach (var x in entityContent.DescendantsOrSelf()) + { + var route = contentCache.GetRouteById(x.Id); + if (IsNotRoute(route)) continue; + var wk = UnwrapToKey(x); + if (wk == null) continue; + + OldRoutes[x.Id] = Tuple.Create(wk.Key, route); + } + } } LockedEvents = true; // we only want to see the "first batch" @@ -268,62 +215,24 @@ namespace Umbraco.Web.Routing var serverRole = ApplicationContext.Current.GetCurrentServerRole(); if (serverRole == ServerRole.Master || serverRole == ServerRole.Single) { - //copy local - var prePublishedContext = PrePublishedContent; + //if the Old routes is empty do not continue + if (OldRoutes.Count == 0) + return; - //cannot continue if this is empty - if (prePublishedContext.Entity == null) return; - - //cannot continue if there is no published cache - var contentCache = GetPublishedCache(); - if (contentCache == null) return; - - //get the entity id out of the event args to compare with the id stored during publishing - if (cacheRefresherEventArgs.MessageType != MessageType.RefreshById || cacheRefresherEventArgs.MessageType != MessageType.RefreshByInstance) return; - - var refreshedEntityId = cacheRefresherEventArgs.MessageType == MessageType.RefreshByInstance - ? ((IContent)cacheRefresherEventArgs.MessageObject).Id - : (int) cacheRefresherEventArgs.MessageObject; - - //if it's not the id that we're targeting, don't continue - if (refreshedEntityId != prePublishedContext.Entity.Id) return; - - //cannot continue if the entity is not found - var entityContent = contentCache.GetById(prePublishedContext.Entity.Id); - if (entityContent == null) return; - - //now we can check if the segment has changed - var newSegment = prePublishedContext.Entity.GetUrlSegment(); try { - if (Moving || newSegment != prePublishedContext.UrlSegment) + foreach (var oldRoute in OldRoutes) { - //it's changed! - // assuming we cannot have 'CacheUpdated' for only part of the infos else we'd need // to set a flag in 'Published' to indicate which entities have been refreshed ok - CreateRedirect(prePublishedContext.Entity.Id, prePublishedContext.Entity.Key, prePublishedContext.UrlSegment); - - //iterate the old descendents and get their old routes - foreach (var x in prePublishedContext.DescendentsDelegate()) - { - //get the old route from the old contextual cache - var oldRoute = prePublishedContext.ContentCache.GetRouteById(x.Id); - if (IsNotRoute(oldRoute)) continue; - var wk = UnwrapToKey(x); - if (wk == null) continue; - - CreateRedirect(wk.Id, wk.Key, oldRoute); - } + CreateRedirect(oldRoute.Key, oldRoute.Value.Item1, oldRoute.Value.Item2); } } finally { - //set all refs to null - prePublishedContext.Entity = null; - prePublishedContext.ContentCache = null; - prePublishedContext.DescendentsDelegate = null; - } + OldRoutes.Clear(); + RequestCache.ClearCacheItem(ContextKey3); + } } } From 4ff728b62a5b77b244905d2e9dca10635ea3c77b Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Mon, 8 Aug 2016 12:55:52 +0200 Subject: [PATCH 174/413] added target blank so it opens in another tab --- .../src/views/dashboard/developer/redirecturls.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html index f7fef60c8e..d6c878e1d5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html @@ -63,11 +63,11 @@
        From 69c952ddda60cbc2423f9f3cba943f3a101ddcf9 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 8 Aug 2016 13:10:02 +0200 Subject: [PATCH 175/413] Don't use HttpContext --- src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs b/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs index 47dd8c5eff..30e539f3ec 100644 --- a/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web/Redirects/RedirectUrlManagementController.cs @@ -2,6 +2,7 @@ using System.Net.Http; using System.Text; using System.Web; +using System.Web.Hosting; using System.Web.Http; using System.Xml; using Umbraco.Core.Configuration; @@ -58,7 +59,7 @@ namespace Umbraco.Web.Redirects [HttpPost] public IHttpActionResult ToggleUrlTracker(bool disable) { - var configFilePath = HttpContext.Current.Server.MapPath("~/config/umbracoSettings.config"); + var configFilePath = HostingEnvironment.MapPath("~/config/umbracoSettings.config"); var action = disable ? "disable" : "enable"; From 55752abdcf6cbc9b5a6015d83b3281e28cbee93f Mon Sep 17 00:00:00 2001 From: Claus Date: Mon, 8 Aug 2016 13:23:46 +0200 Subject: [PATCH 176/413] fixing InsertMacroSplitButton typo in jquery selector causing javascript errors. fixing modal $.browser checks to not cause console errors due to deprecated browser object in jquery 1.9+. --- .../modal/jquery.simplemodal.1.4.1.custom.js | 8 ++++---- src/Umbraco.Web.UI/umbraco_client/modal/modal.js | 2 +- .../umbraco_client/splitbutton/InsertMacroSplitButton.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco_client/modal/jquery.simplemodal.1.4.1.custom.js b/src/Umbraco.Web.UI/umbraco_client/modal/jquery.simplemodal.1.4.1.custom.js index cdcc0cc85f..ff9a1f1834 100644 --- a/src/Umbraco.Web.UI/umbraco_client/modal/jquery.simplemodal.1.4.1.custom.js +++ b/src/Umbraco.Web.UI/umbraco_client/modal/jquery.simplemodal.1.4.1.custom.js @@ -1,7 +1,7 @@ ; (function ($) { - var ie6 = $.browser.msie && parseInt($.browser.version) === 6 && typeof window['XMLHttpRequest'] !== 'object', - ie7 = $.browser.msie && parseInt($.browser.version) === 7, + var ie6 = $.browser && $.browser.msie && parseInt($.browser.version) === 6 && typeof window['XMLHttpRequest'] !== 'object', + ie7 = $.browser && $.browser.msie && parseInt($.browser.version) === 7, ieQuirks = null, w = []; @@ -418,8 +418,8 @@ var el = $(window); // fix a jQuery/Opera bug with determining the window height - var h = $.browser.opera && $.browser.version > '9.5' && $.fn.jquery < '1.3' - || $.browser.opera && $.browser.version < '9.5' && $.fn.jquery > '1.2.6' + var h = $.browser && $.browser.opera && $.browser.version > '9.5' && $.fn.jquery < '1.3' + || $.browser && $.browser.opera && $.browser.version < '9.5' && $.fn.jquery > '1.2.6' ? el[0].innerHeight : el.height(); return [h, el.width()]; diff --git a/src/Umbraco.Web.UI/umbraco_client/modal/modal.js b/src/Umbraco.Web.UI/umbraco_client/modal/modal.js index c2c2812b0e..d792352355 100644 --- a/src/Umbraco.Web.UI/umbraco_client/modal/modal.js +++ b/src/Umbraco.Web.UI/umbraco_client/modal/modal.js @@ -386,7 +386,7 @@ Umbraco.Sys.registerNamespace("Umbraco.Controls"); }, params: {} }; - var s = 0, H = $.jqm.hash, A = [], ie6 = $.browser.msie && ($.browser.version == "6.0"), F = false, + var s = 0, H = $.jqm.hash, A = [], ie6 = $.browser && $.browser.msie && ($.browser.version == "6.0"), F = false, i = $('').css({ opacity: 0 }), e = function(h) { if (ie6) if (h.o) h.o.html('

        ').prepend(i); else if (!$('iframe.jqm', h.w)[0]) h.w.prepend(i); }, //f = function(h) { try { $(':input:visible', h.w)[0].focus(); } catch (_) { } }, diff --git a/src/Umbraco.Web.UI/umbraco_client/splitbutton/InsertMacroSplitButton.js b/src/Umbraco.Web.UI/umbraco_client/splitbutton/InsertMacroSplitButton.js index a0d5ddf192..c64bcafc03 100644 --- a/src/Umbraco.Web.UI/umbraco_client/splitbutton/InsertMacroSplitButton.js +++ b/src/Umbraco.Web.UI/umbraco_client/splitbutton/InsertMacroSplitButton.js @@ -27,7 +27,7 @@ //be the previous element with the class .sbMenu var splitButtons = $('.sbPlaceHolder', container); splitButtons.each(function() { - var menu = $(this).prev(".sbMenu;"); + var menu = $(this).prev(".sbMenu"); $(this).find("a.sbLink").splitbutton({ menu: menu }); }); From 3170d6829a289e3c85155fbc382fcc67fcf353ab Mon Sep 17 00:00:00 2001 From: Claus Date: Mon, 8 Aug 2016 13:26:21 +0200 Subject: [PATCH 177/413] browser object null check in jquery.ba-bbq.min. --- .../umbraco_client/Application/JQuery/jquery.ba-bbq.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/jquery.ba-bbq.min.js b/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/jquery.ba-bbq.min.js index a9fc08814c..a0f5573174 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/jquery.ba-bbq.min.js +++ b/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/jquery.ba-bbq.min.js @@ -15,4 +15,4 @@ * Dual licensed under the MIT and GPL licenses. * http://benalman.com/about/license/ */ -(function ($, e, b) { var c = "hashchange", h = document, f, g = $.event.special, i = h.documentMode, d = "on" + c in e && (i === b || i > 7); function a(j) { j = j || location.href; return "#" + j.replace(/^[^#]*#?(.*)$/, "$1") } $.fn[c] = function (j) { return j ? this.bind(c, j) : this.trigger(c) }; $.fn[c].delay = 50; g[c] = $.extend(g[c], { setup: function () { if (d) { return false } $(f.start) }, teardown: function () { if (d) { return false } $(f.stop) } }); f = (function () { var j = {}, p, m = a(), k = function (q) { return q }, l = k, o = k; j.start = function () { p || n() }; j.stop = function () { p && clearTimeout(p); p = b }; function n() { var r = a(), q = o(m); if (r !== m) { l(m = r, q); $(e).trigger(c) } else { if (q !== m) { location.href = location.href.replace(/#.*/, "") + q } } p = setTimeout(n, $.fn[c].delay) } $.browser.msie && !d && (function () { var q, r; j.start = function () { if (!q) { r = $.fn[c].src; r = r && r + a(); q = $('