diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000000..9515e26654 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,153 @@ +Umbraco Cms Build +-- +---- + +# Quick! + +To build Umbraco, fire PowerShell and move to Umbraco's repository root (the directory that contains `src`, `build`, `README.md`...). There, trigger the build with the following command: + + build\build.ps1 + +By default, this builds the current version. It is possible to specify a different version as a parameter to the build script: + + build\build.ps1 7.6.44 + +Valid version strings are defined in the `Set-UmbracoVersion` documentation below. + +## Notes + +Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details). + +If PowerShell does not execute the script due to a security error, you may have to run the following command from an administrator prompt: + + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned + +# Build + +The Umbraco Build solution relies on a PowerShell module. The module needs to be imported into PowerShell. From within Umbraco's repository root: + + build\build.ps1 -ModuleOnly + +Or the abbreviated form: + + build\build.ps1 -mo + +Once the module has been imported, a set of commands are added to PowerShell. + +## Get-UmbracoBuildEnv + +Gets the Umbraco build environment ie NuGet, Semver, Visual Studio, etc. Downloads things that can be downloaded such as NuGet. Examples: + + $uenv = Get-UmbracoBuildEnv + Write-Host $uenv.SolutionRoot + &$uenv.NuGet help + +The object exposes the following properties: + +* `SolutionRoot`: the absolute path to the solution root +* `VisualStudio`: a Visual Studio object (see below) +* `NuGet`: the absolute path to the NuGet executable +* `Zip`: the absolute path to the 7Zip executable +* `VsWhere`: the absolute path to the VsWhere executable +* `NodePath`: the absolute path to the Node install +* `NpmPath`: the absolute path to the Npm install + +The Visual Studio object is `null` when Visual Studio has not been detected (eg on VSTS). When not null, the object exposes the following properties: + +* `Path`: Visual Studio installation path (eg some place under `Program Files`) +* `Major`: Visual Studio major version (eg `15` for VS 2017) +* `Minor`: Visual Studio minor version +* `MsBUild`: the absolute path to the MsBuild executable + +## Get-UmbracoVersion + +Gets an object representing the current Umbraco version. Example: + + $v = Get-UmbracoVersion + Write-Host $v.Semver + +The object exposes the following properties: + +* `Semver`: the semver object representing the version +* `Release`: the main part of the version (eg `7.6.33`) +* `Comment`: the pre release part of the version (eg `alpha02`) +* `Build`: the build number part of the version (eg `1234`) + +## Set-UmbracoVersion + +Modifies Umbraco files with the new version. + +>This entirely replaces the legacy `UmbracoVersion.txt` file. + +The version must be a valid semver version. It can include a *pre release* part (eg `alpha02`) and/or a *build number* (eg `1234`). Examples: + + Set-UmbracoVersion 7.6.33 + Set-UmbracoVersion 7.6.33-alpha02 + Set-UmbracoVersion 7.6.33+1234 + Set-UmbracoVersion 7.6.33-beta05+5678 + +Note that `Set-UmbracoVersion` enforces a slightly more restrictive naming scheme than what semver would tolerate. The pre release part can only be composed of a-z and 0-9, therefore `alpha033` is considered valid but not `alpha.033` nor `alpha033-preview` nor `RC2` (would need to be lowercased `rc2`). + +>It is considered best to add trailing zeroes to pre releases, else NuGet gets the order of versions wrong. So if you plan to have more than 10, but no more that 100 alpha versions, number the versions `alpha00`, `alpha01`, etc. + +## Build-Umbraco + +Builds Umbraco. Temporary files are generated in `build.tmp` while the actual artifacts (zip files, NuGet packages...) are produced in `build.out`. Example: + + Build-Umbraco + +Some log files, such as MsBuild logs, are produced in `build.tmp` too. The `build` directory should remain clean during a build. + +### web.config + +Building Umbraco requires a clean `web.config` file in the `Umbraco.Web.UI` project. If a `web.config` file already exists, the `pre-build` task (see below) will save it as `web.config.temp-build` and replace it with a clean copy of `web.Template.config`. The original file is replaced once it is safe to do so, by the `pre-packages` task. + +## Build-UmbracoDocs + +Builds umbraco documentation. Temporary files are generated in `build.tmp` while the actual artifacts (docs...) are produced in `build.out`. Example: + + Build-UmbracoDocs + +Some log files, such as MsBuild logs, are produced in `build.tmp` too. The `build` directory should remain clean during a build. + +## Verify-NuGet + +Verifies that projects all require the same version of their dependencies, and that NuSpec files require versions that are consistent with projects. Example: + + Verify-NuGet + +# VSTS + +Continuous integration, nightly builds and release builds run on VSTS. + +VSTS uses the `Build-Umbraco` command several times, each time passing a different *target* parameter. The supported targets are: + +* `pre-build`: prepares the build +* `compile-belle`: compiles Belle +* `compile-umbraco`: compiles Umbraco +* `pre-tests`: prepares the tests +* `compile-tests`: compiles the tests +* `pre-packages`: prepares the packages +* `pkg-zip`: creates the zip files +* `pre-nuget`: prepares NuGet packages +* `pkg-nuget`: creates NuGet packages + +All these targets are executed when `Build-Umbraco` is invoked without a parameter (or with the `all` parameter). On VSTS, compilations (of Umbraco and tests) are performed by dedicated VSTS tasks. Similarly, creating the NuGet packages is also performed by dedicated VSTS tasks. + +Finally, the produced artifacts are published in two containers that can be downloaded from VSTS: `zips` contains the zip files while `nuget` contains the NuGet packages. + +>During a VSTS build, some environment `UMBRACO_*` variables are exported by the `pre-build` target and can be reused in other targets *and* in VSTS tasks. The `UMBRACO_TMP` environment variable is used in `Umbraco.Tests` to disable some tests that have issues with VSTS at the moment. + +# Notes + +*This part needs to be cleaned up* + +Nightlies should use some sort of build number. + +We should increment versions as soon as a version is released. Ie, as soon as `7.6.33` is released, we should `Set-UmbracoVersion 7.6.34-alpha` and push. + +NuGet / NuSpec consistency checks are performed in tests. We should move it so it is done as part of the PowerShell script even before we try to compile and run the tests. + +There are still a few commands in `build` (to build docs, install Git or cleanup the install) that will need to be migrated to PowerShell. + +/eof \ No newline at end of file diff --git a/README.md b/README.md index 3e9be6cb5f..69aba2f06a 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,7 @@ Umbraco is a free open source Content Management System built on the ASP.NET pla ## 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 vs` in `src\Umbraco.Web.UI.Client`. - -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). +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 `gulp dev` in `src\Umbraco.Web.UI.Client`. Note that you can always [download a nightly build](http://nightly.umbraco.org/?container=umbraco-750) so you don't have to build the code yourself. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 4e685e577c..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,45 +0,0 @@ -version: '{build}' -shallow_clone: true -build_script: -- cmd: >- - SET SLN=%CD% - - SET SRC=%SLN%\src - - SET PACKAGES=%SRC%\packages - - CD build - - SET "release=" - - SET "comment=" - - FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SET "release=%%i" - - FOR /F "skip=2 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED comment SET "comment=%%i" - - ECHO AppVeyor Build %release% %comment% %APPVEYOR_BUILD_NUMBER% - - SET PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% - - %SRC%\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL - - XCOPY "%SRC%\Umbraco.Tests\unit-test-log4net.CI.config" "%SRC%\Umbraco.Tests\unit-test-log4net.config" /Y - - build.bat -integration -build:%APPVEYOR_BUILD_NUMBER% -tests -nugetfolder:%PACKAGES% - -test: - assemblies: src\Umbraco.Tests\bin\Debug\Umbraco.Tests.dll -artifacts: -- path: build\UmbracoCms.* - name: UmbracoFiles -- path: build\msbuild.log - name: BuildLog -notifications: -- provider: Slack - auth_token: - secure: v2csJi2V5ghR0rPdODK8GJdOGNCA+XaK84iQ9MdPOClqB+VU+40ybdKp6gPirGSH - channel: '#build-umbraco-core' - on_build_success: false - on_build_failure: true - on_build_status_changed: false \ No newline at end of file diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 83efcc89f0..02c69ceec8 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Reflection; @@ -34,8 +33,8 @@ namespace Umbraco.Core.Composing private readonly object _typesLock = new object(); private readonly Dictionary _types = new Dictionary(); - private long _cachedAssembliesHash = -1; - private long _currentAssembliesHash = -1; + private string _cachedAssembliesHash; + private string _currentAssembliesHash; private IEnumerable _assemblies; private bool _reportedChange; @@ -59,9 +58,9 @@ namespace Umbraco.Core.Composing if (detectChanges) { - //first check if the cached hash is 0, if it is then we ne + //first check if the cached hash is string.Empty, if it is then we need //do the check if they've changed - RequiresRescanning = (CachedAssembliesHash != CurrentAssembliesHash) || CachedAssembliesHash == 0; + RequiresRescanning = CachedAssembliesHash != CurrentAssembliesHash || CachedAssembliesHash == string.Empty; //if they have changed, we need to write the new file if (RequiresRescanning) { @@ -128,23 +127,20 @@ namespace Umbraco.Core.Composing /// /// Gets the currently cached hash value of the scanned assemblies. /// - /// The cached hash value, or 0 if no cache is found. - private long CachedAssembliesHash + /// The cached hash value, or string.Empty if no cache is found. + internal string CachedAssembliesHash { get { - if (_cachedAssembliesHash != -1) + if (_cachedAssembliesHash != null) return _cachedAssembliesHash; var filePath = GetTypesHashFilePath(); - if (File.Exists(filePath) == false) return 0; + if (File.Exists(filePath) == false) return string.Empty; var hash = File.ReadAllText(filePath, Encoding.UTF8); - long val; - if (long.TryParse(hash, out val) == false) return 0; - - _cachedAssembliesHash = val; + _cachedAssembliesHash = hash; return _cachedAssembliesHash; } } @@ -153,11 +149,11 @@ namespace Umbraco.Core.Composing /// Gets the current assemblies hash based on creating a hash from the assemblies in various places. /// /// The current hash. - private long CurrentAssembliesHash + private string CurrentAssembliesHash { get { - if (_currentAssembliesHash != -1) + if (_currentAssembliesHash != null) return _currentAssembliesHash; _currentAssembliesHash = GetFileHash(new List> @@ -182,7 +178,7 @@ namespace Umbraco.Core.Composing private void WriteCacheTypesHash() { var filePath = GetTypesHashFilePath(); - File.WriteAllText(filePath, CurrentAssembliesHash.ToString(), Encoding.UTF8); + File.WriteAllText(filePath, CurrentAssembliesHash, Encoding.UTF8); } /// @@ -193,41 +189,40 @@ namespace Umbraco.Core.Composing /// The hash. /// Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the /// file properties (false) or the file contents (true). - private static long GetFileHash(IEnumerable> filesAndFolders, ProfilingLogger logger) + private static string GetFileHash(IEnumerable> filesAndFolders, ProfilingLogger logger) { using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) { - var hashCombiner = new HashCodeCombiner(); - // get the distinct file infos to hash var uniqInfos = new HashSet(); var uniqContent = new HashSet(); - - foreach (var fileOrFolder in filesAndFolders) + using (var generator = new HashGenerator()) { - var info = fileOrFolder.Item1; - if (fileOrFolder.Item2) + foreach (var fileOrFolder in filesAndFolders) { - // add each unique file's contents to the hash - // normalize the content for cr/lf and case-sensitivity - - if (uniqContent.Contains(info.FullName)) continue; - uniqContent.Add(info.FullName); - if (File.Exists(info.FullName) == false) continue; - var content = RemoveCrLf(File.ReadAllText(info.FullName)); - hashCombiner.AddCaseInsensitiveString(content); - } - else - { - // add each unique folder/file to the hash - - if (uniqInfos.Contains(info.FullName)) continue; - uniqInfos.Add(info.FullName); - hashCombiner.AddFileSystemItem(info); + var info = fileOrFolder.Item1; + if (fileOrFolder.Item2) + { + // add each unique file's contents to the hash + // normalize the content for cr/lf and case-sensitivity + if (uniqContent.Add(info.FullName)) + { + if (File.Exists(info.FullName) == false) continue; + var content = RemoveCrLf(File.ReadAllText(info.FullName)); + generator.AddCaseInsensitiveString(content); + } + } + else + { + // add each unique folder/file to the hash + if (uniqInfos.Add(info.FullName)) + { + generator.AddFileSystemItem(info); + } + } } + return generator.GenerateHash(); } - - return ConvertHashToInt64(hashCombiner.GetCombinedHashCode()); } } @@ -252,36 +247,26 @@ namespace Umbraco.Core.Composing /// A profiling logger. /// The hash. // internal for tests - internal static long GetFileHash(IEnumerable filesAndFolders, ProfilingLogger logger) + internal static string GetFileHash(IEnumerable filesAndFolders, ProfilingLogger logger) { using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) { - var hashCombiner = new HashCodeCombiner(); - - // get the distinct file infos to hash - var uniqInfos = new HashSet(); - - foreach (var fileOrFolder in filesAndFolders) + using (var generator = new HashGenerator()) { - if (uniqInfos.Contains(fileOrFolder.FullName)) continue; - uniqInfos.Add(fileOrFolder.FullName); - hashCombiner.AddFileSystemItem(fileOrFolder); + // get the distinct file infos to hash + var uniqInfos = new HashSet(); + + foreach (var fileOrFolder in filesAndFolders) + { + if (uniqInfos.Contains(fileOrFolder.FullName)) continue; + uniqInfos.Add(fileOrFolder.FullName); + generator.AddFileSystemItem(fileOrFolder); + } + return generator.GenerateHash(); } - - return ConvertHashToInt64(hashCombiner.GetCombinedHashCode()); } } - /// - /// Converts a string hash value into an Int64. - /// - // internal for tests - internal static long ConvertHashToInt64(string val) - { - long outVal; - return long.TryParse(val, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out outVal) ? outVal : 0; - } - #endregion #region Cache @@ -384,6 +369,10 @@ namespace Umbraco.Core.Composing // internal for tests internal void WriteCache() { + // be absolutely sure + if (Directory.Exists(_tempFolder) == false) + Directory.CreateDirectory(_tempFolder); + var filePath = GeTypesListFilePath(); using (var stream = GetFileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, ListFileOpenWriteTimeout)) diff --git a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs b/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs index 842a67cb96..62cd1ab59c 100644 --- a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs +++ b/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs @@ -1,12 +1,19 @@ using System; -using System.Linq; +using System.Collections.Generic; +using System.IO; +using System.Web; using System.Xml.Linq; +using ClientDependency.Core.CompositeFiles.Providers; +using ClientDependency.Core.Config; using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Core.Configuration -{ - internal class ClientDependencyConfiguration +{ + /// + /// A utility class for working with CDF config and cache files - use sparingly! + /// + public class ClientDependencyConfiguration { private readonly ILogger _logger; private readonly string _fileName; @@ -21,7 +28,7 @@ namespace Umbraco.Core.Configuration /// /// Changes the version number in ClientDependency.config to a random value to avoid stale caches /// - internal bool IncreaseVersionNumber() + public bool IncreaseVersionNumber() { try { @@ -49,5 +56,55 @@ namespace Umbraco.Core.Configuration return false; } + + /// + /// Clears the temporary files stored for the ClientDependency folder + /// + /// + public bool ClearTempFiles(HttpContextBase currentHttpContext) + { + var cdfTempDirectories = new HashSet(); + foreach (BaseCompositeFileProcessingProvider provider in ClientDependencySettings.Instance + .CompositeFileProcessingProviderCollection) + { + var path = provider.CompositeFilePath.FullName; + cdfTempDirectories.Add(path); + } + + try + { + var fullPath = currentHttpContext.Server.MapPath(XmlFileMapper.FileMapVirtualFolder); + if (fullPath != null) + { + cdfTempDirectories.Add(fullPath); + } + } + catch (Exception ex) + { + //invalid path format or something... try/catch to be safe + LogHelper.Error("Could not get path from ClientDependency.config", ex); + } + + var success = true; + foreach (var directory in cdfTempDirectories) + { + var directoryInfo = new DirectoryInfo(directory); + if (directoryInfo.Exists == false) + continue; + + try + { + directoryInfo.Delete(true); + } + catch (Exception ex) + { + // Something could be locking the directory or the was another error, making sure we don't break the upgrade installer + LogHelper.Error("Could not clear temp files", ex); + success = false; + } + } + + return success; + } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs b/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs index c0773e64e3..a1221bc0d4 100644 --- a/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs +++ b/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs @@ -6,7 +6,7 @@ using System.Text; namespace Umbraco.Core.Configuration { - public class FileSystemProviderElement : ConfigurationElement + public class FileSystemProviderElement : ConfigurationElement, IFileSystemProviderElement { private const string ALIAS_KEY = "alias"; private const string TYPE_KEY = "type"; @@ -38,5 +38,30 @@ namespace Umbraco.Core.Configuration return ((KeyValueConfigurationCollection)(base[PARAMETERS_KEY])); } } + + string IFileSystemProviderElement.Alias + { + get { return Alias; } + } + + string IFileSystemProviderElement.Type + { + get { return Type; } + } + + private IDictionary _params; + IDictionary IFileSystemProviderElement.Parameters + { + get + { + if (_params != null) return _params; + _params = new Dictionary(); + foreach (KeyValueConfigurationElement element in Parameters) + { + _params.Add(element.Key, element.Value); + } + return _params; + } + } } } diff --git a/src/Umbraco.Core/Configuration/FileSystemProviderElementCollection.cs b/src/Umbraco.Core/Configuration/FileSystemProviderElementCollection.cs index 363eaba00b..26957dd68e 100644 --- a/src/Umbraco.Core/Configuration/FileSystemProviderElementCollection.cs +++ b/src/Umbraco.Core/Configuration/FileSystemProviderElementCollection.cs @@ -7,7 +7,7 @@ using System.Text; namespace Umbraco.Core.Configuration { [ConfigurationCollection(typeof(FileSystemProviderElement), AddItemName = "Provider")] - public class FileSystemProviderElementCollection : ConfigurationElementCollection + public class FileSystemProviderElementCollection : ConfigurationElementCollection, IEnumerable { protected override ConfigurationElement CreateNewElement() { @@ -19,12 +19,25 @@ namespace Umbraco.Core.Configuration return ((FileSystemProviderElement)(element)).Alias; } - new public FileSystemProviderElement this[string key] + public new FileSystemProviderElement this[string key] { get { return (FileSystemProviderElement)BaseGet(key); } } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as IFileSystemProviderElement; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } } } diff --git a/src/Umbraco.Core/Configuration/FileSystemProvidersSection.cs b/src/Umbraco.Core/Configuration/FileSystemProvidersSection.cs index 0da1cb802a..fa32fe0885 100644 --- a/src/Umbraco.Core/Configuration/FileSystemProvidersSection.cs +++ b/src/Umbraco.Core/Configuration/FileSystemProvidersSection.cs @@ -6,7 +6,7 @@ using System.Text; namespace Umbraco.Core.Configuration { - public class FileSystemProvidersSection : ConfigurationSection + public class FileSystemProvidersSection : ConfigurationSection, IFileSystemProvidersSection { [ConfigurationProperty("", IsDefaultCollection = true, IsRequired = true)] @@ -14,5 +14,17 @@ namespace Umbraco.Core.Configuration { get { return ((FileSystemProviderElementCollection)(base[""])); } } + + private IDictionary _providers; + + IDictionary IFileSystemProvidersSection.Providers + { + get + { + if (_providers != null) return _providers; + _providers = Providers.ToDictionary(x => x.Alias, x => x); + return _providers; + } + } } } diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index 517f80b383..aec50725e3 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -9,7 +9,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Configuration.Grid { - class GridEditorsConfig : IGridEditorsConfig + internal class GridEditorsConfig : IGridEditorsConfig { private readonly ILogger _logger; private readonly IRuntimeCacheProvider _runtimeCache; diff --git a/src/Umbraco.Core/Configuration/IFileSystemProviderElement.cs b/src/Umbraco.Core/Configuration/IFileSystemProviderElement.cs new file mode 100644 index 0000000000..9427f42b68 --- /dev/null +++ b/src/Umbraco.Core/Configuration/IFileSystemProviderElement.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Configuration +{ + public interface IFileSystemProviderElement + { + string Alias { get; } + string Type { get; } + IDictionary Parameters { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/IFileSystemProvidersSection.cs b/src/Umbraco.Core/Configuration/IFileSystemProvidersSection.cs new file mode 100644 index 0000000000..108d5a87de --- /dev/null +++ b/src/Umbraco.Core/Configuration/IFileSystemProvidersSection.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Configuration +{ + public interface IFileSystemProvidersSection + { + IDictionary Providers { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs index fd24d840ab..31ee4611d0 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs @@ -5,11 +5,23 @@ bool KeepUserLoggedIn { get; } bool HideDisabledUsersInBackoffice { get; } - + + /// + /// Used to enable/disable the forgot password functionality on the back office login screen + /// bool AllowPasswordReset { get; } string AuthCookieName { get; } - string AuthCookieDomain { get; } + string AuthCookieDomain { get; } + + /// + /// A boolean indicating that by default the email address will be the username + /// + /// + /// Even if this is true and the username is different from the email in the database, the username field will still be shown. + /// When this is false, the username and email fields will be shown in the user section. + /// + bool UsernameIsEmail { get; } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs index 88f5b975b0..8ae5816569 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs @@ -16,12 +16,28 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return GetOptionalTextElement("hideDisabledUsersInBackoffice", false); } } + /// + /// Used to enable/disable the forgot password functionality on the back office login screen + /// [ConfigurationProperty("allowPasswordReset")] internal InnerTextConfigurationElement AllowPasswordReset { get { return GetOptionalTextElement("allowPasswordReset", true); } } + /// + /// A boolean indicating that by default the email address will be the username + /// + /// + /// Even if this is true and the username is different from the email in the database, the username field will still be shown. + /// When this is false, the username and email fields will be shown in the user section. + /// + [ConfigurationProperty("usernameIsEmail")] + internal InnerTextConfigurationElement UsernameIsEmail + { + get { return GetOptionalTextElement("usernameIsEmail", true); } + } + [ConfigurationProperty("authCookieName")] internal InnerTextConfigurationElement AuthCookieName { @@ -44,11 +60,26 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return HideDisabledUsersInBackoffice; } } + /// + /// Used to enable/disable the forgot password functionality on the back office login screen + /// bool ISecuritySection.AllowPasswordReset { get { return AllowPasswordReset; } } + /// + /// A boolean indicating that by default the email address will be the username + /// + /// + /// Even if this is true and the username is different from the email in the database, the username field will still be shown. + /// When this is false, the username and email fields will be shown in the user section. + /// + bool ISecuritySection.UsernameIsEmail + { + get { return UsernameIsEmail; } + } + string ISecuritySection.AuthCookieName { get { return AuthCookieName; } diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index ccaacf6d5b..836f1fa135 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -232,6 +232,16 @@ namespace Umbraco.Core /// Guid for a Forms DataSource. /// public static readonly Guid LanguageGuid = new Guid(Language); + + /// + /// Guid for an Identifier Reservation. + /// + public const string IdReservation = "92849B1E-3904-4713-9356-F646F87C25F4"; + + /// + /// Guid for an Identifier Reservation. + /// + public static readonly Guid IdReservationGuid = new Guid(IdReservation); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 6db909ebe6..71b1d912f6 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -9,7 +9,8 @@ namespace Umbraco.Core { public const string AdminGroupAlias = "admin"; - + public const string TranslatorGroupAlias = "translator"; + public const string BackOfficeAuthenticationType = "UmbracoBackOffice"; public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie"; public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN"; @@ -18,7 +19,7 @@ namespace Umbraco.Core internal const string EmptyPasswordPrefix = "___UIDEMPTYPWORD__"; internal const string ForceReAuthFlag = "umbraco-force-auth"; - + /// /// The prefix used for external identity providers for their authentication type /// diff --git a/src/Umbraco.Core/CoreRuntime.cs b/src/Umbraco.Core/CoreRuntime.cs index bd7d435557..4d0a09f946 100644 --- a/src/Umbraco.Core/CoreRuntime.cs +++ b/src/Umbraco.Core/CoreRuntime.cs @@ -267,6 +267,7 @@ namespace Umbraco.Core // else, keep going, // anything other than install wants a database - see if we can connect + // (since this is an already existing database, assume localdb is ready) for (var i = 0; i < 5; i++) { connect = databaseFactory.CanConnect; diff --git a/src/Umbraco.Core/Deploy/IDeployContext.cs b/src/Umbraco.Core/Deploy/IDeployContext.cs index fe70bf6a81..01d1a8e98c 100644 --- a/src/Umbraco.Core/Deploy/IDeployContext.cs +++ b/src/Umbraco.Core/Deploy/IDeployContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; namespace Umbraco.Core.Deploy { @@ -38,5 +39,10 @@ namespace Umbraco.Core.Deploy /// The key of the item. /// The item with the specified key and type, if any, else null. T Item(string key) where T : class; + + ///// + ///// Gets the global deployment cancellation token. + ///// + //CancellationToken CancellationToken { get; } } } diff --git a/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs b/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs new file mode 100644 index 0000000000..a657ba78b6 --- /dev/null +++ b/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs @@ -0,0 +1,25 @@ +namespace Umbraco.Core.Deploy +{ + /// + /// Provides a method to retrieve an artifact's unique identifier. + /// + /// + /// Artifacts are uniquely identified by their , however they represent + /// elements in Umbraco that may be uniquely identified by another value. For example, + /// a content type is uniquely identified by its alias. If someone creates a new content + /// type, and tries to deploy it to a remote environment where a content type with the + /// same alias already exists, both content types end up having different + /// but the same alias. By default, Deploy would fail and throw when trying to save the + /// new content type (duplicate alias). However, if the connector also implements this + /// interface, the situation can be detected beforehand and reported in a nicer way. + /// + public interface IUniqueIdentifyingServiceConnector + { + /// + /// Gets the unique identifier of the specified artifact. + /// + /// The artifact. + /// The unique identifier. + string GetUniqueIdentifier(IArtifact artifact); + } +} diff --git a/src/Umbraco.Core/EmailSender.cs b/src/Umbraco.Core/EmailSender.cs new file mode 100644 index 0000000000..6f381f693c --- /dev/null +++ b/src/Umbraco.Core/EmailSender.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net.Mail; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using System.Web.Routing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Events; + +namespace Umbraco.Core +{ + /// + /// A utility class for sending emails + /// + public class EmailSender : IEmailSender + { + //TODO: This should encapsulate a BackgroundTaskRunner with a queue to send these emails! + + private readonly bool _enableEvents; + + /// + /// Default constructor + /// + public EmailSender() : this(false) + { + } + + internal EmailSender(bool enableEvents) + { + _enableEvents = enableEvents; + } + + private static readonly Lazy SmtpConfigured = new Lazy(() => GlobalSettings.HasSmtpServerConfigured(HttpRuntime.AppDomainAppVirtualPath)); + + /// + /// Sends the message non-async + /// + /// + public void Send(MailMessage message) + { + if (SmtpConfigured.Value == false && _enableEvents) + { + OnSendEmail(new SendEmailEventArgs(message)); + } + else + { + using (var client = new SmtpClient()) + { + client.Send(message); + } + } + } + + /// + /// Sends the message async + /// + /// + /// + public async Task SendAsync(MailMessage message) + { + if (SmtpConfigured.Value == false && _enableEvents) + { + OnSendEmail(new SendEmailEventArgs(message)); + } + else + { + using (var client = new SmtpClient()) + { + if (client.DeliveryMethod == SmtpDeliveryMethod.Network) + { + await client.SendMailAsync(message); + } + else + { + client.Send(message); + } + } + } + } + + /// + /// Returns true if the application should be able to send a required application email + /// + /// + /// We assume this is possible if either an event handler is registered or an smtp server is configured + /// + internal static bool CanSendRequiredEmail + { + get { return EventHandlerRegistered || SmtpConfigured.Value; } + } + + /// + /// returns true if an event handler has been registered + /// + internal static bool EventHandlerRegistered + { + get { return SendEmail != null; } + } + + /// + /// An event that is raised when no smtp server is configured if events are enabled + /// + internal static event EventHandler SendEmail; + + private static void OnSendEmail(SendEmailEventArgs e) + { + var handler = SendEmail; + if (handler != null) handler(null, e); + } + } +} diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index e5eb7f2d74..87d73fdd4a 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -136,7 +136,7 @@ namespace Umbraco.Core public static bool ContainsAll(this IEnumerable source, IEnumerable other) { if (source == null) throw new ArgumentNullException("source"); - if (other == null) throw new ArgumentNullException("other"); + if (other == null) throw new ArgumentNullException("other"); return other.Except(source).Any() == false; } @@ -352,5 +352,16 @@ namespace Umbraco.Core { return contents.Where(x => types.Contains(x.GetType())); } + + public static IEnumerable SkipLast(this IEnumerable source) + { + using (var e = source.GetEnumerator()) + { + if (e.MoveNext() == false) yield break; + + for (var value = e.Current; e.MoveNext(); value = e.Current) + yield return value; + } + } } } diff --git a/src/Umbraco.Core/Events/CancellableEventArgs.cs b/src/Umbraco.Core/Events/CancellableEventArgs.cs index 247b732145..c82bdf9229 100644 --- a/src/Umbraco.Core/Events/CancellableEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Security.Permissions; -using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.Events { @@ -13,6 +12,9 @@ namespace Umbraco.Core.Events public class CancellableEventArgs : EventArgs, IEquatable { private bool _cancel; + private Dictionary _eventState; + + private static readonly ReadOnlyDictionary EmptyAdditionalData = new ReadOnlyDictionary(new Dictionary()); public CancellableEventArgs(bool canCancel, EventMessages messages, IDictionary additionalData) { @@ -26,7 +28,7 @@ namespace Umbraco.Core.Events if (eventMessages == null) throw new ArgumentNullException("eventMessages"); CanCancel = canCancel; Messages = eventMessages; - AdditionalData = new ReadOnlyDictionary(new Dictionary()); + AdditionalData = EmptyAdditionalData; } public CancellableEventArgs(bool canCancel) @@ -34,18 +36,16 @@ namespace Umbraco.Core.Events CanCancel = canCancel; //create a standalone messages Messages = new EventMessages(); - AdditionalData = new ReadOnlyDictionary(new Dictionary()); + AdditionalData = EmptyAdditionalData; } public CancellableEventArgs(EventMessages eventMessages) : this(true, eventMessages) - { - } + { } public CancellableEventArgs() : this(true) - { - } + { } /// /// Flag to determine if this instance will support being cancellable @@ -100,6 +100,15 @@ namespace Umbraco.Core.Events /// public ReadOnlyDictionary AdditionalData { get; private set; } + /// + /// This can be used by event subscribers to store state in the event args so they easily deal with custom state data between a starting ("ing") + /// event and an ending ("ed") event + /// + public IDictionary EventState + { + get { return _eventState ?? (_eventState = new Dictionary()); } + } + public bool Equals(CancellableEventArgs other) { if (ReferenceEquals(null, other)) return false; @@ -111,13 +120,13 @@ namespace Umbraco.Core.Events { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; + if (obj.GetType() != GetType()) return false; return Equals((CancellableEventArgs) obj); } public override int GetHashCode() { - return (AdditionalData != null ? AdditionalData.GetHashCode() : 0); + return AdditionalData != null ? AdditionalData.GetHashCode() : 0; } public static bool operator ==(CancellableEventArgs left, CancellableEventArgs right) @@ -127,7 +136,7 @@ namespace Umbraco.Core.Events public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right) { - return !Equals(left, right); + return Equals(left, right) == false; } } } diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index a093e82d32..9b0d7a4e76 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -100,6 +100,7 @@ namespace Umbraco.Core.Events public IEnumerable DeletedEntities { get { return EventObject; } + internal set { EventObject = value; } // fixme ouch! } /// diff --git a/src/Umbraco.Core/Events/MoveEventArgs.cs b/src/Umbraco.Core/Events/MoveEventArgs.cs index a9d56a1ca5..8fae8a5e60 100644 --- a/src/Umbraco.Core/Events/MoveEventArgs.cs +++ b/src/Umbraco.Core/Events/MoveEventArgs.cs @@ -6,6 +6,8 @@ namespace Umbraco.Core.Events { public class MoveEventArgs : CancellableObjectEventArgs, IEquatable> { + private IEnumerable> _moveInfoCollection; + /// /// Constructor accepting a collection of MoveEventInfo objects /// @@ -107,7 +109,24 @@ namespace Umbraco.Core.Events /// /// Gets all MoveEventInfo objects used to create the object /// - public IEnumerable> MoveInfoCollection { get; private set; } + public IEnumerable> MoveInfoCollection + { + get { return _moveInfoCollection; } + set + { + var first = value.FirstOrDefault(); + if (first == null) + { + throw new InvalidOperationException("MoveInfoCollection must have at least one item"); + } + + _moveInfoCollection = value; + + //assign the legacy props + EventObject = first.Entity; + ParentId = first.NewParentId; + } + } /// /// The entity being moved diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs index 35d8fd3293..fa3bd269fa 100644 --- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs +++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs @@ -126,7 +126,7 @@ namespace Umbraco.Core.Events /// /// Boolean indicating whether the Recycle Bin was emptied successfully /// - public bool RecycleBinEmptiedSuccessfully { get; private set; } + public bool RecycleBinEmptiedSuccessfully { get; set; } /// /// Boolean indicating whether this event was fired for the Content's Recycle Bin. diff --git a/src/Umbraco.Core/Events/SendEmailEventArgs.cs b/src/Umbraco.Core/Events/SendEmailEventArgs.cs new file mode 100644 index 0000000000..8c6a2138d5 --- /dev/null +++ b/src/Umbraco.Core/Events/SendEmailEventArgs.cs @@ -0,0 +1,15 @@ +using System; +using System.Net.Mail; + +namespace Umbraco.Core.Events +{ + internal class SendEmailEventArgs : EventArgs + { + public MailMessage Message { get; private set; } + + public SendEmailEventArgs(MailMessage message) + { + Message = message; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/GuidUdi.cs b/src/Umbraco.Core/GuidUdi.cs index a80390479f..d78a33a435 100644 --- a/src/Umbraco.Core/GuidUdi.cs +++ b/src/Umbraco.Core/GuidUdi.cs @@ -32,7 +32,11 @@ namespace Umbraco.Core public GuidUdi(Uri uriValue) : base(uriValue) { - Guid = Guid.Parse(uriValue.AbsolutePath.TrimStart('/')); + Guid guid; + if (Guid.TryParse(uriValue.AbsolutePath.TrimStart('/'), out guid) == false) + throw new FormatException("Url \"" + uriValue + "\" is not a guid entity id."); + + Guid = guid; } /// @@ -43,16 +47,17 @@ namespace Umbraco.Core public new static GuidUdi Parse(string s) { var udi = Udi.Parse(s); - if (!(udi is GuidUdi)) + if (udi is GuidUdi == false) throw new FormatException("String \"" + s + "\" is not a guid entity id."); - return (GuidUdi)udi; + + return (GuidUdi) udi; } public static bool TryParse(string s, out GuidUdi udi) { Udi tmp; udi = null; - if (!TryParse(s, out tmp)) return false; + if (TryParse(s, out tmp) == false) return false; udi = tmp as GuidUdi; return udi != null; } @@ -75,10 +80,9 @@ namespace Umbraco.Core get { return Guid == Guid.Empty; } } - /// public GuidUdi EnsureClosed() { - base.EnsureNotRoot(); + EnsureNotRoot(); return this; } } diff --git a/src/Umbraco.Core/HashCodeCombiner.cs b/src/Umbraco.Core/HashCodeCombiner.cs index 73e442a3df..ebeb50fb8f 100644 --- a/src/Umbraco.Core/HashCodeCombiner.cs +++ b/src/Umbraco.Core/HashCodeCombiner.cs @@ -5,12 +5,15 @@ using System.IO; namespace Umbraco.Core { /// - /// Used to create a hash code from multiple objects. + /// Used to create a .NET HashCode from multiple objects. /// /// /// .Net has a class the same as this: System.Web.Util.HashCodeCombiner and of course it works for all sorts of things /// which we've not included here as we just need a quick easy class for this in order to create a unique /// hash of directories/files to see if they have changed. + /// + /// NOTE: It's probably best to not relying on the hashing result across AppDomains! If you need a constant/reliable hash value + /// between AppDomains use SHA1. This is perfect for hashing things in a very fast way for a single AppDomain. /// internal class HashCodeCombiner { diff --git a/src/Umbraco.Core/HashGenerator.cs b/src/Umbraco.Core/HashGenerator.cs new file mode 100644 index 0000000000..7306dc9045 --- /dev/null +++ b/src/Umbraco.Core/HashGenerator.cs @@ -0,0 +1,154 @@ +using System; +using System.Globalization; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Umbraco.Core +{ + /// + /// Used to generate a string hash using crypto libraries over multiple objects + /// + /// + /// This should be used to generate a reliable hash that survives AppDomain restarts. + /// This will use the crypto libs to generate the hash and will try to ensure that + /// strings, etc... are not re-allocated so it's not consuming much memory. + /// + internal class HashGenerator : DisposableObject + { + public HashGenerator() + { + _writer = new StreamWriter(_ms, Encoding.Unicode, 1024, leaveOpen: true); + } + + private readonly MemoryStream _ms = new MemoryStream(); + private StreamWriter _writer; + + internal void AddInt(int i) + { + _writer.Write(i); + } + + internal void AddLong(long i) + { + _writer.Write(i); + } + + internal void AddObject(object o) + { + _writer.Write(o); + } + + internal void AddDateTime(DateTime d) + { + _writer.Write(d.Ticks);; + } + + internal void AddString(string s) + { + if (s != null) + _writer.Write(s); + } + + internal void AddCaseInsensitiveString(string s) + { + //I've tried to no allocate a new string with this which can be done if we use the CompareInfo.GetSortKey method which will create a new + //byte array that we can use to write to the output, however this also allocates new objects so i really don't think the performance + //would be much different. In any case, i'll leave this here for reference. We could write the bytes out based on the sort key, + //this is how we could deal with case insensitivity without allocating another string + //for reference see: https://stackoverflow.com/a/10452967/694494 + //we could go a step further and s.Normalize() but we're not really dealing with crazy unicode with this class so far. + + if (s != null) + _writer.Write(s.ToUpperInvariant()); + } + + internal void AddFileSystemItem(FileSystemInfo f) + { + //if it doesn't exist, don't proceed. + if (f.Exists == false) + return; + + AddCaseInsensitiveString(f.FullName); + AddDateTime(f.CreationTimeUtc); + AddDateTime(f.LastWriteTimeUtc); + + //check if it is a file or folder + var fileInfo = f as FileInfo; + if (fileInfo != null) + { + AddLong(fileInfo.Length); + } + + var dirInfo = f as DirectoryInfo; + if (dirInfo != null) + { + foreach (var d in dirInfo.GetFiles()) + { + AddFile(d); + } + foreach (var s in dirInfo.GetDirectories()) + { + AddFolder(s); + } + } + } + + internal void AddFile(FileInfo f) + { + AddFileSystemItem(f); + } + + internal void AddFolder(DirectoryInfo d) + { + AddFileSystemItem(d); + } + + /// + /// Returns the generated hash output of all added objects + /// + /// + internal string GenerateHash() + { + //flush,close,dispose the writer,then create a new one since it's possible to keep adding after GenerateHash is called. + + _writer.Flush(); + _writer.Close(); + _writer.Dispose(); + _writer = new StreamWriter(_ms, Encoding.UTF8, 1024, leaveOpen: true); + + var hashType = CryptoConfig.AllowOnlyFipsAlgorithms ? "SHA1" : "MD5"; + + //create an instance of the correct hashing provider based on the type passed in + var hasher = HashAlgorithm.Create(hashType); + if (hasher == null) throw new InvalidOperationException("No hashing type found by name " + hashType); + using (hasher) + { + var buffer = _ms.GetBuffer(); + //get the hashed values created by our selected provider + var hashedByteArray = hasher.ComputeHash(buffer); + + //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")); + } + + //return the hashed value + return stringBuilder.ToString(); + } + } + + protected override void DisposeResources() + { + _writer.Close(); + _writer.Dispose(); + _ms.Close(); + _ms.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/IEmailSender.cs b/src/Umbraco.Core/IEmailSender.cs new file mode 100644 index 0000000000..7e6565a53b --- /dev/null +++ b/src/Umbraco.Core/IEmailSender.cs @@ -0,0 +1,13 @@ +using System.Net.Mail; +using System.Threading.Tasks; + +namespace Umbraco.Core +{ + /// + /// Simple abstraction to send an email message + /// + public interface IEmailSender + { + Task SendAsync(MailMessage message); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs index 76866a065b..7bdabb558c 100644 --- a/src/Umbraco.Core/IO/FileSystems.cs +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.IO { public class FileSystems { - private readonly FileSystemProvidersSection _config; + private readonly IFileSystemProvidersSection _config; private readonly ConcurrentSet _wrappers = new ConcurrentSet(); private readonly ILogger _logger; @@ -43,7 +43,8 @@ namespace Umbraco.Core.IO // but IScopeProviderInternal is not public public FileSystems(ILogger logger) { - _config = (FileSystemProvidersSection)ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders"); + // fixme inject config section => can be used by tests + _config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders"); _logger = logger; } @@ -213,8 +214,7 @@ namespace Umbraco.Core.IO private ProviderConstructionInfo GetUnderlyingFileSystemCtor(string alias, Func fallback) { // get config - var providerConfig = _config.Providers[alias]; - if (providerConfig == null) + if (_config.Providers.TryGetValue(alias, out var providerConfig) == false) { if (fallback != null) return null; throw new ArgumentException($"No provider found with alias {alias}."); @@ -232,14 +232,17 @@ namespace Umbraco.Core.IO // find a ctor matching the config parameters var paramCount = providerConfig.Parameters?.Count ?? 0; var constructor = providerType.GetConstructors().SingleOrDefault(x - => x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.AllKeys.Contains(y.Name))); + => x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.Keys.Contains(y.Name))); if (constructor == null) throw new InvalidOperationException($"Type {providerType.FullName} has no ctor matching the {paramCount} configuration parameter(s)."); var parameters = new object[paramCount]; if (providerConfig.Parameters != null) // keeps ReSharper happy + { + var allKeys = providerConfig.Parameters.Keys.ToArray(); for (var i = 0; i < paramCount; i++) - parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value; + parameters[i] = providerConfig.Parameters[allKeys[i]]; + } return new ProviderConstructionInfo { diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 54540b6e08..522c6cd29f 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; -using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -271,7 +270,7 @@ namespace Umbraco.Core.Models /// Value as an public virtual object GetValue(string propertyTypeAlias) { - return Properties[propertyTypeAlias].Value; + return Properties.Contains(propertyTypeAlias) ? Properties[propertyTypeAlias].Value : null; } /// @@ -282,6 +281,11 @@ namespace Umbraco.Core.Models /// Value as a public virtual TPassType GetValue(string propertyTypeAlias) { + if (Properties.Contains(propertyTypeAlias) == false) + { + return default(TPassType); + } + var convertAttempt = Properties[propertyTypeAlias].Value.TryConvertTo(); return convertAttempt.Success ? convertAttempt.Result : default(TPassType); } diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index c879455ec4..da02645cdb 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -1,7 +1,5 @@ using System; using System.ComponentModel; -using System.Diagnostics; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models { @@ -84,11 +82,11 @@ namespace Umbraco.Core.Models /// /// Gets the unique identifier of the published version, if any. /// - Guid PublishedVersionGuid { get; } - + Guid PublishedVersionGuid { get; } + /// /// Gets a value indicating whether the content item is a blueprint. - /// + /// bool IsBlueprint { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 37e7b3f8f4..7ecb500a30 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -247,6 +247,11 @@ namespace Umbraco.Core.Models.Identity } } + /// + /// This is a 1:1 mapping with IUser.IsApproved + /// + internal bool IsApproved { get; set; } + /// /// Overridden to make the retrieval lazy /// diff --git a/src/Umbraco.Core/Models/Identity/IdentityProfile.cs b/src/Umbraco.Core/Models/Identity/IdentityProfile.cs index 6870bff642..46baad29b7 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityProfile.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityProfile.cs @@ -22,6 +22,7 @@ namespace Umbraco.Core.Models.Identity .ForMember(dest => dest.EmailConfirmed, opt => opt.MapFrom(src => src.EmailConfirmedDate.HasValue)) .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) .ForMember(dest => dest.LockoutEndDateUtc, opt => opt.MapFrom(src => src.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?) null)) + .ForMember(dest => dest.IsApproved, opt => opt.MapFrom(src => src.IsApproved)) .ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.Username)) .ForMember(dest => dest.PasswordHash, opt => opt.MapFrom(user => GetPasswordHash(user.RawPasswordValue))) .ForMember(dest => dest.Culture, opt => opt.MapFrom(src => src.GetUserCulture(textService))) diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 07868f911a..00ef115afd 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Models.Membership /// [Serializable] [DataContract(IsReference = true)] - public class User : Entity, IUser + public class User : Entity, IUser, IProfile { /// /// Constructor for creating a new/empty user @@ -411,27 +411,31 @@ namespace Umbraco.Core.Models.Membership //now we need to flag this for saving (hack!) GroupsToSave.Add(realGroup); } - - //now we'll check if the user has a special 1:1 user group created for itself. This will occur if this method is used and also during an upgrade. - //this comes in the alias form of userName + 'Group' - var customUserGroup = groups.FirstOrDefault(x => x.Alias == (Username + "Group")); - if (customUserGroup != null) + else { - //if the group isn't IUserGroup we'll need to look it up - var realGroup = customUserGroup as IUserGroup ?? Current.Services.UserService.GetUserGroupById(customUserGroup.Id); - realGroup.AddAllowedSection(sectionAlias); - //now we need to flag this for saving (hack!) - GroupsToSave.Add(realGroup); + //now we'll check if the user has a special 1:1 user group created for itself. This will occur if this method is used and also during an upgrade. + //this comes in the alias form of userName + 'Group' + var customUserGroup = groups.FirstOrDefault(x => x.Alias == (Username + "Group")); + if (customUserGroup != null) + { + //if the group isn't IUserGroup we'll need to look it up + var realGroup = customUserGroup as IUserGroup ?? ApplicationContext.Current.Services.UserService.GetUserGroupById(customUserGroup.Id); + realGroup.AddAllowedSection(sectionAlias); + //now we need to flag this for saving (hack!) + GroupsToSave.Add(realGroup); + } + + //ok, so the user doesn't have a 1:1 group, we'll need to flag it for creation + var newUserGroup = new UserGroup + { + Alias = Username + "Group", + Name = "Group for " + Username + }; + newUserGroup.AddAllowedSection(sectionAlias); + //add this user to this new group + AddGroup(newUserGroup); + GroupsToSave.Add(newUserGroup); } - - //ok, so the user doesn't have a 1:1 group, we'll need to flag it for creation - var newUserGroup = new UserGroup - { - Alias = Username + "Group", - Name = "Group for " + Username - }; - newUserGroup.AddAllowedSection(sectionAlias); - GroupsToSave.Add(newUserGroup); } /// diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index f206abbe7e..9ededb5404 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -93,6 +93,10 @@ namespace Umbraco.Core.Models //NOTE: Consider checking type before value is set: item.PropertyType.DataTypeId == property.PropertyType.DataTypeId //Transfer the existing value to the new property var property = this[key]; + if (item.Id == 0 && property.Id != 0) + { + item.Id = property.Id; + } if (item.Value == null && property.Value != null) { item.Value = property.Value; diff --git a/src/Umbraco.Core/Models/Rdbms/UserDto.cs b/src/Umbraco.Core/Models/Rdbms/UserDto.cs index 7e39d2c3a3..a0d032745d 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserDto.cs @@ -102,6 +102,7 @@ namespace Umbraco.Core.Models.Rdbms /// [Column("avatar")] [NullSetting(NullSetting = NullSettings.Null)] + [Length(500)] public string Avatar { get; set; } [ResultColumn] diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs index 032b9d85f6..8b7c693518 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs @@ -51,10 +51,12 @@ namespace Umbraco.Core.Models.Rdbms [Column("startContentId")] [NullSetting(NullSetting = NullSettings.Null)] + [ForeignKey(typeof(NodeDto), Name = "FK_startContentId_umbracoNode_id")] public int? StartContentId { get; set; } [Column("startMediaId")] [NullSetting(NullSetting = NullSettings.Null)] + [ForeignKey(typeof(NodeDto), Name = "FK_startMediaId_umbracoNode_id")] public int? StartMediaId { get; set; } [ResultColumn] diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index fc2c7ace6a..799c09b531 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -52,7 +52,7 @@ namespace Umbraco.Core.Models private string _contentTypeThumbnail; public static readonly UmbracoEntity Root = new UmbracoEntity(false) { Path = "-1", Name = "root", HasChildren = true }; - + public UmbracoEntity() { AdditionalData = new Dictionary(); diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index c4513d8d94..f73738609c 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -193,6 +193,14 @@ namespace Umbraco.Core.Models [UmbracoObjectType(Constants.ObjectTypes.DocumentBlueprint, typeof(IContent))] [FriendlyName("DocumentBlueprint")] [UmbracoUdiType(Constants.UdiEntityType.DocumentBluePrint)] - DocumentBlueprint + DocumentBlueprint, + + /// + /// Reserved Identifier + /// + [UmbracoObjectType(Constants.ObjectTypes.IdReservation)] + [FriendlyName("Identifier Reservation")] + IdReservation + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 66aa3a1078..269e211f1c 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -4,8 +4,10 @@ using System.Globalization; using System.Linq; using System.Net; using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; using Umbraco.Core.Composing; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -23,7 +25,7 @@ namespace Umbraco.Core.Models var apps = user.AllowedSections; return apps.Any(uApp => uApp.InvariantEquals(app)); } - + /// /// Determines whether this user is an admin. /// @@ -141,7 +143,7 @@ namespace Umbraco.Core.Models catch (CultureNotFoundException) { //return the default one - return CultureInfo.GetCultureInfo("en"); + return CultureInfo.GetCultureInfo(GlobalSettings.DefaultUILanguage); } } @@ -187,7 +189,7 @@ namespace Umbraco.Core.Models throw new NotSupportedException("Path access is only determined on content or media"); } } - + internal static bool HasPathAccess(string path, int[] startNodeIds, int recycleBinId) { if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); @@ -200,15 +202,72 @@ namespace Umbraco.Core.Models if (startNodeIds.Contains(Constants.System.Root)) return true; - var formattedPath = "," + path + ","; + var formattedPath = string.Concat(",", path, ","); // only users with root access have access to the recycle bin, // if the above check didn't pass then access is denied - if (formattedPath.Contains("," + recycleBinId + ",")) + if (formattedPath.Contains(string.Concat(",", recycleBinId, ","))) return false; // check for a start node in the path - return startNodeIds.Any(x => formattedPath.Contains("," + x + ",")); + return startNodeIds.Any(x => formattedPath.Contains(string.Concat(",", x, ","))); + } + + internal static bool IsInBranchOfStartNode(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId, out bool hasPathAccess) + { + switch (recycleBinId) + { + case Constants.System.RecycleBinMedia: + return IsInBranchOfStartNode(entity.Path, user.CalculateMediaStartNodeIds(entityService), user.GetMediaStartNodePaths(entityService), out hasPathAccess); + case Constants.System.RecycleBinContent: + return IsInBranchOfStartNode(entity.Path, user.CalculateContentStartNodeIds(entityService), user.GetContentStartNodePaths(entityService), out hasPathAccess); + default: + throw new NotSupportedException("Path access is only determined on content or media"); + } + } + + internal static bool IsInBranchOfStartNode(string path, int[] startNodeIds, string[] startNodePaths, out bool hasPathAccess) + { + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", "path"); + + hasPathAccess = false; + + // check for no access + if (startNodeIds.Length == 0) + return false; + + // check for root access + if (startNodeIds.Contains(Constants.System.Root)) + { + hasPathAccess = true; + return true; + } + + //is it self? + var self = startNodePaths.Any(x => x == path); + if (self) + { + hasPathAccess = true; + return true; + } + + //is it ancestor? + var ancestor = startNodePaths.Any(x => x.StartsWith(path)); + if (ancestor) + { + hasPathAccess = false; + return true; + } + + //is it descendant? + var descendant = startNodePaths.Any(x => path.StartsWith(x)); + if (descendant) + { + hasPathAccess = true; + return true; + } + + return false; } // calc. start nodes, combining groups' and user's, and excluding what's in the bin @@ -216,7 +275,7 @@ namespace Umbraco.Core.Models { const string cacheKey = "AllContentStartNodes"; //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); + var valuesInUserCache = FromUserCache(user, cacheKey); if (valuesInUserCache != null) return valuesInUserCache; var gsn = user.Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value).Distinct().ToArray(); @@ -231,7 +290,7 @@ namespace Umbraco.Core.Models { const string cacheKey = "AllMediaStartNodes"; //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); + var valuesInUserCache = FromUserCache(user, cacheKey); if (valuesInUserCache != null) return valuesInUserCache; var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value).Distinct().ToArray(); @@ -241,7 +300,34 @@ namespace Umbraco.Core.Models return vals; } - private static int[] FromUserCache(IUser user, string cacheKey) + public static string[] GetMediaStartNodePaths(this IUser user, IEntityService entityService) + { + const string cacheKey = "MediaStartNodePaths"; + //try to look them up from cache so we don't recalculate + var valuesInUserCache = FromUserCache(user, cacheKey); + if (valuesInUserCache != null) return valuesInUserCache; + + var startNodeIds = user.CalculateMediaStartNodeIds(entityService); + var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray(); + ToUserCache(user, cacheKey, vals); + return vals; + } + + public static string[] GetContentStartNodePaths(this IUser user, IEntityService entityService) + { + const string cacheKey = "ContentStartNodePaths"; + //try to look them up from cache so we don't recalculate + var valuesInUserCache = FromUserCache(user, cacheKey); + if (valuesInUserCache != null) return valuesInUserCache; + + var startNodeIds = user.CalculateContentStartNodeIds(entityService); + var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray(); + ToUserCache(user, cacheKey, vals); + return vals; + } + + private static T FromUserCache(IUser user, string cacheKey) + where T: class { var entityUser = user as User; if (entityUser == null) return null; @@ -250,12 +336,13 @@ namespace Umbraco.Core.Models { object allContentStartNodes; return entityUser.AdditionalData.TryGetValue(cacheKey, out allContentStartNodes) - ? allContentStartNodes as int[] + ? allContentStartNodes as T : null; } } - private static void ToUserCache(IUser user, string cacheKey, int[] vals) + private static void ToUserCache(IUser user, string cacheKey, T vals) + where T: class { var entityUser = user as User; if (entityUser == null) return; @@ -315,8 +402,6 @@ namespace Umbraco.Core.Models var usn = new List(); foreach (var sn in userSn) { - if (groupSn.Contains(sn)) continue; // ignore, already there - string snp; if (paths.TryGetValue(sn, out snp) == false) continue; // ignore rogue node (no path) diff --git a/src/Umbraco.Core/Persistence/LocalDb.cs b/src/Umbraco.Core/Persistence/LocalDb.cs new file mode 100644 index 0000000000..f706979e17 --- /dev/null +++ b/src/Umbraco.Core/Persistence/LocalDb.cs @@ -0,0 +1,959 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlClient; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace Umbraco.Core.Persistence +{ + /// + /// Manages LocalDB databases. + /// + /// + /// Latest version is SQL Server 2016 Express LocalDB, + /// see https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/sql-server-2016-express-localdb + /// which can be installed by downloading the Express installer from https://www.microsoft.com/en-us/sql-server/sql-server-downloads + /// (about 5MB) then select 'download media' to download SqlLocalDB.msi (about 44MB), which you can execute. This installs + /// LocalDB only. Though you probably want to install the full Express. You may also want to install SQL Server Management + /// Studio which can be used to connect to LocalDB databases. + /// See also https://github.com/ritterim/automation-sql which is a somewhat simpler version of this. + /// + internal class LocalDb + { + private int _version; + private bool _hasVersion; + + #region Availability & Version + + /// + /// Gets the LocalDb installed version. + /// + /// If more than one version is installed, returns the highest available. Returns + /// the major version as an integer e.g. 11, 12... + /// Thrown when LocalDb is not available. + public int Version + { + get + { + EnsureVersion(); + if (_version <= 0) + throw new InvalidOperationException("LocalDb is not available."); + return _version; + } + } + + /// + /// Ensures that the LocalDb version is detected. + /// + private void EnsureVersion() + { + if (_hasVersion) return; + DetectVersion(); + _hasVersion = true; + } + + /// + /// Gets a value indicating whether LocalDb is available. + /// + public bool IsAvailable + { + get + { + EnsureVersion(); + return _version > 0; + } + } + + /// + /// Ensures that LocalDb is available. + /// + /// Thrown when LocalDb is not available. + private void EnsureAvailable() + { + if (IsAvailable == false) + throw new InvalidOperationException("LocalDb is not available."); + } + + /// + /// Detects LocalDb installed version. + /// + /// If more than one version is installed, the highest available is detected. + private void DetectVersion() + { + _hasVersion = true; + _version = -1; + + var programFiles = Environment.GetEnvironmentVariable("ProgramFiles"); + if (programFiles == null) return; + + // detect 14, 13, 12, 11 + for (var i = 14; i > 10; i--) + { + var path = Path.Combine(programFiles, string.Format(@"Microsoft SQL Server\{0}0\Tools\Binn\SqlLocalDB.exe", i)); + if (File.Exists(path) == false) continue; + _version = i; + break; + } + } + + #endregion + + #region Instances + + /// + /// Gets the name of existing LocalDb instances. + /// + /// The name of existing LocalDb instances. + /// Thrown when LocalDb is not available. + public string[] GetInstances() + { + EnsureAvailable(); + string output, error; + var rc = ExecuteSqlLocalDb("i", out output, out error); // info + if (rc != 0 || error != string.Empty) return null; + return output.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + } + + /// + /// Gets a value indicating whether a LocalDb instance exists. + /// + /// The name of the instance. + /// A value indicating whether a LocalDb instance with the specified name exists. + /// Thrown when LocalDb is not available. + public bool InstanceExists(string instanceName) + { + EnsureAvailable(); + var instances = GetInstances(); + return instances != null && instances.Contains(instanceName); + } + + /// + /// Creates a LocalDb instance. + /// + /// The name of the instance. + /// A value indicating whether the instance was created without errors. + /// Thrown when LocalDb is not available. + public bool CreateInstance(string instanceName) + { + EnsureAvailable(); + string output, error; + return ExecuteSqlLocalDb(string.Format("c \"{0}\"", instanceName), out output, out error) == 0 && error == string.Empty; + } + + /// + /// Drops a LocalDb instance. + /// + /// The name of the instance. + /// A value indicating whether the instance was dropped without errors. + /// Thrown when LocalDb is not available. + /// + /// When an instance is dropped all the attached database files are deleted. + /// Successful if the instance does not exist. + /// + public bool DropInstance(string instanceName) + { + EnsureAvailable(); + var instance = GetInstance(instanceName); + if (instance == null) return true; + instance.DropDatabases(); // else the files remain + + // -i force NOWAIT, -k kills + string output, error; + return ExecuteSqlLocalDb(string.Format("p \"{0}\" -i", instanceName), out output, out error) == 0 && error == string.Empty + && ExecuteSqlLocalDb(string.Format("d \"{0}\"", instanceName), out output, out error) == 0 && error == string.Empty; + } + + /// + /// Stops a LocalDb instance. + /// + /// The name of the instance. + /// A value indicating whether the instance was stopped without errors. + /// Thrown when LocalDb is not available. + /// + /// Successful if the instance does not exist. + /// + public bool StopInstance(string instanceName) + { + EnsureAvailable(); + if (InstanceExists(instanceName) == false) return true; + + // -i force NOWAIT, -k kills + string output, error; + return ExecuteSqlLocalDb(string.Format("p \"{0}\" -i", instanceName), out output, out error) == 0 && error == string.Empty; + } + + /// + /// Stops a LocalDb instance. + /// + /// The name of the instance. + /// A value indicating whether the instance was started without errors. + /// Thrown when LocalDb is not available. + /// + /// Failed if the instance does not exist. + /// + public bool StartInstance(string instanceName) + { + EnsureAvailable(); + if (InstanceExists(instanceName) == false) return false; + string output, error; + return ExecuteSqlLocalDb(string.Format("s \"{0}\"", instanceName), out output, out error) == 0 && error == string.Empty; + } + + /// + /// Gets a LocalDb instance. + /// + /// The name of the instance. + /// The instance with the specified name if it exists, otherwise null. + /// Thrown when LocalDb is not available. + public Instance GetInstance(string instanceName) + { + EnsureAvailable(); + return InstanceExists(instanceName) ? new Instance(instanceName) : null; + } + + #endregion + + #region Databases + + /// + /// Represents a LocalDb instance. + /// + /// + /// LocalDb is assumed to be available, and the instance is assumed to exist. + /// + public class Instance + { + private readonly string _masterCstr; + + /// + /// Gets the name of the instance. + /// + public string InstanceName { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// + public Instance(string instanceName) + { + InstanceName = instanceName; + _masterCstr = string.Format(@"Server=(localdb)\{0};Integrated Security=True;", instanceName); + } + + /// + /// Gets a LocalDb connection string. + /// + /// The name of the database. + /// The connection string for the specified database. + /// + /// The database should exist in the LocalDb instance. + /// + public string GetConnectionString(string databaseName) + { + return _masterCstr + string.Format(@"Database={0};", databaseName); + } + + /// + /// Gets a LocalDb connection string for an attached database. + /// + /// The name of the database. + /// The directory containing database files. + /// The connection string for the specified database. + /// + /// The database should not exist in the LocalDb instance. + /// It will be attached with its name being its MDF filename (full path), uppercased, when + /// the first connection is opened, and remain attached until explicitely detached. + /// + public string GetAttachedConnectionString(string databaseName, string filesPath) + { + string logName, baseFilename, baseLogFilename, mdfFilename, ldfFilename; + GetDatabaseFiles(databaseName, filesPath, out logName, out baseFilename, out baseLogFilename, out mdfFilename, out ldfFilename); + + return _masterCstr + string.Format(@"AttachDbFileName='{0}';", mdfFilename); + } + + /// + /// Gets the name of existing databases. + /// + /// The name of existing databases. + public string[] GetDatabases() + { + var userDatabases = new List(); + + using (var conn = new SqlConnection(_masterCstr)) + using (var cmd = conn.CreateCommand()) + { + conn.Open(); + + var databases = new Dictionary(); + + SetCommand(cmd, @" + SELECT name, filename FROM sys.sysdatabases"); + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + databases[reader.GetString(0)] = reader.GetString(1); + } + } + + foreach (var database in databases) + { + var dbname = database.Key; + + if (dbname == "master" || dbname == "tempdb" || dbname == "model" || dbname == "msdb") + continue; + + // fixme - shall we deal with stale databases? + // fixme - is it always ok to assume file names? + //var mdf = database.Value; + //var ldf = mdf.Replace(".mdf", "_log.ldf"); + //if (staleOnly && File.Exists(mdf) && File.Exists(ldf)) + // continue; + + //ExecuteDropDatabase(cmd, dbname, mdf, ldf); + //count++; + + userDatabases.Add(dbname); + } + } + + return userDatabases.ToArray(); + } + + /// + /// Gets a value indicating whether a database exists. + /// + /// The name of the database. + /// A value indicating whether a database with the specified name exists. + /// + /// A database exists if it is registered in the instance, and its files exist. If the database + /// is registered but some of its files are missing, the database is dropped. + /// + public bool DatabaseExists(string databaseName) + { + using (var conn = new SqlConnection(_masterCstr)) + using (var cmd = conn.CreateCommand()) + { + conn.Open(); + + var mdf = GetDatabase(cmd, databaseName); + if (mdf == null) return false; + + // it can exist, even though its files have been deleted + // if files exist assume all is ok (should we try to connect?) + var ldf = GetLogFilename(mdf); + if (File.Exists(mdf) && File.Exists(ldf)) + return true; + + ExecuteDropDatabase(cmd, databaseName, mdf, ldf); + } + + return false; + } + + /// + /// Creates a new database. + /// + /// The name of the database. + /// The directory containing database files. + /// A value indicating whether the database was created without errors. + /// + /// Failed if a database with the specified name already exists in the instance, + /// or if the database files already exist in the specified directory. + /// + public bool CreateDatabase(string databaseName, string filesPath) + { + string logName, baseFilename, baseLogFilename, mdfFilename, ldfFilename; + GetDatabaseFiles(databaseName, filesPath, out logName, out baseFilename, out baseLogFilename, out mdfFilename, out ldfFilename); + + using (var conn = new SqlConnection(_masterCstr)) + using (var cmd = conn.CreateCommand()) + { + conn.Open(); + + var mdf = GetDatabase(cmd, databaseName); + if (mdf != null) return false; + + // cannot use parameters on CREATE DATABASE + // ie "CREATE DATABASE @0 ..." does not work + SetCommand(cmd, string.Format(@" + CREATE DATABASE {0} + ON (NAME=N{1}, FILENAME={2}) + LOG ON (NAME=N{3}, FILENAME={4})", + QuotedName(databaseName), + QuotedName(databaseName, '\''), QuotedName(mdfFilename, '\''), + QuotedName(logName, '\''), QuotedName(ldfFilename, '\''))); + + var unused = cmd.ExecuteNonQuery(); + } + return true; + } + + /// + /// Drops a database. + /// + /// The name of the database. + /// A value indicating whether the database was dropped without errors. + /// + /// Successful if the database does not exist. + /// Deletes the database files. + /// + public bool DropDatabase(string databaseName) + { + using (var conn = new SqlConnection(_masterCstr)) + using (var cmd = conn.CreateCommand()) + { + conn.Open(); + + SetCommand(cmd, @" + SELECT name, filename FROM master.dbo.sysdatabases WHERE ('[' + name + ']' = @0 OR name = @0)", + databaseName); + + var mdf = GetDatabase(cmd, databaseName); + if (mdf == null) return true; + + ExecuteDropDatabase(cmd, databaseName, mdf); + } + + return true; + } + + /// + /// Drops stale databases. + /// + /// The number of databases that were dropped. + /// + /// A database is considered stale when its files cannot be found. + /// + public int DropStaleDatabases() + { + return DropDatabases(true); + } + + /// + /// Drops databases. + /// + /// A value indicating whether to delete only stale database. + /// The number of databases that were dropped. + /// + /// A database is considered stale when its files cannot be found. + /// + public int DropDatabases(bool staleOnly = false) + { + var count = 0; + using (var conn = new SqlConnection(_masterCstr)) + using (var cmd = conn.CreateCommand()) + { + conn.Open(); + + var databases = new Dictionary(); + + SetCommand(cmd, @" + SELECT name, filename FROM sys.sysdatabases"); + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + databases[reader.GetString(0)] = reader.GetString(1); + } + } + + foreach (var database in databases) + { + var dbname = database.Key; + + if (dbname == "master" || dbname == "tempdb" || dbname == "model" || dbname == "msdb") + continue; + + var mdf = database.Value; + var ldf = mdf.Replace(".mdf", "_log.ldf"); + if (staleOnly && File.Exists(mdf) && File.Exists(ldf)) + continue; + + ExecuteDropDatabase(cmd, dbname, mdf, ldf); + count++; + } + } + + return count; + } + + /// + /// Detaches a database. + /// + /// The name of the database. + /// The directory containing the database files. + /// Thrown when a database with the specified name does not exist. + public string DetachDatabase(string databaseName) + { + using (var conn = new SqlConnection(_masterCstr)) + using (var cmd = conn.CreateCommand()) + { + conn.Open(); + + var mdf = GetDatabase(cmd, databaseName); + if (mdf == null) + throw new InvalidOperationException("Database does not exist."); + + DetachDatabase(cmd, databaseName); + + return Path.GetDirectoryName(mdf); + } + } + + /// + /// Attaches a database. + /// + /// The name of the database. + /// The directory containing database files. + /// Thrown when a database with the specified name already exists. + public void AttachDatabase(string databaseName, string filesPath) + { + using (var conn = new SqlConnection(_masterCstr)) + using (var cmd = conn.CreateCommand()) + { + conn.Open(); + + var mdf = GetDatabase(cmd, databaseName); + if (mdf != null) + throw new InvalidOperationException("Database already exists."); + + AttachDatabase(cmd, databaseName, filesPath); + } + } + + /// + /// Gets the file names of a database. + /// + /// The name of the database. + /// The MDF logical name. + /// The LDF logical name. + /// The MDF filename. + /// The LDF filename. + public void GetFilenames(string databaseName, + out string mdfName, out string ldfName, + out string mdfFilename, out string ldfFilename) + { + using (var conn = new SqlConnection(_masterCstr)) + using (var cmd = conn.CreateCommand()) + { + conn.Open(); + + GetFilenames(cmd, databaseName, out mdfName, out ldfName, out mdfFilename, out ldfFilename); + } + } + + /// + /// Kills all existing connections. + /// + /// The name of the database. + public void KillConnections(string databaseName) + { + using (var conn = new SqlConnection(_masterCstr)) + using (var cmd = conn.CreateCommand()) + { + conn.Open(); + + SetCommand(cmd, @" + DECLARE @sql VARCHAR(MAX); + SELECT @sql = COALESCE(@sql,'') + 'kill ' + CONVERT(VARCHAR, SPId) + ';' + FROM master.sys.sysprocesses + WHERE DBId = DB_ID(@0) AND SPId <> @@SPId; + EXEC(@sql);", + databaseName); + cmd.ExecuteNonQuery(); + } + } + + /// + /// Gets a database. + /// + /// The Sql Command. + /// The name of the database. + /// The full filename of the MDF file, if the database exists, otherwise null. + private static string GetDatabase(SqlCommand cmd, string databaseName) + { + SetCommand(cmd, @" + SELECT name, filename FROM master.dbo.sysdatabases WHERE ('[' + name + ']' = @0 OR name = @0)", + databaseName); + + string mdf = null; + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + mdf = reader.GetString(1) ?? string.Empty; + while (reader.Read()) + { + } + } + + return mdf; + } + + /// + /// Drops a database and its files. + /// + /// The Sql command. + /// The name of the database. + /// The name of the database (MDF) file. + /// The name of the log (LDF) file. + private static void ExecuteDropDatabase(SqlCommand cmd, string databaseName, string mdf, string ldf = null) + { + try + { + // cannot use parameters on ALTER DATABASE + // ie "ALTER DATABASE @0 ..." does not work + SetCommand(cmd, string.Format(@" + ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE", + QuotedName(databaseName))); + + var unused1 = cmd.ExecuteNonQuery(); + } + catch (SqlException e) + { + if (e.Message.Contains("Unable to open the physical file") && e.Message.Contains("Operating system error 2:")) + { + // quite probably, the files were missing + // yet, it should be possible to drop the database anyways + // but we'll have to deal with the files + } + else + { + // no idea, throw + throw; + } + } + + // cannot use parameters on DROP DATABASE + // ie "DROP DATABASE @0 ..." does not work + SetCommand(cmd, string.Format(@" + DROP DATABASE {0}", + QuotedName(databaseName))); + + var unused2 = cmd.ExecuteNonQuery(); + + // be absolutely sure + if (File.Exists(mdf)) File.Delete(mdf); + ldf = ldf ?? GetLogFilename(mdf); + if (File.Exists(ldf)) File.Delete(ldf); + } + + /// + /// Gets the log (LDF) filename corresponding to a database (MDF) filename. + /// + /// The MDF filename. + /// + private static string GetLogFilename(string mdfFilename) + { + if (mdfFilename.EndsWith(".mdf") == false) + throw new ArgumentException("Not a valid MDF filename (no .mdf extension).", "mdfFilename"); + return mdfFilename.Substring(0, mdfFilename.Length - ".mdf".Length) + "_log.ldf"; + } + + /// + /// Detaches a database. + /// + /// The Sql command. + /// The name of the database. + private static void DetachDatabase(SqlCommand cmd, string databaseName) + { + // cannot use parameters on ALTER DATABASE + // ie "ALTER DATABASE @0 ..." does not work + SetCommand(cmd, string.Format(@" + ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE", + QuotedName(databaseName))); + + var unused1 = cmd.ExecuteNonQuery(); + + SetCommand(cmd, @" + EXEC sp_detach_db @dbname=@0", + databaseName); + + var unused2 = cmd.ExecuteNonQuery(); + } + + /// + /// Attaches a database. + /// + /// The Sql command. + /// The name of the database. + /// The directory containing database files. + private static void AttachDatabase(SqlCommand cmd, string databaseName, string filesPath) + { + string logName, baseFilename, baseLogFilename, mdfFilename, ldfFilename; + GetDatabaseFiles(databaseName, filesPath, out logName, out baseFilename, out baseLogFilename, out mdfFilename, out ldfFilename); + + // cannot use parameters on CREATE DATABASE + // ie "CREATE DATABASE @0 ..." does not work + SetCommand(cmd, string.Format(@" + CREATE DATABASE {0} + ON (NAME=N{1}, FILENAME={2}) + LOG ON (NAME=N{3}, FILENAME={4}) + FOR ATTACH", + QuotedName(databaseName), + QuotedName(databaseName, '\''), QuotedName(mdfFilename, '\''), + QuotedName(logName, '\''), QuotedName(ldfFilename, '\''))); + + var unused = cmd.ExecuteNonQuery(); + } + + /// + /// Sets a database command. + /// + /// The command. + /// The command text. + /// The command arguments. + /// + /// The command text must refer to arguments as @0, @1... each referring + /// to the corresponding position in . + /// + private static void SetCommand(SqlCommand cmd, string sql, params object[] args) + { + cmd.CommandType = CommandType.Text; + cmd.CommandText = sql; + cmd.Parameters.Clear(); + for (var i = 0; i < args.Length; i++) + cmd.Parameters.AddWithValue("@" + i, args[i]); + } + + /// + /// Gets the file names of a database. + /// + /// The Sql command. + /// The name of the database. + /// The MDF logical name. + /// The LDF logical name. + /// The MDF filename. + /// The LDF filename. + private void GetFilenames(SqlCommand cmd, string databaseName, + out string mdfName, out string ldfName, + out string mdfFilename, out string ldfFilename) + { + mdfName = ldfName = mdfFilename = ldfFilename = null; + + SetCommand(cmd, @" + SELECT DB_NAME(database_id), type_desc, name, physical_name + FROM master.sys.master_files + WHERE database_id=DB_ID(@0)", + databaseName); + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var type = reader.GetString(1); + if (type == "ROWS") + { + mdfName = reader.GetString(2); + ldfName = reader.GetString(3); + } + else if (type == "LOG") + { + ldfName = reader.GetString(2); + ldfFilename = reader.GetString(3); + } + } + } + } + } + + /// + /// Copy database files. + /// + /// The name of the source database. + /// The directory containing source database files. + /// The name of the target database. + /// The directory containing target database files. + /// The source database files extension. + /// The target database files extension. + /// A value indicating whether to overwrite the target files. + /// A value indicating whether to delete the source files. + /// + /// The , , + /// and parameters are optional. If they result in target being identical + /// to source, no copy is performed. If is false, nothing happens, otherwise the source + /// files are deleted. + /// If target is not identical to source, files are copied or moved, depending on the value of . + /// Extensions are used eg to copy MyDatabase.mdf to MyDatabase.mdf.temp. + /// + public void CopyDatabaseFiles(string databaseName, string filesPath, + string targetDatabaseName = null, string targetFilesPath = null, + string sourceExtension = null, string targetExtension = null, + bool overwrite = false, bool delete = false) + { + var nop = (targetFilesPath == null || targetFilesPath == filesPath) + && (targetDatabaseName == null || targetDatabaseName == databaseName) + && (sourceExtension == null && targetExtension == null || sourceExtension == targetExtension); + if (nop && delete == false) return; + + string logName, baseFilename, baseLogFilename, mdfFilename, ldfFilename; + GetDatabaseFiles(databaseName, filesPath, + out logName, out baseFilename, out baseLogFilename, out mdfFilename, out ldfFilename); + + if (sourceExtension != null) + { + mdfFilename += "." + sourceExtension; + ldfFilename += "." + sourceExtension; + } + + if (nop) + { + // delete + if (File.Exists(mdfFilename)) File.Delete(mdfFilename); + if (File.Exists(ldfFilename)) File.Delete(ldfFilename); + } + else + { + // copy or copy+delete ie move + string targetLogName, targetBaseFilename, targetLogFilename, targetMdfFilename, targetLdfFilename; + GetDatabaseFiles(targetDatabaseName ?? databaseName, targetFilesPath ?? filesPath, + out targetLogName, out targetBaseFilename, out targetLogFilename, out targetMdfFilename, out targetLdfFilename); + + if (targetExtension != null) + { + targetMdfFilename += "." + targetExtension; + targetLdfFilename += "." + targetExtension; + } + + if (delete) + { + if (overwrite && File.Exists(targetMdfFilename)) File.Delete(targetMdfFilename); + if (overwrite && File.Exists(targetLdfFilename)) File.Delete(targetLdfFilename); + File.Move(mdfFilename, targetMdfFilename); + File.Move(ldfFilename, targetLdfFilename); + } + else + { + File.Copy(mdfFilename, targetMdfFilename, overwrite); + File.Copy(ldfFilename, targetLdfFilename, overwrite); + } + } + } + + /// + /// Gets a value indicating whether database files exist. + /// + /// The name of the source database. + /// The directory containing source database files. + /// The database files extension. + /// A value indicating whether the database files exist. + /// + /// Extensions are used eg to copy MyDatabase.mdf to MyDatabase.mdf.temp. + /// + public bool DatabaseFilesExist(string databaseName, string filesPath, string extension = null) + { + string logName, baseFilename, baseLogFilename, mdfFilename, ldfFilename; + GetDatabaseFiles(databaseName, filesPath, + out logName, out baseFilename, out baseLogFilename, out mdfFilename, out ldfFilename); + + if (extension != null) + { + mdfFilename += "." + extension; + ldfFilename += "." + extension; + } + + return File.Exists(mdfFilename) && File.Exists(ldfFilename); + } + + /// + /// Gets the name of the database files. + /// + /// The name of the database. + /// The directory containing database files. + /// The name of the log. + /// The base filename (the MDF filename without the .mdf extension). + /// The base log filename (the LDF filename without the .ldf extension). + /// The MDF filename. + /// The LDF filename. + private static void GetDatabaseFiles(string databaseName, string filesPath, + out string logName, + out string baseFilename, out string baseLogFilename, + out string mdfFilename, out string ldfFilename) + { + logName = databaseName + "_log"; + baseFilename = Path.Combine(filesPath, databaseName); + baseLogFilename = Path.Combine(filesPath, logName); + mdfFilename = baseFilename + ".mdf"; + ldfFilename = baseFilename + "_log.ldf"; + } + + #endregion + + #region SqlLocalDB + + /// + /// Executes the SqlLocalDB command. + /// + /// The arguments. + /// The command standard output. + /// The command error output. + /// The process exit code. + /// + /// Execution is successful if the exit code is zero, and error is empty. + /// + private int ExecuteSqlLocalDb(string args, out string output, out string error) + { + var programFiles = Environment.GetEnvironmentVariable("ProgramFiles"); + if (programFiles == null) + { + output = string.Empty; + error = "SqlLocalDB.exe not found"; + return -1; + } + + var path = Path.Combine(programFiles, string.Format(@"Microsoft SQL Server\{0}0\Tools\Binn\SqlLocalDB.exe", _version)); + + var p = new Process + { + StartInfo = + { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + FileName = path, + Arguments = args, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden + } + }; + p.Start(); + output = p.StandardOutput.ReadToEnd(); + error = p.StandardError.ReadToEnd(); + p.WaitForExit(); + + return p.ExitCode; + } + + /// + /// Returns a Unicode string with the delimiters added to make the input string a valid SQL Server delimited identifier. + /// + /// The name to quote. + /// A quote character. + /// + /// + /// This is a C# implementation of T-SQL QUOTEDNAME. + /// is optional, it can be '[' (default), ']', '\'' or '"'. + /// + private static string QuotedName(string name, char quote = '[') + { + switch (quote) + { + case '[': + case ']': + return "[" + name.Replace("]", "]]") + "]"; + case '\'': + return "'" + name.Replace("'", "''") + "'"; + case '"': + return "\"" + name.Replace("\"", "\"\"") + "\""; + default: + throw new NotSupportedException("Not a valid quote character."); + } + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index b81674974e..369a0f7995 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -177,7 +177,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial private void CreateUmbracoUserData() { - _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); + _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en-US", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); } private void CreateUmbracoUserGroupData() @@ -185,7 +185,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = "translator", Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); } private void CreateUmbracoUser2UserGroupData() @@ -196,14 +196,19 @@ namespace Umbraco.Core.Persistence.Migrations.Initial private void CreateUmbracoUserGroup2AppData() { _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Content }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer }); _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Media }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users }); _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Members }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Forms }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Content }); _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Media }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Forms }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 4, AppAlias = Constants.Applications.Translation }); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/AddIndexToDictionaryKeyColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddIndexToDictionaryKeyColumn.cs similarity index 95% rename from src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/AddIndexToDictionaryKeyColumn.cs rename to src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddIndexToDictionaryKeyColumn.cs index d75a9165de..5f682e07cd 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/AddIndexToDictionaryKeyColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddIndexToDictionaryKeyColumn.cs @@ -1,7 +1,7 @@ using System.Linq; using Umbraco.Core.Persistence.SqlSyntax; -namespace Umbraco.Core.Persistence.Migrations.Upgrades.Temp8 +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero { [Migration("7.7.0", 5, Constants.System.UmbracoMigrationName)] public class AddIndexToDictionaryKeyColumn : MigrationBase diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs similarity index 98% rename from src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/AddUserGroupTables.cs rename to src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs index bee1d0e11e..3061fe97bc 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/AddUserGroupTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs @@ -2,7 +2,7 @@ using System.Linq; using Umbraco.Core.Models.Rdbms; -namespace Umbraco.Core.Persistence.Migrations.Upgrades.Temp8 +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero { [Migration("8.0.0", 1, Constants.System.UmbracoMigrationName)] public class AddUserGroupTables : MigrationBase diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/AddUserStartNodeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs similarity index 94% rename from src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/AddUserStartNodeTable.cs rename to src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs index a542acbc3e..c446cd072d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/AddUserStartNodeTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs @@ -1,7 +1,7 @@ using System.Linq; using Umbraco.Core.Models.Rdbms; -namespace Umbraco.Core.Persistence.Migrations.Upgrades.Temp8 +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero { [Migration("8.0.0", 2, Constants.System.UmbracoMigrationName)] public class AddUserStartNodeTable : MigrationBase diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/EnsureContentTemplatePermissions.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/EnsureContentTemplatePermissions.cs similarity index 94% rename from src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/EnsureContentTemplatePermissions.cs rename to src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/EnsureContentTemplatePermissions.cs index 9f04ab8130..d5ff6ff067 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/EnsureContentTemplatePermissions.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/EnsureContentTemplatePermissions.cs @@ -1,7 +1,7 @@ using System.Linq; using Umbraco.Core.Models.Rdbms; -namespace Umbraco.Core.Persistence.Migrations.Upgrades.Temp8 +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero { /// /// Ensures the built-in user groups have the blueprint permission by default on upgrade diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/ReduceDictionaryKeyColumnsSize.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/ReduceDictionaryKeyColumnsSize.cs similarity index 95% rename from src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/ReduceDictionaryKeyColumnsSize.cs rename to src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/ReduceDictionaryKeyColumnsSize.cs index 053a967dc0..fac6b19026 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/ReduceDictionaryKeyColumnsSize.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/ReduceDictionaryKeyColumnsSize.cs @@ -1,7 +1,7 @@ using System.Linq; using Umbraco.Core.Persistence.SqlSyntax; -namespace Umbraco.Core.Persistence.Migrations.Upgrades.Temp8 +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero { [Migration("8.0.0", 4, Constants.System.UmbracoMigrationName)] public class ReduceDictionaryKeyColumnsSize : MigrationBase diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/UpdateUserTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs similarity index 96% rename from src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/UpdateUserTables.cs rename to src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs index 81459d851e..0030812f32 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/Temp8/UpdateUserTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Security; -namespace Umbraco.Core.Persistence.Migrations.Upgrades.Temp8 +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero { [Migration("8.0.0", 0, Constants.System.UmbracoMigrationName)] public class UpdateUserTables : MigrationBase diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/ReduceLoginNameColumnsSize.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/ReduceLoginNameColumnsSize.cs index 304b0d2bbf..4f6d0de714 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/ReduceLoginNameColumnsSize.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/ReduceLoginNameColumnsSize.cs @@ -29,10 +29,13 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero var local = Context.GetLocalMigration(); - //if it exists we need to drop it first - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName"))) + //if an index exists on this table we need to drop it. Normally we'd check via index name but in some odd cases (i.e. Our) + //the index name is something odd (starts with "mi_"). In any case, the index cannot exist if we want to alter the column + //so we'll drop whatever index is there and add one with the correct name after. + var loginNameIndex = dbIndexes.FirstOrDefault(x => x.TableName.InvariantEquals("cmsMember") && x.ColumnName.InvariantEquals("LoginName")); + if (loginNameIndex != null) { - local.Delete.Index("IX_cmsMember_LoginName").OnTable("cmsMember"); + local.Delete.Index(loginNameIndex.IndexName).OnTable("cmsMember"); } //we can apply the col length change diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddIndexToUmbracoNodeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddIndexToUmbracoNodeTable.cs deleted file mode 100644 index ba2de4fe2f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddIndexToUmbracoNodeTable.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwoZero -{ - [Migration("7.2.0", 3, Constants.System.UmbracoMigrationName)] - public class AddIndexToUmbracoNodeTable : MigrationBase - { - private readonly bool _skipIndexCheck; - - internal AddIndexToUmbracoNodeTable(bool skipIndexCheck, IMigrationContext context) - : base(context) - { - _skipIndexCheck = skipIndexCheck; - } - - public AddIndexToUmbracoNodeTable(IMigrationContext context) - : base(context) - { } - - public override void Up() - { - var dbIndexes = _skipIndexCheck ? new DbIndexDefinition[] { } : SqlSyntax.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodeUniqueID")) == false) - { - Create.Index("IX_umbracoNodeUniqueID").OnTable("umbracoNode").OnColumn("uniqueID").Ascending().WithOptions().NonClustered(); - } - } - - public override void Down() - { - Delete.Index("IX_umbracoNodeUniqueID").OnTable("umbracoNode"); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 30b176ca30..318c1f03ee 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -28,6 +28,7 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ITagRepository _tagRepository; private readonly CacheHelper _cacheHelper; private PermissionRepository _permissionRepository; + private readonly ContentByGuidReadRepository _contentByGuidReadRepository; public ContentRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection settings) : base(work, cacheHelper, logger) @@ -39,6 +40,7 @@ namespace Umbraco.Core.Persistence.Repositories _publishedQuery = work.Query().Where(x => x.Published); // fixme not used? + _contentByGuidReadRepository = new ContentByGuidReadRepository(this, work, cacheHelper, logger, syntaxProvider); EnsureUniqueNaming = settings.EnsureUniqueNaming; } @@ -194,6 +196,8 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsTask WHERE nodeId = @Id", "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoUserStartNode WHERE startNode = @Id", + "UPDATE umbracoUserGroup SET startContentId = NULL WHERE startContentId = @Id", "DELETE FROM umbracoRelation WHERE parentId = @Id", "DELETE FROM umbracoRelation WHERE childId = @Id", "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", @@ -205,7 +209,7 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsContentXml WHERE nodeId = @Id", "DELETE FROM cmsContent WHERE nodeId = @Id", "DELETE FROM umbracoAccess WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" + "DELETE FROM umbracoNode WHERE id = @Id" }; return list; } @@ -338,7 +342,24 @@ namespace Umbraco.Core.Persistence.Repositories nodeDto.Path = parent.Path; nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); nodeDto.SortOrder = sortOrder; - var unused = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); + + // note: + // there used to be a check on Database.IsNew(nodeDto) here to either Insert or Update, + // but I cannot figure out what was the point, as the node should obviously be new if + // we reach that point - removed. + + // see if there's a reserved identifier for this unique id + var sql = new Sql("SELECT id FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", nodeDto.UniqueId, Constants.ObjectTypes.IdReservationGuid); + var id = Database.ExecuteScalar(sql); + if (id > 0) + { + nodeDto.NodeId = id; + Database.Update(nodeDto); + } + else + { + Database.Insert(nodeDto); + } //Update with new correct path nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); @@ -697,7 +718,7 @@ namespace Umbraco.Core.Persistence.Repositories { PermissionRepository.AddOrUpdate(permission); } - + /// /// Gets paged content results /// @@ -766,6 +787,113 @@ WHERE (@path LIKE {5})", #endregion + #region Read Repository implementation for GUID keys + public IContent Get(Guid id) + { + return _contentByGuidReadRepository.Get(id); + } + + IEnumerable IReadRepository.GetAll(params Guid[] ids) + { + return _contentByGuidReadRepository.GetAll(ids); + } + + public bool Exists(Guid id) + { + return _contentByGuidReadRepository.Exists(id); + } + + /// + /// A reading repository purely for looking up by GUID + /// + /// + /// TODO: This is ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things! + /// Then we can do the same thing with repository instances and we wouldn't need to leave all these methods as not implemented because we wouldn't need to implement them + /// + private class ContentByGuidReadRepository : PetaPocoRepositoryBase + { + private readonly ContentRepository _outerRepo; + + public ContentByGuidReadRepository(ContentRepository outerRepo, + IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) + { + _outerRepo = outerRepo; + } + + protected override IContent PerformGet(Guid id) + { + var sql = _outerRepo.GetBaseQuery(BaseQueryType.FullSingle) + .Where(GetBaseWhereClause(), new { Id = id }) + .Where(x => x.Newest, SqlSyntax) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + + if (dto == null) + return null; + + var content = _outerRepo.CreateContentFromDto(dto, sql); + + return content; + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + Func translate = s => + { + if (ids.Any()) + { + s.Where("umbracoNode.uniqueID in (@ids)", new { ids }); + } + //we only want the newest ones with this method + s.Where(x => x.Newest, SqlSyntax); + return s; + }; + + var sqlBaseFull = _outerRepo.GetBaseQuery(BaseQueryType.FullMultiple); + var sqlBaseIds = _outerRepo.GetBaseQuery(BaseQueryType.Ids); + + return _outerRepo.ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds))); + } + + protected override Sql GetBaseQuery(bool isCount) + { + return _outerRepo.GetBaseQuery(isCount); + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.uniqueID = @Id"; + } + + protected override Guid NodeObjectTypeId + { + get { return _outerRepo.NodeObjectTypeId; } + } + + #region Not needed to implement + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + protected override IEnumerable GetDeleteClauses() + { + throw new NotImplementedException(); + } + protected override void PersistNewItem(IContent entity) + { + throw new NotImplementedException(); + } + protected override void PersistUpdatedItem(IContent entity) + { + throw new NotImplementedException(); + } + #endregion + } + #endregion + protected override string GetDatabaseFieldNameForOrderBy(string orderBy) { // NOTE see sortby.prevalues.controller.js for possible values @@ -933,31 +1061,10 @@ WHERE (@path LIKE {5})", if (EnsureUniqueNaming == false) return nodeName; - var sql = Sql() - .SelectAll() - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); + var names = Database.Fetch("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId", + new { objectType = NodeObjectTypeId, parentId }); - int uniqueNumber = 1; - var currentName = nodeName; - - var dtos = Database.Fetch(sql); - if (dtos.Any()) - { - var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); - foreach (var dto in results) - { - if (id != 0 && id == dto.NodeId) continue; - - if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) - { - currentName = nodeName + $" ({uniqueNumber})"; - uniqueNumber++; - } - } - } - - return currentName; + return SimilarNodeName.GetUniqueName(names, id, nodeName); } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 4444050efb..541209bbb7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -461,31 +461,10 @@ AND umbracoNode.id <> @id", private string EnsureUniqueNodeName(string nodeName, int id = 0) { - var sql = Sql() - .SelectAll() - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId && x.Text.StartsWith(nodeName)); + var names = Database.Fetch("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType", + new { objectType = NodeObjectTypeId }); - int uniqueNumber = 1; - var currentName = nodeName; - - var dtos = Database.Fetch(sql); - if (dtos.Any()) - { - var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); - foreach (var dto in results) - { - if (id != 0 && id == dto.NodeId) continue; - - if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) - { - currentName = nodeName + string.Format(" ({0})", uniqueNumber); - uniqueNumber++; - } - } - } - - return currentName; + return SimilarNodeName.GetUniqueName(names, id, nodeName); } /// diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs index bc33fd0aef..a0d9d14359 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs @@ -249,6 +249,19 @@ namespace Umbraco.Core.Persistence.Repositories return GetByQuery(query); } + public Dictionary GetDictionaryItemKeyMap() + { + var columns = new[] { "key", "id" }.Select(x => (object) SqlSyntax.GetQuotedColumnName(x)).ToArray(); + var sql = new Sql().Select(columns).From(SqlSyntax); + return Database.Fetch(sql).ToDictionary(x => x.Key, x => x.Id); + } + + private class DictionaryItemKeyIdDto + { + public string Key { get; set; } + public Guid Id { get; set; } + } + public IEnumerable GetDictionaryItemDescendants(Guid? parentId) { //This methods will look up children at each level, since we do not store a path for dictionary (ATM), we need to do a recursive diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index 6641fe69b9..48f25c9196 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository + public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IReadRepository { /// /// Get the count of published items diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs index 8962b027e1..d4cbde17ee 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs @@ -9,5 +9,6 @@ namespace Umbraco.Core.Persistence.Repositories IDictionaryItem Get(Guid uniqueId); IDictionaryItem Get(string key); IEnumerable GetDictionaryItemDescendants(Guid? parentId); + Dictionary GetDictionaryItemKeyMap(); } -} +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index 17a4e21564..f88309aa5b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -2,8 +2,26 @@ namespace Umbraco.Core.Persistence.Repositories { - public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository + public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository, IReadRepository { - IMedia GetMediaByPath(string mediaPath); + /// + /// Used to add/update published xml for the media item + /// + /// + /// + void AddOrUpdateContentXml(IMedia content, Func xml); + + /// + /// Used to remove the content xml for a content item + /// + /// + void DeleteContentXml(IMedia content); + + /// + /// Used to add/update preview xml for the content item + /// + /// + /// + void AddOrUpdatePreviewXml(IMedia content, Func xml); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs index 12d23bb0b8..28835aa56a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs @@ -44,13 +44,18 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - /// + /// /// - /// Optional parameter to filter by specified user groups + /// + /// A filter to only include user that belong to these user groups + /// + /// + /// A filter to only include users that do not belong to these user groups + /// /// Optional parameter to filter by specfied user state /// - IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - Expression> orderBy, Direction orderDirection = Direction.Ascending, + IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + Expression> orderBy, Direction orderDirection = Direction.Ascending, string[] userGroups = null, UserState[] userState = null, IQuery filter = null); /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 2b47f05e87..44bd3767ce 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -24,12 +24,14 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly IMediaTypeRepository _mediaTypeRepository; private readonly ITagRepository _tagRepository; + private readonly MediaByGuidReadRepository _mediaByGuidReadRepository; public MediaRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection) : base(work, cache, logger /*, contentSection*/) { _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); + _mediaByGuidReadRepository = new MediaByGuidReadRepository(this, work, cache, logger, sqlSyntax); EnsureUniqueNaming = contentSection.EnsureUniqueNaming; } @@ -128,6 +130,8 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsTask WHERE nodeId = @Id", "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoUserStartNode WHERE startNode = @Id", + "UPDATE umbracoUserGroup SET startMediaId = NULL WHERE startMediaId = @Id", "DELETE FROM umbracoRelation WHERE parentId = @Id", "DELETE FROM umbracoRelation WHERE childId = @Id", "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", @@ -248,7 +252,24 @@ namespace Umbraco.Core.Persistence.Repositories nodeDto.Path = parent.Path; nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); nodeDto.SortOrder = sortOrder; - var unused = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); + + // note: + // there used to be a check on Database.IsNew(nodeDto) here to either Insert or Update, + // but I cannot figure out what was the point, as the node should obviously be new if + // we reach that point - removed. + + // see if there's a reserved identifier for this unique id + var sql = new Sql("SELECT id FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", nodeDto.UniqueId, Constants.ObjectTypes.IdReservationGuid); + var id = Database.ExecuteScalar(sql); + if (id > 0) + { + nodeDto.NodeId = id; + Database.Update(nodeDto); + } + else + { + Database.Insert(nodeDto); + } //Update with new correct path nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); @@ -397,6 +418,104 @@ namespace Umbraco.Core.Persistence.Repositories #endregion + #region Read Repository implementation for GUID keys + public IMedia Get(Guid id) + { + return _mediaByGuidReadRepository.Get(id); + } + + IEnumerable IReadRepository.GetAll(params Guid[] ids) + { + return _mediaByGuidReadRepository.GetAll(ids); + } + + public bool Exists(Guid id) + { + return _mediaByGuidReadRepository.Exists(id); + } + + /// + /// A reading repository purely for looking up by GUID + /// + /// + /// TODO: This is ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things! + /// Then we can do the same thing with repository instances and we wouldn't need to leave all these methods as not implemented because we wouldn't need to implement them + /// + private class MediaByGuidReadRepository : PetaPocoRepositoryBase + { + private readonly MediaRepository _outerRepo; + + public MediaByGuidReadRepository(MediaRepository outerRepo, + IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) + { + _outerRepo = outerRepo; + } + + protected override IMedia PerformGet(Guid id) + { + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id }); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); + + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + + if (dto == null) + return null; + + var content = _outerRepo.CreateMediaFromDto(dto, sql); + + return content; + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + var sql = GetBaseQuery(false); + if (ids.Any()) + { + sql.Where("umbracoNode.uniqueID in (@ids)", new { ids = ids }); + } + + return _outerRepo.ProcessQuery(sql, new PagingSqlQuery(sql)); + } + + protected override Sql GetBaseQuery(bool isCount) + { + return _outerRepo.GetBaseQuery(isCount); + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.uniqueID = @Id"; + } + + protected override Guid NodeObjectTypeId + { + get { return _outerRepo.NodeObjectTypeId; } + } + + #region Not needed to implement + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + protected override IEnumerable GetDeleteClauses() + { + throw new NotImplementedException(); + } + protected override void PersistNewItem(IMedia entity) + { + throw new NotImplementedException(); + } + protected override void PersistUpdatedItem(IMedia entity) + { + throw new NotImplementedException(); + } + #endregion + } + #endregion + /// /// Gets paged media results /// @@ -509,31 +628,10 @@ namespace Umbraco.Core.Persistence.Repositories if (EnsureUniqueNaming == false) return nodeName; - var sql = Sql() - .SelectAll() - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); + var names = Database.Fetch("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId", + new { objectType = NodeObjectTypeId, parentId }); - int uniqueNumber = 1; - var currentName = nodeName; - - var dtos = Database.Fetch(sql); - if (dtos.Any()) - { - var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); - foreach (var dto in results) - { - if (id != 0 && id == dto.NodeId) continue; - - if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) - { - currentName = $"{nodeName} ({uniqueNumber})"; - uniqueNumber++; - } - } - } - - return currentName; + return SimilarNodeName.GetUniqueName(names, id, nodeName); } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index d87504b514..cd7a701b4c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -169,30 +169,16 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetMemberGroupsForMember(string username) { - //find the member by username - var memberObjectType = new Guid(Constants.ObjectTypes.Member); - - var memberSql = Sql() - .Select("umbracoNode.id") - .From() - .InnerJoin() - .On(dto => dto.NodeId, dto => dto.NodeId) - .Where( x => x.NodeObjectType == memberObjectType) - .Where(x => x.LoginName == username); - var memberIdUsername = Database.Fetch(memberSql).FirstOrDefault(); - if (memberIdUsername.HasValue == false) - { - return Enumerable.Empty(); - } - var sql = Sql() - .Select("umbracoNode.*") - .From() - .InnerJoin() - .On( dto => dto.NodeId, dto => dto.MemberGroup) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.Member == memberIdUsername.Value); - + .Select("un.*") + .From("umbracoNode AS un") + .InnerJoin("cmsMember2MemberGroup") + .On("un.id = cmsMember2MemberGroup.MemberGroup") + .LeftJoin("(SELECT umbracoNode.id, cmsMember.LoginName FROM umbracoNode INNER JOIN cmsMember ON umbracoNode.id = cmsMember.nodeId) AS member") + .On("member.id = cmsMember2MemberGroup.Member") + .Where("un.nodeObjectType=@objectType", new {objectType = NodeObjectTypeId }) + .Where("member.LoginName=@loginName", new {loginName = username}); + return Database.Fetch(sql) .DistinctBy(dto => dto.NodeId) .Select(x => _modelFactory.BuildEntity(x)); diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index d1d9a22d95..a4beb5d604 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using NPoco; +using Microsoft.AspNet.Identity; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; @@ -238,6 +239,18 @@ namespace Umbraco.Core.Persistence.Repositories //Create the first entry in cmsMember dto.NodeId = nodeDto.NodeId; + + //if the password is empty, generate one with the special prefix + //this will hash the guid with a salt so should be nicely random + if (entity.RawPasswordValue.IsNullOrWhiteSpace()) + { + var aspHasher = new PasswordHasher(); + dto.Password = Constants.Security.EmptyPasswordPrefix + + aspHasher.HashPassword(Guid.NewGuid().ToString("N")); + //re-assign + entity.RawPasswordValue = dto.Password; + } + Database.Insert(dto); //Create the PropertyData for this version - cmsPropertyData @@ -406,7 +419,7 @@ namespace Umbraco.Core.Persistence.Repositories return MapQueryDtos(Database.Fetch(sql), true); } - + public override IMember GetByVersion(Guid versionId) { var sql = GetBaseQuery(false); diff --git a/src/Umbraco.Core/Persistence/Repositories/SimilarNodeName.cs b/src/Umbraco.Core/Persistence/Repositories/SimilarNodeName.cs new file mode 100644 index 0000000000..371f73b27f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/SimilarNodeName.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Persistence.Repositories +{ + internal class SimilarNodeName + { + private int _numPos = -2; + + public int Id { get; set; } + public string Name { get; set; } + + // cached - reused + public int NumPos + { + get + { + if (_numPos != -2) return _numPos; + + var name = Name; + + if (name[name.Length - 1] != ')') + return _numPos = -1; + + var pos = name.LastIndexOf('('); + if (pos < 2 || pos == name.Length - 2) // < 2 and not < 0, because we want at least "x (" + return _numPos = -1; + + return _numPos = pos; + } + } + + // not cached - used only once + public int NumVal + { + get + { + if (NumPos < 0) + throw new InvalidOperationException(); + int num; + if (int.TryParse(Name.Substring(NumPos + 1, Name.Length - 2 - NumPos), out num)) + return num; + return 0; + } + } + + // compare without allocating, nor parsing integers + internal class Comparer : IComparer + { + public int Compare(SimilarNodeName x, SimilarNodeName y) + { + if (x == null) throw new ArgumentNullException("x"); + if (y == null) throw new ArgumentNullException("y"); + + var xpos = x.NumPos; + var ypos = y.NumPos; + + var xname = x.Name; + var yname = y.Name; + + if (xpos < 0 || ypos < 0 || xpos != ypos) + return string.Compare(xname, yname, StringComparison.Ordinal); + + // compare the part before (number) + var n = string.Compare(xname, 0, yname, 0, xpos, StringComparison.Ordinal); + if (n != 0) + return n; + + // compare (number) lengths + var diff = xname.Length - yname.Length; + if (diff != 0) return diff < 0 ? -1 : +1; + + // actually compare (number) + var i = xpos; + while (i < xname.Length - 1) + { + if (xname[i] != yname[i]) + return xname[i] < yname[i] ? -1 : +1; + i++; + } + return 0; + } + } + + // gets a unique name + public static string GetUniqueName(IEnumerable names, int nodeId, string nodeName) + { + var uniqueNumber = 1; + var uniqueing = false; + foreach (var name in names.OrderBy(x => x, new Comparer())) + { + // ignore self + if (nodeId != 0 && name.Id == nodeId) continue; + + if (uniqueing) + { + if (name.NumPos > 0 && name.Name.StartsWith(nodeName) && name.NumVal == uniqueNumber) + uniqueNumber++; + else + break; + } + else if (name.Name.InvariantEquals(nodeName)) + { + uniqueing = true; + } + } + + return uniqueing ? string.Concat(nodeName, " (", uniqueNumber.ToString(), ")") : nodeName; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/SimilarNodeNameComparer.cs b/src/Umbraco.Core/Persistence/Repositories/SimilarNodeNameComparer.cs deleted file mode 100644 index 96f503aee9..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/SimilarNodeNameComparer.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Comparer that takes into account the duplicate index of a node name - /// This is needed as a normal alphabetic sort would go Page (1), Page (10), Page (2) etc. - /// - internal class SimilarNodeNameComparer : IComparer - { - public int Compare(string x, string y) - { - if (x.LastIndexOf('(') != -1 && x.LastIndexOf(')') == x.Length - 1 && y.LastIndexOf(')') == y.Length - 1) - { - if (x.ToLower().Substring(0, x.LastIndexOf('(')) == y.ToLower().Substring(0, y.LastIndexOf('('))) - { - int xDuplicateIndex = ExtractDuplicateIndex(x); - int yDuplicateIndex = ExtractDuplicateIndex(y); - - if (xDuplicateIndex != 0 && yDuplicateIndex != 0) - { - return xDuplicateIndex.CompareTo(yDuplicateIndex); - } - } - } - return String.Compare(x.ToLower(), y.ToLower(), StringComparison.Ordinal); - } - - private int ExtractDuplicateIndex(string text) - { - int index = 0; - - if (text.LastIndexOf('(') != -1 && text.LastIndexOf('(') < text.Length - 2) - { - int startPos = text.LastIndexOf('(') + 1; - int length = text.Length - 1 - startPos; - - int.TryParse(text.Substring(startPos, length), out index); - } - - return index; - } - } -} diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index b5f4970c36..ec15b22c7b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly IMapperCollection _mapperCollection; private readonly IDictionary _passwordConfig; - + /// /// Constructor /// @@ -49,8 +49,8 @@ namespace Umbraco.Core.Persistence.Repositories ? null : new Dictionary { { "hashAlgorithm", Membership.HashAlgorithmType } }; } - - // for tests + + // for tests internal UserRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, IMapperCollection mapperCollection, IDictionary passwordConfig) : base(work, cacheHelper, logger) { @@ -586,15 +586,20 @@ ORDER BY colName"; /// /// /// - /// Optional parameter to filter by specified user groups + /// + /// A filter to only include user that belong to these user groups + /// + /// + /// A filter to only include users that do not belong to these user groups + /// /// Optional parameter to filter by specfied user state /// /// /// /// The query supplied will ONLY work with data specifically on the umbracoUser table because we are using NPoco paging (SQL paging) /// - public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - Expression> orderBy, Direction orderDirection = Direction.Ascending, + public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + Expression> orderBy, Direction orderDirection = Direction.Ascending, string[] userGroups = null, UserState[] userState = null, IQuery filter = null) { if (orderBy == null) throw new ArgumentNullException(nameof(orderBy)); @@ -607,34 +612,49 @@ ORDER BY colName"; if (mappedField.IsNullOrWhiteSpace()) throw new ArgumentException("Could not find a mapping for the column specified in the orderBy clause"); - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, mappedField, orderDirection, userGroups, userState, filter); + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, mappedField, orderDirection, includeUserGroups, excludeUserGroups, userState, filter); } - private IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + private IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection = Direction.Ascending, - string[] userGroups = null, UserState[] userState = null, IQuery filter = null) + string[] includeUserGroups = null, string[] excludeUserGroups = null, UserState[] userState = null, IQuery filter = null) { if (string.IsNullOrWhiteSpace(orderBy)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(orderBy)); - var filterSql = filter != null - || userGroups != null && userGroups.Length > 0 - || userState != null && userState.Length > 0 && userState.Contains(UserState.All) == false - ? Sql() : null; + Sql filterSql = null; + var customFilterWheres = filter != null ? filter.GetWhereClauses().ToArray() : null; + var hasCustomFilter = customFilterWheres != null && customFilterWheres.Length > 0; + if (hasCustomFilter + || includeUserGroups != null && includeUserGroups.Length > 0 + || excludeUserGroups != null && excludeUserGroups.Length > 0 + || userState != null && userState.Length > 0 && userState.Contains(UserState.All) == false) + filterSql = Sql(); - if (filter != null) + if (hasCustomFilter) { - foreach (var clause in filter.GetWhereClauses()) + foreach (var clause in customFilterWheres) filterSql.Append($"AND ({clause.Item1})", clause.Item2); } - if (userGroups != null && userGroups.Length > 0) + + if (includeUserGroups != null && includeUserGroups.Length > 0) { const string subQuery = @"AND (umbracoUser.id IN (SELECT DISTINCT umbracoUser.id FROM umbracoUser INNER JOIN umbracoUser2UserGroup ON umbracoUser2UserGroup.userId = umbracoUser.id INNER JOIN umbracoUserGroup ON umbracoUserGroup.id = umbracoUser2UserGroup.userGroupId WHERE umbracoUserGroup.userGroupAlias IN (@userGroups)))"; - filterSql.Append(subQuery, new { userGroups }); + filterSql.Append(subQuery, new { userGroups = includeUserGroups }); + } + + if (excludeUserGroups != null && excludeUserGroups.Length > 0) + { + var subQuery = @"AND (umbracoUser.id NOT IN (SELECT DISTINCT umbracoUser.id + FROM umbracoUser + INNER JOIN umbracoUser2UserGroup ON umbracoUser2UserGroup.userId = umbracoUser.id + INNER JOIN umbracoUserGroup ON umbracoUserGroup.id = umbracoUser2UserGroup.userGroupId + WHERE umbracoUserGroup.userGroupAlias IN (@userGroups)))"; + filterSql.Append(subQuery, new { userGroups = excludeUserGroups }); } if (userState != null && userState.Length > 0) @@ -654,16 +674,19 @@ ORDER BY colName"; { if (appended) sb.Append(" OR "); sb.Append("(userDisabled = 1)"); + appended = true; } if (userState.Contains(UserState.LockedOut)) { if (appended) sb.Append(" OR "); sb.Append("(userNoConsole = 1)"); + appended = true; } if (userState.Contains(UserState.Invited)) { if (appended) sb.Append(" OR "); sb.Append("(lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL)"); + appended = true; } sb.Append(")"); diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 571a279afa..14208548ed 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -787,7 +787,7 @@ namespace Umbraco.Core foreach (var b in hashedByteArray) { //append it to our StringBuilder - stringBuilder.Append(b.ToString("x2").ToLower()); + stringBuilder.Append(b.ToString("x2")); } //return the hashed value @@ -902,7 +902,7 @@ namespace Umbraco.Core } /// - /// Ensures that the folder path endds with a DirectorySeperatorChar + /// Ensures that the folder path ends with a DirectorySeperatorChar /// /// /// diff --git a/src/Umbraco.Core/StringUdi.cs b/src/Umbraco.Core/StringUdi.cs index 0e1201665e..7b32399599 100644 --- a/src/Umbraco.Core/StringUdi.cs +++ b/src/Umbraco.Core/StringUdi.cs @@ -57,17 +57,18 @@ namespace Umbraco.Core public new static StringUdi Parse(string s) { var udi = Udi.Parse(s); - if (!(udi is StringUdi)) + if (udi is StringUdi == false) throw new FormatException("String \"" + s + "\" is not a string entity id."); - return (StringUdi)udi; + + return (StringUdi) udi; } public static bool TryParse(string s, out StringUdi udi) { udi = null; Udi tmp; - if (!TryParse(s, out tmp) || !(tmp is StringUdi)) return false; - udi = (StringUdi)tmp; + if (TryParse(s, out tmp) == false || tmp is StringUdi == false) return false; + udi = (StringUdi) tmp; return true; } @@ -77,10 +78,9 @@ namespace Umbraco.Core get { return Id == string.Empty; } } - /// public StringUdi EnsureClosed() { - base.EnsureNotRoot(); + EnsureNotRoot(); return this; } } diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index 3c44bc237b..b5dede2b2e 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -3,7 +3,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Reflection; using Umbraco.Core.Deploy; using Umbraco.Core.Composing; @@ -16,7 +15,15 @@ namespace Umbraco.Core [TypeConverter(typeof(UdiTypeConverter))] public abstract class Udi : IComparable { - private static readonly Lazy> UdiTypes; + // notes - see U4-10409 + // if this class is used during application pre-start it cannot scans the assemblies, + // this is addressed by lazily-scanning, with the following caveats: + // - parsing a root udi still requires a scan and therefore still breaks + // - parsing an invalid udi ("umb://should-be-guid/") corrupts KnowUdiTypes + + private static volatile bool _scanned; + private static readonly object ScanLocker = new object(); + private static ConcurrentDictionary _udiTypes; private static readonly ConcurrentDictionary RootUdis = new ConcurrentDictionary(); internal readonly Uri UriValue; // internal for UdiRange @@ -43,51 +50,16 @@ namespace Umbraco.Core static Udi() { - UdiTypes = new Lazy>(() => - { - var result = new Dictionary(); + // initialize with known (built-in) Udi types + // we will add scanned types later on + _udiTypes = new ConcurrentDictionary(Constants.UdiEntityType.GetTypes()); + } - // known types: - foreach (var fi in typeof(Constants.UdiEntityType).GetFields(BindingFlags.Public | BindingFlags.Static)) - { - // IsLiteral determines if its value is written at - // compile time and not changeable - // IsInitOnly determine if the field can be set - // in the body of the constructor - // for C# a field which is readonly keyword would have both true - // but a const field would have only IsLiteral equal to true - if (fi.IsLiteral && fi.IsInitOnly == false) - { - var udiType = fi.GetCustomAttribute(); - - if (udiType == null) - throw new InvalidOperationException("All Constants listed in UdiEntityType must be attributed with " + typeof(Constants.UdiTypeAttribute)); - result[fi.GetValue(null).ToString()] = udiType.UdiType; - } - } - - // Scan for unknown UDI types - // there is no way we can get the "registered" service connectors, as registration - // happens in Deploy, not in Core, and the Udi class belongs to Core - therefore, we - // just pick every service connectors - just making sure that not two of them - // would register the same entity type, with different udi types (would not make - // much sense anyways). - // fixme - current! replace static ctor w/ component? - var connectors = Current.TypeLoader.GetTypes(); - foreach (var connector in connectors) - { - var attrs = connector.GetCustomAttributes(false); - foreach (var attr in attrs) - { - UdiType udiType; - if (result.TryGetValue(attr.EntityType, out udiType) && udiType != attr.UdiType) - throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType)); - result[attr.EntityType] = attr.UdiType; - } - } - - return result; - }); + // for tests, totally unsafe + internal static void ResetUdiTypes() + { + _udiTypes = new ConcurrentDictionary(Constants.UdiEntityType.GetTypes()); + _scanned = false; } /// @@ -115,17 +87,65 @@ namespace Umbraco.Core public static Udi Parse(string s) { Udi udi; - ParseInternal(s, false, out udi); + ParseInternal(s, false, false, out udi); return udi; } - public static bool TryParse(string s, out Udi udi) + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// A value indicating whether to only deal with known types. + /// An Udi instance that contains the value that was parsed. + /// + /// If is true, and the string could not be parsed because + /// the entity type was not known, the method succeeds but sets udito an + /// value. + /// If is true, assemblies are not scanned for types, + /// and therefore only builtin types may be known. Unless scanning already took place. + /// + public static Udi Parse(string s, bool knownTypes) { - return ParseInternal(s, true, out udi); + Udi udi; + ParseInternal(s, false, knownTypes, out udi); + return udi; } - private static bool ParseInternal(string s, bool tryParse, out Udi udi) + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// An Udi instance that contains the value that was parsed. + /// A boolean value indicating whether the string could be parsed. + public static bool TryParse(string s, out Udi udi) { + return ParseInternal(s, true, false, out udi); + } + + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// A value indicating whether to only deal with known types. + /// An Udi instance that contains the value that was parsed. + /// A boolean value indicating whether the string could be parsed. + /// + /// If is true, and the string could not be parsed because + /// the entity type was not known, the method returns false but still sets udi + /// to an value. + /// If is true, assemblies are not scanned for types, + /// and therefore only builtin types may be known. Unless scanning already took place. + /// + public static bool TryParse(string s, bool knownTypes, out Udi udi) + { + return ParseInternal(s, true, knownTypes, out udi); + } + + private static bool ParseInternal(string s, bool tryParse, bool knownTypes, out Udi udi) + { + if (knownTypes == false) + EnsureScanForUdiTypes(); + udi = null; Uri uri; @@ -138,12 +158,21 @@ namespace Umbraco.Core var entityType = uri.Host; UdiType udiType; - if (UdiTypes.Value.TryGetValue(entityType, out udiType) == false) + if (_udiTypes.TryGetValue(entityType, out udiType) == false) { + if (knownTypes) + { + // not knowing the type is not an error + // just return the unknown type udi + udi = UnknownTypeUdi.Instance; + return false; + } if (tryParse) return false; throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType)); } + var path = uri.AbsolutePath.TrimStart('/'); + if (udiType == UdiType.GuidUdi) { if (path == string.Empty) @@ -160,21 +189,25 @@ namespace Umbraco.Core udi = new GuidUdi(uri.Host, guid); return true; } + if (udiType == UdiType.StringUdi) { udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, Uri.UnescapeDataString(path)); return true; } + if (tryParse) return false; - throw new InvalidOperationException("Internal error."); + throw new InvalidOperationException(string.Format("Invalid udi type \"{0}\".", udiType)); } private static Udi GetRootUdi(string entityType) { + EnsureScanForUdiTypes(); + return RootUdis.GetOrAdd(entityType, x => { UdiType udiType; - if (UdiTypes.Value.TryGetValue(x, out udiType) == false) + if (_udiTypes.TryGetValue(x, out udiType) == false) throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType)); return udiType == UdiType.StringUdi ? (Udi)new StringUdi(entityType, string.Empty) @@ -182,6 +215,46 @@ namespace Umbraco.Core }); } + /// + /// When required scan assemblies for known UDI types based on instances + /// + /// + /// This is only required when needing to resolve root udis + /// + private static void EnsureScanForUdiTypes() + { + if (_scanned) return; + + lock (ScanLocker) + { + // Scan for unknown UDI types + // there is no way we can get the "registered" service connectors, as registration + // happens in Deploy, not in Core, and the Udi class belongs to Core - therefore, we + // just pick every service connectors - just making sure that not two of them + // would register the same entity type, with different udi types (would not make + // much sense anyways). + var connectors = Current.TypeLoader.GetTypes(); + var result = new Dictionary(); + foreach (var connector in connectors) + { + var attrs = connector.GetCustomAttributes(false); + foreach (var attr in attrs) + { + UdiType udiType; + if (result.TryGetValue(attr.EntityType, out udiType) && udiType != attr.UdiType) + throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType)); + result[attr.EntityType] = attr.UdiType; + } + } + + // merge these into the known list + foreach (var item in result) + _udiTypes.TryAdd(item.Key, item.Value); + + _scanned = true; + } + } + /// /// Creates a root Udi for an entity type. /// @@ -201,8 +274,9 @@ namespace Umbraco.Core public static Udi Create(string entityType, string id) { UdiType udiType; - if (UdiTypes.Value.TryGetValue(entityType, out udiType) == false) + if (_udiTypes.TryGetValue(entityType, out udiType) == false) throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); + if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("Value cannot be null or whitespace.", "id"); if (udiType != UdiType.StringUdi) @@ -220,24 +294,31 @@ namespace Umbraco.Core public static Udi Create(string entityType, Guid id) { UdiType udiType; - if (UdiTypes.Value.TryGetValue(entityType, out udiType) == false) + if (_udiTypes.TryGetValue(entityType, out udiType) == false) throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); + if (udiType != UdiType.GuidUdi) - throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", entityType)); + throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", entityType)); if (id == default(Guid)) throw new ArgumentException("Cannot be an empty guid.", "id"); + return new GuidUdi(entityType, id); } internal static Udi Create(Uri uri) { + // if it's a know type go fast and use ctors + // else fallback to parsing the string (and guess the type) + UdiType udiType; - if (UdiTypes.Value.TryGetValue(uri.Host, out udiType) == false) + if (_udiTypes.TryGetValue(uri.Host, out udiType) == false) throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", uri.Host), "uri"); + if (udiType == UdiType.GuidUdi) return new GuidUdi(uri); if (udiType == UdiType.GuidUdi) return new StringUdi(uri); + throw new ArgumentException(string.Format("Uri \"{0}\" is not a valid udi.", uri)); } @@ -286,6 +367,19 @@ namespace Umbraco.Core { return (udi1 == udi2) == false; } - } + private class UnknownTypeUdi : Udi + { + private UnknownTypeUdi() + : base("unknown", "umb://unknown/") + { } + + public static readonly UnknownTypeUdi Instance = new UnknownTypeUdi(); + + public override bool IsRoot + { + get { return false; } + } + } + } } diff --git a/src/Umbraco.Core/UdiEntityType.cs b/src/Umbraco.Core/UdiEntityType.cs index 1f64adb3fb..50d2b4e46b 100644 --- a/src/Umbraco.Core/UdiEntityType.cs +++ b/src/Umbraco.Core/UdiEntityType.cs @@ -1,9 +1,9 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core { - public static partial class Constants { /// @@ -13,83 +13,101 @@ namespace Umbraco.Core /// but entity types are strings and so can be extended beyond what is defined here. public static class UdiEntityType { - [UdiType(UdiType.Unknown)] + // note: const fields in this class MUST be consistent with what GetTypes returns + // this is validated by UdiTests.ValidateUdiEntityType + // also, this is used exclusively in Udi static ctor, only once, so there is no + // need to keep it around in a field nor to make it readonly + + internal static Dictionary GetTypes() + { + return new Dictionary + { + { Unknown, UdiType.Unknown }, + + { AnyGuid, UdiType.GuidUdi }, + { Document, UdiType.GuidUdi }, + { DocumentBluePrint, UdiType.GuidUdi }, + { Media, UdiType.GuidUdi }, + { Member, UdiType.GuidUdi }, + { DictionaryItem, UdiType.GuidUdi }, + { Macro, UdiType.GuidUdi }, + { Template, UdiType.GuidUdi }, + { DocumentType, UdiType.GuidUdi }, + { DocumentTypeContainer, UdiType.GuidUdi }, + { DocumentTypeBluePrints, UdiType.GuidUdi }, + { MediaType, UdiType.GuidUdi }, + { MediaTypeContainer, UdiType.GuidUdi }, + { DataType, UdiType.GuidUdi }, + { DataTypeContainer, UdiType.GuidUdi }, + { MemberType, UdiType.GuidUdi }, + { MemberGroup, UdiType.GuidUdi }, + { RelationType, UdiType.GuidUdi }, + { FormsForm, UdiType.GuidUdi }, + { FormsPreValue, UdiType.GuidUdi }, + { FormsDataSource, UdiType.GuidUdi }, + + { AnyString, UdiType.StringUdi}, + { Language, UdiType.StringUdi}, + { MacroScript, UdiType.StringUdi}, + { MediaFile, UdiType.StringUdi}, + { TemplateFile, UdiType.StringUdi}, + { Script, UdiType.StringUdi}, + { PartialView, UdiType.StringUdi}, + { PartialViewMacro, UdiType.StringUdi}, + { Stylesheet, UdiType.StringUdi}, + { UserControl, UdiType.StringUdi}, + { Xslt, UdiType.StringUdi}, + }; + } + public const string Unknown = "unknown"; // guid entity types - [UdiType(UdiType.GuidUdi)] public const string AnyGuid = "any-guid"; // that one is for tests - [UdiType(UdiType.GuidUdi)] public const string Document = "document"; - [UdiType(UdiType.GuidUdi)] public const string DocumentBluePrint = "document-blueprint"; - [UdiType(UdiType.GuidUdi)] public const string Media = "media"; - [UdiType(UdiType.GuidUdi)] public const string Member = "member"; - [UdiType(UdiType.GuidUdi)] public const string DictionaryItem = "dictionary-item"; - [UdiType(UdiType.GuidUdi)] public const string Macro = "macro"; - [UdiType(UdiType.GuidUdi)] public const string Template = "template"; - [UdiType(UdiType.GuidUdi)] public const string DocumentType = "document-type"; - [UdiType(UdiType.GuidUdi)] public const string DocumentTypeContainer = "document-type-container"; - [UdiType(UdiType.GuidUdi)] + + public const string DocumentTypeBluePrints = "document-type-blueprints"; public const string MediaType = "media-type"; - [UdiType(UdiType.GuidUdi)] public const string MediaTypeContainer = "media-type-container"; - [UdiType(UdiType.GuidUdi)] public const string DataType = "data-type"; - [UdiType(UdiType.GuidUdi)] public const string DataTypeContainer = "data-type-container"; - [UdiType(UdiType.GuidUdi)] public const string MemberType = "member-type"; - [UdiType(UdiType.GuidUdi)] public const string MemberGroup = "member-group"; - [UdiType(UdiType.GuidUdi)] public const string RelationType = "relation-type"; // forms - [UdiType(UdiType.GuidUdi)] public const string FormsForm = "forms-form"; - [UdiType(UdiType.GuidUdi)] public const string FormsPreValue = "forms-prevalue"; - [UdiType(UdiType.GuidUdi)] public const string FormsDataSource = "forms-datasource"; // string entity types - [UdiType(UdiType.StringUdi)] public const string AnyString = "any-string"; // that one is for tests - [UdiType(UdiType.StringUdi)] public const string Language = "language"; - [UdiType(UdiType.StringUdi)] public const string MacroScript = "macroscript"; - [UdiType(UdiType.StringUdi)] public const string MediaFile = "media-file"; - [UdiType(UdiType.StringUdi)] public const string TemplateFile = "template-file"; - [UdiType(UdiType.StringUdi)] public const string Script = "script"; - [UdiType(UdiType.StringUdi)] public const string Stylesheet = "stylesheet"; - [UdiType(UdiType.StringUdi)] public const string PartialView = "partial-view"; - [UdiType(UdiType.StringUdi)] public const string PartialViewMacro = "partial-view-macro"; - [UdiType(UdiType.StringUdi)] public const string Xslt = "xslt"; public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType) @@ -181,16 +199,5 @@ namespace Umbraco.Core string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType)); } } - - [AttributeUsage(AttributeTargets.Field)] - internal class UdiTypeAttribute : Attribute - { - public UdiType UdiType { get; private set; } - - public UdiTypeAttribute(UdiType udiType) - { - UdiType = udiType; - } - } } } diff --git a/src/Umbraco.Core/UdiRange.cs b/src/Umbraco.Core/UdiRange.cs index 937798f69e..b70cf43d18 100644 --- a/src/Umbraco.Core/UdiRange.cs +++ b/src/Umbraco.Core/UdiRange.cs @@ -62,8 +62,8 @@ namespace Umbraco.Core { Uri uri; - if (!Uri.IsWellFormedUriString(s, UriKind.Absolute) - || !Uri.TryCreate(s, UriKind.Absolute, out uri)) + if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false + || Uri.TryCreate(s, UriKind.Absolute, out uri) == false) { //if (tryParse) return false; throw new FormatException(string.Format("String \"{0}\" is not a valid udi range.", s)); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8461862f59..0f4e43ce12 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -219,6 +219,8 @@ + + @@ -331,6 +333,7 @@ + @@ -338,6 +341,7 @@ + @@ -398,9 +402,11 @@ + + @@ -1040,7 +1046,6 @@ - @@ -1056,12 +1061,12 @@ - - - - - - + + + + + + diff --git a/src/Umbraco.Web.UI.Client/.eslintrc b/src/Umbraco.Web.UI.Client/.eslintrc index 811fabdabf..205d8dcf50 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc +++ b/src/Umbraco.Web.UI.Client/.eslintrc @@ -39,7 +39,7 @@ "globals": { "angular": false, "_": false, - "$", false, + "$": false, "tinymce": false, "tinyMCE": false, "FileReader": false, @@ -47,7 +47,7 @@ "window": false, "LazyLoad": false, "ActiveXObject": false, - "Bloodhound", false + "Bloodhound": false } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/README.md b/src/Umbraco.Web.UI.Client/README.md deleted file mode 100644 index 6001d65271..0000000000 --- a/src/Umbraco.Web.UI.Client/README.md +++ /dev/null @@ -1,97 +0,0 @@ -#Belle - -Umbraco 7 UI, codename "Belle" Built on AngularJS, bower, Lazyload.js and Twitter Bootstrap - - -##Introduction -Slides from the initial demonstration of Belle done at the Umbraco DK Fest can be found here: - -http://rawgithub.com/umbraco/Umbraco.Web.Ui.Client/build/master/Presentation/index.html - - -##Running the site with mocked data - -This won't require any database or setup, as everything is running through node. All you have to do is install -node and grunt on either windows or OSX and the entire setup is ready for you. - - -###Install node.js -We need node to run tests and automated less compiling and other automated tasks. go to http://nodejs.org. Node.js is a powerfull javascript engine, which allows us to run all our tests and tasks written in javascript locally. - -*note:* On windows you might need to restart explorer.exe to register node. - - -###Install dependencies -Next we need to install all the required packages. This is done with the package tool, included with node.js, open /src/Umbraco.Web.UI.Client in cmd.exe or osx terminal and run the command: - - npm install - -this will fetch all needed packages to your local machine. - - -###Install grunt globally -Grunt is a task runner for node.js, and we use it for all automated tasks in the build process. For convenience we need to install it globally on your machine, so it can be used directly in cmd.exe or the terminal. - -So run the command: - - npm install grunt-cli -g - -*note:* On windows you might need to restart explorer.exe to register the grunt cmd. - -*note:* On OSX you might need to run: - - sudo npm install grunt-cli -g - -Now that you have node and grunt installed, you can open `/src/Umbraco.Web.UI.Client` in either `cmd.exe` or terminal and run: - - grunt dev - -This will build the site, merge less files, run tests and create the /Build folder, and finally open the site in your -browser. - - -##Limitations -The current prototype simply uses in-memory storage, so no database dependencies. It is aimed at showing UI, not a complete functional client-server setup. - - -##Project Structure - -All project files are located in /src/Umbraco.Web.UI.Client which only contains client-side files, everything -related to asp.net are in /src/Umbraco.Web.UI - -after building Belle files are located in /build/belle, with all files following AngularJs -conventions: - -###Folders -- */Umbraco.Web.Ui.Client/build/lib:* Dependencies -- */Umbraco.Web.Ui.Client/build/js:* Application javascript files -- */Umbraco.Web.Ui.Client/build/views/common/:* Main application views -- */Umbraco.Web.Ui.Client/build/views/[sectioname]/pagename Editors html -- */Umbraco.Web.Ui.Client/build/views/propertyeditors:* Property Editors html - - -###Files -- */Umbraco.Web.Ui.Client/build/js/app.js:* Main umbraco application / modules -- */Umbraco.Web.Ui.Client/build/js/loader.js:* lazyload configuration for dependencies -- */Umbraco.Web.Ui.Client/build/js/routes.js:* Application routes -- */Umbraco.Web.Ui.Client/build/js/umbraco.controllers.js:* Application controllers -- */Umbraco.Web.Ui.Client/build/js/umbraco.services.js:* Application services -- */Umbraco.Web.Ui.Client/build/js/umbraco.filters.js:* Application filters -- */Umbraco.Web.Ui.Client/build/js/umbraco.directives.js:* Application directives -- */Umbraco.Web.Ui.Client/build/js/umbraco.resources.js:* Application resources, like content, media, users, members etc -- */Umbraco.Web.Ui.Client/build/js/umbraco.mocks.js:* Fake Application resources, for running the app without a server - -##Getting started -The current app is built, following conventions from angularJs and bootstrap. To get started with the applicaton you will need to atleast know the basics of these frameworks - -###AngularJS -- Excellent introduction videos on http://www.egghead.io/ -- Official guide at: http://docs.angularjs.org/guide/ - -###Require.js -- Introduction: http://javascriptplayground.com/blog/2012/07/requirejs-amd-tutorial-introduction -- Require.js website: http://requirejs.org/ - - - - diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 7c808a1f6d..236527907c 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -31,6 +31,59 @@ "angular-local-storage": "~0.2.3", "moment": "~2.10.3", "ace-builds": "^1.2.3", + "font-awesome": "~4.2", "clipboard": "1.7.1" + }, + + "install": { + + "path": "lib-bower", + + "ignore": [ + "font-awesome", + "angular", + "bootstrap", + "codemirror" + ], + + "sources": { + "moment": "bower_components/moment/min/moment-with-locales.js", + + "underscore": [ + "bower_components/underscore/underscore-min.js", + "bower_components/underscore/underscore-min.map" + ], + + "jquery": [ + "bower_components/jquery/dist/jquery.min.js", + "bower_components/jquery/dist/jquery.min.map" + ], + + "angular-dynamic-locale": [ + "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js", + "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map" + ], + + "angular-local-storage": [ + "bower_components/angular-local-storage/dist/angular-local-storage.min.js", + "bower_components/angular-local-storage/dist/angular-local-storage.min.js.map" + ], + + "tinymce": [ + "bower_components/tinymce/tinymce.min.js" + ], + + "typeahead.js": "bower_components/typeahead.js/dist/typeahead.bundle.min.js", + + "rgrove-lazyload":"bower_components/rgrove-lazyload/lazyload.js", + + "ng-file-upload":"bower_components/ng-file-upload/ng-file-upload.min.js", + + "jquery-ui":"bower_components/jquery-ui/jquery-ui.min.js", + + "jquery-migrate":"bower_components/jquery-migrate/jquery-migrate.min.js", + + "clipboard": "bower_components/clipboard/dist/clipboard.min.js" + } } } diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js deleted file mode 100644 index 5170fddcba..0000000000 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ /dev/null @@ -1,601 +0,0 @@ -module.exports = function (grunt) { - - - - // Default task. - grunt.registerTask('default', ['jshint:dev', 'build', 'karma:unit']); - grunt.registerTask('dev', ['jshint:dev', 'build-dev', 'webserver', 'open:dev', 'watch']); - grunt.registerTask('docserve', ['docs:api', 'connect:docserver', 'open:docs', 'watch:docs']); - grunt.registerTask('vs', ['jshint:dev', 'build-dev', 'watch']); - - //TODO: Too much watching, this brings windows to it's knees when in dev mode - //run by the watch task - grunt.registerTask('watch-js', ['jshint:dev', 'concat', 'copy:app', 'copy:mocks', 'copy:canvasdesigner', 'copy:vs', 'karma:unit']); - grunt.registerTask('watch-less', ['recess:build', 'recess:installer', 'recess:nonodes', 'recess:canvasdesigner', 'postcss', 'copy:canvasdesigner', 'copy:assets', 'copy:vs']); - grunt.registerTask('watch-html', ['copy:views', 'copy:vs']); - grunt.registerTask('watch-installer', ['concat:install', 'concat:installJs', 'copy:installer', 'copy:vs']); - grunt.registerTask('watch-canvasdesigner', ['copy:canvasdesigner', 'concat:canvasdesignerJs', 'copy:vs']); - grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']); - - //triggered from grunt - grunt.registerTask('build', ['concat', 'recess:build', 'recess:installer', 'recess:nonodes', 'recess:canvasdesigner', 'postcss', 'bower-install-simple', 'bower', 'copy', 'clean:post']); - - //triggered from grunt dev vs or grunt vs - grunt.registerTask('build-dev', ['clean:pre', 'concat', 'recess:build', 'recess:installer', 'recess:nonodes', 'postcss', 'bower-install-simple', 'bower', 'copy']); - - //utillity tasks - grunt.registerTask('docs', ['ngdocs']); - grunt.registerTask('webserver', ['connect:devserver']); - - - // Print a timestamp (useful for when watching) - grunt.registerTask('timestamp', function () { - grunt.log.subhead(Date()); - }); - - // Project configuration. - grunt.initConfig({ - buildVersion: grunt.option('buildversion') || '7', - connect: { - devserver: { - options: { - port: 9990, - hostname: '0.0.0.0', - base: './build', - middleware: function(connect, options) { - return [ - //uncomment to enable CSP - // util.csp(), - //util.rewrite(), - connect.favicon('images/favicon.ico'), - connect.static(options.base), - connect.directory(options.base) - ]; - } - } - }, - testserver: {}, - docserver: { - options: { - port: 8880, - hostname: '0.0.0.0', - base: './docs/api', - middleware: function(connect, options) { - return [ - //uncomment to enable CSP - // util.csp(), - //util.rewrite(), - connect.static(options.base), - connect.directory(options.base) - ]; - } - } - }, - }, - - open: { - dev: { - path: 'http://localhost:9990/belle/' - }, - docs: { - path: 'http://localhost:8880/index.html' - } - }, - - distdir: 'build/belle', - vsdir: '../Umbraco.Web.UI/umbraco', - pkg: grunt.file.readJSON('package.json'), - banner: - '/*! <%= pkg.title || pkg.name %>\n' + - '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' + - ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;\n' + - ' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n', - src: { - js: ['src/**/*.js', 'src/*.js'], - - common: ['src/common/**/*.js'], - controllers: ['src/**/*.controller.js'], - - specs: ['test/**/*.spec.js'], - scenarios: ['test/**/*.scenario.js'], - samples: ['sample files/*.js'], - html: ['src/index.html', 'src/install.html'], - - everything: ['src/**/*.*', 'test/**/*.*', 'docs/**/*.*'], - - tpl: { - app: ['src/views/**/*.html'], - common: ['src/common/**/*.tpl.html'] - }, - less: ['src/less/belle.less'], // recess:build doesn't accept ** in its file patterns - prod: ['<%= distdir %>/js/*.js'] - }, - - clean: { - pre: ['<%= distdir %>/*'], - post: ['<%= distdir %>/js/*.dev.js'] - }, - - copy: { - assets: { - files: [{ dest: '<%= distdir %>/assets', src: '**', expand: true, cwd: 'src/assets/' }] - }, - - config: { - files: [{ dest: '<%= distdir %>/../config', src: '**', expand: true, cwd: 'src/config/' }] - }, - - installer: { - files: [{ dest: '<%= distdir %>/views/install', src: '**/*.html', expand: true, cwd: 'src/installer/steps' }] - }, - - canvasdesigner: { - files: [ - { dest: '<%= distdir %>/preview', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner' }, - { dest: '<%= distdir %>/preview/editors', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner/editors' }, - { dest: '<%= distdir %>/assets/less', src: '**/*.less', expand: true, cwd: 'src/canvasdesigner/editors' }, - { dest: '<%= distdir %>/js', src: 'canvasdesigner.config.js', expand: true, cwd: 'src/canvasdesigner/config' }, - { dest: '<%= distdir %>/js', src: 'canvasdesigner.palettes.js', expand: true, cwd: 'src/canvasdesigner/config' }, - { dest: '<%= distdir %>/js', src: 'canvasdesigner.front.js', expand: true, cwd: 'src/canvasdesigner' } - ] - }, - - vendor: { - files: [{ dest: '<%= distdir %>/lib', src: '**', expand: true, cwd: 'lib/' }] - }, - - views: { - files: [{ dest: '<%= distdir %>/views', src: ['**/*.*', '!**/*.controller.js'], expand: true, cwd: 'src/views' }] - }, - - app: { - files: [ - { dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/' } - ] - }, - - mocks: { - files: [{ dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/common/mocks/' }] - }, - - vs: { - files: [ - //everything except the index.html root file! - //then we need to figure out how to not copy all the test stuff either!? - { dest: '<%= vsdir %>/assets', src: '**', expand: true, cwd: '<%= distdir %>/assets' }, - { dest: '<%= vsdir %>/js', src: '**', expand: true, cwd: '<%= distdir %>/js' }, - { dest: '<%= vsdir %>/views', src: '**', expand: true, cwd: '<%= distdir %>/views' }, - { dest: '<%= vsdir %>/preview', src: '**', expand: true, cwd: '<%= distdir %>/preview' }, - { dest: '<%= vsdir %>/lib', src: '**', expand: true, cwd: '<%= distdir %>/lib' } - ] - } - }, - - karma: { - unit: { configFile: 'test/config/karma.conf.js', keepalive: true }, - e2e: { configFile: 'test/config/e2e.js', keepalive: true }, - watch: { configFile: 'test/config/unit.js', singleRun: false, autoWatch: true, keepalive: true } - }, - - concat: { - index: { - src: ['src/index.html'], - dest: '<%= distdir %>/index.html', - options: { - process: true - } - }, - install: { - src: ['src/installer/installer.html'], - dest: '<%= distdir %>/installer.html', - options: { - process: true - } - }, - - installJs: { - src: ['src/installer/**/*.js'], - dest: '<%= distdir %>/js/umbraco.installer.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - - canvasdesignerJs: { - src: ['src/canvasdesigner/canvasdesigner.global.js', 'src/canvasdesigner/canvasdesigner.controller.js', 'src/canvasdesigner/editors/*.js', 'src/canvasdesigner/lib/*.js'], - dest: '<%= distdir %>/js/canvasdesigner.panel.js' - }, - - controllers: { - src: ['src/controllers/**/*.controller.js', 'src/views/**/*.controller.js'], - dest: '<%= distdir %>/js/umbraco.controllers.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - - services: { - src: ['src/common/services/*.js'], - dest: '<%= distdir %>/js/umbraco.services.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - - security: { - src: ['src/common/security/*.js'], - dest: '<%= distdir %>/js/umbraco.security.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - - resources: { - src: ['src/common/resources/*.js'], - dest: '<%= distdir %>/js/umbraco.resources.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - - testing: { - src: ['src/common/mocks/*/*.js'], - dest: '<%= distdir %>/js/umbraco.testing.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - - directives: { - src: ['src/common/directives/**/*.js'], - dest: '<%= distdir %>/js/umbraco.directives.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - - filters: { - src: ['src/common/filters/*.js'], - dest: '<%= distdir %>/js/umbraco.filters.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - } - }, - - uglify: { - options: { - mangle: true - }, - combine: { - files: { - '<%= distdir %>/js/umbraco.min.js': ['<%= distdir %>/js/umbraco.*.js'] - } - } - }, - - recess: { - build: { - files: { - '<%= distdir %>/assets/css/<%= pkg.name %>.css': - ['<%= src.less %>'] - }, - options: { - compile: true, - compress: true - } - }, - nonodes: { - files: { - '<%= distdir %>/assets/css/nonodes.style.min.css': - ['src/less/pages/nonodes.less'] - }, - options: { - compile: true, - compress: true - } - }, - installer: { - files: { - '<%= distdir %>/assets/css/installer.css': - ['src/less/installer.less'] - }, - options: { - compile: true, - compress: true - } - }, - canvasdesigner: { - files: { - '<%= distdir %>/assets/css/canvasdesigner.css': - ['src/less/canvas-designer.less', 'src/less/helveticons.less'] - }, - options: { - compile: true, - compress: true - } - } - }, - - postcss: { - options: { - processors: [ - // add vendor prefixes - require('autoprefixer-core')({ - browsers: 'last 2 versions' - }) - ] - }, - dist: { - src: '<%= distdir %>/assets/css/<%= pkg.name %>.css' - } - }, - - ngTemplateCache: { - views: { - files: { - '<%= distdir %>/js/umbraco.views.js': 'src/views/**/*.html' - }, - options: { - trim: 'src/', - module: 'umbraco.views' - } - } - }, - - watch: { - docs: { - files: ['docs/src/**/*.md'], - tasks: ['watch-docs', 'timestamp'] - }, - css: { - files: 'src/**/*.less', - tasks: ['watch-less', 'timestamp'], - options: { - livereload: true, - }, - }, - js: { - files: ['src/**/*.js', 'src/*.js'], - tasks: ['watch-js', 'timestamp'], - }, - test: { - files: ['test/**/*.js'], - tasks: ['watch-test', 'timestamp'], - }, - installer: { - files: ['src/installer/**/*.*'], - tasks: ['watch-installer', 'timestamp'], - }, - canvasdesigner: { - files: ['src/canvasdesigner/**/*.*'], - tasks: ['watch-canvasdesigner', 'timestamp'], - }, - html: { - files: ['src/views/**/*.html', 'src/*.html'], - tasks: ['watch-html', 'timestamp'] - }, - options: { - interval: 500 - } - }, - - - ngdocs: { - options: { - dest: 'docs/api', - startPage: '/api', - title: "Umbraco Backoffice UI API Documentation", - html5Mode: false, - styles: [ - 'docs/umb-docs.css' - ], - image: "https://our.umbraco.org/assets/images/logo.svg" - }, - api: { - src: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'], - title: 'API Documentation' - }, - tutorials: { - src: [], - title: '' - } - }, - - eslint:{ - src: ['<%= src.common %>','<%= src.controllers %>'], - options: {quiet: true} - }, - - jshint: { - dev: { - files: { - src: ['<%= src.common %>'] - }, - options: { - curly: true, - eqeqeq: true, - immed: true, - latedef: "nofunc", - newcap: true, - noarg: true, - sub: true, - boss: true, - //NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete - es5: true, - eqnull: true, - //NOTE: we need to use eval sometimes so ignore it - evil: true, - //NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those - scripturl: true, - //NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used - smarttabs: true, - globals: {} - } - }, - build: { - files: { - src: ['<%= src.prod %>'] - }, - options: { - curly: true, - eqeqeq: true, - immed: true, - latedef: "nofunc", - newcap: true, - noarg: true, - sub: true, - boss: true, - //NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete - es5: true, - eqnull: true, - //NOTE: we need to use eval sometimes so ignore it - evil: true, - //NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those - scripturl: true, - //NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used - smarttabs: true, - globalstrict: true, - globals: { $: false, jQuery: false, define: false, require: false, window: false } - } - } - }, - - bower: { - dev: { - dest: '<%= distdir %>/lib', - options: { - expand: true, - ignorePackages: ['bootstrap'], - packageSpecific: { - 'moment': { - keepExpandedHierarchy: false, - files: ['min/moment-with-locales.js'] - }, - 'typeahead.js': { - keepExpandedHierarchy: false, - files: ['dist/typeahead.bundle.min.js'] - }, - 'underscore': { - files: ['underscore-min.js', 'underscore-min.map'] - }, - 'rgrove-lazyload': { - files: ['lazyload.js'] - }, - 'bootstrap-social': { - files: ['bootstrap-social.css'] - }, - 'font-awesome': { - files: ['css/font-awesome.min.css', 'fonts/*'] - }, - "jquery": { - keepExpandedHierarchy: false, - files: ['dist/jquery.min.js', 'dist/jquery.min.map'] - }, - 'jquery-ui': { - keepExpandedHierarchy: false, - files: ['jquery-ui.min.js'] - }, - 'jquery-migrate': { - keepExpandedHierarchy: false, - files: ['jquery-migrate.min.js'] - }, - 'tinymce': { - files: ['plugins/**', 'themes/**', 'tinymce.min.js'] - }, - 'angular-dynamic-locale': { - files: ['tmhDynamicLocale.min.js', 'tmhDynamicLocale.min.js.map'] - }, - 'ng-file-upload': { - keepExpandedHierarchy: false, - files: ['ng-file-upload.min.js'] - }, - 'angular-local-storage': { - keepExpandedHierarchy: false, - files: ['dist/angular-local-storage.min.js'] - }, - 'codemirror': { - files: [ - 'lib/codemirror.js', - 'lib/codemirror.css', - - 'mode/css/*', - 'mode/javascript/*', - 'mode/xml/*', - 'mode/htmlmixed/*', - - 'addon/search/*', - 'addon/edit/*', - 'addon/selection/*', - 'addon/dialog/*' - ] - }, - 'ace-builds': { - files: [ - 'src-min-noconflict/ace.js', - - 'src-min-noconflict/ext-language_tools.js', - 'src-min-noconflict/ext-searchbox.js', - 'src-min-noconflict/ext-settings_menu.js', - - 'src-min-noconflict/snippets/text.js', - 'src-min-noconflict/snippets/javascript.js', - - 'src-min-noconflict/theme-chrome.js', - - 'src-min-noconflict/mode-razor.js', - 'src-min-noconflict/mode-javascript.js', - - 'src-min-noconflict/worker-javascript.js', - ] - }, - 'clipboard': { - keepExpandedHierarchy: false, - files: ['dist/clipboard.min.js'] - } - } - } - }, - options: { - expand: true - } - }, - - "bower-install-simple": { - options: { - color: true - }, - "dev": {} - } - }); - - - - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-copy'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-recess'); - grunt.loadNpmTasks('grunt-postcss'); - - grunt.loadNpmTasks('grunt-karma'); - - grunt.loadNpmTasks('grunt-open'); - grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks("grunt-bower-install-simple"); - grunt.loadNpmTasks('grunt-bower'); - grunt.loadNpmTasks('grunt-ngdocs'); - - grunt.loadNpmTasks('grunt-eslint'); - grunt.loadNpmTasks('grunt-hustler'); -}; diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js new file mode 100644 index 0000000000..089f55f9bf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -0,0 +1,389 @@ +var gulp = require('gulp'); +var watch = require('gulp-watch'); +var concat = require('gulp-concat'); +var rename = require('gulp-rename'); +var wrap = require("gulp-wrap-js"); +var sort = require('gulp-sort'); +var connect = require('gulp-connect'); +var open = require('gulp-open'); +var runSequence = require('run-sequence'); + +var _ = require('lodash'); +var MergeStream = require('merge-stream'); + +//Less + css +var postcss = require('gulp-postcss'); +var less = require('gulp-less'); +var autoprefixer = require('autoprefixer'); +var cssnano = require('cssnano'); + +// Documentation +var gulpDocs = require('gulp-ngdocs'); + +// Testing +var karmaServer = require('karma').Server; + +/*************************************************************** +Helper functions +***************************************************************/ +function processJs(files, out) { + + return gulp.src(files) + .pipe(sort()) + .pipe(concat(out)) + .pipe(wrap('(function(){\n%= body %\n})();')) + .pipe(gulp.dest(root + targets.js)); + + console.log(out + " compiled"); +} + +function processLess(files, out) { + + var processors = [ + autoprefixer, + cssnano({zindex: false}), + ]; + + return gulp.src(files) + .pipe(less()) + .pipe(postcss(processors)) + .pipe(rename(out)) + .pipe(gulp.dest(root + targets.css)); + + console.log(out + " compiled"); +} + +/*************************************************************** +Paths and destinations +Each group is iterated automatically in the setup tasks below +***************************************************************/ +var sources = { + + //less files used by backoffice and preview + //processed in the less task + less: { + installer: { files: ["src/less/installer.less"], out: "installer.css" }, + nonodes: { files: ["src/less/pages/nonodes.less"], out: "nonodes.style.min.css"}, + preview: { files: ["src/less/canvas-designer.less"], out: "canvasdesigner.css" }, + umbraco: { files: ["src/less/belle.less"], out: "umbraco.css" } + }, + + //js files for backoffie + //processed in the js task + js: { + preview: { files: ["src/canvasdesigner/**/*.js"], out: "umbraco.canvasdesigner.js" }, + installer: { files: ["src/installer/**/*.js"], out: "umbraco.installer.js" }, + + controllers: { files: ["src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" }, + directives: { files: ["src/common/directives/**/*.js"], out: "umbraco.directives.js" }, + filters: { files: ["src/common/filters/**/*.js"], out: "umbraco.filters.js" }, + resources: { files: ["src/common/resources/**/*.js"], out: "umbraco.resources.js" }, + services: { files: ["src/common/services/**/*.js"], out: "umbraco.services.js" }, + security: { files: ["src/common/security/**/*.js"], out: "umbraco.security.js" } + }, + + //selectors for copying all views into the build + //processed in the views task + views:{ + umbraco: {files: ["src/views/**/*html"], folder: ""}, + preview: { files: ["src/canvasdesigner/**/*.html"], folder: "../preview"}, + installer: {files: ["src/installer/steps/*.html"], folder: "install"} + }, + + //globs for file-watching + globs:{ + views: "./src/views/**/*.html", + less: "./src/less/**/*.less", + js: "./src/*.js", + lib: "./lib/**/*", + bower: "./lib-bower/**/*", + assets: "./src/assets/**" + } +}; + +var root = "../Umbraco.Web.UI/Umbraco/"; +var targets = { + js: "js/", + lib: "lib/", + views: "views/", + css: "assets/css/", + assets: "assets/" +}; + + +/************************** + * Main tasks for the project to prepare backoffice files + **************************/ + + // Build - build the files ready for production +gulp.task('build', function(cb) { + runSequence(["dependencies", "js", "less", "views"], "test:unit", cb); +}); + +// Dev - build the files ready for development and start watchers +gulp.task('dev', function(cb) { + runSequence(["dependencies", "js", "less", "views"], "watch", cb); +}); + +// Docserve - build and open the back office documentation +gulp.task('docserve', function(cb) { + runSequence('docs', 'connect:docs', 'open:docs', cb); +}); + +/************************** + * Task processes and copies all dependencies, either installed by bower, npm or stored locally in the project + **************************/ +gulp.task('dependencies', function () { + + //bower component specific copy rules + //this is to patch the sometimes wonky rules these libs are distrbuted under + + //as we do multiple things in this task, we merge the multiple streams + var stream = new MergeStream(); + + //Tinymce + stream.add( + gulp.src(["./bower_components/tinymce/plugins/**", + "./bower_components/tinymce/themes/**"], + { base: "./bower_components/tinymce/" }) + .pipe(gulp.dest(root + targets.lib + "/tinymce")) + ); + + //font-awesome + stream.add( + gulp.src(["./bower_components/font-awesome/fonts/*", + "./bower_components/font-awesome/css/font-awesome.min.css"], + { base: "./bower_components/font-awesome/" }) + .pipe(gulp.dest(root + targets.lib + "/font-awesome")) + ); + + // ace Editor + stream.add( + gulp.src(["bower_components/ace-builds/src-min-noconflict/ace.js", + "bower_components/ace-builds/src-min-noconflict/ext-language_tools.js", + "bower_components/ace-builds/src-min-noconflict/ext-searchbox.js", + "bower_components/ace-builds/src-min-noconflict/ext-settings_menu.js", + "bower_components/ace-builds/src-min-noconflict/snippets/text.js", + "bower_components/ace-builds/src-min-noconflict/snippets/javascript.js", + "bower_components/ace-builds/src-min-noconflict/theme-chrome.js", + "bower_components/ace-builds/src-min-noconflict/mode-razor.js", + "bower_components/ace-builds/src-min-noconflict/mode-javascript.js", + "bower_components/ace-builds/src-min-noconflict/worker-javascript.js"], + { base: "./bower_components/ace-builds/" }) + .pipe(gulp.dest(root + targets.lib + "/ace-builds")) + ); + + // code mirror + stream.add( + gulp.src([ + "bower_components/codemirror/lib/codemirror.js", + "bower_components/codemirror/lib/codemirror.css", + + "bower_components/codemirror/mode/css/*", + "bower_components/codemirror/mode/javascript/*", + "bower_components/codemirror/mode/xml/*", + "bower_components/codemirror/mode/htmlmixed/*", + + "bower_components/codemirror/addon/search/*", + "bower_components/codemirror/addon/edit/*", + "bower_components/codemirror/addon/selection/*", + "bower_components/codemirror/addon/dialog/*"], + { base: "./bower_components/codemirror/" }) + .pipe(gulp.dest(root + targets.lib + "/codemirror")) + ); + + //copy over libs which are not on bower (/lib) and + //libraries that have been managed by bower-installer (/lib-bower) + stream.add( + gulp.src(sources.globs.lib) + .pipe(gulp.dest(root + targets.lib)) + ); + + stream.add( + gulp.src(sources.globs.bower) + .pipe(gulp.dest(root + targets.lib)) + ); + + //Copies all static assets into /root / assets folder + //css, fonts and image files + stream.add( + gulp.src(sources.globs.assets) + .pipe(gulp.dest(root + targets.assets)) + ); + + // Copies all the less files related to the preview into their folder + //these are not pre-processed as preview has its own less combiler client side + stream.add( + gulp.src("src/canvasdesigner/editors/*.less") + .pipe(gulp.dest(root + targets.assets + "/less")) + ); + + return stream; +}); + + +/************************** + * Copies all angular JS files into their seperate umbraco.*.js file + **************************/ +gulp.task('js', function () { + + //we run multiple streams, so merge them all together + var stream = new MergeStream(); + + stream.add( + gulp.src(sources.globs.js) + .pipe(gulp.dest(root + targets.js)) + ); + + _.forEach(sources.js, function (group) { + stream.add (processJs(group.files, group.out) ); + }); + + return stream; +}); + +gulp.task('less', function () { + + var stream = new MergeStream(); + + _.forEach(sources.less, function (group) { + stream.add( processLess(group.files, group.out) ); + }); + + return stream; +}); + + +gulp.task('views', function () { + + var stream = new MergeStream(); + + _.forEach(sources.views, function (group) { + + console.log("copying " + group.files + " to " + root + targets.views + group.folder) + + stream.add ( + gulp.src(group.files) + .pipe( gulp.dest(root + targets.views + group.folder) ) + ); + + }); + + return stream; +}); + + +gulp.task('watch', function () { + + var stream = new MergeStream(); + var watchInterval = 500; + + //Setup a watcher for all groups of javascript files + _.forEach(sources.js, function (group) { + + if(group.watch !== false){ + + stream.add( + + watch(group.files, { ignoreInitial: true, interval: watchInterval }, function (file) { + + console.info(file.path + " has changed, added to: " + group.out); + processJs(group.files, group.out); + + }) + + ); + + } + + }); + + stream.add( + //watch all less files and trigger the less task + watch(sources.globs.less, { ignoreInitial: true, interval: watchInterval }, function () { + gulp.run(['less']); + }) + ); + + //watch all views - copy single file changes + stream.add( + watch(sources.globs.views, { interval: watchInterval }) + .pipe(gulp.dest(root + targets.views)) + ); + + //watch all app js files that will not be merged - copy single file changes + stream.add( + watch(sources.globs.js, { interval: watchInterval }) + .pipe(gulp.dest(root + targets.js)) + ); + + return stream; +}); + +/************************** + * Build Backoffice UI API documentation + **************************/ +gulp.task('docs', [], function (cb) { + + var options = { + html5Mode: false, + startPage: '/api', + title: "Umbraco Backoffice UI API Documentation", + dest: 'docs/api', + styles: ['docs/umb-docs.css'], + image: "https://our.umbraco.org/assets/images/logo.svg" + } + + return gulpDocs.sections({ + api: { + glob: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'], + api: true, + title: 'API Documentation' + } + }) + .pipe(gulpDocs.process(options)) + .pipe(gulp.dest('docs/api')); + cb(); +}); + +gulp.task('connect:docs', function (cb) { + connect.server({ + root: 'docs/api', + livereload: true, + fallback: 'docs/api/index.html', + port: 8880 + }); + cb(); +}); + +gulp.task('open:docs', function (cb) { + + var options = { + uri: 'http://localhost:8880/index.html' + }; + + gulp.src(__filename) + .pipe(open(options)); + cb(); +}); + +/************************** + * Build tests + **************************/ + + // Karma test +gulp.task('test:unit', function() { + new karmaServer({ + configFile: __dirname + "/test/config/karma.conf.js", + keepalive: true + }) + .start(); +}); + +gulp.task('test:e2e', function() { + new karmaServer({ + configFile: __dirname + "/test/config/e2e.js", + keepalive: true + }) + .start(); +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navbar.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navbar.less index a448255731..b4e1edf65f 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navbar.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navbar.less @@ -193,10 +193,10 @@ // Reset container width // Required here as we reset the width earlier on and the grid mixins don't override early enough -.navbar-static-top .container, +.navbar-static-top .container, .navbar-fixed-top .container, -.navbar-fixed-bottom .container { - #grid > .core > .span(@gridColumns); +.navbar-fixed-bottom .container { +width: (@gridColumnWidth * @gridColumns) + (@gridGutterWidth * (@gridColumns - 1)); } // Fixed to top diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.eot b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.eot deleted file mode 100755 index 0350809a8c..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.eot and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.svg b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.svg deleted file mode 100755 index 92a12b2740..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.svg +++ /dev/null @@ -1,150 +0,0 @@ - - - - -This is a custom SVG font generated by IcoMoon. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.ttf b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.ttf deleted file mode 100755 index 4529badb87..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.ttf and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.woff b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.woff deleted file mode 100755 index 33eea8ea65..0000000000 Binary files a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/icomoon.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/readme.md b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/readme.md index 253012e724..fa5d63946c 100755 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/readme.md +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/readme.md @@ -1 +1 @@ -Icons are generated and provided by the http://icomoon.io service. +Icons are generated and provided by the http://icomoon.io service. diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.eot b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.eot new file mode 100644 index 0000000000..b144ba0bd9 Binary files /dev/null and b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.eot differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.svg b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.svg new file mode 100644 index 0000000000..b4ee6f4088 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.svg @@ -0,0 +1,63 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.ttf b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.ttf new file mode 100644 index 0000000000..a983e2dc4c Binary files /dev/null and b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.ttf differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.woff b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.woff new file mode 100644 index 0000000000..d8962df76e Binary files /dev/null and b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce-small.woff differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.eot b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.eot new file mode 100644 index 0000000000..f99c13f32f Binary files /dev/null and b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.eot differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.svg b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.svg new file mode 100644 index 0000000000..5727cea425 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.svg @@ -0,0 +1,131 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.ttf b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.ttf new file mode 100644 index 0000000000..16536bfd7a Binary files /dev/null and b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.ttf differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.woff b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.woff new file mode 100644 index 0000000000..74b50f4c30 Binary files /dev/null and b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/fonts/tinymce.woff differ diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.classic.min.css b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.classic.min.css index 86cd46b2d0..18ceba8d63 100755 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.classic.min.css +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.classic.min.css @@ -1 +1 @@ -.mce-container,.mce-container *,.mce-widget,.mce-widget *{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-container ::-webkit-scrollbar{width:8px;height:8px;-webkit-border-radius:4px}.mce-container ::-webkit-scrollbar-track,.mce-container ::-webkit-scrollbar-track-piece{background-color:transparent}.mce-container ::-webkit-scrollbar-thumb{background-color:rgba(53,57,71,0.3);width:6px;height:6px;-webkit-border-radius:4px}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:visible!important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block;border-radius:2px}.mce-wordcount{float:right;padding:8px}.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid #c5c5c5;width:20px;height:20px;line-height:20px;text-align:center;vertical-align:center;padding:2px}.mce-charmap td:hover{background:#d9d9d9}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover{border-color:#c5c5c5}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#e8e8e8;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#c4daff;background:#deeafa}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scroll{position:relative}.mce-panel{border:0 solid #9e9e9e;background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}.mce-floatpanel{position:absolute;-webkit-box-shadow:#ccc 5px 5px 5px;-moz-box-shadow:#ccc 5px 5px 5px;box-shadow:#ccc 5px 5px 5px}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{position:absolute;top:0;left:0;background:#fff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#FFF;position:fixed;top:0;left:0;opacity:0;-webkit-transition:opacity 150ms ease-in;transition:opacity 150ms ease-in}.mce-window.mce-in{opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #EEE;position:relative}.mce-window-head .mce-close{position:absolute;right:15px;top:9px;font-size:20px;font-weight:bold;line-height:20px;color:#CCC;text-shadow:0 1px 0 white;cursor:pointer;height:20px;overflow:hidden}.mce-close:hover{color:#AAA}.mce-window-head .mce-title{display:inline-block;*display:inline;*zoom:1;line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:10px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:whiteSmoke;border-top:1px solid #DDD;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window.mce-fullscreen,.mce-window.mce-fullscreen .mce-foot{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:#fff;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-inner{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-tooltip-inner{-webkit-box-shadow:0 0 5px #000;-moz-box-shadow:0 0 5px #000;box-shadow:0 0 5px #000}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-n .mce-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:0;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-btn{border:1px solid #c5c5c5;position:relative;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-btn:hover,.mce-btn:focus{text-decoration:none;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn.mce-disabled,.mce-btn.mce-disabled:hover{cursor:default;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn button{padding:4px 10px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px #fff}.mce-primary{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);zoom:1;border-color:#04c #04c #002b80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary:hover,.mce-primary:focus{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#005fb3;background-image:-moz-linear-gradient(top,#0077b3,#003cb3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0077b3),to(#003cb3));background-image:-webkit-linear-gradient(top,#0077b3,#003cb3);background-image:-o-linear-gradient(top,#0077b3,#003cb3);background-image:linear-gradient(to bottom,#0077b3,#003cb3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0077b3',endColorstr='#ff003cb3',GradientType=0);zoom:1;border-color:#003cb3 #003cb3 #026;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary button{color:#fff}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:3px 5px;font-size:12px;line-height:15px}.mce-btn-small i{margin-top:0}.mce-btn .mce-caret{margin-top:8px;*margin-top:6px;margin-left:0}.mce-btn-small .mce-caret{margin-top:6px;*margin-top:4px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #444;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#999}.mce-caret.mce-up{border-bottom:4px solid #444;border-top:0}.mce-btn-group .mce-btn{border-width:1px 0 1px 0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-btn-group .mce-btn:hover,.mce-btn-group .mce-btn:focus{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-disabled,.mce-btn-group .mce-btn.mce-disabled:hover{-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-active,.mce-btn-group .mce-btn.mce-active:hover,.mce-btn-group .mce-btn:active{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn-group .mce-btn.mce-disabled button{opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn-group .mce-first{border-left:1px solid #c5c5c5;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.mce-btn-group .mce-last{border-right:1px solid #c5c5c5;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.mce-btn-group .mce-first.mce-last{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0}.mce-checked i.mce-i-checkbox{color:#000;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox{border:1px solid #59a5e1;border:1px solid rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-colorbutton .mce-ico{position:relative}.mce-colorpicker{background:#FFF}.mce-colorbutton-grid{margin:4px}.mce-grid td div{border:1px solid #808080;width:12px;height:12px;margin:2px;cursor:pointer}.mce-grid td div:hover{border-color:black}.mce-grid td div:focus{border-color:#59a5e1;outline:1px solid rgba(82,168,236,0.8);border-color:rgba(82,168,236,0.8)}.mce-colorbutton{position:relative}.mce-colorbutton .mce-preview{display:block;position:absolute;left:50%;top:50%;margin-left:-8px;margin-top:7px;background:gray;width:16px;height:2px;overflow:hidden}.mce-combobox{display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:100px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-combobox input{border-color:1px solid #c5c5c5;border-right-color:rgba(0,0,0,0.15);height:28px}.mce-combobox.mce-has-open input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.mce-combobox .mce-btn{border-left:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox *:focus{border-color:#59a5e1;border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#000}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:gray;color:white}.mce-path .mce-divider{display:inline}.mce-fieldset{border:0 solid #9e9e9e;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-iframe{border:0 solid #c5c5c5;width:100%;height:100%}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);border:0 solid #c5c5c5;overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label-disabled .mce-text{color:#999}.mce-label.mce-multiline{white-space:pre-wrap}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:none}.mce-menubar{border:1px solid #ddd}.mce-menubar .mce-menubtn button{color:#000}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubtn:focus{border-color:transparent;background:#ddd;filter:none}.mce-menubtn.mce-disabled span{color:#999}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-listbox span{width:100%;display:block;overflow:hidden}.mce-menu-item{display:block;padding:6px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal}.mce-menu-item.mce-disabled .mce-text{color:#999}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0);zoom:1}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text{color:#fff}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:white}.mce-menu-shortcut{display:inline-block;color:#999}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 20px 0 20px}.mce-menu-item .mce-caret{margin-top:6px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #666}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret{border-left-color:#FFF}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item-sep,.mce-menu-item-sep:hover{padding:0;height:1px;margin:9px 1px;overflow:hidden;background:#e5e5e5;border-bottom:1px solid white;cursor:default;filter:none}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item.mce-active{background-color:#c8def4;outline:1px solid #c5c5c5}.mce-menu-item-checkbox.mce-active{background-color:#FFF;outline:0}.mce-menu{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:2px 0 0;min-width:160px;background:#FFF;border:1px solid #CCC;border:1px solid rgba(0,0,0,0.2);z-index:1002;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline;*zoom:1}.mce-menu-sub{margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}i.mce-radio{padding:1px;margin:0 3px 0 0;background-color:#fafafa;border:1px solid #cacece;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}i.mce-radio:after{font-family:Arial;font-size:12px;color:#000;content:'\25cf'}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#000}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent;border-right:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#c5c5c5;border-right-color:#c5c5c5}.mce-splitbtn button{padding-right:4px}.mce-splitbtn .mce-open{padding-left:4px}.mce-splitbtn .mce-open.mce-active{-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #ccc}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #ccc;border-width:1px 1px 0 0;background:#e3e3e3;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#fdfdfd}.mce-tab.mce-active{background:#fdfdfd;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-textbox{background:#FFF;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);display:inline-block;-webkit-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:normal;color:#000}.mce-textbox:focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}@font-face{font-family:'tinymce';src:url('fonts/icomoon.eot');src:url('fonts/icomoon.eot?#iefix') format('embedded-opentype'),url('fonts/icomoon.svg#icomoon') format('svg'),url('fonts/icomoon.woff') format('woff'),url('fonts/icomoon.ttf') format('truetype');font-weight:normal;font-style:normal}.mce-ico{font-family:'tinymce',Arial;font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333}.mce-i-save:before{content:"\e000"}.mce-i-newdocument:before{content:"\e001"}.mce-i-fullpage:before{content:"\e002"}.mce-i-alignleft:before{content:"\e003"}.mce-i-aligncenter:before{content:"\e004"}.mce-i-alignright:before{content:"\e005"}.mce-i-alignjustify:before{content:"\e006"}.mce-i-cut:before{content:"\e007"}.mce-i-paste:before{content:"\e008"}.mce-i-searchreplace:before{content:"\e009"}.mce-i-bullist:before{content:"\e00a"}.mce-i-numlist:before{content:"\e00b"}.mce-i-indent:before{content:"\e00c"}.mce-i-outdent:before{content:"\e00d"}.mce-i-blockquote:before{content:"\e00e"}.mce-i-undo:before{content:"\e00f"}.mce-i-redo:before{content:"\e010"}.mce-i-link:before{content:"\e011"}.mce-i-unlink:before{content:"\e012"}.mce-i-anchor:before{content:"\e013"}.mce-i-image:before{content:"\e014"}.mce-i-media:before{content:"\e015"}.mce-i-help:before{content:"\e016"}.mce-i-code:before{content:"\e017"}.mce-i-inserttime:before{content:"\e018"}.mce-i-preview:before{content:"\e019"}.mce-i-forecolor:before{content:"\e01a"}.mce-i-backcolor:before{content:"\e01a"}.mce-i-table:before{content:"\e01b"}.mce-i-hr:before{content:"\e01c"}.mce-i-removeformat:before{content:"\e01d"}.mce-i-subscript:before{content:"\e01e"}.mce-i-superscript:before{content:"\e01f"}.mce-i-charmap:before{content:"\e020"}.mce-i-emoticons:before{content:"\e021"}.mce-i-print:before{content:"\e022"}.mce-i-fullscreen:before{content:"\e023"}.mce-i-spellchecker:before{content:"\e024"}.mce-i-nonbreaking:before{content:"\e025"}.mce-i-template:before{content:"\e026"}.mce-i-pagebreak:before{content:"\e027"}.mce-i-restoredraft:before{content:"\e028"}.mce-i-untitled:before{content:"\e029"}.mce-i-bold:before{content:"\e02a"}.mce-i-italic:before{content:"\e02b"}.mce-i-underline:before{content:"\e02c"}.mce-i-strikethrough:before{content:"\e02d"}.mce-i-visualchars:before{content:"\e02e"}.mce-i-visualblocks:before{content:"\e026"}.mce-i-ltr:before{content:"\e02f"}.mce-i-rtl:before{content:"\e030"}.mce-i-copy:before{content:"\e031"}.mce-i-resize:before{content:"\e032"}.mce-i-browse:before{content:"\e034"}.mce-i-checkbox:before,.mce-i-selected:before{content:"\e033"}.mce-i-selected{visibility:hidden}i.mce-i-backcolor{text-shadow:none;background:#BBB} \ No newline at end of file +.mce-container,.mce-container *,.mce-widget,.mce-widget *{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-container ::-webkit-scrollbar{width:8px;height:8px;-webkit-border-radius:4px}.mce-container ::-webkit-scrollbar-track,.mce-container ::-webkit-scrollbar-track-piece{background-color:transparent}.mce-container ::-webkit-scrollbar-thumb{background-color:rgba(53,57,71,0.3);width:6px;height:6px;-webkit-border-radius:4px}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:visible!important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block;border-radius:2px}.mce-wordcount{float:right;padding:8px}.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid #c5c5c5;width:20px;height:20px;line-height:20px;text-align:center;vertical-align:center;padding:2px}.mce-charmap td:hover{background:#d9d9d9}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover{border-color:#c5c5c5}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#e8e8e8;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#c4daff;background:#deeafa}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scroll{position:relative}.mce-panel{border:0 solid #9e9e9e;background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}.mce-floatpanel{position:absolute;-webkit-box-shadow:#ccc 5px 5px 5px;-moz-box-shadow:#ccc 5px 5px 5px;box-shadow:#ccc 5px 5px 5px}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{position:absolute;top:0;left:0;background:#fff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#FFF;position:fixed;top:0;left:0;opacity:0;-webkit-transition:opacity 150ms ease-in;transition:opacity 150ms ease-in}.mce-window.mce-in{opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #EEE;position:relative}.mce-window-head .mce-close{position:absolute;right:15px;top:9px;font-size:20px;font-weight:bold;line-height:20px;color:#CCC;text-shadow:0 1px 0 white;cursor:pointer;height:20px;overflow:hidden}.mce-close:hover{color:#AAA}.mce-window-head .mce-title{display:inline-block;*display:inline;*zoom:1;line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:10px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:whiteSmoke;border-top:1px solid #DDD;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window.mce-fullscreen,.mce-window.mce-fullscreen .mce-foot{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:#fff;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-inner{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-tooltip-inner{-webkit-box-shadow:0 0 5px #000;-moz-box-shadow:0 0 5px #000;box-shadow:0 0 5px #000}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-n .mce-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:0;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-btn{border:1px solid #c5c5c5;position:relative;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-btn:hover,.mce-btn:focus{text-decoration:none;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn.mce-disabled,.mce-btn.mce-disabled:hover{cursor:default;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn button{padding:4px 10px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px #fff}.mce-primary{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);zoom:1;border-color:#04c #04c #002b80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary:hover,.mce-primary:focus{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#005fb3;background-image:-moz-linear-gradient(top,#0077b3,#003cb3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0077b3),to(#003cb3));background-image:-webkit-linear-gradient(top,#0077b3,#003cb3);background-image:-o-linear-gradient(top,#0077b3,#003cb3);background-image:linear-gradient(to bottom,#0077b3,#003cb3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0077b3',endColorstr='#ff003cb3',GradientType=0);zoom:1;border-color:#003cb3 #003cb3 #026;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary button{color:#fff}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:3px 5px;font-size:12px;line-height:15px}.mce-btn-small i{margin-top:0}.mce-btn .mce-caret{margin-top:8px;*margin-top:6px;margin-left:0}.mce-btn-small .mce-caret{margin-top:6px;*margin-top:4px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #444;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#999}.mce-caret.mce-up{border-bottom:4px solid #444;border-top:0}.mce-btn-group .mce-btn{border-width:1px 0 1px 0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-btn-group .mce-btn:hover,.mce-btn-group .mce-btn:focus{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-disabled,.mce-btn-group .mce-btn.mce-disabled:hover{-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-active,.mce-btn-group .mce-btn.mce-active:hover,.mce-btn-group .mce-btn:active{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn-group .mce-btn.mce-disabled button{opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn-group .mce-first{border-left:1px solid #c5c5c5;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.mce-btn-group .mce-last{border-right:1px solid #c5c5c5;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.mce-btn-group .mce-first.mce-last{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0}.mce-checked i.mce-i-checkbox{color:#000;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox{border:1px solid #59a5e1;border:1px solid rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-colorbutton .mce-ico{position:relative}.mce-colorpicker{background:#FFF}.mce-colorbutton-grid{margin:4px}.mce-grid td div{border:1px solid #808080;width:12px;height:12px;margin:2px;cursor:pointer}.mce-grid td div:hover{border-color:black}.mce-grid td div:focus{border-color:#59a5e1;outline:1px solid rgba(82,168,236,0.8);border-color:rgba(82,168,236,0.8)}.mce-colorbutton{position:relative}.mce-colorbutton .mce-preview{display:block;position:absolute;left:50%;top:50%;margin-left:-8px;margin-top:7px;background:gray;width:16px;height:2px;overflow:hidden}.mce-combobox{display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:100px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-combobox input{border-color:1px solid #c5c5c5;border-right-color:rgba(0,0,0,0.15);height:28px}.mce-combobox.mce-has-open input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.mce-combobox .mce-btn{border-left:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox *:focus{border-color:#59a5e1;border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#000}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:gray;color:white}.mce-path .mce-divider{display:inline}.mce-fieldset{border:0 solid #9e9e9e;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-iframe{border:0 solid #c5c5c5;width:100%;height:100%}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);border:0 solid #c5c5c5;overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label-disabled .mce-text{color:#999}.mce-label.mce-multiline{white-space:pre-wrap}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:none}.mce-menubar{border:1px solid #ddd}.mce-menubar .mce-menubtn button{color:#000}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubtn:focus{border-color:transparent;background:#ddd;filter:none}.mce-menubtn.mce-disabled span{color:#999}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-listbox span{width:100%;display:block;overflow:hidden}.mce-menu-item{display:block;padding:6px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal}.mce-menu-item.mce-disabled .mce-text{color:#999}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0);zoom:1}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text{color:#fff}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:white}.mce-menu-shortcut{display:inline-block;color:#999}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 20px 0 20px}.mce-menu-item .mce-caret{margin-top:6px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #666}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret{border-left-color:#FFF}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item-sep,.mce-menu-item-sep:hover{padding:0;height:1px;margin:9px 1px;overflow:hidden;background:#e5e5e5;border-bottom:1px solid white;cursor:default;filter:none}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item.mce-active{background-color:#c8def4;outline:1px solid #c5c5c5}.mce-menu-item-checkbox.mce-active{background-color:#FFF;outline:0}.mce-menu{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:2px 0 0;min-width:160px;background:#FFF;border:1px solid #CCC;border:1px solid rgba(0,0,0,0.2);z-index:1002;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline;*zoom:1}.mce-menu-sub{margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}i.mce-radio{padding:1px;margin:0 3px 0 0;background-color:#fafafa;border:1px solid #cacece;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}i.mce-radio:after{font-family:Arial;font-size:12px;color:#000;content:'\25cf'}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#000}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent;border-right:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#c5c5c5;border-right-color:#c5c5c5}.mce-splitbtn button{padding-right:4px}.mce-splitbtn .mce-open{padding-left:4px}.mce-splitbtn .mce-open.mce-active{-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #ccc}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #ccc;border-width:1px 1px 0 0;background:#e3e3e3;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#fdfdfd}.mce-tab.mce-active{background:#fdfdfd;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-textbox{background:#FFF;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);display:inline-block;-webkit-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:normal;color:#000}.mce-textbox:focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}@font-face{font-family:'tinymce';src:url('fonts/tinymce.eot');src:url('fonts/tinymce.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce.svg#icomoon') format('svg'),url('fonts/tinymce.woff') format('woff'),url('fonts/tinymce.ttf') format('truetype');font-weight:normal;font-style:normal}.mce-ico{font-family:'tinymce',Arial;font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333}.mce-i-save:before{content:"\e000"}.mce-i-newdocument:before{content:"\e001"}.mce-i-fullpage:before{content:"\e002"}.mce-i-alignleft:before{content:"\e003"}.mce-i-aligncenter:before{content:"\e004"}.mce-i-alignright:before{content:"\e005"}.mce-i-alignjustify:before{content:"\e006"}.mce-i-cut:before{content:"\e007"}.mce-i-paste:before{content:"\e008"}.mce-i-searchreplace:before{content:"\e009"}.mce-i-bullist:before{content:"\e00a"}.mce-i-numlist:before{content:"\e00b"}.mce-i-indent:before{content:"\e00c"}.mce-i-outdent:before{content:"\e00d"}.mce-i-blockquote:before{content:"\e00e"}.mce-i-undo:before{content:"\e00f"}.mce-i-redo:before{content:"\e010"}.mce-i-link:before{content:"\e011"}.mce-i-unlink:before{content:"\e012"}.mce-i-anchor:before{content:"\e013"}.mce-i-image:before{content:"\e014"}.mce-i-media:before{content:"\e015"}.mce-i-help:before{content:"\e016"}.mce-i-code:before{content:"\e017"}.mce-i-inserttime:before{content:"\e018"}.mce-i-preview:before{content:"\e019"}.mce-i-forecolor:before{content:"\e01a"}.mce-i-backcolor:before{content:"\e01a"}.mce-i-table:before{content:"\e01b"}.mce-i-hr:before{content:"\e01c"}.mce-i-removeformat:before{content:"\e01d"}.mce-i-subscript:before{content:"\e01e"}.mce-i-superscript:before{content:"\e01f"}.mce-i-charmap:before{content:"\e020"}.mce-i-emoticons:before{content:"\e021"}.mce-i-print:before{content:"\e022"}.mce-i-fullscreen:before{content:"\e023"}.mce-i-spellchecker:before{content:"\e024"}.mce-i-nonbreaking:before{content:"\e025"}.mce-i-template:before{content:"\e026"}.mce-i-pagebreak:before{content:"\e027"}.mce-i-restoredraft:before{content:"\e028"}.mce-i-untitled:before{content:"\e029"}.mce-i-bold:before{content:"\e02a"}.mce-i-italic:before{content:"\e02b"}.mce-i-underline:before{content:"\e02c"}.mce-i-strikethrough:before{content:"\e02d"}.mce-i-visualchars:before{content:"\e02e"}.mce-i-visualblocks:before{content:"\e026"}.mce-i-ltr:before{content:"\e02f"}.mce-i-rtl:before{content:"\e030"}.mce-i-copy:before{content:"\e031"}.mce-i-resize:before{content:"\e032"}.mce-i-browse:before{content:"\e034"}.mce-i-checkbox:before,.mce-i-selected:before{content:"\e033"}.mce-i-selected{visibility:hidden}i.mce-i-backcolor{text-shadow:none;background:#BBB} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.ie7.min.css b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.ie7.min.css index 3c200ea3a0..bb0ca2bac6 100755 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.ie7.min.css +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.ie7.min.css @@ -1 +1 @@ -.mce-container,.mce-container *,.mce-widget,.mce-widget *{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-container ::-webkit-scrollbar{width:8px;height:8px;-webkit-border-radius:4px}.mce-container ::-webkit-scrollbar-track,.mce-container ::-webkit-scrollbar-track-piece{background-color:transparent}.mce-container ::-webkit-scrollbar-thumb{background-color:rgba(53,57,71,0.3);width:6px;height:6px;-webkit-border-radius:4px}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:visible!important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block;border-radius:2px}.mce-wordcount{float:right;padding:8px}.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid #c5c5c5;width:20px;height:20px;line-height:20px;text-align:center;vertical-align:center;padding:2px}.mce-charmap td:hover{background:#d9d9d9}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover{border-color:#c5c5c5}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#e8e8e8;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#c4daff;background:#deeafa}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scroll{position:relative}.mce-panel{border:0 solid #9e9e9e;background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}.mce-floatpanel{position:absolute;-webkit-box-shadow:#ccc 5px 5px 5px;-moz-box-shadow:#ccc 5px 5px 5px;box-shadow:#ccc 5px 5px 5px}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{position:absolute;top:0;left:0;background:#fff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#FFF;position:fixed;top:0;left:0;opacity:0;-webkit-transition:opacity 150ms ease-in;transition:opacity 150ms ease-in}.mce-window.mce-in{opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #EEE;position:relative}.mce-window-head .mce-close{position:absolute;right:15px;top:9px;font-size:20px;font-weight:bold;line-height:20px;color:#CCC;text-shadow:0 1px 0 white;cursor:pointer;height:20px;overflow:hidden}.mce-close:hover{color:#AAA}.mce-window-head .mce-title{display:inline-block;*display:inline;*zoom:1;line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:10px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:whiteSmoke;border-top:1px solid #DDD;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window.mce-fullscreen,.mce-window.mce-fullscreen .mce-foot{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:#fff;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-inner{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-tooltip-inner{-webkit-box-shadow:0 0 5px #000;-moz-box-shadow:0 0 5px #000;box-shadow:0 0 5px #000}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-n .mce-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:0;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-btn{border:1px solid #c5c5c5;position:relative;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-btn:hover,.mce-btn:focus{text-decoration:none;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn.mce-disabled,.mce-btn.mce-disabled:hover{cursor:default;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn button{padding:4px 10px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px #fff}.mce-primary{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);zoom:1;border-color:#04c #04c #002b80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary:hover,.mce-primary:focus{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#005fb3;background-image:-moz-linear-gradient(top,#0077b3,#003cb3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0077b3),to(#003cb3));background-image:-webkit-linear-gradient(top,#0077b3,#003cb3);background-image:-o-linear-gradient(top,#0077b3,#003cb3);background-image:linear-gradient(to bottom,#0077b3,#003cb3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0077b3',endColorstr='#ff003cb3',GradientType=0);zoom:1;border-color:#003cb3 #003cb3 #026;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary button{color:#fff}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:3px 5px;font-size:12px;line-height:15px}.mce-btn-small i{margin-top:0}.mce-btn .mce-caret{margin-top:8px;*margin-top:6px;margin-left:0}.mce-btn-small .mce-caret{margin-top:6px;*margin-top:4px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #444;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#999}.mce-caret.mce-up{border-bottom:4px solid #444;border-top:0}.mce-btn-group .mce-btn{border-width:1px 0 1px 0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-btn-group .mce-btn:hover,.mce-btn-group .mce-btn:focus{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-disabled,.mce-btn-group .mce-btn.mce-disabled:hover{-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-active,.mce-btn-group .mce-btn.mce-active:hover,.mce-btn-group .mce-btn:active{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn-group .mce-btn.mce-disabled button{opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn-group .mce-first{border-left:1px solid #c5c5c5;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.mce-btn-group .mce-last{border-right:1px solid #c5c5c5;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.mce-btn-group .mce-first.mce-last{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0}.mce-checked i.mce-i-checkbox{color:#000;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox{border:1px solid #59a5e1;border:1px solid rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-colorbutton .mce-ico{position:relative}.mce-colorpicker{background:#FFF}.mce-colorbutton-grid{margin:4px}.mce-grid td div{border:1px solid #808080;width:12px;height:12px;margin:2px;cursor:pointer}.mce-grid td div:hover{border-color:black}.mce-grid td div:focus{border-color:#59a5e1;outline:1px solid rgba(82,168,236,0.8);border-color:rgba(82,168,236,0.8)}.mce-colorbutton{position:relative}.mce-colorbutton .mce-preview{display:block;position:absolute;left:50%;top:50%;margin-left:-8px;margin-top:7px;background:gray;width:16px;height:2px;overflow:hidden}.mce-combobox{display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:100px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-combobox input{border-color:1px solid #c5c5c5;border-right-color:rgba(0,0,0,0.15);height:28px}.mce-combobox.mce-has-open input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.mce-combobox .mce-btn{border-left:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox *:focus{border-color:#59a5e1;border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#000}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:gray;color:white}.mce-path .mce-divider{display:inline}.mce-fieldset{border:0 solid #9e9e9e;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-iframe{border:0 solid #c5c5c5;width:100%;height:100%}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);border:0 solid #c5c5c5;overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label-disabled .mce-text{color:#999}.mce-label.mce-multiline{white-space:pre-wrap}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:none}.mce-menubar{border:1px solid #ddd}.mce-menubar .mce-menubtn button{color:#000}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubtn:focus{border-color:transparent;background:#ddd;filter:none}.mce-menubtn.mce-disabled span{color:#999}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-listbox span{width:100%;display:block;overflow:hidden}.mce-menu-item{display:block;padding:6px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal}.mce-menu-item.mce-disabled .mce-text{color:#999}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0);zoom:1}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text{color:#fff}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:white}.mce-menu-shortcut{display:inline-block;color:#999}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 20px 0 20px}.mce-menu-item .mce-caret{margin-top:6px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #666}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret{border-left-color:#FFF}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item-sep,.mce-menu-item-sep:hover{padding:0;height:1px;margin:9px 1px;overflow:hidden;background:#e5e5e5;border-bottom:1px solid white;cursor:default;filter:none}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item.mce-active{background-color:#c8def4;outline:1px solid #c5c5c5}.mce-menu-item-checkbox.mce-active{background-color:#FFF;outline:0}.mce-menu{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:2px 0 0;min-width:160px;background:#FFF;border:1px solid #CCC;border:1px solid rgba(0,0,0,0.2);z-index:1002;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline;*zoom:1}.mce-menu-sub{margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}i.mce-radio{padding:1px;margin:0 3px 0 0;background-color:#fafafa;border:1px solid #cacece;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}i.mce-radio:after{font-family:Arial;font-size:12px;color:#000;content:'\25cf'}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#000}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent;border-right:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#c5c5c5;border-right-color:#c5c5c5}.mce-splitbtn button{padding-right:4px}.mce-splitbtn .mce-open{padding-left:4px}.mce-splitbtn .mce-open.mce-active{-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #ccc}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #ccc;border-width:1px 1px 0 0;background:#e3e3e3;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#fdfdfd}.mce-tab.mce-active{background:#fdfdfd;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-textbox{background:#FFF;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);display:inline-block;-webkit-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:normal;color:#000}.mce-textbox:focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}@font-face{font-family:'icomoon';src:url('fonts/icomoon.eot');src:url('fonts/icomoon.eot?#iefix') format('embedded-opentype'),url('fonts/icomoon.svg#icomoon') format('svg'),url('fonts/icomoon.woff') format('woff'),url('fonts/icomoon.ttf') format('truetype');font-weight:normal;font-style:normal}.mce-ico{font-family:'icomoon';font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333;-ie7-icon:' '}.mce-ico,i.mce-i-checkbox{zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = this.currentStyle['-ie7-icon'].substr(1,1)+' ')}.mce-i-save{-ie7-icon:"\e000"}.mce-i-newdocument{-ie7-icon:"\e001"}.mce-i-fullpage{-ie7-icon:"\e002"}.mce-i-alignleft{-ie7-icon:"\e003"}.mce-i-aligncenter{-ie7-icon:"\e004"}.mce-i-alignright{-ie7-icon:"\e005"}.mce-i-alignjustify{-ie7-icon:"\e006"}.mce-i-cut{-ie7-icon:"\e007"}.mce-i-paste{-ie7-icon:"\e008"}.mce-i-searchreplace{-ie7-icon:"\e009"}.mce-i-bullist{-ie7-icon:"\e00a"}.mce-i-numlist{-ie7-icon:"\e00b"}.mce-i-indent{-ie7-icon:"\e00c"}.mce-i-outdent{-ie7-icon:"\e00d"}.mce-i-blockquote{-ie7-icon:"\e00e"}.mce-i-undo{-ie7-icon:"\e00f"}.mce-i-redo{-ie7-icon:"\e010"}.mce-i-link{-ie7-icon:"\e011"}.mce-i-unlink{-ie7-icon:"\e012"}.mce-i-anchor{-ie7-icon:"\e013"}.mce-i-image{-ie7-icon:"\e014"}.mce-i-media{-ie7-icon:"\e015"}.mce-i-help{-ie7-icon:"\e016"}.mce-i-code{-ie7-icon:"\e017"}.mce-i-inserttime{-ie7-icon:"\e018"}.mce-i-preview{-ie7-icon:"\e019"}.mce-i-forecolor{-ie7-icon:"\e01a"}.mce-i-backcolor{-ie7-icon:"\e01a"}.mce-i-table{-ie7-icon:"\e01b"}.mce-i-hr{-ie7-icon:"\e01c"}.mce-i-removeformat{-ie7-icon:"\e01d"}.mce-i-subscript{-ie7-icon:"\e01e"}.mce-i-superscript{-ie7-icon:"\e01f"}.mce-i-charmap{-ie7-icon:"\e020"}.mce-i-emoticons{-ie7-icon:"\e021"}.mce-i-print{-ie7-icon:"\e022"}.mce-i-fullscreen{-ie7-icon:"\e023"}.mce-i-spellchecker{-ie7-icon:"\e024"}.mce-i-nonbreaking{-ie7-icon:"\e025"}.mce-i-template{-ie7-icon:"\e026"}.mce-i-pagebreak{-ie7-icon:"\e027"}.mce-i-restoredraft{-ie7-icon:"\e028"}.mce-i-untitled{-ie7-icon:"\e029"}.mce-i-bold{-ie7-icon:"\e02a"}.mce-i-italic{-ie7-icon:"\e02b"}.mce-i-underline{-ie7-icon:"\e02c"}.mce-i-strikethrough{-ie7-icon:"\e02d"}.mce-i-visualchars{-ie7-icon:"\e02e"}.mce-i-visualblocks:before{content:"\e026"}.mce-i-ltr{-ie7-icon:"\e02f"}.mce-i-rtl{-ie7-icon:"\e030"}.mce-i-copy{-ie7-icon:"\e031"}.mce-i-resize{-ie7-icon:"\e032"}.mce-i-browse{-ie7-icon:"\e034"}.mce-i-checkbox,.mce-i-selected{-ie7-icon:"\e033"}.mce-i-selected{visibility:hidden}.mce-i-backcolor{background:#BBB} \ No newline at end of file +.mce-container,.mce-container *,.mce-widget,.mce-widget *{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-container ::-webkit-scrollbar{width:8px;height:8px;-webkit-border-radius:4px}.mce-container ::-webkit-scrollbar-track,.mce-container ::-webkit-scrollbar-track-piece{background-color:transparent}.mce-container ::-webkit-scrollbar-thumb{background-color:rgba(53,57,71,0.3);width:6px;height:6px;-webkit-border-radius:4px}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:visible!important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block;border-radius:2px}.mce-wordcount{float:right;padding:8px}.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid #c5c5c5;width:20px;height:20px;line-height:20px;text-align:center;vertical-align:center;padding:2px}.mce-charmap td:hover{background:#d9d9d9}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover{border-color:#c5c5c5}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#e8e8e8;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#c4daff;background:#deeafa}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scroll{position:relative}.mce-panel{border:0 solid #9e9e9e;background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}.mce-floatpanel{position:absolute;-webkit-box-shadow:#ccc 5px 5px 5px;-moz-box-shadow:#ccc 5px 5px 5px;box-shadow:#ccc 5px 5px 5px}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{position:absolute;top:0;left:0;background:#fff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#FFF;position:fixed;top:0;left:0;opacity:0;-webkit-transition:opacity 150ms ease-in;transition:opacity 150ms ease-in}.mce-window.mce-in{opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #EEE;position:relative}.mce-window-head .mce-close{position:absolute;right:15px;top:9px;font-size:20px;font-weight:bold;line-height:20px;color:#CCC;text-shadow:0 1px 0 white;cursor:pointer;height:20px;overflow:hidden}.mce-close:hover{color:#AAA}.mce-window-head .mce-title{display:inline-block;*display:inline;*zoom:1;line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:10px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:whiteSmoke;border-top:1px solid #DDD;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window.mce-fullscreen,.mce-window.mce-fullscreen .mce-foot{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:#fff;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-inner{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-tooltip-inner{-webkit-box-shadow:0 0 5px #000;-moz-box-shadow:0 0 5px #000;box-shadow:0 0 5px #000}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-n .mce-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:0;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-btn{border:1px solid #c5c5c5;position:relative;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-btn:hover,.mce-btn:focus{text-decoration:none;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn.mce-disabled,.mce-btn.mce-disabled:hover{cursor:default;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn button{padding:4px 10px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px #fff}.mce-primary{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);zoom:1;border-color:#04c #04c #002b80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary:hover,.mce-primary:focus{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#005fb3;background-image:-moz-linear-gradient(top,#0077b3,#003cb3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0077b3),to(#003cb3));background-image:-webkit-linear-gradient(top,#0077b3,#003cb3);background-image:-o-linear-gradient(top,#0077b3,#003cb3);background-image:linear-gradient(to bottom,#0077b3,#003cb3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0077b3',endColorstr='#ff003cb3',GradientType=0);zoom:1;border-color:#003cb3 #003cb3 #026;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary button{color:#fff}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:3px 5px;font-size:12px;line-height:15px}.mce-btn-small i{margin-top:0}.mce-btn .mce-caret{margin-top:8px;*margin-top:6px;margin-left:0}.mce-btn-small .mce-caret{margin-top:6px;*margin-top:4px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #444;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#999}.mce-caret.mce-up{border-bottom:4px solid #444;border-top:0}.mce-btn-group .mce-btn{border-width:1px 0 1px 0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-btn-group .mce-btn:hover,.mce-btn-group .mce-btn:focus{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-disabled,.mce-btn-group .mce-btn.mce-disabled:hover{-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-active,.mce-btn-group .mce-btn.mce-active:hover,.mce-btn-group .mce-btn:active{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn-group .mce-btn.mce-disabled button{opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn-group .mce-first{border-left:1px solid #c5c5c5;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.mce-btn-group .mce-last{border-right:1px solid #c5c5c5;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.mce-btn-group .mce-first.mce-last{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0}.mce-checked i.mce-i-checkbox{color:#000;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox{border:1px solid #59a5e1;border:1px solid rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-colorbutton .mce-ico{position:relative}.mce-colorpicker{background:#FFF}.mce-colorbutton-grid{margin:4px}.mce-grid td div{border:1px solid #808080;width:12px;height:12px;margin:2px;cursor:pointer}.mce-grid td div:hover{border-color:black}.mce-grid td div:focus{border-color:#59a5e1;outline:1px solid rgba(82,168,236,0.8);border-color:rgba(82,168,236,0.8)}.mce-colorbutton{position:relative}.mce-colorbutton .mce-preview{display:block;position:absolute;left:50%;top:50%;margin-left:-8px;margin-top:7px;background:gray;width:16px;height:2px;overflow:hidden}.mce-combobox{display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:100px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-combobox input{border-color:1px solid #c5c5c5;border-right-color:rgba(0,0,0,0.15);height:28px}.mce-combobox.mce-has-open input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.mce-combobox .mce-btn{border-left:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox *:focus{border-color:#59a5e1;border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#000}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:gray;color:white}.mce-path .mce-divider{display:inline}.mce-fieldset{border:0 solid #9e9e9e;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-iframe{border:0 solid #c5c5c5;width:100%;height:100%}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);border:0 solid #c5c5c5;overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label-disabled .mce-text{color:#999}.mce-label.mce-multiline{white-space:pre-wrap}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:none}.mce-menubar{border:1px solid #ddd}.mce-menubar .mce-menubtn button{color:#000}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubtn:focus{border-color:transparent;background:#ddd;filter:none}.mce-menubtn.mce-disabled span{color:#999}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-listbox span{width:100%;display:block;overflow:hidden}.mce-menu-item{display:block;padding:6px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal}.mce-menu-item.mce-disabled .mce-text{color:#999}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0);zoom:1}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text{color:#fff}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:white}.mce-menu-shortcut{display:inline-block;color:#999}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 20px 0 20px}.mce-menu-item .mce-caret{margin-top:6px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #666}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret{border-left-color:#FFF}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item-sep,.mce-menu-item-sep:hover{padding:0;height:1px;margin:9px 1px;overflow:hidden;background:#e5e5e5;border-bottom:1px solid white;cursor:default;filter:none}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item.mce-active{background-color:#c8def4;outline:1px solid #c5c5c5}.mce-menu-item-checkbox.mce-active{background-color:#FFF;outline:0}.mce-menu{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:2px 0 0;min-width:160px;background:#FFF;border:1px solid #CCC;border:1px solid rgba(0,0,0,0.2);z-index:1002;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline;*zoom:1}.mce-menu-sub{margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}i.mce-radio{padding:1px;margin:0 3px 0 0;background-color:#fafafa;border:1px solid #cacece;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}i.mce-radio:after{font-family:Arial;font-size:12px;color:#000;content:'\25cf'}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#000}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent;border-right:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#c5c5c5;border-right-color:#c5c5c5}.mce-splitbtn button{padding-right:4px}.mce-splitbtn .mce-open{padding-left:4px}.mce-splitbtn .mce-open.mce-active{-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #ccc}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #ccc;border-width:1px 1px 0 0;background:#e3e3e3;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#fdfdfd}.mce-tab.mce-active{background:#fdfdfd;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-textbox{background:#FFF;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);display:inline-block;-webkit-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:normal;color:#000}.mce-textbox:focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}@font-face{font-family:'icomoon';src:url('fonts/tinymce.eot');src:url('fonts/tinymce.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce.svg#icomoon') format('svg'),url('fonts/tinymce.woff') format('woff'),url('fonts/tinymce.ttf') format('truetype');font-weight:normal;font-style:normal}.mce-ico{font-family:'icomoon';font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333;-ie7-icon:' '}.mce-ico,i.mce-i-checkbox{zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = this.currentStyle['-ie7-icon'].substr(1,1)+' ')}.mce-i-save{-ie7-icon:"\e000"}.mce-i-newdocument{-ie7-icon:"\e001"}.mce-i-fullpage{-ie7-icon:"\e002"}.mce-i-alignleft{-ie7-icon:"\e003"}.mce-i-aligncenter{-ie7-icon:"\e004"}.mce-i-alignright{-ie7-icon:"\e005"}.mce-i-alignjustify{-ie7-icon:"\e006"}.mce-i-cut{-ie7-icon:"\e007"}.mce-i-paste{-ie7-icon:"\e008"}.mce-i-searchreplace{-ie7-icon:"\e009"}.mce-i-bullist{-ie7-icon:"\e00a"}.mce-i-numlist{-ie7-icon:"\e00b"}.mce-i-indent{-ie7-icon:"\e00c"}.mce-i-outdent{-ie7-icon:"\e00d"}.mce-i-blockquote{-ie7-icon:"\e00e"}.mce-i-undo{-ie7-icon:"\e00f"}.mce-i-redo{-ie7-icon:"\e010"}.mce-i-link{-ie7-icon:"\e011"}.mce-i-unlink{-ie7-icon:"\e012"}.mce-i-anchor{-ie7-icon:"\e013"}.mce-i-image{-ie7-icon:"\e014"}.mce-i-media{-ie7-icon:"\e015"}.mce-i-help{-ie7-icon:"\e016"}.mce-i-code{-ie7-icon:"\e017"}.mce-i-inserttime{-ie7-icon:"\e018"}.mce-i-preview{-ie7-icon:"\e019"}.mce-i-forecolor{-ie7-icon:"\e01a"}.mce-i-backcolor{-ie7-icon:"\e01a"}.mce-i-table{-ie7-icon:"\e01b"}.mce-i-hr{-ie7-icon:"\e01c"}.mce-i-removeformat{-ie7-icon:"\e01d"}.mce-i-subscript{-ie7-icon:"\e01e"}.mce-i-superscript{-ie7-icon:"\e01f"}.mce-i-charmap{-ie7-icon:"\e020"}.mce-i-emoticons{-ie7-icon:"\e021"}.mce-i-print{-ie7-icon:"\e022"}.mce-i-fullscreen{-ie7-icon:"\e023"}.mce-i-spellchecker{-ie7-icon:"\e024"}.mce-i-nonbreaking{-ie7-icon:"\e025"}.mce-i-template{-ie7-icon:"\e026"}.mce-i-pagebreak{-ie7-icon:"\e027"}.mce-i-restoredraft{-ie7-icon:"\e028"}.mce-i-untitled{-ie7-icon:"\e029"}.mce-i-bold{-ie7-icon:"\e02a"}.mce-i-italic{-ie7-icon:"\e02b"}.mce-i-underline{-ie7-icon:"\e02c"}.mce-i-strikethrough{-ie7-icon:"\e02d"}.mce-i-visualchars{-ie7-icon:"\e02e"}.mce-i-visualblocks:before{content:"\e026"}.mce-i-ltr{-ie7-icon:"\e02f"}.mce-i-rtl{-ie7-icon:"\e030"}.mce-i-copy{-ie7-icon:"\e031"}.mce-i-resize{-ie7-icon:"\e032"}.mce-i-browse{-ie7-icon:"\e034"}.mce-i-checkbox,.mce-i-selected{-ie7-icon:"\e033"}.mce-i-selected{visibility:hidden}.mce-i-backcolor{background:#BBB} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.min.css b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.min.css index 83e57ee88b..c17e10b917 100755 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.min.css +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.min.css @@ -1 +1 @@ -.mce-container,.mce-container *,.mce-widget,.mce-widget *{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-container ::-webkit-scrollbar{width:8px;height:8px;-webkit-border-radius:4px}.mce-container ::-webkit-scrollbar-track,.mce-container ::-webkit-scrollbar-track-piece{background-color:transparent}.mce-container ::-webkit-scrollbar-thumb{background-color:rgba(53,57,71,0.3);width:6px;height:6px;-webkit-border-radius:4px}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:visible!important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block;border-radius:2px}.mce-wordcount{float:right;padding:8px}.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid #c5c5c5;width:20px;height:20px;line-height:20px;text-align:center;vertical-align:center;padding:2px}.mce-charmap td:hover{background:#d9d9d9}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover{border-color:#c5c5c5}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#e8e8e8;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#c4daff;background:#deeafa}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scroll{position:relative}.mce-panel{border:0 solid #9e9e9e;background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}.mce-floatpanel{position:absolute;-webkit-box-shadow:#ccc 5px 5px 5px;-moz-box-shadow:#ccc 5px 5px 5px;box-shadow:#ccc 5px 5px 5px}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{position:absolute;top:0;left:0;background:#fff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#FFF;position:fixed;top:0;left:0;opacity:0;-webkit-transition:opacity 150ms ease-in;transition:opacity 150ms ease-in}.mce-window.mce-in{opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #EEE;position:relative}.mce-window-head .mce-close{position:absolute;right:15px;top:9px;font-size:20px;font-weight:bold;line-height:20px;color:#CCC;text-shadow:0 1px 0 white;cursor:pointer;height:20px;overflow:hidden}.mce-close:hover{color:#AAA}.mce-window-head .mce-title{display:inline-block;*display:inline;*zoom:1;line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:10px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:whiteSmoke;border-top:1px solid #DDD;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window.mce-fullscreen,.mce-window.mce-fullscreen .mce-foot{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:#fff;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-inner{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-tooltip-inner{-webkit-box-shadow:0 0 5px #000;-moz-box-shadow:0 0 5px #000;box-shadow:0 0 5px #000}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-n .mce-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:0;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-btn{border:1px solid #c5c5c5;position:relative;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-btn:hover,.mce-btn:focus{text-decoration:none;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn.mce-disabled,.mce-btn.mce-disabled:hover{cursor:default;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn button{padding:4px 10px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px #fff}.mce-primary{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);zoom:1;border-color:#04c #04c #002b80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary:hover,.mce-primary:focus{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#005fb3;background-image:-moz-linear-gradient(top,#0077b3,#003cb3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0077b3),to(#003cb3));background-image:-webkit-linear-gradient(top,#0077b3,#003cb3);background-image:-o-linear-gradient(top,#0077b3,#003cb3);background-image:linear-gradient(to bottom,#0077b3,#003cb3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0077b3',endColorstr='#ff003cb3',GradientType=0);zoom:1;border-color:#003cb3 #003cb3 #026;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary button{color:#333}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:3px 5px;font-size:12px;line-height:15px}.mce-btn-small i{margin-top:0}.mce-btn .mce-caret{margin-top:8px;*margin-top:6px;margin-left:0}.mce-btn-small .mce-caret{margin-top:6px;*margin-top:4px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #444;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#999}.mce-caret.mce-up{border-bottom:4px solid #444;border-top:0}.mce-btn-group .mce-btn{border-width:1px 0 1px 0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-btn-group .mce-btn:hover,.mce-btn-group .mce-btn:focus{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-disabled,.mce-btn-group .mce-btn.mce-disabled:hover{-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-active,.mce-btn-group .mce-btn.mce-active:hover,.mce-btn-group .mce-btn:active{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn-group .mce-btn.mce-disabled button{opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn-group .mce-first{border-left:1px solid #c5c5c5;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.mce-btn-group .mce-last{border-right:1px solid #c5c5c5;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.mce-btn-group .mce-first.mce-last{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0}.mce-checked i.mce-i-checkbox{color:#000;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox{border:1px solid #59a5e1;border:1px solid rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-colorbutton .mce-ico{position:relative}.mce-colorpicker{background:#FFF}.mce-colorbutton-grid{margin:4px}.mce-grid td div{border:1px solid #808080;width:12px;height:12px;margin:2px;cursor:pointer}.mce-grid td div:hover{border-color:black}.mce-grid td div:focus{border-color:#59a5e1;outline:1px solid rgba(82,168,236,0.8);border-color:rgba(82,168,236,0.8)}.mce-colorbutton{position:relative}.mce-colorbutton .mce-preview{display:block;position:absolute;left:50%;top:50%;margin-left:-8px;margin-top:7px;background:gray;width:16px;height:2px;overflow:hidden}.mce-combobox{display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:100px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-combobox input{border-color:1px solid #c5c5c5;border-right-color:rgba(0,0,0,0.15);height:28px}.mce-combobox.mce-has-open input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.mce-combobox .mce-btn{border-left:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox *:focus{border-color:#59a5e1;border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#000}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:gray;color:white}.mce-path .mce-divider{display:inline}.mce-fieldset{border:0 solid #9e9e9e;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-iframe{border:0 solid #c5c5c5;width:100%;height:100%}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);border:0 solid #c5c5c5;overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label-disabled .mce-text{color:#999}.mce-label.mce-multiline{white-space:pre-wrap}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:none}.mce-menubar{border:1px solid #ddd}.mce-menubar .mce-menubtn button{color:#000}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubtn:focus{border-color:transparent;background:#ddd;filter:none}.mce-menubtn.mce-disabled span{color:#999}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-listbox span{width:100%;display:block;overflow:hidden}.mce-menu-item{display:block;padding:6px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal}.mce-menu-item.mce-disabled .mce-text{color:#999}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0);zoom:1}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text{color:#fff}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:white}.mce-menu-shortcut{display:inline-block;color:#999}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 20px 0 20px}.mce-menu-item .mce-caret{margin-top:6px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #666}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret{border-left-color:#FFF}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item-sep,.mce-menu-item-sep:hover{padding:0;height:1px;margin:9px 1px;overflow:hidden;background:#e5e5e5;border-bottom:1px solid white;cursor:default;filter:none}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item.mce-active{background-color:#c8def4;outline:1px solid #c5c5c5}.mce-menu-item-checkbox.mce-active{background-color:#FFF;outline:0}.mce-menu{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:2px 0 0;min-width:160px;background:#FFF;border:1px solid #CCC;border:1px solid rgba(0,0,0,0.2);z-index:1002;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline;*zoom:1}.mce-menu-sub{margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}i.mce-radio{padding:1px;margin:0 3px 0 0;background-color:#fafafa;border:1px solid #cacece;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}i.mce-radio:after{font-family:Arial;font-size:12px;color:#000;content:'\25cf'}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#000}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent;border-right:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#c5c5c5;border-right-color:#c5c5c5}.mce-splitbtn button{padding-right:4px}.mce-splitbtn .mce-open{padding-left:4px}.mce-splitbtn .mce-open.mce-active{-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #ccc}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #ccc;border-width:1px 1px 0 0;background:#e3e3e3;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#fdfdfd}.mce-tab.mce-active{background:#fdfdfd;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-textbox{background:#FFF;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);display:inline-block;-webkit-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:normal;color:#000}.mce-textbox:focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}@font-face{font-family:'tinymce';src:url('fonts/icomoon.eot');src:url('fonts/icomoon.eot?#iefix') format('embedded-opentype'),url('fonts/icomoon.svg#icomoon') format('svg'),url('fonts/icomoon.woff') format('woff'),url('fonts/icomoon.ttf') format('truetype');font-weight:normal;font-style:normal}.mce-ico{font-family:'tinymce',Arial;font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333}.mce-i-save:before{content:"\e000"}.mce-i-newdocument:before{content:"\e001"}.mce-i-fullpage:before{content:"\e002"}.mce-i-alignleft:before{content:"\e003"}.mce-i-aligncenter:before{content:"\e004"}.mce-i-alignright:before{content:"\e005"}.mce-i-alignjustify:before{content:"\e006"}.mce-i-cut:before{content:"\e007"}.mce-i-paste:before{content:"\e008"}.mce-i-searchreplace:before{content:"\e009"}.mce-i-bullist:before{content:"\e00a"}.mce-i-numlist:before{content:"\e00b"}.mce-i-indent:before{content:"\e00c"}.mce-i-outdent:before{content:"\e00d"}.mce-i-blockquote:before{content:"\e00e"}.mce-i-undo:before{content:"\e00f"}.mce-i-redo:before{content:"\e010"}.mce-i-link:before{content:"\e011"}.mce-i-unlink:before{content:"\e012"}.mce-i-anchor:before{content:"\e013"}.mce-i-image:before{content:"\e014"}.mce-i-media:before{content:"\e015"}.mce-i-help:before{content:"\e016"}.mce-i-code:before{content:"\e017"}.mce-i-inserttime:before{content:"\e018"}.mce-i-preview:before{content:"\e019"}.mce-i-forecolor:before{content:"\e01a"}.mce-i-backcolor:before{content:"\e01a"}.mce-i-table:before{content:"\e01b"}.mce-i-hr:before{content:"\e01c"}.mce-i-removeformat:before{content:"\e01d"}.mce-i-subscript:before{content:"\e01e"}.mce-i-superscript:before{content:"\e01f"}.mce-i-charmap:before{content:"\e020"}.mce-i-emoticons:before{content:"\e021"}.mce-i-print:before{content:"\e022"}.mce-i-fullscreen:before{content:"\e023"}.mce-i-spellchecker:before{content:"\e024"}.mce-i-nonbreaking:before{content:"\e025"}.mce-i-template:before{content:"\e026"}.mce-i-pagebreak:before{content:"\e027"}.mce-i-restoredraft:before{content:"\e028"}.mce-i-untitled:before{content:"\e029"}.mce-i-bold:before{content:"\e02a"}.mce-i-italic:before{content:"\e02b"}.mce-i-underline:before{content:"\e02c"}.mce-i-strikethrough:before{content:"\e02d"}.mce-i-visualchars:before{content:"\e02e"}.mce-i-visualblocks:before{content:"\e026"}.mce-i-ltr:before{content:"\e02f"}.mce-i-rtl:before{content:"\e030"}.mce-i-copy:before{content:"\e031"}.mce-i-resize:before{content:"\e032"}.mce-i-browse:before{content:"\e034"}.mce-i-checkbox:before,.mce-i-selected:before{content:"\e033"}.mce-i-selected{visibility:hidden}i.mce-i-backcolor{text-shadow:none;background:#BBB} \ No newline at end of file +.mce-container,.mce-container *,.mce-widget,.mce-widget *{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-container ::-webkit-scrollbar{width:8px;height:8px;-webkit-border-radius:4px}.mce-container ::-webkit-scrollbar-track,.mce-container ::-webkit-scrollbar-track-piece{background-color:transparent}.mce-container ::-webkit-scrollbar-thumb{background-color:rgba(53,57,71,0.3);width:6px;height:6px;-webkit-border-radius:4px}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:visible!important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block;border-radius:2px}.mce-wordcount{float:right;padding:8px}.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid #c5c5c5;width:20px;height:20px;line-height:20px;text-align:center;vertical-align:center;padding:2px}.mce-charmap td:hover{background:#d9d9d9}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover{border-color:#c5c5c5}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#e8e8e8;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#c4daff;background:#deeafa}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scroll{position:relative}.mce-panel{border:0 solid #9e9e9e;background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}.mce-floatpanel{position:absolute;-webkit-box-shadow:#ccc 5px 5px 5px;-moz-box-shadow:#ccc 5px 5px 5px;box-shadow:#ccc 5px 5px 5px}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{position:absolute;top:0;left:0;background:#fff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#FFF;position:fixed;top:0;left:0;opacity:0;-webkit-transition:opacity 150ms ease-in;transition:opacity 150ms ease-in}.mce-window.mce-in{opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #EEE;position:relative}.mce-window-head .mce-close{position:absolute;right:15px;top:9px;font-size:20px;font-weight:bold;line-height:20px;color:#CCC;text-shadow:0 1px 0 white;cursor:pointer;height:20px;overflow:hidden}.mce-close:hover{color:#AAA}.mce-window-head .mce-title{display:inline-block;*display:inline;*zoom:1;line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:10px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:whiteSmoke;border-top:1px solid #DDD;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window.mce-fullscreen,.mce-window.mce-fullscreen .mce-foot{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:#fff;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-inner{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-tooltip-inner{-webkit-box-shadow:0 0 5px #000;-moz-box-shadow:0 0 5px #000;box-shadow:0 0 5px #000}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-n .mce-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:0;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-btn{border:1px solid #c5c5c5;position:relative;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-btn:hover,.mce-btn:focus{text-decoration:none;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn.mce-disabled,.mce-btn.mce-disabled:hover{cursor:default;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn button{padding:4px 10px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px #fff}.mce-primary{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);zoom:1;border-color:#04c #04c #002b80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary:hover,.mce-primary:focus{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#005fb3;background-image:-moz-linear-gradient(top,#0077b3,#003cb3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0077b3),to(#003cb3));background-image:-webkit-linear-gradient(top,#0077b3,#003cb3);background-image:-o-linear-gradient(top,#0077b3,#003cb3);background-image:linear-gradient(to bottom,#0077b3,#003cb3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0077b3',endColorstr='#ff003cb3',GradientType=0);zoom:1;border-color:#003cb3 #003cb3 #026;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary button{color:#333}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:3px 5px;font-size:12px;line-height:15px}.mce-btn-small i{margin-top:0}.mce-btn .mce-caret{margin-top:8px;*margin-top:6px;margin-left:0}.mce-btn-small .mce-caret{margin-top:6px;*margin-top:4px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #444;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#999}.mce-caret.mce-up{border-bottom:4px solid #444;border-top:0}.mce-btn-group .mce-btn{border-width:1px 0 1px 0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-btn-group .mce-btn:hover,.mce-btn-group .mce-btn:focus{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-disabled,.mce-btn-group .mce-btn.mce-disabled:hover{-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-active,.mce-btn-group .mce-btn.mce-active:hover,.mce-btn-group .mce-btn:active{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn-group .mce-btn.mce-disabled button{opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn-group .mce-first{border-left:1px solid #c5c5c5;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.mce-btn-group .mce-last{border-right:1px solid #c5c5c5;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.mce-btn-group .mce-first.mce-last{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0}.mce-checked i.mce-i-checkbox{color:#000;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox{border:1px solid #59a5e1;border:1px solid rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-colorbutton .mce-ico{position:relative}.mce-colorpicker{background:#FFF}.mce-colorbutton-grid{margin:4px}.mce-grid td div{border:1px solid #808080;width:12px;height:12px;margin:2px;cursor:pointer}.mce-grid td div:hover{border-color:black}.mce-grid td div:focus{border-color:#59a5e1;outline:1px solid rgba(82,168,236,0.8);border-color:rgba(82,168,236,0.8)}.mce-colorbutton{position:relative}.mce-colorbutton .mce-preview{display:block;position:absolute;left:50%;top:50%;margin-left:-8px;margin-top:7px;background:gray;width:16px;height:2px;overflow:hidden}.mce-combobox{display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:100px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-combobox input{border-color:1px solid #c5c5c5;border-right-color:rgba(0,0,0,0.15);height:28px}.mce-combobox.mce-has-open input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.mce-combobox .mce-btn{border-left:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox *:focus{border-color:#59a5e1;border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#000}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:gray;color:white}.mce-path .mce-divider{display:inline}.mce-fieldset{border:0 solid #9e9e9e;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-iframe{border:0 solid #c5c5c5;width:100%;height:100%}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);border:0 solid #c5c5c5;overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label-disabled .mce-text{color:#999}.mce-label.mce-multiline{white-space:pre-wrap}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:none}.mce-menubar{border:1px solid #ddd}.mce-menubar .mce-menubtn button{color:#000}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubtn:focus{border-color:transparent;background:#ddd;filter:none}.mce-menubtn.mce-disabled span{color:#999}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-listbox span{width:100%;display:block;overflow:hidden}.mce-menu-item{display:block;padding:6px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal}.mce-menu-item.mce-disabled .mce-text{color:#999}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0);zoom:1}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text{color:#fff}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:white}.mce-menu-shortcut{display:inline-block;color:#999}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 20px 0 20px}.mce-menu-item .mce-caret{margin-top:6px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #666}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret{border-left-color:#FFF}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item-sep,.mce-menu-item-sep:hover{padding:0;height:1px;margin:9px 1px;overflow:hidden;background:#e5e5e5;border-bottom:1px solid white;cursor:default;filter:none}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item.mce-active{background-color:#c8def4;outline:1px solid #c5c5c5}.mce-menu-item-checkbox.mce-active{background-color:#FFF;outline:0}.mce-menu{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:2px 0 0;min-width:160px;background:#FFF;border:1px solid #CCC;border:1px solid rgba(0,0,0,0.2);z-index:1002;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline;*zoom:1}.mce-menu-sub{margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}i.mce-radio{padding:1px;margin:0 3px 0 0;background-color:#fafafa;border:1px solid #cacece;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}i.mce-radio:after{font-family:Arial;font-size:12px;color:#000;content:'\25cf'}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#000}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent;border-right:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#c5c5c5;border-right-color:#c5c5c5}.mce-splitbtn button{padding-right:4px}.mce-splitbtn .mce-open{padding-left:4px}.mce-splitbtn .mce-open.mce-active{-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #ccc}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #ccc;border-width:1px 1px 0 0;background:#e3e3e3;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#fdfdfd}.mce-tab.mce-active{background:#fdfdfd;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-textbox{background:#FFF;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);display:inline-block;-webkit-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:normal;color:#000}.mce-textbox:focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}@font-face{font-family:'tinymce';src:url('fonts/tinymce.eot');src:url('fonts/tinymce.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce.woff') format('woff'),url('fonts/tinymce.ttf') format('truetype'),url('fonts/tinymce.svg#tinymce') format('svg');font-weight:normal;font-style:normal}@font-face{font-family:'tinymce-small';src:url('fonts/tinymce-small.eot');src:url('fonts/tinymce-small.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce-small.woff') format('woff'),url('fonts/tinymce-small.ttf') format('truetype'),url('fonts/tinymce-small.svg#tinymce') format('svg');font-weight:normal;font-style:normal}.mce-ico{font-family:'tinymce',Arial;font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333}.mce-i-save:before{content:"\e000"}.mce-i-newdocument:before{content:"\e001"}.mce-i-fullpage:before{content:"\e002"}.mce-i-alignleft:before{content:"\e003"}.mce-i-aligncenter:before{content:"\e004"}.mce-i-alignright:before{content:"\e005"}.mce-i-alignjustify:before{content:"\e006"}.mce-i-cut:before{content:"\e007"}.mce-i-paste:before{content:"\e008"}.mce-i-searchreplace:before{content:"\e009"}.mce-i-bullist:before{content:"\e00a"}.mce-i-numlist:before{content:"\e00b"}.mce-i-indent:before{content:"\e00c"}.mce-i-outdent:before{content:"\e00d"}.mce-i-blockquote:before{content:"\e00e"}.mce-i-undo:before{content:"\e00f"}.mce-i-redo:before{content:"\e010"}.mce-i-link:before{content:"\e011"}.mce-i-unlink:before{content:"\e012"}.mce-i-anchor:before{content:"\e013"}.mce-i-image:before{content:"\e014"}.mce-i-media:before{content:"\e015"}.mce-i-help:before{content:"\e016"}.mce-i-code:before{content:"\e017"}.mce-i-inserttime:before{content:"\e018"}.mce-i-preview:before{content:"\e019"}.mce-i-forecolor:before{content:"\e01a"}.mce-i-backcolor:before{content:"\e01a"}.mce-i-table:before{content:"\e01b"}.mce-i-hr:before{content:"\e01c"}.mce-i-removeformat:before{content:"\e01d"}.mce-i-subscript:before{content:"\e01e"}.mce-i-superscript:before{content:"\e01f"}.mce-i-charmap:before{content:"\e020"}.mce-i-emoticons:before{content:"\e021"}.mce-i-print:before{content:"\e022"}.mce-i-fullscreen:before{content:"\e023"}.mce-i-spellchecker:before{content:"\e024"}.mce-i-nonbreaking:before{content:"\e025"}.mce-i-template:before{content:"\e026"}.mce-i-pagebreak:before{content:"\e027"}.mce-i-restoredraft:before{content:"\e028"}.mce-i-untitled:before{content:"\e029"}.mce-i-bold:before{content:"\e02a"}.mce-i-italic:before{content:"\e02b"}.mce-i-underline:before{content:"\e02c"}.mce-i-strikethrough:before{content:"\e02d"}.mce-i-visualchars:before{content:"\e02e"}.mce-i-visualblocks:before{content:"\e026"}.mce-i-ltr:before{content:"\e02f"}.mce-i-rtl:before{content:"\e030"}.mce-i-copy:before{content:"\e031"}.mce-i-resize:before{content:"\e032"}.mce-i-browse:before{content:"\e034"}.mce-i-checkbox:before,.mce-i-selected:before{content:"\e033"}.mce-i-selected{visibility:hidden}i.mce-i-backcolor{text-shadow:none;background:#BBB}.mce-i-tablerowprops:before{content:"\e604"}.mce-i-tablecellprops:before{content:"\e605"}.mce-i-table2:before{content:"\e606"}.mce-i-tablemergecells:before{content:"\e607"}.mce-i-tableinsertcolbefore:before{content:"\e608"}.mce-i-tableinsertcolafter:before{content:"\e609"}.mce-i-tableinsertrowbefore:before{content:"\e60a"}.mce-i-tableinsertrowafter:before{content:"\e60b"}.mce-i-tablesplitcells:before{content:"\e60d"}.mce-i-tabledelete:before{content:"\e60e"}.mce-i-tableleftheader:before{content:"\e62a"}.mce-i-tabletopheader:before{content:"\e62b"}.mce-i-tabledeleterow:before{content:"\e800"}.mce-i-tabledeletecol:before{content:"\e801"} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js index 71e7afec12..cc2b949d22 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js @@ -182,7 +182,7 @@ Umbraco.Sys.registerNamespace("Umbraco.Application"); var injector = getRootInjector(); var navService = injector.get("navigationService"); var localizationService = injector.get("localizationService"); - var userResource = injector.get("userResource"); + var usersResource = injector.get("usersResource"); //var appState = injector.get("appState"); var angularHelper = injector.get("angularHelper"); var $rootScope = injector.get("$rootScope"); diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index d2af0e2b83..0286d5f358 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -6,7 +6,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git@github.com:umbraco/umbraco-cms.git" + "url": "https://github.com/umbraco/Umbraco-CMS.git" }, "bugs": { "url": "https://issues.umbraco.org" @@ -14,38 +14,35 @@ "engines": { "node": ">= 0.8.4" }, + "scripts": { + "install": "bower-installer", + "test": "karma start test/config/karma.conf.js --singlerun", + "build": "gulp" + }, "dependencies": {}, "devDependencies": { - "autoprefixer-core": "~5.2.1", - "bower": "^1.4.1", - "eslint": "^0.23.0", - "eslint-plugin-angular": "0.0.13", - "grunt": "~0.4.0", - "grunt-bower": "^0.19.0", - "grunt-bower-install-simple": "^1.1.3", - "grunt-contrib-clean": "~0.4.0", - "grunt-contrib-concat": "~0.1.3", - "grunt-contrib-connect": "~0.3.0", - "grunt-contrib-copy": "~0.7.0", - "grunt-contrib-jshint": "~0.2.0", - "grunt-contrib-uglify": "~0.1.1", - "grunt-contrib-watch": "~0.3.1", - "grunt-eslint": "^15.0.0", - "grunt-html2js": "~0.1.0", - "grunt-hustler": "^4.0.6", - "grunt-karma": "~0.5", - "grunt-ngdocs": "~0.1.2", - "grunt-open": "~0.2.0", - "grunt-postcss": "~0.6.0", - "grunt-recess": "~0.3", - "karma": "~0.9", - "karma-chrome-launcher": "0.0.2", - "karma-coffee-preprocessor": "0.0.1", - "karma-firefox-launcher": "0.0.2", - "karma-jasmine": "0.0.1", - "karma-phantomjs-launcher": "0.0.2", - "karma-requirejs": "0.0.1", - "karma-script-launcher": "0.0.1", - "phantomjs": "~1.9.1-0" + "autoprefixer": "^6.5.0", + "bower-installer": "^1.2.0", + "cssnano": "^3.7.6", + "gulp": "^3.9.1", + "gulp-concat": "^2.6.0", + "gulp-connect": "^5.0.0", + "gulp-less": "^3.1.0", + "gulp-ngdocs": "^0.3.0", + "gulp-open": "^2.0.0", + "gulp-postcss": "^6.2.0", + "gulp-rename": "^1.2.2", + "gulp-sort": "^2.0.0", + "gulp-watch": "^4.3.10", + "gulp-wrap": "^0.13.0", + "gulp-wrap-js": "^0.4.1", + "jasmine-core": "2.5.2", + "karma": "^1.7.0", + "karma-jasmine": "^1.1.0", + "karma-phantomjs-launcher": "^1.0.4", + "less": "^2.6.1", + "lodash": "^4.16.3", + "merge-stream": "^1.0.1", + "run-sequence": "^2.1.0" } } diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js index 98b49168b8..10afba2d76 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js @@ -14,7 +14,7 @@ LazyLoad.js([ '../lib/spectrum/spectrum.js', '../lib/signalr/jquery.signalR.js', '/umbraco/signalr/hubs', - '../js/canvasdesigner.panel.js' + '../js/umbraco.canvasdesigner.js' ], function () { jQuery(document).ready(function () { angular.bootstrap(document, ['Umbraco.canvasdesigner']); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index 5ce3eac8a0..436dcfcd0d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -3,7 +3,7 @@ * @name umbraco.directives.directive:umbSections * @restrict E **/ -function sectionsDirective($timeout, $window, navigationService, treeService, sectionResource, appState, eventsService, $location) { +function sectionsDirective($timeout, $window, navigationService, treeService, sectionService, appState, eventsService, $location) { return { restrict: "E", // restrict to an element replace: true, // replace the html element with the template @@ -31,7 +31,7 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se }; function loadSections(){ - sectionResource.getSections() + sectionService.getSectionsForUser() .then(function (result) { scope.sections = result; calculateHeight(); @@ -154,8 +154,16 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se } navigationService.hideSearch(); - navigationService.showTree(section.alias); - $location.path("/" + section.alias); + navigationService.showTree(section.alias); + + //in some cases the section will have a custom route path specified, if there is one we'll use it + if (section.routePath) { + $location.path(section.routePath); + } + else { + $location.path(section.alias).search(''); + } + }; scope.sectionDblClick = function(section){ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index db5def83d8..7d60a83b89 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -211,12 +211,20 @@ // outwith the initial scoped request. This trick will fix that. // var previewWindow = $window.open('preview/?id=' + content.id, 'umbpreview'); - $scope.save().then(function (data) { - // Build the correct path so both /#/ and #/ work. - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + data.id; - previewWindow.location.href = redirect; - }); + // Build the correct path so both /#/ and #/ work. + var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id; + + //The user cannot save if they don't have access to do that, in which case we just want to preview + //and that's it otherwise they'll get an unauthorized access message + if (!_.contains(content.allowedActions, "A")) { + previewWindow.location.href = redirect; + } + else { + $scope.save().then(function (data) { + previewWindow.location.href = redirect; + }); + } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbbox.directive.js new file mode 100644 index 0000000000..0702864f03 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbbox.directive.js @@ -0,0 +1,44 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbBox +@restrict E + +@description +Use this directive to render an already styled empty div tag. + +

Markup example

+
+    
+        
+        
+            // Content here
+        
+    
+
+ +

Use in combination with:

+
    +
  • {@link umbraco.directives.directive:umbBoxHeader umbBoxHeader}
  • +
  • {@link umbraco.directives.directive:umbBoxContent umbBoxContent}
  • +
+**/ + +(function(){ + 'use strict'; + + function BoxDirective() { + + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/html/umb-box/umb-box.html' + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbBox', BoxDirective); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbboxcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbboxcontent.directive.js new file mode 100644 index 0000000000..256dcfac4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbboxcontent.directive.js @@ -0,0 +1,43 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbBoxContent +@restrict E + +@description +Use this directive to render an empty container. Recommended to use it inside an {@link umbraco.directives.directive:umbBox umbBox} directive. See documentation for {@link umbraco.directives.directive:umbBox umbBox}. + +

Markup example

+
+    
+        
+        
+            // Content here
+        
+    
+
+ +

Use in combination with:

+
    +
  • {@link umbraco.directives.directive:umbBox umbBox}
  • +
  • {@link umbraco.directives.directive:umbBoxHeader umbBoxHeader}
  • +
+**/ + +(function(){ + 'use strict'; + + function BoxContentDirective() { + + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/html/umb-box/umb-box-content.html' + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbBoxContent', BoxContentDirective); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbboxheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbboxheader.directive.js new file mode 100644 index 0000000000..bb16edf761 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbboxheader.directive.js @@ -0,0 +1,69 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbBoxHeader +@restrict E +@scope + +@description +Use this directive to construct a title. Recommended to use it inside an {@link umbraco.directives.directive:umbBox umbBox} directive. See documentation for {@link umbraco.directives.directive:umbBox umbBox}. + +

Markup example

+
+    
+        
+        
+            // Content here
+        
+    
+
+ +

Markup example with using titleKey

+
+    
+        // the title-key property needs an areaAlias_keyAlias from the language files
+        
+        
+            // Content here
+        
+    
+
+{@link https://our.umbraco.org/documentation/extending/language-files/ Here you can see more about the language files} + +

Use in combination with:

+
    +
  • {@link umbraco.directives.directive:umbBox umbBox}
  • +
  • {@link umbraco.directives.directive:umbBoxContent umbBoxContent}
  • +
+ +@param {string=} title (attrbute): Custom title text. +@param {string=} titleKey (attrbute): The translation key from the language xml files. +@param {string=} description (attrbute): Custom description text. +@param {string=} descriptionKey (attrbute): The translation key from the language xml files. +**/ + + +(function(){ + 'use strict'; + + function BoxHeaderDirective() { + + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/html/umb-box/umb-box-header.html', + scope: { + titleKey: "@?", + title: "@?", + descriptionKey: "@?", + description: "@?" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbBoxHeader', BoxHeaderDirective); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbcontrolgroup.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbcontrolgroup.directive.js index 94ab5537dc..c4382240c0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbcontrolgroup.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbcontrolgroup.directive.js @@ -10,8 +10,9 @@ angular.module("umbraco.directives.html") label: "@label", description: "@", hideLabel: "@", - alias: "@", - labelFor: "@" + alias: "@", + labelFor: "@", + required: "@?" }, require: '?^form', transclude: true, @@ -21,7 +22,7 @@ angular.module("umbraco.directives.html") link: function (scope, element, attr, formCtrl) { scope.formValid = function () { - if (formCtrl && scope.labelFor) { + if (formCtrl && scope.labelFor) { //if a label-for has been set, use that for the validation return formCtrl[scope.labelFor].$valid; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 1a652a787c..6ed1a21306 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -31,7 +31,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat //var showheader = (attrs.showheader !== 'false'); var hideoptions = (attrs.hideoptions === 'true') ? "hide-options" : ""; var template = '
  • '; - template += '
    ' + + template += '
    ' + '
    ' + ' {{tree.name}}
    ' + '' + @@ -310,6 +310,25 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat } + /** Returns the css classses assigned to the node (div element) */ + scope.getNodeCssClass = function (node) { + if (!node) { + return ''; + } + + //TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time + // it would be better if we could cache the processing. The problem is that some of these things are dynamic. + + var css = []; + if (node.cssClasses) { + _.each(node.cssClasses, function (c) { + css.push(c); + }); + } + + return css.join(" "); + }; + scope.selectEnabledNodeClass = function (node) { return node ? node.selected ? @@ -383,6 +402,12 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat defined on the tree */ scope.select = function (n, ev) { + + if (n.metaData && n.metaData.noAccess === true) { + ev.preventDefault(); + return; + } + //on tree select we need to remove the current node - // whoever handles this will need to make sure the correct node is selected //reset current node selection diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js index b32942791c..25c1becc87 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js @@ -41,7 +41,7 @@ angular.module("umbraco.directives") //'' + ' ' + '' + - '' + + '' + //NOTE: These are the 'option' elipses '' + '
    ' + @@ -112,6 +112,10 @@ angular.module("umbraco.directives") if (!node) { return ''; } + + //TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time + // it would be better if we could cache the processing. The problem is that some of these things are dynamic. + var css = []; if (node.cssClasses) { _.each(node.cssClasses, function(c) { @@ -121,6 +125,7 @@ angular.module("umbraco.directives") if (node.selected) { css.push("umb-tree-node-checked"); } + return css.join(" "); }; @@ -158,6 +163,11 @@ angular.module("umbraco.directives") return; } + if (n.metaData && n.metaData.noAccess === true) { + ev.preventDefault(); + return; + } + emitEvent("treeNodeSelect", { element: element, tree: scope.tree, node: n, event: ev }); ev.preventDefault(); }; @@ -229,6 +239,12 @@ angular.module("umbraco.directives") setupNodeDom(scope.node, scope.tree); + // load the children if the current user don't have access to the node + // it is used to auto expand the tree to the start nodes the user has access to + if(scope.node.hasChildren && scope.node.metaData.noAccess) { + scope.loadChildren(scope.node); + } + var template = '
    '; var newElement = angular.element(template); $compile(newElement)(scope); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js index f2834e4a36..ff25fae3f5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/users/changepassword.directive.js @@ -17,6 +17,8 @@ } */ + $scope.showReset = false; + //set defaults if they are not available if ($scope.config.disableToggle === undefined) { $scope.config.disableToggle = false; @@ -30,19 +32,24 @@ if ($scope.config.requiresQuestionAnswer === undefined) { $scope.config.requiresQuestionAnswer = false; } - if ($scope.config.enableReset === undefined) { - $scope.config.enableReset = true; + //don't enable reset if it is new - that doesn't make sense + if (isNew === "true") { + $scope.config.enableReset = false; } + else if ($scope.config.enableReset === undefined) { + $scope.config.enableReset = true; + } + if ($scope.config.minPasswordLength === undefined) { $scope.config.minPasswordLength = 0; } - + //set the model defaults if (!angular.isObject($scope.passwordValues)) { //if it's not an object then just create a new one $scope.passwordValues = { newPassword: null, - oldPassword: null, + oldPassword: null, reset: null, answer: null }; @@ -61,11 +68,11 @@ //the value to compare to match passwords if (!isNew) { - $scope.confirm = ""; + $scope.passwordValues.confirm = ""; } else if ($scope.passwordValues.newPassword && $scope.passwordValues.newPassword.length > 0) { //if it is new and a new password has been set, then set the confirm password too - $scope.confirm = $scope.passwordValues.newPassword; + $scope.passwordValues.confirm = $scope.passwordValues.newPassword; } } @@ -86,6 +93,7 @@ $scope.changing = true; //if there was a previously generated password displaying, clear it $scope.passwordValues.generatedPassword = null; + $scope.passwordValues.confirm = null; }; $scope.cancelChange = function () { @@ -120,25 +128,13 @@ unsubscribe[u](); } }); - - $scope.showReset = function () { - return $scope.config.enableReset; - }; - + $scope.showOldPass = function () { return $scope.config.hasPassword && !$scope.config.allowManuallyChangingPassword && - !$scope.config.enablePasswordRetrieval && !$scope.passwordValues.reset; + !$scope.config.enablePasswordRetrieval && !$scope.showReset; }; - - $scope.showNewPass = function () { - return !$scope.passwordValues.reset; - }; - - $scope.showConfirmPass = function () { - return !$scope.passwordValues.reset; - }; - + //TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this directive $scope.showCancelBtn = function () { return $scope.config.disableToggle !== true && $scope.config.hasPassword; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/users/umbusergrouppreview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/users/umbusergrouppreview.directive.js index 7a777b76c5..fdbfd088ab 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/users/umbusergrouppreview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/users/umbusergrouppreview.directive.js @@ -1,3 +1,51 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbUserGroupPreview +@restrict E +@scope + +@description +Use this directive to render a user group preview, where you can see the permissions the user or group has in the back office. + +

    Markup example

    +
    +    
    + + +
    +
    + +@param {string} icon (binding): The user group icon. +@param {string} name (binding): The user group name. +@param {array} sections (binding) Lists out the sections where the user has authority to edit. +@param {string} contentStartNode (binding) +
      +
    • The starting point in the tree of the content section.
    • +
    • So the user has no authority to work on other branches, only on this branch in the content section.
    • +
    +@param {boolean} hideContentStartNode (binding) Hides the contentStartNode. +@param {string} mediaStartNode (binding) +
      +
    • The starting point in the tree of the media section.
    • +
    • So the user has no authority to work on other branches, only on this branch in the media section.
    • +
    +@param {boolean} hideMediaStartNode (binding) Hides the mediaStartNode. +@param {array} permissions (binding) A list of permissions, the user can have. +@param {boolean} allowRemove (binding): Shows or Hides the remove button. +@param {function} onRemove (expression): Callback function when the remove button is clicked. +@param {boolean} allowEdit (binding): Shows or Hides the edit button. +@param {function} onEdit (expression): Callback function when the edit button is clicked. +**/ + + (function () { 'use strict'; @@ -14,10 +62,11 @@ scope: { icon: "=?", name: "=", - description: "=?", sections: "=?", - contentStartNode: "=?", + contentStartNode: "=?", + hideContentStartNode: "@?", mediaStartNode: "=?", + hideMediaStartNode: "@?", permissions: "=?", allowRemove: "=?", allowEdit: "=?", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js index 37a773ae97..0c2339dd5c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js @@ -5,13 +5,22 @@ function link(scope, el, attr, ctrl) { - var initValue = attr.umbSetDirtyOnChange; - - attr.$observe("umbSetDirtyOnChange", function (newValue) { - if(newValue !== initValue) { + if(attr.ngModel) { + scope.$watch(attr.ngModel, function(newValue, oldValue) { + if (!newValue) {return;} + if (newValue === oldValue) {return;} ctrl.$setDirty(); - } - }); + }, true); + + } else { + var initValue = attr.umbSetDirtyOnChange; + + attr.$observe("umbSetDirtyOnChange", function (newValue) { + if(newValue !== initValue) { + ctrl.$setDirty(); + } + }); + } } diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js index 7c75dc2abb..bf9a94a446 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js @@ -132,6 +132,7 @@ angular.module('umbraco.mocks'). "content_membertype": "Member Type", "content_noDate": "No date chosen", "content_nodeName": "Page Title", + "defaultdialogs_nodeNameLinkPicker": "Link title", "content_otherElements": "Properties", "content_parentNotPublished": "This document is published but is not visible because the parent '%0%' is unpublished", "content_parentNotPublishedAnomaly": "This document is published but is not in the cache", @@ -148,7 +149,8 @@ angular.module('umbraco.mocks'). "content_updateDate": "Last edited", "content_updateDateDesc": "Date/time this document was created", "content_uploadClear": "Remove file", - "content_urls": "Link to document", + "content_urls": "Link to document", + "defaultdialogs_urlLinkPicker":"Link", "content_memberof": "Member of group(s)", "content_notmemberof": "Not a member of group(s)", "content_childItems": "Child items", diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index d24a29dc61..642e4fb13b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -418,14 +418,14 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to retrieve data for empty content item type ' + alias); }, - getBlueprintScaffold: function (blueprintId) { + getBlueprintScaffold: function (parentId, blueprintId) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", - "GetEmpty", - [{ blueprintId: blueprintId }])), + "GetEmpty", + [{ blueprintId: blueprintId }, { parentId: parentId}])), 'Failed to retrieve blueprint for id ' + blueprintId); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index 365b4dfd44..8bfcdfcc5a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -266,6 +266,17 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateContainer", { parentId: parentId, name: name })), 'Failed to create a folder under parent id ' + parentId); + }, + + renameContainer: function(id, name) { + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", + "PostRenameContainer", + { id: id, name: name })), + "Failed to rename the folder with id " + id + ); + } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js index fadbb88bb7..233d510c3e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js @@ -5,48 +5,54 @@ * * **/ -function currentUserResource($q, $http, umbRequestHelper) { +function currentUserResource($q, $http, umbRequestHelper, umbDataFormatter) { - //the factory object returned - return { + //the factory object returned + return { - performSetInvitedUserPassword: function (newPassword) { + performSetInvitedUserPassword: function (newPassword) { - if (!newPassword) { - return angularHelper.rejectedPromise({ errorMsg: 'newPassword cannot be empty' }); - } + if (!newPassword) { + return angularHelper.rejectedPromise({ errorMsg: 'newPassword cannot be empty' }); + } - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "currentUserApiBaseUrl", - "PostSetInvitedUserPassword"), - angular.toJson(newPassword)), - 'Failed to change password'); - }, + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "currentUserApiBaseUrl", + "PostSetInvitedUserPassword"), + angular.toJson(newPassword)), + 'Failed to change password'); + }, - /** - * @ngdoc method - * @name umbraco.resources.currentUserResource#changePassword - * @methodOf umbraco.resources.currentUserResource - * - * @description - * Changes the current users password - * - * @returns {Promise} resourcePromise object containing the user array. - * - */ - changePassword: function (changePasswordArgs) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "currentUserApiBaseUrl", - "PostChangePassword"), - changePasswordArgs), - 'Failed to change password'); - } - - }; + /** + * @ngdoc method + * @name umbraco.resources.currentUserResource#changePassword + * @methodOf umbraco.resources.currentUserResource + * + * @description + * Changes the current users password + * + * @returns {Promise} resourcePromise object containing the user array. + * + */ + changePassword: function (changePasswordArgs) { + + changePasswordArgs = umbDataFormatter.formatChangePasswordModel(changePasswordArgs); + if (!changePasswordArgs) { + throw 'No password data to change'; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "currentUserApiBaseUrl", + "PostChangePassword"), + changePasswordArgs), + 'Failed to change password'); + } + + }; } angular.module('umbraco.resources').factory('currentUserResource', currentUserResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js index 7bc65c89a6..11b6969479 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js @@ -35,12 +35,12 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetPreValues", - [{ editorAlias: editorAlias }, { dataTypeId: dataTypeId }])), - "Failed to retrieve pre values for editor alias " + editorAlias); + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetPreValues", + [{ editorAlias: editorAlias }, { dataTypeId: dataTypeId }])), + "Failed to retrieve pre values for editor alias " + editorAlias); }, /** @@ -66,12 +66,12 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { getById: function (id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetById", - [{ id: id }])), - "Failed to retrieve data for data type id " + id); + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetById", + [{ id: id }])), + "Failed to retrieve data for data type id " + id); }, /** @@ -97,24 +97,24 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { getByName: function (name) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetByName", - [{ name: name }])), - "Failed to retrieve data for data type with name: " + name); + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetByName", + [{ name: name }])), + "Failed to retrieve data for data type with name: " + name); }, getAll: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetAll")), - "Failed to retrieve data"); + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetAll")), + "Failed to retrieve data"); }, - + getGroupedDataTypes: function () { return umbRequestHelper.resourcePromise( $http.get( @@ -124,7 +124,7 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { "Failed to retrieve data"); }, - getGroupedPropertyEditors : function(){ + getGroupedPropertyEditors: function () { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( @@ -173,11 +173,11 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { getScaffold: function (parentId) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetEmpty", { parentId: parentId })), - "Failed to retrieve data for empty datatype"); + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetEmpty", { parentId: parentId })), + "Failed to retrieve data for empty datatype"); }, /** * @ngdoc method @@ -199,7 +199,7 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - deleteById: function(id) { + deleteById: function (id) { return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( @@ -212,12 +212,12 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { deleteContainerById: function (id) { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "DeleteContainer", - [{ id: id }])), - 'Failed to delete content type contaier'); + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete content type contaier'); }, @@ -242,16 +242,16 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * */ - getCustomListView: function (contentTypeAlias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetCustomListView", - { contentTypeAlias: contentTypeAlias } - )), - "Failed to retrieve data for custom listview datatype"); - }, + getCustomListView: function (contentTypeAlias) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetCustomListView", + { contentTypeAlias: contentTypeAlias } + )), + "Failed to retrieve data for custom listview datatype"); + }, /** * @ngdoc method @@ -271,16 +271,16 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the listview datatype. * */ - createCustomListView: function (contentTypeAlias) { - return umbRequestHelper.resourcePromise( + createCustomListView: function (contentTypeAlias) { + return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "dataTypeApiBaseUrl", "PostCreateCustomListView", { contentTypeAlias: contentTypeAlias } - )), + )), "Failed to create a custom listview datatype"); - }, + }, /** * @ngdoc method @@ -301,7 +301,7 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { var saveModel = umbDataFormatter.formatDataTypePostData(dataType, preValues, "save" + (isNew ? "New" : "")); return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), "Failed to save data for data type id " + dataType.id); }, @@ -351,12 +351,22 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { createContainer: function (parentId, name) { return umbRequestHelper.resourcePromise( - $http.post( + $http.post( umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "PostCreateContainer", - { parentId: parentId, name: name })), + "dataTypeApiBaseUrl", + "PostCreateContainer", + { parentId: parentId, name: name })), 'Failed to create a folder under parent id ' + parentId); + }, + + renameContainer: function (id, name) { + return umbRequestHelper.resourcePromise( + $http.post + (umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "PostRenameContainer", + { id: id, name: name })), + "Failed to rename the folder with id " + id); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js index 117edef77f..06df2e0695 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js @@ -203,6 +203,17 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { "PostCreateContainer", { parentId: parentId, name: name })), 'Failed to create a folder under parent id ' + parentId); + }, + + renameContainer: function (id, name) { + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", + "PostRenameContainer", + { id: id, name: name })), + "Failed to rename the folder with id " + id + ); + } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/usergroups.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/usergroups.resource.js index 90d9e58736..a0901ce0db 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/usergroups.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/usergroups.resource.js @@ -50,12 +50,21 @@ } - function getUserGroups() { + function getUserGroups(args) { + + if (!args) { + args = { onlyCurrentUserGroups: true }; + } + if (args.onlyCurrentUserGroups === undefined || args.onlyCurrentUserGroups === null) { + args.onlyCurrentUserGroups = true; + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "userGroupsApiBaseUrl", - "GetUserGroups")), + "GetUserGroups", + args)), "Failed to retrieve user groups"); } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 0acd30afe0..72564398c0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -4,13 +4,33 @@ * @function * * @description - * Used by the users section to get users and send requests to create, invite, delete, etc. users. + * Used by the users section to get users and send requests to create, invite, disable, etc. users. */ (function () { 'use strict'; function usersResource($http, umbRequestHelper, $q, umbDataFormatter) { + /** + * @ngdoc method + * @name umbraco.resources.usersResource#clearAvatar + * @methodOf umbraco.resources.usersResource + * + * @description + * Deletes the user avatar + * + * ##usage + *
    +          * usersResource.clearAvatar(1)
    +          *    .then(function() {
    +          *        alert("avatar is gone");
    +          *    });
    +          * 
    + * + * @param {Array} id id of user. + * @returns {Promise} resourcePromise object. + * + */ function clearAvatar(userId) { return umbRequestHelper.resourcePromise( @@ -22,6 +42,26 @@ 'Failed to clear the user avatar ' + userId); } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#disableUsers + * @methodOf umbraco.resources.usersResource + * + * @description + * Disables a collection of users + * + * ##usage + *
    +          * usersResource.disableUsers([1, 2, 3, 4, 5])
    +          *    .then(function() {
    +          *        alert("users were disabled");
    +          *    });
    +          * 
    + * + * @param {Array} ids ids of users to disable. + * @returns {Promise} resourcePromise object. + * + */ function disableUsers(userIds) { if (!userIds) { throw "userIds not specified"; @@ -39,6 +79,26 @@ 'Failed to disable the users ' + userIds.join(",")); } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#enableUsers + * @methodOf umbraco.resources.usersResource + * + * @description + * Enables a collection of users + * + * ##usage + *
    +          * usersResource.enableUsers([1, 2, 3, 4, 5])
    +          *    .then(function() {
    +          *        alert("users were enabled");
    +          *    });
    +          * 
    + * + * @param {Array} ids ids of users to enable. + * @returns {Promise} resourcePromise object. + * + */ function enableUsers(userIds) { if (!userIds) { throw "userIds not specified"; @@ -55,6 +115,63 @@ 'Failed to enable the users ' + userIds.join(",")); } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#unlockUsers + * @methodOf umbraco.resources.usersResource + * + * @description + * Unlocks a collection of users + * + * ##usage + *
    +          * usersResource.unlockUsers([1, 2, 3, 4, 5])
    +          *    .then(function() {
    +          *        alert("users were unlocked");
    +          *    });
    +          * 
    + * + * @param {Array} ids ids of users to unlock. + * @returns {Promise} resourcePromise object. + * + */ + function unlockUsers(userIds) { + if (!userIds) { + throw "userIds not specified"; + } + + //we need to create a custom query string for the usergroup array, so create it now and we can append the user groups if needed + var qry = "userIds=" + userIds.join("&userIds="); + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "PostUnlockUsers", qry)), + 'Failed to enable the users ' + userIds.join(",")); + } + + /** + * @ngdoc method + * @name umbraco.resources.usersResource#setUserGroupsOnUsers + * @methodOf umbraco.resources.usersResource + * + * @description + * Overwrites the existing user groups on a collection of users + * + * ##usage + *
    +          * usersResource.setUserGroupsOnUsers(['admin', 'editor'], [1, 2, 3, 4, 5])
    +          *    .then(function() {
    +          *        alert("users were updated");
    +          *    });
    +          * 
    + * + * @param {Array} userGroupAliases aliases of user groups. + * @param {Array} ids ids of users to update. + * @returns {Promise} resourcePromise object. + * + */ function setUserGroupsOnUsers(userGroups, userIds) { var userGroupAliases = userGroups.map(function(o) { return o.alias; }); var query = "userGroupAliases=" + userGroupAliases.join("&userGroupAliases=") + "&userIds=" + userIds.join("&userIds="); @@ -67,6 +184,34 @@ 'Failed to set user groups ' + userGroupAliases.join(",") + ' on the users ' + userIds.join(",")); } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#getPagedResults + * @methodOf umbraco.resources.usersResource + * + * @description + * Get users + * + * ##usage + *
    +          * usersResource.getPagedResults({pageSize: 10, pageNumber: 2})
    +          *    .then(function(data) {
    +          *        var users = data.items;
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of users per page, default = 25 + * @param {Int} options.pageNumber if paging data, current page index, default = 1 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order users by, default: `Username` + * @param {Array} options.userGroups property to filter users by user group + * @param {Array} options.userStates property to filter users by user state + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ function getPagedResults(options) { var defaults = { pageSize: 25, @@ -119,6 +264,26 @@ 'Failed to retrieve users paged result'); } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#getUser + * @methodOf umbraco.resources.usersResource + * + * @description + * Gets a user + * + * ##usage + *
    +          * usersResource.getUser(1)
    +          *    .then(function(user) {
    +          *        alert("It's here");
    +          *    });
    +          * 
    + * + * @param {Array} id user id. + * @returns {Promise} resourcePromise object containing the user. + * + */ function getUser(userId) { return umbRequestHelper.resourcePromise( @@ -130,6 +295,26 @@ "Failed to retrieve data for user " + userId); } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#createUser + * @methodOf umbraco.resources.usersResource + * + * @description + * Creates a new user + * + * ##usage + *
    +          * usersResource.createUser(user)
    +          *    .then(function(newUser) {
    +          *        alert("It's here");
    +          *    });
    +          * 
    + * + * @param {Object} user user to create + * @returns {Promise} resourcePromise object containing the new user. + * + */ function createUser(user) { if (!user) { throw "user not specified"; @@ -147,6 +332,26 @@ "Failed to save user"); } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#inviteUser + * @methodOf umbraco.resources.usersResource + * + * @description + * Creates and sends an email invitation to a new user + * + * ##usage + *
    +          * usersResource.inviteUser(user)
    +          *    .then(function(newUser) {
    +          *        alert("It's here");
    +          *    });
    +          * 
    + * + * @param {Object} user user to invite + * @returns {Promise} resourcePromise object containing the new user. + * + */ function inviteUser(user) { if (!user) { throw "user not specified"; @@ -164,6 +369,26 @@ "Failed to invite user"); } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#saveUser + * @methodOf umbraco.resources.usersResource + * + * @description + * Saves a user + * + * ##usage + *
    +          * usersResource.saveUser(user)
    +          *    .then(function(updatedUser) {
    +          *        alert("It's here");
    +          *    });
    +          * 
    + * + * @param {Object} user object to save + * @returns {Promise} resourcePromise object containing the updated user. + * + */ function saveUser(user) { if (!user) { throw "user not specified"; @@ -185,6 +410,7 @@ var resource = { disableUsers: disableUsers, enableUsers: enableUsers, + unlockUsers: unlockUsers, setUserGroupsOnUsers: setUserGroupsOnUsers, getPagedResults: getPagedResults, getUser: getUser, diff --git a/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js index 6f5712a78d..b283a1fec8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js @@ -120,6 +120,8 @@ angular.module('umbraco.security.interceptor') // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block. .config(['$httpProvider', function ($httpProvider) { + $httpProvider.defaults.xsrfHeaderName = 'X-UMB-XSRF-TOKEN'; + $httpProvider.defaults.xsrfCookieName = 'UMB-XSRF-TOKEN'; $httpProvider.responseInterceptors.push('securityInterceptor'); $httpProvider.interceptors.push('umbracoRequestInterceptor'); }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index d1014adc69..e439eda1ba 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -176,13 +176,22 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var buttonOrder = ["U", "H", "A"]; //Create the first button (primary button) - //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item. + //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item. + //Another tricky rule is if they only have Create + Browse permissions but not Save but if it's being created then they will + // require the Save button in order to create. + //So this code is going to create the primary button (either Publish, SendToPublish, Save) if we are not in create mode + // or if the user has access to create. if (!args.create || _.contains(args.content.allowedActions, "C")) { for (var b in buttonOrder) { if (_.contains(args.content.allowedActions, buttonOrder[b])) { buttons.defaultButton = createButtonDefinition(buttonOrder[b]); break; } + } + //Here's the special check, if the button still isn't set and we are creating and they have create access + //we need to add the Save button + if (!buttons.defaultButton && args.create && _.contains(args.content.allowedActions, "C")) { + buttons.defaultButton = createButtonDefinition("A"); } } @@ -449,14 +458,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //indicates we've handled the server result return true; } - else { - dialogService.ysodDialog(args.err); - } } - else { - dialogService.ysodDialog(args.err); - } - return false; }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index 5d5b509315..21c7742c61 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -150,9 +150,7 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati dialogService.ysodDialog(err); } } - else { - dialogService.ysodDialog(err); - } + }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index c692fda7ba..8738c1011e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -44,7 +44,7 @@ angular.module('umbraco.services') return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { _.each(data, function (item) { - configureMemberResult(item); + searchResultFormatter.configureMemberResult(item); }); return data; }); @@ -69,7 +69,7 @@ angular.module('umbraco.services') return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) { _.each(data, function (item) { - configureContentResult(item); + searchResultFormatter.configureContentResult(item); }); return data; }); @@ -94,7 +94,7 @@ angular.module('umbraco.services') return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) { _.each(data, function (item) { - configureMediaResult(item); + searchResultFormatter.configureMediaResult(item); }); return data; }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/section.service.js b/src/Umbraco.Web.UI.Client/src/common/services/section.service.js new file mode 100644 index 0000000000..e563a83722 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/section.service.js @@ -0,0 +1,43 @@ +/** + * @ngdoc service + * @name umbraco.services.sectionService + * + * + * @description + * A service to return the sections (applications) to be listed in the navigation which are contextual to the current user + */ +(function () { + 'use strict'; + + function sectionService(userService, $q, sectionResource) { + + function getSectionsForUser() { + var deferred = $q.defer(); + userService.getCurrentUser().then(function (u) { + //if they've already loaded, return them + if (u.sections) { + deferred.resolve(u.sections); + } + else { + sectionResource.getSections().then(function (sections) { + //set these to the user (cached), then the user changes, these will be wiped + u.sections = sections; + deferred.resolve(u.sections); + }); + } + }); + return deferred.promise; + } + + var service = { + getSectionsForUser: getSectionsForUser + }; + + return service; + + } + + angular.module('umbraco.services').factory('sectionService', sectionService); + + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 8753bb5316..86de5b586d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -87,6 +87,7 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro editor.addButton('umbmediapicker', { icon: 'custom icon-picture', tooltip: 'Media Picker', + stateSelector: 'img', onclick: function () { var selectedElm = editor.selection.getNode(), diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index 5469c12c30..888a067a66 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -44,52 +44,69 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!parentNode.section) { parentNode.section = section; } + + if (parentNode.metaData && parentNode.metaData.noAccess === true) { + if (!parentNode.cssClasses) { + parentNode.cssClasses = []; + } + parentNode.cssClasses.push("no-access"); + } + //create a method outside of the loop to return the parent - otherwise jshint blows up var funcParent = function() { return parentNode; }; for (var i = 0; i < treeNodes.length; i++) { - treeNodes[i].level = childLevel; + var treeNode = treeNodes[i]; + + treeNode.level = childLevel; //create a function to get the parent node, we could assign the parent node but // then we cannot serialize this entity because we have a cyclical reference. // Instead we just make a function to return the parentNode. - treeNodes[i].parent = funcParent; + treeNode.parent = funcParent; //set the section for each tree node - this allows us to reference this easily when accessing tree nodes - treeNodes[i].section = section; + treeNode.section = section; //if there is not route path specified, then set it automatically, //if this is a tree root node then we want to route to the section's dashboard - if (!treeNodes[i].routePath) { + if (!treeNode.routePath) { - if (treeNodes[i].metaData && treeNodes[i].metaData["treeAlias"]) { + if (treeNode.metaData && treeNode.metaData["treeAlias"]) { //this is a root node - treeNodes[i].routePath = section; + treeNode.routePath = section; } else { - var treeAlias = this.getTreeAlias(treeNodes[i]); - treeNodes[i].routePath = section + "/" + treeAlias + "/edit/" + treeNodes[i].id; + var treeAlias = this.getTreeAlias(treeNode); + treeNode.routePath = section + "/" + treeAlias + "/edit/" + treeNode.id; } } - + //now, format the icon data - if (treeNodes[i].iconIsClass === undefined || treeNodes[i].iconIsClass) { - var converted = iconHelper.convertFromLegacyTreeNodeIcon(treeNodes[i]); - treeNodes[i].cssClass = standardCssClass + " " + converted; + if (treeNode.iconIsClass === undefined || treeNode.iconIsClass) { + var converted = iconHelper.convertFromLegacyTreeNodeIcon(treeNode); + treeNode.cssClass = standardCssClass + " " + converted; if (converted.startsWith('.')) { //its legacy so add some width/height - treeNodes[i].style = "height:16px;width:16px;"; + treeNode.style = "height:16px;width:16px;"; } else { - treeNodes[i].style = ""; + treeNode.style = ""; } } else { - treeNodes[i].style = "background-image: url('" + treeNodes[i].iconFilePath + "');"; + treeNode.style = "background-image: url('" + treeNode.iconFilePath + "');"; //we need an 'icon-' class in there for certain styles to work so if it is image based we'll add this - treeNodes[i].cssClass = standardCssClass + " legacy-custom-file"; + treeNode.cssClass = standardCssClass + " legacy-custom-file"; + } + + if (treeNode.metaData && treeNode.metaData.noAccess === true) { + if (!treeNode.cssClasses) { + treeNode.cssClasses = []; + } + treeNode.cssClasses.push("no-access"); } } }, @@ -375,9 +392,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } for (var i = 0; i < treeNode.children.length; i++) { - if (treeNode.children[i].children && angular.isArray(treeNode.children[i].children) && treeNode.children[i].children.length > 0) { + var child = treeNode.children[i]; + if (child.children && angular.isArray(child.children) && child.children.length > 0) { //recurse - found = this.getDescendantNode(treeNode.children[i], id); + found = this.getDescendantNode(child, id); if (found) { return found; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index 75c7b02d55..5fc2416927 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -7,8 +7,30 @@ * @description A helper object used to format/transform JSON Umbraco data, mostly used for persisting data to the server **/ function umbDataFormatter() { + return { + formatChangePasswordModel: function(model) { + if (!model) { + return null; + } + var trimmed = _.omit(model, ["confirm", "generatedPassword"]) + + //ensure that the pass value is null if all child properties are null + var allNull = true; + var vals = _.values(trimmed); + for (var k = 0; k < vals.length; k++) { + if (vals[k] !== null && vals[k] !== undefined) { + allNull = false; + } + } + if (allNull) { + return null; + } + + return trimmed; + }, + formatContentTypePostData: function (displayModel, action) { //create the save model from the display model @@ -82,6 +104,7 @@ //create the save model from the display model var saveModel = _.pick(displayModel, 'id', 'parentId', 'name', 'username', 'culture', 'email', 'startContentIds', 'startMediaIds', 'userGroups', 'message', 'changePassword'); + saveModel.changePassword = this.formatChangePasswordModel(saveModel.changePassword); //make sure the userGroups are just a string array var currGroups = saveModel.userGroups; @@ -221,7 +244,8 @@ }); saveModel.email = propEmail.value; saveModel.username = propLogin.value; - saveModel.password = propPass.value; + + saveModel.password = this.formatChangePasswordModel(propPass.value); var selectedGroups = []; for (var n in propGroups.value) { diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index 5c70ca6a90..631e38657b 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -7,7 +7,7 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi // it cannot be static $.ajaxSetup({ beforeSend: function (xhr) { - xhr.setRequestHeader("X-XSRF-TOKEN", $cookies["XSRF-TOKEN"]); + xhr.setRequestHeader("X-UMB-XSRF-TOKEN", $cookies["UMB-XSRF-TOKEN"]); if (queryStrings.getParams().umbDebug === "true" || queryStrings.getParams().umbdebug === "true") { xhr.setRequestHeader("X-UMB-DEBUG", "true"); } diff --git a/src/Umbraco.Web.UI.Client/src/installer/_module.js b/src/Umbraco.Web.UI.Client/src/installer/_module.js index 03ba93c846..76393089ce 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/_module.js +++ b/src/Umbraco.Web.UI.Client/src/installer/_module.js @@ -1 +1 @@ -angular.module("umbraco.install", ["umbraco.directives"]); \ No newline at end of file +angular.module("umbraco.install", ["umbraco.directives"]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/alerts.less b/src/Umbraco.Web.UI.Client/src/less/alerts.less index 47ee38a70f..7a760b05ea 100644 --- a/src/Umbraco.Web.UI.Client/src/less/alerts.less +++ b/src/Umbraco.Web.UI.Client/src/less/alerts.less @@ -74,10 +74,14 @@ .alert-form.-no-border { border: none !important; + margin-left: 1px; + margin-bottom: 1px; } .alert-form h4 { - color: @gray-3; + color: @black; + font-weight: bold; + margin-bottom: 5px; } 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 c1db0579ad..f6879bb679 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -70,7 +70,7 @@ body { #leftcolumn { height: 100%; - z-index: 20; + z-index: 1100; width: 80px; float: left; position: absolute; @@ -125,7 +125,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/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index e7aa9d859f..7410c09580 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -1,4 +1,6 @@ +@import "helveticons.less"; + /******* font-face *******/ @font-face { @@ -14,7 +16,7 @@ body { overflow: hidden; height: 100%; width: 100%; - width: ~"(calc(~'100%' - ~'80px'))"; // 80px is the fixed left menu for toggling the different browser sizes + width: calc(~"100% - 80px"); // 80px is the fixed left menu for toggling the different browser sizes position: absolute; padding: 0; margin: 0; 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 3710a4d7b1..68d3b20cab 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -2,7 +2,7 @@ position: fixed; overflow: hidden; background: @white; - z-index: 996660; + z-index: 7500; animation: fadeIn 0.2s; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); } @@ -28,13 +28,13 @@ font-size: @fontSizeLarge; color: @black; font-weight: bold; - margin: 7px 0; } .umb-overlay .umb-overlay__subtitle { font-size: @fontSizeSmall; color: @gray-3; + margin: 0; } .umb-overlay .umb-overlay-container { @@ -155,7 +155,7 @@ @media (max-width: 500px) { .umb-overlay.umb-overlay-left { margin-left: 41px; - width: ~"(calc(~'100%' - ~'41px'))"; + width: calc(100% - 41px); } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less index 6998afa556..522b7564c1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less @@ -9,7 +9,6 @@ display: inline-flex; align-items: center; justify-content: center; - text-transform: capitalize; } // Colors diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less index cfbd929a3a..492a257b38 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less @@ -15,6 +15,13 @@ font-weight: bold; } +.umb-box-header-description { + font-size: 13px; + color: @gray-3; + line-height: 1.6em; + margin-top: 1px; +} + .umb-box-content { padding: 20px; } \ No newline at end of file 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 fb2cc036f4..6fe68ff490 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 @@ -6,7 +6,7 @@ left: 0; width: 100%; height: 100%; - z-index: 999; + z-index: 5000; display: flex; align-items: center; justify-content: center; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less index be63bb3a9c..3d16b76167 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less @@ -10,6 +10,11 @@ a.umb-list-item:focus { text-decoration: none; } +.umb-list-item--disabled { + cursor: not-allowed; + opacity: 0.6; +} + .umb-list-item:hover .umb-list-checkbox, .umb-list-item--selected .umb-list-checkbox { opacity: 1; 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 9f9d288f09..85c8ae61e1 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 @@ -329,7 +329,7 @@ a.umb-package-details__back-link { .umb-package-details__main-content { flex: 1 1 auto; margin-right: 30px; - width: ~"(calc(~'100%' - ~'@{sidebarwidth}' - ~'30px'))"; // Make sure that the main content area doesn't gets affected by inline styling + width: calc(~'100%' - ~'@{sidebarwidth}' - ~'30px'); // Make sure that the main content area doesn't gets affected by inline styling } .umb-package-details__sidebar { diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 2c7561a282..702f80a67f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -76,13 +76,6 @@ padding: 0px; } -.umb-dialog .umb-btn-toolbar { - text-align: right; - padding: 15px 20px 10px 20px; - margin-top: 30px; - clear: both; - background: @white; -} .umb-dialog .umb-btn-toolbar .umb-control-group{ border: none; padding: none; @@ -94,20 +87,22 @@ top: 0px; left: 0px; right: 0px; - bottom: 90px; + bottom: 52px; } .umb-dialog-body .umb-pane{margin-top: 15px;} .umb-dialog-footer{ - border-top: @purple-l3 1px solid; - position: absolute; - overflow:auto; - text-align: right; - - height: 60px; - left: 0px; - right: 0px; - bottom: 0px; + position: absolute; + overflow:auto; + text-align: right; + height: 32px; + left: 0px; + right: 0px; + bottom: 0px; + padding: 10px 20px; + margin: 0; + background: @gray-10; + border-top: 1px solid @purple-l3; } /*we will always make sure to wrap iframe dialogs in proper padding*/ diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index 378f6228a6..8005d37ba8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -356,7 +356,7 @@ .umb-panel-header-content-wrapper { display: flex; flex-direction: column; - height: 100px; + height: 99px; padding: 0 20px; } 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 2388aae2b2..ede8245008 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -304,7 +304,7 @@ ul.color-picker li a { top: 0; left: 0; cursor: move; - z-index: 6001; + z-index: 499; position: absolute; } diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index e90a053a3d..03d25a78f5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -131,7 +131,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 { diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index 5de27de9f3..c859fae991 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -377,6 +377,13 @@ div.locked:before{ bottom: 0; } +.umb-tree li div.no-access .umb-tree-icon, +.umb-tree li div.no-access .root-link, +.umb-tree li div.no-access .umb-tree-item__label { + color: @gray-7; + cursor: not-allowed; +} + // Tree context menu // ------------------------- .umb-actions { diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 69e6779a15..2fb5f9beee 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -98,14 +98,31 @@ app.config(function ($routeProvider) { resolve: doLogout() }) .when('/:section', { - templateUrl: function (rp) { - if (rp.section.toLowerCase() === "default" || rp.section.toLowerCase() === "umbraco" || rp.section === "") - { - rp.section = "content"; + //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. + template: "
    ", + //This controller will execute for this route, then we can execute some code in order to set the template Url + controller: function ($scope, $route, $routeParams, $location, sectionService) { + if ($routeParams.section.toLowerCase() === "default" || $routeParams.section.toLowerCase() === "umbraco" || $routeParams.section === "") { + $routeParams.section = "content"; } - - rp.url = "dashboard.aspx?app=" + rp.section; - return 'views/common/dashboard.html'; + + //We are going to check the currently loaded sections for the user and if the section we are navigating + //to has a custom route path we'll use that + sectionService.getSectionsForUser().then(function(sections) { + //find the one we're requesting + var found = _.find(sections, function(s) { + return s.alias === $routeParams.section; + }) + if (found && found.routePath) { + //there's a custom route path so redirect + $location.path(found.routePath); + } + else { + //there's no custom route path so continue as normal + $routeParams.url = "dashboard.aspx?app=" + $routeParams.section; + $scope.templateUrl = 'views/common/dashboard.html'; + } + }); }, resolve: canRoute(true) }) @@ -121,15 +138,11 @@ app.config(function ($routeProvider) { }) .when('/:section/:tree/:method', { templateUrl: function (rp) { + + //if there is no method registered for this then show the dashboard if (!rp.method) return "views/common/dashboard.html"; - - //NOTE: This current isn't utilized by anything but does open up some cool opportunities for - // us since we'll be able to have specialized views for individual sections which is something - // we've never had before. So could utilize this for a new dashboard model when we get native - // angular dashboards working. Perhaps a normal section dashboard would list out the registered - // dashboards (as tabs if we wanted) and each tab could actually be a route link to one of these views? - + return ('views/' + rp.tree + '/' + rp.method + '.html'); }, resolve: canRoute(true) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html index c159aafd6e..4e482c26b1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html @@ -7,7 +7,7 @@ style="width: 100%" ng-model="searchTerm" class="umb-search-field search-query input-block-level" - localize="plceholder" + localize="placeholder" placeholder="@placeholders_filter" no-dirty-check>
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html index badcc14487..442357edcc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html @@ -1,8 +1,8 @@
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html index a4e99546fa..34c5db4d3c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html @@ -12,17 +12,18 @@
    - + + no-dirty-check + ng-change="$parent.$parent.showReset = !$parent.$parent.showReset"/> - + - + - - +
    + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html index a4efc9194c..8617f94e5e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html @@ -13,7 +13,7 @@ -
    +
    Content start node: No start node selected @@ -21,7 +21,7 @@
    -
    +
    Media start node: No start node selected diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.copy.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.copy.controller.js index 9940aebe64..fa2d831eac 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.copy.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.copy.controller.js @@ -1,5 +1,5 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController", - function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) { + function ($scope, userService, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) { var dialogOptions = $scope.dialogOptions; var searchText = "Search..."; @@ -18,6 +18,12 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController", results: [], selectedSearchResults: [] } + $scope.treeModel = { + hideHeader: false + } + userService.getCurrentUser().then(function (userData) { + $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1; + }); var node = dialogOptions.currentNode; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 94562c6a32..77f3ca0c7e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -12,7 +12,7 @@ function ContentEditController($scope, $routeParams, contentResource) { return contentResource.getScaffold($routeParams.id, $routeParams.doctype); } function scaffoldBlueprint() { - return contentResource.getBlueprintScaffold($routeParams.blueprintId); + return contentResource.getBlueprintScaffold($routeParams.id, $routeParams.blueprintId); } $scope.contentId = $routeParams.id; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js index ce81a60a38..984c147ee4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.move.controller.js @@ -1,5 +1,5 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController", - function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) { + function ($scope, userService, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) { var dialogOptions = $scope.dialogOptions; var searchText = "Search..."; @@ -15,7 +15,13 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController", showSearch: false, results: [], selectedSearchResults: [] - } + } + $scope.treeModel = { + hideHeader: false + } + userService.getCurrentUser().then(function (userData) { + $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1; + }); var node = dialogOptions.currentNode; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js index eace76ac75..6217cb2d2a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js @@ -1,28 +1,35 @@ (function () { "use strict"; - function ContentRightsController($scope, contentResource) { + function ContentRightsController($scope, $timeout, contentResource, localizationService, angularHelper) { var vm = this; + var currentForm; vm.availableUserGroups = []; vm.selectedUserGroups = []; vm.removedUserGroups = []; vm.viewState = "manageGroups"; - + vm.labels = {}; + vm.showNotification = false; + vm.setViewSate = setViewSate; vm.editPermissions = editPermissions; vm.setPermissions = setPermissions; vm.save = save; vm.removePermissions = removePermissions; vm.cancelManagePermissions = cancelManagePermissions; + vm.closeDialog = closeDialog; + vm.stay = stay; function onInit() { vm.loading = true; contentResource.getDetailedPermissions($scope.currentNode.id).then(function (userGroups) { initData(userGroups); vm.loading = false; + currentForm = angularHelper.getCurrentForm($scope); }); + } /** @@ -51,6 +58,9 @@ //if no permissions are explicitly set this means we need to show the defaults vm.selectedUserGroup.permissions = vm.selectedUserGroup.defaultPermissions; } + localizationService.localize("defaultdialogs_permissionsSetForGroup", [$scope.currentNode.name, vm.selectedUserGroup.name]).then(function (value) { + vm.labels.permissionsSetForGroup = value; + }); setViewSate("managePermissions"); } @@ -135,6 +145,14 @@ //re-assign model from server since it could have changed initData(userGroups); + // clear dirty state on the form so we don't see the discard changes notification + // we use a timeout here because in some cases the initData reformats the userGroups model and triggers a change after the form state was changed + $timeout(function() { + if(currentForm) { + currentForm.$dirty = false; + } + }); + vm.saveState = "success"; vm.saveSuccces = true; }, function(error){ @@ -143,6 +161,21 @@ }); } + function stay() { + vm.showNotification = false; + } + + function closeDialog() { + + // check if form has been changed. If it has show discard changes notification + if (currentForm && currentForm.$dirty) { + vm.showNotification = true; + } else { + $scope.nav.hideDialog(); + } + + } + onInit(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/copy.html b/src/Umbraco.Web.UI.Client/src/views/content/copy.html index c19ec72dbd..ca7714c284 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/copy.html @@ -48,7 +48,7 @@
    -
    -
    -
    -
    -
    {{vm.saveError.errorMsg}}
    -
    {{vm.saveError.data.message}}
    + + +
    + +
    + +
    +
    +
    {{vm.saveError.errorMsg}}
    +
    {{vm.saveError.data.message}}
    +
    -
    -
    -
    - Permissions saved for {{currentNode.name}} +
    +
    + {{currentNode.name}} +
    -
    -
    Ut tempor dui nec consectetur efficitur.
    -

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam tellus purus, malesuada sed purus ut, semper sollicitudin odio.

    +
    Set permissions for {{ currentNode.name }}
    +

    - + -
    - - -
    +
    + + +
    + +
    +
    +

    +

    + + + + +
    +
    + +
    +