From 7a615133ff9de84ee667fb7794169af65e2b4d7a Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 29 Jun 2018 19:52:40 +0200 Subject: [PATCH] Renormalize --- build/NuSpecs/tools/Readme.txt | 50 +- build/NuSpecs/tools/install.ps1 | 322 +- build/RevertToCleanInstall.bat | 252 +- build/RevertToEmptyInstall.bat | 324 +- docs/README.md | 96 +- src/SolutionInfo.cs | 40 +- src/Umbraco.Core/AssemblyExtensions.cs | 144 +- src/Umbraco.Core/Attempt.cs | 252 +- src/Umbraco.Core/Cache/CacheKeys.cs | 102 +- .../Cache/CacheProviderExtensions.cs | 140 +- src/Umbraco.Core/Cache/CacheRefresherBase.cs | 240 +- .../Cache/CacheRefresherEventArgs.cs | 38 +- .../Cache/DictionaryCacheProviderBase.cs | 626 +- .../Cache/HttpRequestCacheProvider.cs | 318 +- .../Cache/HttpRuntimeCacheProvider.cs | 474 +- src/Umbraco.Core/Cache/ICacheProvider.cs | 136 +- src/Umbraco.Core/Cache/ICacheRefresher.cs | 66 +- src/Umbraco.Core/Cache/IJsonCacheRefresher.cs | 28 +- .../Cache/IRuntimeCacheProvider.cs | 70 +- .../Cache/JsonCacheRefresherBase.cs | 58 +- src/Umbraco.Core/Cache/NullCacheProvider.cs | 132 +- .../Cache/ObjectCacheRuntimeCacheProvider.cs | 714 +- src/Umbraco.Core/Cache/StaticCacheProvider.cs | 158 +- .../Cache/TypedCacheRefresherBase.cs | 72 +- .../CodeAnnotations/FriendlyNameAttribute.cs | 70 +- .../UmbracoExperimentalFeatureAttribute.cs | 70 +- .../UmbracoObjectTypeAttribute.cs | 52 +- .../UmbracoProposedPublicAttribute.cs | 72 +- .../UmbracoWillObsoleteAttribute.cs | 74 +- .../CaseInsensitiveEnumConfigConverter.cs | 64 +- .../ClientDependencyConfiguration.cs | 356 +- .../CommaDelimitedConfigurationElement.cs | 140 +- .../Configuration/Dashboard/AccessElement.cs | 68 +- .../Configuration/Dashboard/AccessItem.cs | 30 +- .../Configuration/Dashboard/AccessType.cs | 18 +- .../Configuration/Dashboard/AreaCollection.cs | 64 +- .../Configuration/Dashboard/AreaElement.cs | 24 +- .../Configuration/Dashboard/AreasElement.cs | 30 +- .../Dashboard/ControlCollection.cs | 74 +- .../Configuration/Dashboard/ControlElement.cs | 148 +- .../Dashboard/DashboardSection.cs | 48 +- .../Configuration/Dashboard/IAccess.cs | 18 +- .../Configuration/Dashboard/IAccessItem.cs | 30 +- .../Configuration/Dashboard/IArea.cs | 14 +- .../Dashboard/IDashboardControl.cs | 30 +- .../Dashboard/IDashboardSection.cs | 18 +- .../Configuration/Dashboard/IDashboardTab.cs | 26 +- .../Configuration/Dashboard/ISection.cs | 30 +- .../Dashboard/SectionCollection.cs | 74 +- .../Configuration/Dashboard/SectionElement.cs | 100 +- .../Configuration/Dashboard/TabCollection.cs | 74 +- .../Configuration/Dashboard/TabElement.cs | 76 +- .../FileSystemProviderElement.cs | 134 +- .../FileSystemProviderElementCollection.cs | 86 +- .../FileSystemProvidersSection.cs | 60 +- .../Configuration/GlobalSettings.cs | 760 +- .../InnerTextConfigurationElement.cs | 138 +- ...ionalCommaDelimitedConfigurationElement.cs | 86 +- .../OptionalInnerTextConfigurationElement.cs | 46 +- .../RawXmlConfigurationElement.cs | 60 +- .../Configuration/UmbracoConfig.cs | 390 +- .../UmbracoConfigurationSection.cs | 64 +- .../AppCodeFileExtensionsCollection.cs | 72 +- .../AppCodeFileExtensionsElement.cs | 34 +- .../UmbracoSettings/CharCollection.cs | 72 +- .../UmbracoSettings/CharElement.cs | 64 +- .../UmbracoSettings/ContentElement.cs | 292 +- .../ContentError404Collection.cs | 74 +- .../ContentErrorPageElement.cs | 130 +- .../UmbracoSettings/ContentErrorsElement.cs | 88 +- .../UmbracoSettings/ContentImagingElement.cs | 162 +- .../ContentScriptEditorElement.cs | 54 +- .../DisabledLogTypesCollection.cs | 62 +- .../UmbracoSettings/ExternalLoggerElement.cs | 50 +- .../UmbracoSettings/FileExtensionElement.cs | 42 +- .../Configuration/UmbracoSettings/IChar.cs | 16 +- .../UmbracoSettings/IContentErrorPage.cs | 28 +- .../UmbracoSettings/IContentSection.cs | 140 +- .../UmbracoSettings/IFileExtension.cs | 14 +- .../IImagingAutoFillUploadField.cs | 36 +- .../Configuration/UmbracoSettings/ILogType.cs | 14 +- .../UmbracoSettings/ILoggingSection.cs | 34 +- .../UmbracoSettings/IProvidersSection.cs | 18 +- .../UmbracoSettings/IRequestHandlerSection.cs | 38 +- .../UmbracoSettings/IScheduledTask.cs | 26 +- .../UmbracoSettings/IScheduledTasksSection.cs | 22 +- .../UmbracoSettings/ISecuritySection.cs | 54 +- .../UmbracoSettings/ITemplatesSection.cs | 18 +- .../IUmbracoSettingsSection.cs | 52 +- .../UmbracoSettings/IWebRoutingSection.cs | 40 +- .../ImagingAutoFillPropertiesCollection.cs | 74 +- .../ImagingAutoFillUploadFieldElement.cs | 134 +- .../UmbracoSettings/LogTypeElement.cs | 22 +- .../UmbracoSettings/LoggingElement.cs | 184 +- .../UmbracoSettings/NotificationsElement.cs | 40 +- .../UmbracoSettings/ProvidersElement.cs | 36 +- .../UmbracoSettings/RequestHandlerElement.cs | 254 +- .../UmbracoSettings/ScheduledTaskElement.cs | 62 +- .../ScheduledTasksCollection.cs | 62 +- .../UmbracoSettings/ScheduledTasksElement.cs | 52 +- .../UmbracoSettings/SecurityElement.cs | 186 +- .../UmbracoSettings/TemplatesElement.cs | 40 +- .../UmbracoSettings/UmbracoSettingsSection.cs | 222 +- .../UmbracoSettings/UrlReplacingElement.cs | 58 +- .../UmbracoSettings/UserProviderElement.cs | 42 +- .../UmbracoSettings/WebRoutingElement.cs | 96 +- .../Configuration/UmbracoVersion.cs | 142 +- src/Umbraco.Core/Constants-Applications.cs | 302 +- src/Umbraco.Core/Constants-Conventions.cs | 692 +- src/Umbraco.Core/Constants-Examine.cs | 48 +- src/Umbraco.Core/Constants-ObjectTypes.cs | 274 +- src/Umbraco.Core/Constants-Packaging.cs | 110 +- src/Umbraco.Core/Constants-PropertyEditors.cs | 432 +- src/Umbraco.Core/Constants-System.cs | 130 +- src/Umbraco.Core/Constants.cs | 20 +- .../CustomBooleanTypeConverter.cs | 64 +- src/Umbraco.Core/DataTableExtensions.cs | 238 +- src/Umbraco.Core/DateTimeExtensions.cs | 166 +- src/Umbraco.Core/DelegateEqualityComparer.cs | 120 +- .../Dictionary/ICultureDictionary.cs | 62 +- .../Dictionary/ICultureDictionaryFactory.cs | 14 +- src/Umbraco.Core/DictionaryExtensions.cs | 568 +- src/Umbraco.Core/DisposableObject.cs | 128 +- src/Umbraco.Core/DisposableTimer.cs | 222 +- src/Umbraco.Core/Enum.cs | 218 +- src/Umbraco.Core/EnumerableExtensions.cs | 690 +- .../Events/CancellableEventArgs.cs | 284 +- .../Events/CancellableObjectEventArgs.cs | 352 +- .../Events/ContentCacheEventArgs.cs | 8 +- src/Umbraco.Core/Events/CopyEventArgs.cs | 174 +- .../Events/DatabaseCreationEventArgs.cs | 8 +- src/Umbraco.Core/Events/DeleteEventArgs.cs | 404 +- .../Events/DeleteRevisionsEventArgs.cs | 138 +- src/Umbraco.Core/Events/EventExtensions.cs | 92 +- .../Events/ImportPackageEventArgs.cs | 152 +- .../Events/MacroErrorEventArgs.cs | 84 +- src/Umbraco.Core/Events/MigrationEventArgs.cs | 182 +- src/Umbraco.Core/Events/MoveEventArgs.cs | 328 +- src/Umbraco.Core/Events/NewEventArgs.cs | 254 +- src/Umbraco.Core/Events/PublishEventArgs.cs | 278 +- .../Events/RecycleBinEventArgs.cs | 160 +- .../Events/RefreshContentEventArgs.cs | 8 +- src/Umbraco.Core/Events/RollbackEventArgs.cs | 42 +- src/Umbraco.Core/Events/SaveEventArgs.cs | 242 +- .../Events/SendToPublishEventArgs.cs | 42 +- src/Umbraco.Core/Events/TypedEventHandler.cs | 14 +- src/Umbraco.Core/ExpressionExtensions.cs | 48 +- src/Umbraco.Core/ExpressionHelper.cs | 744 +- src/Umbraco.Core/HashCodeCombiner.cs | 200 +- src/Umbraco.Core/IDisposeOnRequestEnd.cs | 28 +- src/Umbraco.Core/IO/FileSecurityException.cs | 40 +- src/Umbraco.Core/IO/FileSystemExtensions.cs | 138 +- .../IO/FileSystemProviderAttribute.cs | 38 +- src/Umbraco.Core/IO/FileSystemWrapper.cs | 236 +- src/Umbraco.Core/IO/IFileSystem.cs | 356 +- src/Umbraco.Core/IO/IOHelper.cs | 778 +- src/Umbraco.Core/IO/MediaFileSystem.cs | 640 +- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 890 +- src/Umbraco.Core/IO/SystemDirectories.cs | 150 +- src/Umbraco.Core/IO/SystemFiles.cs | 84 +- src/Umbraco.Core/IfExtensions.cs | 134 +- src/Umbraco.Core/IntExtensions.cs | 64 +- src/Umbraco.Core/LambdaExpressionCacheKey.cs | 166 +- .../AsynchronousRollingFileAppender.cs | 412 +- .../Logging/LoggingTaskExtension.cs | 88 +- src/Umbraco.Core/Macros/MacroTagParser.cs | 422 +- src/Umbraco.Core/Manifest/ManifestParser.cs | 344 +- src/Umbraco.Core/Manifest/PackageManifest.cs | 54 +- src/Umbraco.Core/Media/IEmbedProvider.cs | 18 +- .../Media/IEmbedSettingProvider.cs | 18 +- src/Umbraco.Core/Media/ProviderSetting.cs | 16 +- src/Umbraco.Core/Media/Result.cs | 22 +- src/Umbraco.Core/Media/Status.cs | 22 +- src/Umbraco.Core/Models/ApplicationTree.cs | 282 +- src/Umbraco.Core/Models/Content.cs | 1242 +- src/Umbraco.Core/Models/ContentBase.cs | 820 +- src/Umbraco.Core/Models/ContentExtensions.cs | 1032 +- src/Umbraco.Core/Models/ContentStatus.cs | 48 +- src/Umbraco.Core/Models/ContentType.cs | 318 +- src/Umbraco.Core/Models/ContentTypeBase.cs | 1048 +- .../Models/ContentTypeCompositionBase.cs | 548 +- src/Umbraco.Core/Models/ContentTypeSort.cs | 160 +- src/Umbraco.Core/Models/DictionaryItem.cs | 184 +- .../Models/DictionaryTranslation.cs | 252 +- .../Models/Editors/ContentPropertyData.cs | 90 +- src/Umbraco.Core/Models/File.cs | 362 +- src/Umbraco.Core/Models/IContent.cs | 440 +- src/Umbraco.Core/Models/IContentBase.cs | 258 +- src/Umbraco.Core/Models/IContentType.cs | 80 +- src/Umbraco.Core/Models/IContentTypeBase.cs | 252 +- .../Models/IContentTypeComposition.cs | 116 +- src/Umbraco.Core/Models/IDictionaryItem.cs | 56 +- .../Models/IDictionaryTranslation.cs | 46 +- src/Umbraco.Core/Models/IFile.cs | 96 +- src/Umbraco.Core/Models/ILanguage.cs | 74 +- src/Umbraco.Core/Models/IMacro.cs | 150 +- src/Umbraco.Core/Models/IMacroProperty.cs | 84 +- src/Umbraco.Core/Models/IMedia.cs | 54 +- src/Umbraco.Core/Models/IMediaType.cs | 36 +- src/Umbraco.Core/Models/IMember.cs | 40 +- src/Umbraco.Core/Models/IMemberType.cs | 100 +- src/Umbraco.Core/Models/IRelation.cs | 76 +- src/Umbraco.Core/Models/IRelationType.cs | 82 +- src/Umbraco.Core/Models/ITag.cs | 58 +- src/Umbraco.Core/Models/ITemplate.cs | 76 +- src/Umbraco.Core/Models/Language.cs | 150 +- src/Umbraco.Core/Models/Macro.cs | 606 +- src/Umbraco.Core/Models/MacroProperty.cs | 348 +- .../Models/MacroPropertyCollection.cs | 136 +- src/Umbraco.Core/Models/MacroTypes.cs | 40 +- src/Umbraco.Core/Models/Media.cs | 234 +- src/Umbraco.Core/Models/MediaType.cs | 144 +- src/Umbraco.Core/Models/Member.cs | 1232 +- src/Umbraco.Core/Models/MemberType.cs | 340 +- .../Models/Membership/EntityPermission.cs | 132 +- .../Models/Membership/IMembershipUser.cs | 94 +- .../Models/Membership/IProfile.cs | 22 +- src/Umbraco.Core/Models/Membership/IUser.cs | 106 +- .../Membership/MembershipUserExtensions.cs | 100 +- .../Membership/UmbracoMembershipMember.cs | 256 +- src/Umbraco.Core/Models/Membership/User.cs | 1060 +- src/Umbraco.Core/Models/PagedResult.cs | 120 +- src/Umbraco.Core/Models/Property.cs | 842 +- src/Umbraco.Core/Models/PropertyCollection.cs | 436 +- src/Umbraco.Core/Models/PropertyGroup.cs | 198 +- .../Models/PropertyGroupCollection.cs | 350 +- src/Umbraco.Core/Models/PropertyType.cs | 914 +- .../Models/PropertyTypeCollection.cs | 320 +- .../PublishedContent/PublishedContentModel.cs | 38 +- .../PublishedContent/PublishedContentType.cs | 342 +- .../PublishedContentWrapped.cs | 270 +- .../PublishedContent/PublishedPropertyBase.cs | 134 +- .../PublishedContent/PublishedPropertyType.cs | 604 +- src/Umbraco.Core/Models/PublishedState.cs | 122 +- src/Umbraco.Core/Models/Relation.cs | 178 +- src/Umbraco.Core/Models/RelationType.cs | 206 +- src/Umbraco.Core/Models/Script.cs | 68 +- src/Umbraco.Core/Models/Section.cs | 52 +- src/Umbraco.Core/Models/ServerRegistration.cs | 252 +- src/Umbraco.Core/Models/Stylesheet.cs | 350 +- src/Umbraco.Core/Models/StylesheetProperty.cs | 120 +- src/Umbraco.Core/Models/Tag.cs | 122 +- .../Models/TaggableObjectTypes.cs | 26 +- src/Umbraco.Core/Models/Task.cs | 200 +- src/Umbraco.Core/Models/TaskType.cs | 78 +- src/Umbraco.Core/Models/Template.cs | 204 +- src/Umbraco.Core/Models/UmbracoObjectTypes.cs | 376 +- src/Umbraco.Core/Models/UserExtensions.cs | 858 +- .../RequiredForPersistenceAttribute.cs | 62 +- .../NameValueCollectionExtensions.cs | 84 +- src/Umbraco.Core/NetworkHelper.cs | 100 +- src/Umbraco.Core/ObjectExtensions.cs | 1584 +- .../Packaging/IPackageExtraction.cs | 124 +- .../Packaging/IPackageInstallation.cs | 26 +- .../Packaging/PackageBinaryInspector.cs | 590 +- .../Packaging/PackageExtraction.cs | 382 +- .../Packaging/PackageInstallation.cs | 1172 +- .../ConstraintAttribute.cs | 50 +- .../ForeignKeyAttribute.cs | 66 +- .../DatabaseAnnotations/IndexAttribute.cs | 70 +- .../DatabaseAnnotations/IndexTypes.cs | 24 +- .../DatabaseAnnotations/LengthAttribute.cs | 44 +- .../NullSettingAttribute.cs | 40 +- .../DatabaseAnnotations/NullSettings.cs | 22 +- .../PrimaryKeyColumnAttribute.cs | 118 +- .../ReferencesAttribute.cs | 42 +- .../SpecialDbTypeAttribute.cs | 48 +- .../DatabaseAnnotations/SpecialDbTypes.cs | 24 +- .../ColumnDefinition.cs | 70 +- .../ConstraintDefinition.cs | 44 +- .../ConstraintType.cs | 18 +- .../DefinitionFactory.cs | 356 +- .../DeletionDataDefinition.cs | 18 +- .../DatabaseModelDefinitions/Direction.cs | 16 +- .../ForeignKeyDefinition.cs | 54 +- .../IndexColumnDefinition.cs | 16 +- .../IndexDefinition.cs | 44 +- .../InsertionDataDefinition.cs | 18 +- .../ModificationType.cs | 26 +- .../DatabaseModelDefinitions/SystemMethods.cs | 20 +- .../TableDefinition.cs | 40 +- .../Factories/ContentTypeFactory.cs | 324 +- .../Factories/DictionaryItemFactory.cs | 132 +- .../Factories/DictionaryTranslationFactory.cs | 110 +- .../Persistence/Factories/LanguageFactory.cs | 52 +- .../Persistence/Factories/MacroFactory.cs | 152 +- .../Factories/MemberTypeReadOnlyFactory.cs | 404 +- .../Factories/ModelFactoryConfiguration.cs | 40 +- .../Persistence/Factories/PropertyFactory.cs | 284 +- .../Factories/PropertyGroupFactory.cs | 324 +- .../Persistence/Factories/RelationFactory.cs | 118 +- .../Factories/RelationTypeFactory.cs | 108 +- .../Factories/ServerRegistrationFactory.cs | 68 +- .../Persistence/Factories/TagFactory.cs | 54 +- .../Persistence/Factories/TemplateFactory.cs | 214 +- .../Persistence/Factories/UserFactory.cs | 206 +- .../ITransientErrorDetectionStrategy.cs | 34 +- .../RetryLimitExceededException.cs | 124 +- .../Persistence/FaultHandling/RetryPolicy.cs | 480 +- .../FaultHandling/RetryPolicyFactory.cs | 114 +- .../FaultHandling/RetryStrategy.cs | 222 +- .../FaultHandling/RetryingEventArgs.cs | 80 +- .../Strategies/ExponentialBackoff.cs | 200 +- .../FaultHandling/Strategies/FixedInterval.cs | 192 +- .../FaultHandling/Strategies/Incremental.cs | 172 +- ...tworkConnectivityErrorDetectionStrategy.cs | 62 +- ...SqlAzureTransientErrorDetectionStrategy.cs | 322 +- .../FaultHandling/ThrottlingCondition.cs | 660 +- .../Persistence/Mappers/BaseMapper.cs | 138 +- .../Persistence/Mappers/ContentMapper.cs | 98 +- .../Persistence/Mappers/ContentTypeMapper.cs | 82 +- .../Persistence/Mappers/DictionaryMapper.cs | 54 +- .../Mappers/DictionaryTranslationMapper.cs | 54 +- .../Persistence/Mappers/DtoMapModel.cs | 38 +- .../Persistence/Mappers/LanguageMapper.cs | 52 +- .../Persistence/Mappers/MacroMapper.cs | 58 +- .../Persistence/Mappers/MapperForAttribute.cs | 38 +- .../Persistence/Mappers/MediaMapper.cs | 80 +- .../Persistence/Mappers/MediaTypeMapper.cs | 82 +- .../Persistence/Mappers/MemberMapper.cs | 118 +- .../Persistence/Mappers/MemberTypeMapper.cs | 82 +- .../Mappers/PropertyGroupMapper.cs | 52 +- .../Persistence/Mappers/PropertyMapper.cs | 40 +- .../Persistence/Mappers/PropertyTypeMapper.cs | 70 +- .../Persistence/Mappers/RelationMapper.cs | 58 +- .../Persistence/Mappers/RelationTypeMapper.cs | 58 +- .../Mappers/ServerRegistrationMapper.cs | 52 +- .../Persistence/Mappers/TagMapper.cs | 56 +- .../Mappers/UmbracoEntityMapper.cs | 56 +- .../Persistence/Mappers/UserMapper.cs | 70 +- .../Persistence/Mappers/UserSectionMapper.cs | 66 +- .../Persistence/Mappers/UserTypeMapper.cs | 56 +- .../Querying/ExpressionVisitorBase.cs | 1750 +- .../Persistence/Querying/IQuery.cs | 84 +- .../Persistence/Querying/Query.cs | 198 +- .../Persistence/Querying/SqlTranslator.cs | 62 +- .../Persistence/SqlSyntax/ColumnInfo.cs | 62 +- .../Persistence/SqlSyntax/DbTypes.cs | 58 +- .../SqlSyntax/ISqlSyntaxProvider.cs | 178 +- .../MicrosoftSqlSyntaxProviderBase.cs | 478 +- .../SqlSyntax/MySqlSyntaxProvider.cs | 810 +- .../SqlSyntax/SqlCeSyntaxProvider.cs | 434 +- .../SqlSyntax/SqlServerSyntaxProvider.cs | 510 +- .../SqlSyntax/SqlSyntaxProviderAttribute.cs | 42 +- .../SqlSyntax/SqlSyntaxProviderBase.cs | 1180 +- .../Persistence/UmbracoDatabase.cs | 522 +- src/Umbraco.Core/Properties/AssemblyInfo.cs | 82 +- .../IPropertyValueConverter.cs | 200 +- .../PropertyEditors/PropertyCacheLevel.cs | 78 +- .../PropertyValueConverterBase.cs | 82 +- .../DatePickerValueConverter.cs | 108 +- .../ValueConverters/IntegerValueConverter.cs | 46 +- .../ValueConverters/JsonValueConverter.cs | 140 +- .../MarkdownEditorValueConverter.cs | 80 +- .../MultipleTextStringValueConverter.cs | 156 +- .../TextStringValueConverter.cs | 90 +- .../ValueConverters/TinyMceValueConverter.cs | 86 +- .../ValueConverters/YesNoValueConverter.cs | 108 +- src/Umbraco.Core/ReadLock.cs | 72 +- src/Umbraco.Core/RenderingEngine.cs | 18 +- .../Security/MembershipProviderBase.cs | 1932 +- .../Security/UmbracoBackOfficeIdentity.cs | 458 +- .../AbstractSerializationService.cs | 66 +- src/Umbraco.Core/Serialization/Formatter.cs | 42 +- src/Umbraco.Core/Serialization/IFormatter.cs | 18 +- src/Umbraco.Core/Serialization/ISerializer.cs | 24 +- .../Serialization/IStreamedResult.cs | 20 +- .../Serialization/JsonNetSerializer.cs | 144 +- .../Serialization/JsonToStringConverter.cs | 66 +- .../Serialization/SerializationExtensions.cs | 80 +- .../Serialization/SerializationService.cs | 90 +- .../Serialization/StreamedResult.cs | 42 +- .../Services/ContentServiceExtensions.cs | 152 +- .../Services/IApplicationTreeService.cs | 274 +- src/Umbraco.Core/Services/IContentService.cs | 886 +- .../Services/IContentTypeService.cs | 70 +- src/Umbraco.Core/Services/IDataTypeService.cs | 184 +- src/Umbraco.Core/Services/IEntityService.cs | 606 +- src/Umbraco.Core/Services/IFileService.cs | 724 +- .../Services/ILocalizationService.cs | 340 +- src/Umbraco.Core/Services/IMacroService.cs | 128 +- src/Umbraco.Core/Services/IMediaService.cs | 914 +- src/Umbraco.Core/Services/IMemberService.cs | 450 +- .../Services/IMemberTypeService.cs | 24 +- .../Services/IMembershipUserService.cs | 50 +- src/Umbraco.Core/Services/IRelationService.cs | 576 +- src/Umbraco.Core/Services/ISectionService.cs | 228 +- src/Umbraco.Core/Services/IService.cs | 24 +- src/Umbraco.Core/Services/ITagService.cs | 298 +- src/Umbraco.Core/Services/IUserService.cs | 538 +- src/Umbraco.Core/Services/ServiceContext.cs | 554 +- src/Umbraco.Core/StringAliasCaseType.cs | 24 +- src/Umbraco.Core/StringExtensions.cs | 3020 +-- src/Umbraco.Core/Strings/CleanStringType.cs | 246 +- .../Strings/ContentBaseExtensions.cs | 60 +- .../Strings/DefaultShortStringHelper.cs | 1350 +- .../Strings/DefaultUrlSegmentProvider.cs | 62 +- .../Strings/IShortStringHelper.cs | 238 +- .../Strings/IUrlSegmentProvider.cs | 54 +- .../Strings/StringAliasCaseTypeExtensions.cs | 54 +- .../Strings/Utf8ToAsciiConverter.cs | 7256 +++--- .../Sync/DatabaseServerRegistrar.cs | 108 +- src/Umbraco.Core/Sync/IServerAddress.cs | 30 +- src/Umbraco.Core/Sync/IServerMessenger.cs | 162 +- src/Umbraco.Core/Sync/IServerRegistrar.cs | 58 +- src/Umbraco.Core/Sync/MessageType.cs | 32 +- .../Sync/ServerSyncWebServiceClient.cs | 492 +- src/Umbraco.Core/SystemUtilities.cs | 96 +- src/Umbraco.Core/TypeExtensions.cs | 962 +- src/Umbraco.Core/Umbraco.Core.csproj | 3046 +-- src/Umbraco.Core/UmbracoApplicationBase.cs | 504 +- src/Umbraco.Core/UpgradeableReadLock.cs | 90 +- src/Umbraco.Core/UriExtensions.cs | 672 +- src/Umbraco.Core/WriteLock.cs | 74 +- src/Umbraco.Core/Xml/DynamicContext.cs | 624 +- .../Xml/XPath/INavigableContent.cs | 118 +- .../Xml/XPath/INavigableContentType.cs | 48 +- .../Xml/XPath/INavigableFieldType.cs | 52 +- .../Xml/XPath/INavigableSource.cs | 68 +- src/Umbraco.Core/Xml/XPath/MacroNavigator.cs | 2084 +- .../Xml/XPath/NavigableNavigator.cs | 2468 +- .../Xml/XPathNavigatorExtensions.cs | 114 +- src/Umbraco.Core/Xml/XPathVariable.cs | 64 +- src/Umbraco.Core/Xml/XmlNamespaces.cs | 82 +- src/Umbraco.Core/Xml/XmlNodeListFactory.cs | 356 +- src/Umbraco.Core/XmlExtensions.cs | 622 +- src/Umbraco.Tests/App.config | 346 +- src/Umbraco.Tests/Cache/CacheProviderTests.cs | 514 +- .../Cache/HttpRequestCacheProviderTests.cs | 104 +- .../Cache/HttpRuntimeCacheProviderTests.cs | 118 +- .../Cache/ObjectCacheProviderTests.cs | 72 +- .../PublishedContentCacheTests.cs | 254 +- .../Cache/RuntimeCacheProviderTests.cs | 58 +- .../DashboardSettings/Dashboard.config | 228 +- .../DashboardSettingsTests.cs | 232 +- .../DashboardSettings/web.config | 26 +- .../Configurations/FileSystemProviderTests.cs | 50 +- .../ContentElementDefaultTests.cs | 90 +- .../UmbracoSettings/ContentElementTests.cs | 408 +- .../LoggingElementDefaultTests.cs | 60 +- .../UmbracoSettings/LoggingElementTests.cs | 86 +- .../ProvidersElementDefaultTests.cs | 28 +- .../UmbracoSettings/ProvidersElementTests.cs | 30 +- .../RequestHandlerElementDefaultTests.cs | 26 +- .../RequestHandlerElementTests.cs | 88 +- .../ScheduledTasksElementDefaultTests.cs | 40 +- .../ScheduledTasksElementTests.cs | 48 +- .../SecurityElementDefaultTests.cs | 26 +- .../UmbracoSettings/SecurityElementTests.cs | 78 +- .../TemplateElementDefaultTests.cs | 26 +- .../UmbracoSettings/TemplateElementTests.cs | 32 +- .../UmbracoSettings/UmbracoSettingsTests.cs | 74 +- .../WebRoutingElementDefaultTests.cs | 76 +- .../UmbracoSettings/WebRoutingElementTests.cs | 52 +- .../UmbracoSettings/umbracoSettings.config | 398 +- .../umbracoSettings.minimal.config | 114 +- .../Configurations/UmbracoSettings/web.config | 30 +- .../CoreXml/FrameworkXmlTests.cs | 478 +- .../CoreXml/NavigableNavigatorTests.cs | 2498 +- .../IO/AbstractFileSystemTests.cs | 390 +- .../IO/PhysicalFileSystemTests.cs | 146 +- src/Umbraco.Tests/Macros/MacroParserTests.cs | 872 +- src/Umbraco.Tests/Macros/MacroTests.cs | 234 +- .../Manifest/ManifestParserTests.cs | 662 +- .../Membership/MembershipProviderBaseTests.cs | 838 +- .../Migrations/AlterMigrationTests.cs | 276 +- .../Migrations/SqlScripts/MySqlTotal-480.sql | 1646 +- .../Migrations/SqlScripts/SqlCeTotal-480.sql | 1274 +- .../SqlScripts/SqlResources.Designer.cs | 336 +- .../Migrations/SqlScripts/SqlResources.resx | 264 +- .../SqlScripts/SqlServerTotal-480.sql | 3342 +-- .../Stubs/AlterUserTableMigrationStub.cs | 38 +- src/Umbraco.Tests/Migrations/Stubs/Dummy.cs | 32 +- .../Collections/PropertyCollectionTests.cs | 178 +- .../Models/ContentExtensionsTests.cs | 366 +- src/Umbraco.Tests/Models/ContentTests.cs | 1802 +- src/Umbraco.Tests/Models/ContentXmlTest.cs | 120 +- .../Mapping/ContentWebModelMappingTests.cs | 564 +- src/Umbraco.Tests/Models/MediaXmlTest.cs | 150 +- src/Umbraco.Tests/Models/StylesheetTests.cs | 238 +- .../Models/UserExtensionsTests.cs | 206 +- .../Packaging/PackageInstallationTest.cs | 148 +- .../Persistence/DatabaseContextTests.cs | 262 +- .../FaultHandling/ConnectionRetryTest.cs | 94 +- .../Persistence/Mappers/ContentMapperTest.cs | 80 +- .../Mappers/ContentTypeMapperTest.cs | 108 +- .../Mappers/DictionaryMapperTest.cs | 86 +- .../DictionaryTranslationMapperTest.cs | 86 +- .../Persistence/Mappers/LanguageMapperTest.cs | 84 +- .../Persistence/Mappers/MediaMapperTest.cs | 80 +- .../Mappers/PropertyGroupMapperTest.cs | 98 +- .../Mappers/PropertyTypeMapperTest.cs | 142 +- .../Persistence/Mappers/RelationMapperTest.cs | 120 +- .../Mappers/RelationTypeMapperTest.cs | 104 +- .../ContentTypeRepositorySqlClausesTest.cs | 332 +- ...aTypeDefinitionRepositorySqlClausesTest.cs | 88 +- .../Persistence/Querying/ExpressionTests.cs | 548 +- .../Querying/MediaRepositorySqlClausesTest.cs | 94 +- .../MediaTypeRepositorySqlClausesTest.cs | 88 +- .../Persistence/Querying/QueryBuilderTests.cs | 236 +- .../Repositories/ContentRepositoryTest.cs | 1834 +- .../Repositories/ContentTypeRepositoryTest.cs | 1942 +- .../DataTypeDefinitionRepositoryTest.cs | 812 +- .../Repositories/DictionaryRepositoryTest.cs | 796 +- .../Repositories/LanguageRepositoryTest.cs | 664 +- .../Repositories/MacroRepositoryTest.cs | 872 +- .../Repositories/MediaRepositoryTest.cs | 1178 +- .../Repositories/MediaTypeRepositoryTest.cs | 884 +- .../Repositories/MemberRepositoryTest.cs | 738 +- .../Repositories/MemberTypeRepositoryTest.cs | 604 +- .../Repositories/RelationRepositoryTest.cs | 602 +- .../RelationTypeRepositoryTest.cs | 486 +- .../Repositories/ScriptRepositoryTest.cs | 722 +- .../ServerRegistrationRepositoryTest.cs | 522 +- .../Repositories/StylesheetRepositoryTest.cs | 720 +- .../Repositories/TagRepositoryTest.cs | 1944 +- .../Repositories/UserRepositoryTest.cs | 772 +- .../Persistence/SchemaValidationTest.cs | 64 +- .../Persistence/SqlCeTableByTableTest.cs | 1106 +- src/Umbraco.Tests/Properties/AssemblyInfo.cs | 72 +- .../PropertyEditors/ColorListValidatorTest.cs | 86 +- .../EnsureUniqueValuesValidatorTest.cs | 120 +- .../PropertyEditorValueConverterTests.cs | 248 +- .../PropertyEditorValueEditorTests.cs | 384 +- .../PublishedContentDataTableTests.cs | 486 +- .../PublishedContentExtensionTests.cs | 206 +- .../PublishedContentMoreTests.cs | 580 +- .../PublishedContentTestBase.cs | 122 +- .../PublishedContent/PublishedContentTests.cs | 1288 +- .../PublishedContent/PublishedMediaTests.cs | 974 +- .../Publishing/PublishingStrategyTests.cs | 130 +- .../Routing/ContentFinderByAliasTests.cs | 286 +- .../ContentFinderByAliasWithDomainsTests.cs | 146 +- .../Routing/ContentFinderByIdTests.cs | 54 +- .../ContentFinderByPageIdQueryTests.cs | 70 +- .../Routing/DomainsAndCulturesTests.cs | 786 +- .../Routing/RenderRouteHandlerTests.cs | 386 +- .../Routing/RouteTestExtensions.cs | 116 +- .../Routing/SiteDomainHelperTests.cs | 748 +- .../Routing/UmbracoModuleTests.cs | 298 +- .../Routing/UrlsWithNestedDomains.cs | 346 +- .../Services/ContentServicePerformanceTest.cs | 588 +- .../Services/ContentServiceTests.cs | 5944 ++--- .../Services/ContentTypeServiceTests.cs | 3720 +-- .../Services/DataTypeServiceTests.cs | 158 +- .../Services/EntityServiceTests.cs | 1494 +- .../CheckboxList-Content-Package.xml | 152 +- .../Importing/ImportResources.Designer.cs | 766 +- .../Services/Importing/ImportResources.resx | 312 +- .../Importing/InheritedDocTypes-Package.xml | 888 +- .../Services/Importing/PackageImportTests.cs | 1488 +- .../Services/Importing/SingleDocType.xml | 66 +- .../Importing/StandardMvc-Package.xml | 4558 ++-- .../Importing/TemplateOnly-Package.xml | 178 +- .../TemplateOnly-Updated-Package.xml | 172 +- .../Services/Importing/XsltSearch-Package.xml | 502 +- .../Services/Importing/uBlogsy-Package.xml | 4320 ++-- .../Services/LocalizationServiceTests.cs | 864 +- .../Services/MacroServiceTests.cs | 552 +- .../Services/PackagingServiceTests.cs | 304 +- .../Services/PerformanceTests.cs | 732 +- .../Services/ThreadSafetyServiceTest.cs | 472 +- .../Services/UserServiceTests.cs | 1958 +- src/Umbraco.Tests/TeamCity.proj | 36 +- .../Templates/MasterPageHelperTests.cs | 42 +- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 138 +- src/Umbraco.Tests/TestHelpers/BaseWebTest.cs | 204 +- .../TestHelpers/Entities/MockedContent.cs | 320 +- .../Entities/MockedContentTypes.cs | 992 +- .../TestHelpers/Entities/MockedEntity.cs | 52 +- .../TestHelpers/Entities/MockedMedia.cs | 166 +- .../TestHelpers/Entities/MockedMember.cs | 164 +- .../TestHelpers/Entities/MockedUser.cs | 88 +- .../TestHelpers/FakeHttpContextFactory.cs | 242 +- .../TestHelpers/SettingsForTests.cs | 318 +- .../Stubs/TestControllerFactory.cs | 142 +- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 472 +- .../TreesAndSections/ApplicationTreeTest.cs | 794 +- .../TreesAndSections/SectionTests.cs | 506 +- src/Umbraco.Tests/UI/LegacyDialogTests.cs | 72 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1206 +- .../UmbracoExamine/EventsTest.cs | 92 +- .../UmbracoExamine/ExamineBaseTest.cs | 76 +- .../UmbracoExamine/IndexInitializer.cs | 438 +- src/Umbraco.Tests/UmbracoExamine/IndexTest.cs | 472 +- .../UmbracoExamine/SearchTests.cs | 234 +- .../UmbracoExamine/TestDataService.cs | 64 +- .../UmbracoExamine/TestFiles.resx | 258 +- .../UmbracoExamine/TestFiles/media.xml | 100 +- .../TestFiles/umbraco-sort.config | 258 +- .../UmbracoExamine/TestFiles/umbraco.config | 1234 +- .../Web/Mvc/UmbracoViewPageTests.cs | 930 +- src/Umbraco.Tests/unit-test-log4net.config | 82 +- .../docs/src/api/index.ngdoc | 118 +- .../bootstrap/css/bootstrap-responsive.css | 2116 +- .../css/bootstrap-responsive.min.css | 18 +- .../lib/bootstrap/css/bootstrap.cosmo.min.css | 40 +- .../lib/bootstrap/css/bootstrap.css | 16 +- .../lib/bootstrap/css/bootstrap.min.css | 18 +- .../lib/bootstrap/css/responsive.css | 16 +- .../lib/bootstrap/less/accordion.less | 68 +- .../lib/bootstrap/less/alerts.less | 158 +- .../lib/bootstrap/less/bootstrap.less | 126 +- .../lib/bootstrap/less/breadcrumbs.less | 48 +- .../lib/bootstrap/less/button-groups.less | 458 +- .../lib/bootstrap/less/buttons.less | 456 +- .../lib/bootstrap/less/carousel.less | 316 +- .../lib/bootstrap/less/close.less | 62 +- .../lib/bootstrap/less/code.less | 120 +- .../bootstrap/less/component-animations.less | 44 +- .../lib/bootstrap/less/dropdowns.less | 474 +- .../lib/bootstrap/less/forms.less | 1380 +- .../lib/bootstrap/less/grid.less | 42 +- .../lib/bootstrap/less/hero-unit.less | 50 +- .../lib/bootstrap/less/labels-badges.less | 168 +- .../lib/bootstrap/less/layouts.less | 30 +- .../lib/bootstrap/less/media.less | 110 +- .../lib/bootstrap/less/mixins.less | 1404 +- .../lib/bootstrap/less/modals.less | 190 +- .../lib/bootstrap/less/navbar.less | 994 +- .../lib/bootstrap/less/navs.less | 818 +- .../lib/bootstrap/less/pager.less | 84 +- .../lib/bootstrap/less/pagination.less | 246 +- .../lib/bootstrap/less/popovers.less | 266 +- .../lib/bootstrap/less/progress-bars.less | 244 +- .../lib/bootstrap/less/reset.less | 432 +- .../bootstrap/less/responsive-1200px-min.less | 56 +- .../bootstrap/less/responsive-767px-max.less | 386 +- .../less/responsive-768px-979px.less | 38 +- .../lib/bootstrap/less/responsive-navbar.less | 378 +- .../bootstrap/less/responsive-utilities.less | 118 +- .../lib/bootstrap/less/responsive.less | 96 +- .../lib/bootstrap/less/scaffolding.less | 106 +- .../lib/bootstrap/less/sprites.less | 394 +- .../lib/bootstrap/less/tables.less | 488 +- .../lib/bootstrap/less/tests/buttons.html | 278 +- .../lib/bootstrap/less/tests/css-tests.css | 300 +- .../lib/bootstrap/less/tests/css-tests.html | 2798 +-- .../less/tests/forms-responsive.html | 142 +- .../lib/bootstrap/less/tests/forms.html | 358 +- .../less/tests/navbar-fixed-top.html | 208 +- .../less/tests/navbar-static-top.html | 214 +- .../lib/bootstrap/less/tests/navbar.html | 214 +- .../lib/bootstrap/less/thumbnails.less | 116 +- .../lib/bootstrap/less/tooltip.less | 140 +- .../lib/bootstrap/less/type.less | 494 +- .../lib/bootstrap/less/utilities.less | 60 +- .../lib/bootstrap/less/variables.less | 602 +- .../lib/bootstrap/less/wells.less | 58 +- .../lib/markdown/markdown.converter.js | 2686 +- .../lib/markdown/markdown.css | 160 +- .../lib/markdown/markdown.editor.js | 4218 ++-- .../lib/markdown/markdown.sanitizer.js | 222 +- .../lib/markdown/red.css | 10 +- .../lib/tinymce/skins/umbraco/content.min.css | 196 +- .../lib/umbraco/Extensions.js | 684 +- .../lib/umbraco/LegacySpeechBubble.js | 152 +- .../lib/umbraco/LegacyUmbClientMgr.js | 826 +- .../lib/umbraco/NamespaceManager.js | 32 +- .../lib/umbraco/compat.js | 92 +- src/Umbraco.Web.UI.Client/src/app.js | 186 +- .../assets/fonts/helveticons/helveticons.svg | 12350 ++++----- .../src/assets/img/applicationIcons/help.svg | 44 +- .../applicationIcons/hlvticons-umbraco.svg | 174 +- .../src/common/directives/_module.js | 6 +- .../_obsolete/umbItemSorter.directive.js | 138 +- .../_obsolete/umbcontentname.directive.js | 184 +- .../_obsolete/umblogin.directive.js | 38 +- .../_obsolete/umbphotofolder.directive.js | 140 +- .../application/umbcontextmenu.directive.js | 44 +- .../application/umbnavigation.directive.js | 28 +- .../application/umbsections.directive.js | 292 +- .../html/umbcontrolgroup.directive.js | 110 +- .../components/html/umbpanel.directive.js | 28 +- .../umbnotifications.directive.js | 72 +- .../components/tree/umbtree.directive.js | 736 +- .../components/tree/umbtreeitem.directive.js | 412 +- .../components/umbconfirm.directive.js | 146 +- .../components/umbtable.directive.js | 372 +- .../upload/umbfileupload.directive.js | 46 +- .../common/mocks/editors/prevalues.mocks.js | 160 +- .../src/common/mocks/resources/_utils.js | 710 +- .../common/mocks/resources/content.mocks.js | 342 +- .../mocks/resources/contenttype.mocks.js | 60 +- .../common/mocks/resources/dashboard.mocks.js | 46 +- .../common/mocks/resources/datatype.mocks.js | 226 +- .../common/mocks/resources/entity.mocks.js | 176 +- .../src/common/mocks/resources/macro.mocks.js | 62 +- .../src/common/mocks/resources/media.mocks.js | 162 +- .../mocks/resources/section.resource.js | 76 +- .../src/common/mocks/resources/tree.mocks.js | 482 +- .../src/common/mocks/resources/user.mocks.js | 174 +- .../mocks/services/localization.mocks.js | 1506 +- .../src/common/mocks/services/util.mocks.js | 42 +- .../src/common/mocks/umbraco.httpbackend.js | 62 +- .../common/mocks/umbraco.servervariables.js | 82 +- .../src/common/resources/auth.resource.js | 860 +- .../src/common/resources/content.resource.js | 1536 +- .../common/resources/contenttype.resource.js | 628 +- .../src/common/resources/datatype.resource.js | 740 +- .../src/common/resources/entity.resource.js | 1110 +- .../src/common/resources/legacy.resource.js | 56 +- .../src/common/resources/log.resource.js | 340 +- .../src/common/resources/macro.resource.js | 166 +- .../src/common/resources/media.resource.js | 1068 +- .../common/resources/mediatype.resource.js | 528 +- .../src/common/resources/member.resource.js | 496 +- .../common/resources/membertype.resource.js | 220 +- .../src/common/resources/section.resource.js | 74 +- .../src/common/resources/tree.resource.js | 182 +- .../src/common/services/_module.js | 2 +- .../common/services/angularhelper.service.js | 354 +- .../src/common/services/assets.service.js | 594 +- .../services/contenteditinghelper.service.js | 1262 +- .../src/common/services/dialog.service.js | 1078 +- .../src/common/services/events.service.js | 86 +- .../common/services/filemanager.service.js | 128 +- .../src/common/services/formhelper.service.js | 376 +- .../src/common/services/history.service.js | 260 +- .../common/services/localization.service.js | 602 +- .../src/common/services/macro.service.js | 414 +- .../common/services/menuactions.service.js | 210 +- .../common/services/notifications.service.js | 598 +- .../src/common/services/search.service.js | 306 +- .../services/servervalidationmgr.service.js | 764 +- .../src/common/services/tinymce.service.js | 1632 +- .../src/common/services/tree.service.js | 1634 +- .../services/umbrequesthelper.service.js | 860 +- .../src/common/services/user.service.js | 564 +- .../src/common/services/util.service.js | 594 +- .../src/controllers/search.controller.js | 314 +- src/Umbraco.Web.UI.Client/src/index.html | 54 +- .../src/less/alerts.less | 200 +- src/Umbraco.Web.UI.Client/src/less/belle.less | 378 +- .../src/less/buttons.less | 632 +- .../src/less/components/umb-breadcrumbs.less | 86 +- .../src/less/components/umb-grid.less | 2206 +- .../src/less/components/umb-locked-field.less | 98 +- .../src/less/components/umb-tabs.less | 148 +- .../src/less/dragdrop.less | 72 +- src/Umbraco.Web.UI.Client/src/less/fonts.less | 290 +- .../src/less/footer.less | 4 +- src/Umbraco.Web.UI.Client/src/less/forms.less | 1664 +- .../src/less/gridview.less | 1794 +- src/Umbraco.Web.UI.Client/src/less/hacks.less | 438 +- .../src/less/helveticons.less | 3744 +-- .../src/less/listview.less | 528 +- src/Umbraco.Web.UI.Client/src/less/main.less | 1288 +- .../src/less/mixins.less | 1422 +- .../src/less/modals.less | 392 +- src/Umbraco.Web.UI.Client/src/less/navs.less | 858 +- src/Umbraco.Web.UI.Client/src/less/panel.less | 924 +- .../src/less/property-editors.less | 1790 +- .../src/less/sections.less | 230 +- .../src/less/tables.less | 502 +- src/Umbraco.Web.UI.Client/src/less/tree.less | 1102 +- .../src/less/variables.less | 896 +- .../src/views/common/dashboard.controller.js | 88 +- .../src/views/common/dashboard.html | 84 +- .../views/common/dialogs/login.controller.js | 824 +- .../src/views/common/dialogs/login.html | 506 +- .../views/common/dialogs/ysod.controller.js | 44 +- .../src/views/common/dialogs/ysod.html | 54 +- .../src/views/common/legacy.controller.js | 66 +- .../src/views/common/legacy.html | 4 +- .../src/views/common/login.controller.js | 48 +- .../src/views/common/login.html | 4 +- .../views/common/overlays/embed/embed.html | 54 +- .../overlays/macropicker/macropicker.html | 126 +- .../mediaPicker/mediapicker.controller.js | 782 +- .../overlays/mediaPicker/mediapicker.html | 300 +- .../membergrouppicker.controller.js | 128 +- .../membergrouppicker/membergrouppicker.html | 24 +- .../treepicker/treepicker.controller.js | 1232 +- .../overlays/treepicker/treepicker.html | 120 +- .../common/overlays/ysod/ysod.controller.js | 66 +- .../src/views/common/overlays/ysod/ysod.html | 54 +- .../application/umb-contextmenu.html | 30 +- .../application/umb-navigation.html | 98 +- .../components/application/umb-sections.html | 66 +- .../components/html/umb-control-group.html | 26 +- .../src/views/components/html/umb-pane.html | 4 +- .../src/views/components/html/umb-panel.html | 6 +- .../notifications/umb-notifications.html | 42 +- .../src/views/components/umb-confirm.html | 20 +- .../src/views/components/umb-table.html | 140 +- .../content/content.create.controller.js | 118 +- .../content/content.delete.controller.js | 134 +- .../content.emptyrecyclebin.controller.js | 76 +- .../src/views/content/copy.html | 184 +- .../src/views/content/create.html | 108 +- .../src/views/content/delete.html | 30 +- .../src/views/content/emptyrecyclebin.html | 34 +- .../src/views/content/move.html | 164 +- .../src/views/content/recyclebin.html | 36 +- .../src/views/dashboard/ChangePassword.html | 36 +- .../dashboard/dashboard.tabs.controller.js | 724 +- .../default/StartupDashboardIntro.html | 140 +- .../default/StartupDashboardVideos.html | 36 +- .../developer/developerdashboardvideos.html | 32 +- .../dashboard/media/mediadashboardvideos.html | 34 +- .../settings/settingsdashboardvideos.html | 34 +- .../datatypes/datatype.edit.controller.js | 400 +- .../src/views/datatypes/edit.html | 156 +- .../_obsolete/umb-content-name.html | 36 +- .../directives/_obsolete/umb-header.html | 28 +- .../directives/_obsolete/umb-item-sorter.html | 76 +- .../views/directives/_obsolete/umb-login.html | 24 +- .../directives/_obsolete/umb-tab-view.html | 10 +- .../_obsolete/umb-upload-dropzone.html | 14 +- .../views/documenttypes/edit.controller.js | 834 +- .../documenttypes/views/design/design.html | 8 +- .../src/views/media/create.html | 94 +- .../src/views/media/edit.html | 162 +- .../views/media/media.create.controller.js | 42 +- .../src/views/media/media.edit.controller.js | 498 +- .../src/views/media/move.html | 112 +- .../src/views/mediatypes/edit.controller.js | 762 +- .../src/views/member/create.html | 50 +- .../src/views/member/delete.html | 24 +- .../src/views/member/edit.html | 154 +- .../views/member/member.create.controller.js | 32 +- .../views/member/member.delete.controller.js | 72 +- .../views/member/member.edit.controller.js | 384 +- .../prevalueeditors/multivalues.controller.js | 188 +- .../views/prevalueeditors/multivalues.html | 42 +- .../src/views/prevalueeditors/number.html | 20 +- .../prevalueeditors/readonlykeyvalues.html | 12 +- .../views/prevalueeditors/requiredfield.html | 22 +- .../prevalueeditors/treepicker.controller.js | 268 +- .../src/views/prevalueeditors/treepicker.html | 66 +- .../boolean/boolean.controller.js | 78 +- .../propertyeditors/boolean/boolean.html | 10 +- .../changepassword.controller.js | 128 +- .../changepassword/changepassword.html | 12 +- .../checkboxlist/checkboxlist.controller.js | 128 +- .../checkboxlist/checkboxlist.html | 28 +- .../colorpicker/colorpicker.html | 32 +- .../contentpicker/contentpicker.controller.js | 742 +- .../contentpicker/contentpicker.html | 174 +- .../datepicker/datepicker.controller.js | 390 +- .../datepicker/datepicker.html | 64 +- .../dropdown/dropdown.controller.js | 140 +- .../propertyeditors/dropdown/dropdown.html | 38 +- .../views/propertyeditors/email/email.html | 32 +- .../entitypicker/entitypicker.controller.js | 82 +- .../entitypicker/entitypicker.html | 38 +- .../fileupload/fileupload.controller.js | 360 +- .../fileupload/fileupload.html | 72 +- .../folderbrowser/folderbrowser.html | 6 +- .../propertyeditors/integer/integer.html | 32 +- .../listview/listview.controller.js | 1326 +- .../propertyeditors/listview/listview.html | 396 +- .../listview/orderDirection.prevalues.html | 28 +- .../markdowneditor.controller.js | 162 +- .../markdowneditor/markdowneditor.html | 30 +- .../mediapicker/mediapicker.controller.js | 456 +- .../mediapicker/mediapicker.html | 104 +- .../membergrouppicker.controller.js | 166 +- .../membergrouppicker/membergrouppicker.html | 56 +- .../membergroups/membergroups.controller.js | 70 +- .../membergroups/membergroups.html | 36 +- .../memberpicker/memberpicker.controller.js | 238 +- .../memberpicker/memberpicker.html | 56 +- .../multipletextbox.controller.js | 204 +- .../multipletextbox/multipletextbox.html | 42 +- .../radiobuttons/radiobuttons.html | 22 +- .../readonlyvalue/readonlyvalue.controller.js | 94 +- .../readonlyvalue/readonlyvalue.html | 4 +- .../relatedlinks/relatedlinks.controller.js | 462 +- .../relatedlinks/relatedlinks.html | 194 +- .../propertyeditors/rte/rte.controller.js | 788 +- .../src/views/propertyeditors/rte/rte.html | 70 +- .../slider/orientation.prevalues.html | 20 +- .../slider/slider.controller.js | 434 +- .../views/propertyeditors/slider/slider.html | 8 +- .../propertyeditors/tags/tags.controller.js | 386 +- .../src/views/propertyeditors/tags/tags.html | 52 +- .../templatepicker/templatepicker.html | 4 +- .../propertyeditors/textarea/textarea.html | 26 +- .../propertyeditors/textbox/textbox.html | 40 +- .../urllist/urllist.controller.js | 88 +- .../propertyeditors/urllist/urllist.html | 20 +- .../validationtest/validationtest.html | 32 +- src/Umbraco.Web.UI.Client/src/web.config | 14 +- .../test/config/app.unit.js | 20 +- src/Umbraco.Web.UI.Client/test/config/e2e.js | 106 +- .../test/config/karma.conf.js | 198 +- .../app/admin/users/users-edit.scenario.js | 44 +- .../test/e2e/index.scenario.js | 18 +- .../test/lib/angular/angular-mocks.js | 3770 +-- .../content/edit-content-controller.spec.js | 148 +- .../content-picker-controller.spec.js | 218 +- .../dropdown-controller.spec.js | 178 +- .../common/services/assets-service.spec.js | 60 +- .../services/content-editing-helper.spec.js | 470 +- .../common/services/content-factory.spec.js | 130 +- .../services/content-type-factory.spec.js | 76 +- .../unit/common/services/file-manager.spec.js | 64 +- .../unit/common/services/icon-helper.spec.js | 78 +- .../common/services/keyboard-service.spec.js | 100 +- .../common/services/macro-service.spec.js | 338 +- .../common/services/notifications.spec.js | 66 +- .../server-validation-manager.spec.js | 548 +- .../unit/common/services/tree-service.spec.js | 788 +- .../common/services/umb-image-helper.spec.js | 52 +- .../services/umb-request-helper.spec.js | 70 +- src/Umbraco.Web.UI/App_Browsers/Form.browser | 18 +- .../App_Browsers/w3cvalidator.browser | 50 +- src/Umbraco.Web.UI/Global.asax | 2 +- src/Umbraco.Web.UI/Properties/AssemblyInfo.cs | 26 +- .../Properties/Settings.Designer.cs | 70 +- .../Properties/Settings.settings | 16 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1336 +- src/Umbraco.Web.UI/Umbraco/Create.aspx.cs | 80 +- .../Umbraco/Create.aspx.designer.cs | 50 +- src/Umbraco.Web.UI/Umbraco/Logout.aspx | 84 +- .../Templates/Breadcrumb.cshtml | 48 +- .../Templates/EditProfile.cshtml | 130 +- .../Templates/Gallery.cshtml | 92 +- .../ListAncestorsFromCurrentPage.cshtml | 50 +- .../ListChildPagesFromChangeableSource.cshtml | 64 +- .../ListChildPagesFromCurrentPage.cshtml | 48 +- .../ListChildPagesOrderedByDate.cshtml | 46 +- .../ListChildPagesOrderedByName.cshtml | 44 +- .../ListChildPagesOrderedByProperty.cshtml | 60 +- .../ListChildPagesWithDoctype.cshtml | 46 +- .../ListDescendantsFromCurrentPage.cshtml | 136 +- .../ListImagesFromMediaFolder.cshtml | 66 +- .../PartialViewMacros/Templates/Login.cshtml | 80 +- .../Templates/LoginStatus.cshtml | 84 +- .../Templates/MultinodeTree-picker.cshtml | 48 +- .../Templates/Navigation.cshtml | 44 +- .../Templates/RegisterMember.cshtml | 206 +- .../Templates/SiteMap.cshtml | 86 +- src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs | 42 +- .../Umbraco/TreeInit.aspx.designer.cs | 138 +- .../Umbraco/Views/Default.cshtml | 278 +- .../Umbraco/config/create/UI.Release.xml | 458 +- .../Umbraco/config/create/UI.xml | 476 +- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 2926 +-- src/Umbraco.Web.UI/Umbraco/config/lang/de.xml | 2064 +- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 4066 +-- .../Umbraco/config/lang/en_us.xml | 4328 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/es.xml | 3744 +-- src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml | 2856 +-- src/Umbraco.Web.UI/Umbraco/config/lang/he.xml | 1864 +- src/Umbraco.Web.UI/Umbraco/config/lang/it.xml | 1852 +- src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml | 2494 +- src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml | 1828 +- src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml | 2874 +-- src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml | 3386 +-- src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml | 1796 +- src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml | 3544 +-- src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml | 1906 +- src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml | 2850 +-- .../Umbraco/controls/PasswordChanger.ascx.cs | 56 +- .../controls/PasswordChanger.ascx.designer.cs | 138 +- .../Umbraco/controls/Tree/TreeControl.ascx | 112 +- .../Umbraco/controls/passwordChanger.ascx | 266 +- src/Umbraco.Web.UI/Umbraco/create.aspx | 84 +- src/Umbraco.Web.UI/Umbraco/create/simple.ascx | 44 +- .../Umbraco/dashboard/FeedProxy.aspx | 4 +- .../Umbraco/dashboard/UserControlProxy.aspx | 62 +- .../dashboard/UserControlProxy.aspx.cs | 80 +- .../UserControlProxy.aspx.designer.cs | 228 +- .../developer/Macros/EditMacro.aspx.cs | 684 +- .../Macros/EditMacro.aspx.designer.cs | 432 +- .../Umbraco/developer/Macros/editMacro.aspx | 378 +- .../Packages/DirectoryBrowser.aspx.cs | 280 +- .../DirectoryBrowser.aspx.designer.cs | 84 +- .../developer/Packages/directoryBrowser.aspx | 52 +- .../developer/Packages/editPackage.aspx | 464 +- .../RelationTypes/EditRelationType.aspx | 290 +- .../RelationTypes/NewRelationType.aspx | 98 +- .../RelationTypesWebService.asmx | 2 +- .../TreeMenu/ActionDeleteRelationType.js | 42 +- .../TreeMenu/ActionNewRelationType.js | 4 +- .../Umbraco/dialogs/AssignDomain2.aspx | 156 +- .../Umbraco/dialogs/ChangeDocType.aspx | 240 +- .../Umbraco/dialogs/ChangeDocType.aspx.cs | 690 +- .../dialogs/ChangeDocType.aspx.designer.cs | 426 +- .../Umbraco/dialogs/Preview.aspx | 36 +- .../Umbraco/dialogs/SendPublish.aspx | 26 +- .../Umbraco/dialogs/Sort.aspx.cs | 22 +- .../Umbraco/dialogs/Sort.aspx.designer.cs | 66 +- src/Umbraco.Web.UI/Umbraco/dialogs/about.aspx | 66 +- .../Umbraco/dialogs/create.aspx | 134 +- src/Umbraco.Web.UI/Umbraco/dialogs/cruds.aspx | 48 +- src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm | 18 +- .../Umbraco/dialogs/exportDocumenttype.aspx | 2 +- .../Umbraco/dialogs/importDocumenttype.aspx | 98 +- .../Umbraco/dialogs/insertMacro.aspx | 204 +- .../dialogs/insertMasterpageContent.aspx | 62 +- .../dialogs/insertMasterpagePlaceholder.aspx | 70 +- .../Umbraco/dialogs/mediaPicker.aspx | 194 +- .../Umbraco/dialogs/notifications.aspx | 34 +- .../Umbraco/dialogs/protectPage.aspx | 396 +- .../Umbraco/dialogs/republish.aspx | 80 +- .../Umbraco/dialogs/rollBack.aspx | 196 +- .../Umbraco/dialogs/sendToTranslation.aspx | 72 +- src/Umbraco.Web.UI/Umbraco/dialogs/sort.aspx | 170 +- .../Umbraco/dialogs/treePicker.aspx | 44 +- .../Umbraco/dialogs/umbracoField.aspx | 282 +- .../Umbraco/dialogs/uploadImage.aspx | 56 +- .../Umbraco/dialogs/viewAuditTrail.aspx | 146 +- src/Umbraco.Web.UI/Umbraco/endPreview.aspx | 6 +- .../Umbraco/js/UmbracoSpeechBubbleBackEnd.js | 134 +- .../Umbraco/js/dualSelectBox.js | 94 +- src/Umbraco.Web.UI/Umbraco/js/guiFunctions.js | 176 +- .../Umbraco/js/umbracoCheckKeys.js | 204 +- src/Umbraco.Web.UI/Umbraco/js/web.config | 14 +- .../Umbraco/masterpages/Default.Master.cs | 22 +- .../masterpages/Default.Master.designer.cs | 30 +- .../masterpages/UmbracoDialog.master.cs | 22 +- .../UmbracoDialog.master.designer.cs | 48 +- .../Umbraco/masterpages/UmbracoPage.master.cs | 22 +- .../UmbracoPage.master.designer.cs | 48 +- .../Umbraco/masterpages/default.Master | 4 +- .../Umbraco/masterpages/umbracoDialog.Master | 76 +- .../Umbraco/masterpages/umbracoPage.Master | 86 +- src/Umbraco.Web.UI/Umbraco/ping.aspx | 2 +- .../Umbraco/settings/DictionaryItemList.aspx | 30 +- .../Umbraco/settings/EditDictionaryItem.aspx | 28 +- .../Umbraco/translation/default.aspx | 110 +- .../Umbraco/translation/details.aspx | 86 +- .../Umbraco/translation/preview.aspx | 78 +- .../Umbraco/translation/translationTasks.dtd | 78 +- .../Umbraco/translation/xml.aspx | 2 +- src/Umbraco.Web.UI/Umbraco/treeInit.aspx | 94 +- src/Umbraco.Web.UI/Umbraco/umbraco.aspx | 4 +- src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs | 38 +- .../Umbraco/umbraco.aspx.designer.cs | 30 +- .../Umbraco/webservices/CheckForUpgrade.asmx | 2 +- .../Umbraco/webservices/Developer.asmx | 2 +- .../Umbraco/webservices/ajax.js | 1610 +- .../Umbraco/webservices/codeEditorSave.asmx | 2 +- .../Umbraco/webservices/legacyAjaxCalls.asmx | 2 +- .../Umbraco/webservices/nodeSorter.asmx | 2 +- .../Umbraco/webservices/templates.asmx | 2 +- src/Umbraco.Web.UI/Views/Web.config | 94 +- .../config/404handlers.Release.config | 12 +- src/Umbraco.Web.UI/config/404handlers.config | 12 +- .../config/ClientDependency.Release.config | 132 +- .../config/Dashboard.Release.config | 240 +- src/Umbraco.Web.UI/config/Dashboard.config | 252 +- .../config/EmbeddedMedia.Release.config | 238 +- .../config/EmbeddedMedia.config | 238 +- .../config/ExamineIndex.Release.config | 54 +- src/Umbraco.Web.UI/config/ExamineIndex.config | 58 +- .../config/ExamineSettings.Release.config | 82 +- .../config/ExamineSettings.config | 82 +- .../config/FileSystemProviders.Release.config | 22 +- .../config/FileSystemProviders.config | 22 +- .../config/applications.Release.config | 20 +- src/Umbraco.Web.UI/config/applications.config | 20 +- .../config/feedProxy.Release.config | 22 +- src/Umbraco.Web.UI/config/feedProxy.config | 22 +- .../config/log4net.Release.config | 98 +- src/Umbraco.Web.UI/config/log4net.config | 96 +- .../config/metablogConfig.Release.config | 4 +- .../config/metablogConfig.config | 36 +- .../config/scripting.Release.config | 20 +- src/Umbraco.Web.UI/config/scripting.config | 20 +- .../config/splashes/NoNodes.aspx.cs | 50 +- .../config/splashes/NoNodes.aspx.designer.cs | 48 +- .../config/splashes/booting.aspx | 58 +- .../config/splashes/noNodes.aspx | 122 +- .../config/tinyMceConfig.Release.config | 546 +- .../config/tinyMceConfig.config | 548 +- .../config/trees.Release.config | 72 +- src/Umbraco.Web.UI/config/trees.config | 80 +- .../config/umbracoSettings.Release.config | 246 +- .../config/umbracoSettings.config | 464 +- src/Umbraco.Web.UI/default.aspx | 4 +- .../umbraco_client/Application/Extensions.js | 804 +- .../JQuery/jquery-fieldselection.js | 166 +- .../Application/JQuery/jquery.cookie.js | 190 +- .../Application/JQuery/jquery.hotkeys.js | 196 +- .../Application/JQuery/jquery.metadata.min.js | 24 +- .../Application/JQuery/jquery.validate.min.js | 6 +- .../JQuery/jquery.validate.unobtrusive.min.js | 8 +- .../Application/NamespaceManager.js | 32 +- .../Application/UmbracoApplicationActions.js | 760 +- .../Application/UmbracoClientManager.js | 514 +- .../Application/UmbracoUtils.js | 20 +- .../umbraco_client/Dialogs/AssignDomain2.css | 40 +- .../umbraco_client/Dialogs/AssignDomain2.js | 290 +- .../umbraco_client/Dialogs/SortDialog.css | 116 +- .../umbraco_client/Dialogs/SortDialog.js | 244 +- .../umbraco_client/Dialogs/UmbracoField.js | 288 +- .../umbraco_client/menuicon/style.css | 66 +- .../modal/jquery.simplemodal.1.4.1.custom.js | 1274 +- .../umbraco_client/modal/modal.js | 802 +- .../umbraco_client/modal/style.css | 130 +- .../scrollingmenu/javascript.js | 208 +- .../umbraco_client/scrollingmenu/style.css | 38 +- .../splitbutton/InsertMacroSplitButton.js | 112 +- .../splitbutton/jquery.splitbutton.js | 1058 +- .../splitbutton/splitbutton.css | 312 +- .../tablesorting/jquery.tablesorter.min.js | 6 +- .../tablesorting/tableDragAndDrop.js | 788 +- src/Umbraco.Web.UI/umbraco_client/ui/base2.js | 3350 +-- .../umbraco_client/ui/default.css | 1524 +- .../umbraco_client/ui/default.js | 148 +- .../umbraco_client/ui/jquery.js | 8 +- .../umbraco_client/ui/jqueryui.js | 24 +- src/Umbraco.Web.UI/umbraco_client/ui/json2.js | 2 +- .../umbraco_client/ui/knockout.js | 172 +- .../umbraco_client/ui/knockout.mapping.js | 40 +- src/Umbraco.Web.UI/web.Template.Debug.config | 928 +- .../web.Template.Release.config | 30 +- src/Umbraco.Web.UI/web.Template.config | 628 +- .../Cache/ApplicationCacheRefresher.cs | 92 +- .../Cache/ApplicationTreeCacheRefresher.cs | 92 +- .../Cache/ContentTypeCacheRefresher.cs | 304 +- .../Cache/DataTypeCacheRefresher.cs | 228 +- .../Cache/DictionaryCacheRefresher.cs | 82 +- src/Umbraco.Web/Cache/DistributedCache.cs | 352 +- .../Cache/DistributedCacheExtensions.cs | 688 +- src/Umbraco.Web/Cache/DomainCacheRefresher.cs | 178 +- .../Cache/LanguageCacheRefresher.cs | 78 +- src/Umbraco.Web/Cache/MacroCacheRefresher.cs | 238 +- src/Umbraco.Web/Cache/MediaCacheRefresher.cs | 310 +- src/Umbraco.Web/Cache/MemberCacheRefresher.cs | 164 +- .../Cache/TemplateCacheRefresher.cs | 126 +- src/Umbraco.Web/Cache/UserCacheRefresher.cs | 102 +- src/Umbraco.Web/CacheHelperExtensions.cs | 124 +- .../Controllers/UmbLoginController.cs | 86 +- .../Controllers/UmbLoginStatusController.cs | 76 +- .../Controllers/UmbProfileController.cs | 94 +- .../Controllers/UmbRegisterController.cs | 166 +- .../Dictionary/UmbracoCultureDictionary.cs | 262 +- .../UmbracoCultureDictionaryFactory.cs | 32 +- .../Editors/AuthenticationController.cs | 1046 +- .../Editors/BackOfficeController.cs | 1282 +- src/Umbraco.Web/Editors/ContentController.cs | 2342 +- .../Editors/ContentControllerBase.cs | 310 +- .../Editors/ContentPostValidateAttribute.cs | 292 +- .../Editors/ContentTypeController.cs | 708 +- .../Editors/ContentTypeControllerBase.cs | 1012 +- .../Editors/DashboardController.cs | 260 +- src/Umbraco.Web/Editors/DashboardSecurity.cs | 242 +- src/Umbraco.Web/Editors/DataTypeController.cs | 770 +- .../Editors/DataTypeValidateAttribute.cs | 202 +- src/Umbraco.Web/Editors/EntityController.cs | 1876 +- src/Umbraco.Web/Editors/ImagesController.cs | 246 +- src/Umbraco.Web/Editors/LegacyController.cs | 118 +- src/Umbraco.Web/Editors/LogController.cs | 130 +- src/Umbraco.Web/Editors/MacroController.cs | 334 +- src/Umbraco.Web/Editors/MediaController.cs | 1788 +- .../Editors/MediaPostValidateAttribute.cs | 186 +- .../Editors/MediaTypeController.cs | 612 +- src/Umbraco.Web/Editors/MemberController.cs | 1664 +- .../Editors/MemberTypeController.cs | 328 +- src/Umbraco.Web/Editors/SectionController.cs | 154 +- .../Editors/StylesheetController.cs | 70 +- src/Umbraco.Web/Editors/TemplateController.cs | 394 +- .../UmbracoAuthorizedJsonController.cs | 34 +- .../Editors/UpdateCheckController.cs | 138 +- src/Umbraco.Web/ExamineExtensions.cs | 64 +- .../FormDataCollectionExtensions.cs | 170 +- src/Umbraco.Web/FormlessPage.cs | 40 +- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 1650 +- src/Umbraco.Web/HttpCookieExtensions.cs | 432 +- src/Umbraco.Web/HttpRequestExtensions.cs | 166 +- src/Umbraco.Web/HttpUrlHelperExtensions.cs | 304 +- .../Install/FilePermissionHelper.cs | 512 +- src/Umbraco.Web/Install/InstallHelper.cs | 380 +- .../Macros/PartialViewMacroController.cs | 80 +- .../Macros/PartialViewMacroEngine.cs | 252 +- .../Macros/PartialViewMacroPage.cs | 26 +- .../EmbedProviders/AbstractOEmbedProvider.cs | 164 +- .../Media/EmbedProviders/AbstractProvider.cs | 28 +- .../Media/EmbedProviders/OEmbedRich.cs | 12 +- .../Media/EmbedProviders/OEmbedVideo.cs | 30 +- .../EmbedProviders/Settings/Dictionary.cs | 28 +- .../Media/EmbedProviders/Settings/String.cs | 26 +- .../Media/EmbedProviders/Twitgoo.cs | 46 +- .../MembershipProviderExtensions.cs | 76 +- src/Umbraco.Web/ModelStateExtensions.cs | 244 +- .../Models/ChangingPasswordModel.cs | 106 +- .../Models/ContentEditing/AuditLog.cs | 60 +- .../Models/ContentEditing/ContentItemBasic.cs | 218 +- .../ContentEditing/ContentItemDisplay.cs | 132 +- .../ContentEditing/ContentItemDisplayBase.cs | 102 +- .../Models/ContentEditing/ContentItemDto.cs | 28 +- .../Models/ContentEditing/ContentItemSave.cs | 70 +- .../ContentEditing/ContentPropertyBasic.cs | 92 +- .../ContentEditing/ContentPropertyDisplay.cs | 84 +- .../ContentEditing/ContentPropertyDto.cs | 38 +- .../ContentEditing/ContentSaveAction.cs | 76 +- .../Models/ContentEditing/ContentSortOrder.cs | 70 +- .../Models/ContentEditing/ContentTypeBasic.cs | 238 +- .../ContentTypeCompositionDisplay.cs | 170 +- .../Models/ContentEditing/DashboardControl.cs | 56 +- .../Models/ContentEditing/DataTypeBasic.cs | 54 +- .../Models/ContentEditing/DataTypeDisplay.cs | 78 +- .../Models/ContentEditing/DataTypeSave.cs | 102 +- .../Models/ContentEditing/EntityBasic.cs | 146 +- .../Models/ContentEditing/IErrorModel.cs | 34 +- .../ContentEditing/IHaveUploadedFiles.cs | 20 +- .../ContentEditing/INotificationModel.cs | 28 +- .../Models/ContentEditing/MacroParameter.cs | 96 +- .../Models/ContentEditing/MediaItemDisplay.cs | 38 +- .../Models/ContentEditing/MemberDisplay.cs | 76 +- .../Models/ContentEditing/MemberSave.cs | 82 +- .../ContentEditing/MessagesExtensions.cs | 114 +- .../ContentEditing/ModelWithNotifications.cs | 62 +- .../Models/ContentEditing/MoveOrCopy.cs | 88 +- .../Models/ContentEditing/Notification.cs | 56 +- .../ContentEditing/PropertyEditorBasic.cs | 42 +- .../ContentEditing/RichTextEditorCommand.cs | 80 +- .../RichTextEditorConfiguration.cs | 56 +- .../ContentEditing/RichTextEditorPlugin.cs | 38 +- .../Models/ContentEditing/SearchResultItem.cs | 30 +- .../Models/ContentEditing/Section.cs | 54 +- .../Models/ContentEditing/StyleSheet.cs | 36 +- .../Models/ContentEditing/StylesheetRule.cs | 40 +- src/Umbraco.Web/Models/ContentEditing/Tab.cs | 54 +- .../ContentEditing/TabbedContentItem.cs | 100 +- .../ContentEditing/UmbracoEntityTypes.cs | 182 +- .../Models/ContentEditing/UserBasic.cs | 136 +- .../Models/ContentEditing/UserDetail.cs | 126 +- src/Umbraco.Web/Models/LoginModel.cs | 36 +- src/Umbraco.Web/Models/LoginStatusModel.cs | 140 +- .../AvailablePropertyEditorsResolver.cs | 56 +- .../Mapping/ContentPropertyBasicConverter.cs | 144 +- .../ContentPropertyDisplayConverter.cs | 144 +- .../Mapping/ContentPropertyDtoConverter.cs | 66 +- .../Models/Mapping/CreatorResolver.cs | 52 +- .../Models/Mapping/DatabaseTypeResolver.cs | 48 +- .../Models/Mapping/OwnerResolver.cs | 56 +- .../Mapping/PropertyTypeGroupResolver.cs | 460 +- .../Mapping/TabsAndPropertiesResolver.cs | 630 +- .../Models/PartialViewMacroModel.cs | 100 +- .../Models/PasswordChangedModel.cs | 40 +- src/Umbraco.Web/Models/ProfileModel.cs | 216 +- .../Models/PublishedContentBase.cs | 312 +- src/Umbraco.Web/Models/RegisterModel.cs | 206 +- src/Umbraco.Web/Models/TagModel.cs | 50 +- .../Models/Trees/ActionMenuItem.cs | 84 +- .../Models/Trees/ActionMenuItemAttribute.cs | 78 +- .../Models/Trees/CreateChildEntity.cs | 20 +- src/Umbraco.Web/Models/Trees/MenuItem.cs | 466 +- .../Models/Trees/MenuItemCollection.cs | 88 +- src/Umbraco.Web/Models/Trees/MenuItemList.cs | 362 +- src/Umbraco.Web/Models/Trees/RefreshNode.cs | 20 +- .../Models/Trees/SectionRootNode.cs | 110 +- src/Umbraco.Web/Models/Trees/TreeNode.cs | 250 +- .../Models/Trees/TreeNodeCollection.cs | 20 +- .../Models/Trees/TreeNodeExtensions.cs | 156 +- src/Umbraco.Web/Models/UmbracoProperty.cs | 82 +- .../Models/UpgradeCheckResponse.cs | 64 +- .../Mvc/AreaRegistrationExtensions.cs | 264 +- src/Umbraco.Web/Mvc/BackOfficeArea.cs | 118 +- src/Umbraco.Web/Mvc/Constants.cs | 24 +- src/Umbraco.Web/Mvc/ControllerExtensions.cs | 412 +- .../Mvc/ControllerFactoryExtensions.cs | 76 +- src/Umbraco.Web/Mvc/HtmlTagWrapper.cs | 424 +- src/Umbraco.Web/Mvc/HtmlTagWrapperTextNode.cs | 36 +- .../Mvc/IFilteredControllerFactory.cs | 40 +- src/Umbraco.Web/Mvc/IHtmlTagWrapper.cs | 18 +- src/Umbraco.Web/Mvc/IRenderMvcController.cs | 44 +- .../Mvc/MasterControllerFactory.cs | 226 +- .../Mvc/MemberAuthorizeAttribute.cs | 192 +- .../MergeModelStateToChildActionAttribute.cs | 84 +- .../MergeParentContextViewDataAttribute.cs | 112 +- src/Umbraco.Web/Mvc/NotChildAction.cs | 40 +- src/Umbraco.Web/Mvc/PluginController.cs | 244 +- src/Umbraco.Web/Mvc/PluginControllerArea.cs | 196 +- .../Mvc/PluginControllerAttribute.cs | 52 +- .../Mvc/PluginControllerMetadata.cs | 42 +- src/Umbraco.Web/Mvc/PluginViewEngine.cs | 212 +- src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs | 24 +- src/Umbraco.Web/Mvc/ProfilingView.cs | 62 +- src/Umbraco.Web/Mvc/ProfilingViewEngine.cs | 102 +- .../Mvc/QueryStringFilterAttribute.cs | 116 +- .../Mvc/RedirectToUmbracoPageResult.cs | 462 +- src/Umbraco.Web/Mvc/RenderActionInvoker.cs | 84 +- .../Mvc/RenderControllerFactory.cs | 98 +- src/Umbraco.Web/Mvc/RenderMvcController.cs | 186 +- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 914 +- src/Umbraco.Web/Mvc/RenderViewEngine.cs | 200 +- src/Umbraco.Web/Mvc/RouteDefinition.cs | 62 +- .../Mvc/RouteValueDictionaryExtensions.cs | 82 +- src/Umbraco.Web/Mvc/Strings.Designer.cs | 156 +- src/Umbraco.Web/Mvc/Strings.resx | 246 +- src/Umbraco.Web/Mvc/SurfaceController.cs | 348 +- src/Umbraco.Web/Mvc/SurfaceRouteHandler.cs | 32 +- .../Mvc/UmbracoAuthorizeAttribute.cs | 218 +- .../Mvc/UmbracoAuthorizedController.cs | 28 +- src/Umbraco.Web/Mvc/UmbracoController.cs | 184 +- .../Mvc/UmbracoControllerFactory.cs | 170 +- src/Umbraco.Web/Mvc/UmbracoMvcHandler.cs | 64 +- src/Umbraco.Web/Mvc/UmbracoPageResult.cs | 362 +- src/Umbraco.Web/Mvc/UmbracoViewPage.cs | 14 +- .../Mvc/UmbracoViewPageOfTModel.cs | 498 +- src/Umbraco.Web/Mvc/UrlHelperExtensions.cs | 224 +- src/Umbraco.Web/Mvc/ViewContextExtensions.cs | 106 +- .../Mvc/ViewDataContainerExtensions.cs | 98 +- .../Mvc/ViewDataDictionaryExtensions.cs | 36 +- src/Umbraco.Web/Properties/AssemblyInfo.cs | 78 +- src/Umbraco.Web/Properties/Settings.settings | 34 +- .../Properties/Settings1.Designer.cs | 128 +- .../CheckBoxListPropertyEditor.cs | 72 +- .../ColorPickerPropertyEditor.cs | 34 +- .../PropertyEditors/DatePropertyEditor.cs | 38 +- .../PropertyEditors/DateTimePropertyEditor.cs | 64 +- .../PropertyEditors/DateTimeValidator.cs | 72 +- .../DropDownMultiplePropertyEditor.cs | 60 +- .../DropDownMultipleWithKeysPropertyEditor.cs | 74 +- .../PropertyEditors/DropDownPropertyEditor.cs | 74 +- .../DropDownWithKeysPropertyEditor.cs | 72 +- .../EmailAddressPropertyEditor.cs | 62 +- .../FileUploadPropertyEditor.cs | 326 +- .../FileUploadPropertyValueEditor.cs | 266 +- .../PropertyEditors/IntegerPropertyEditor.cs | 58 +- .../PropertyEditors/ListViewPropertyEditor.cs | 50 +- .../MacroContainerPropertyEditor.cs | 34 +- .../PropertyEditors/MarkdownPropertyEditor.cs | 46 +- .../MemberGroupPickerPropertyEditor.cs | 28 +- .../ContentTypeParameterEditor.cs | 46 +- .../MultipleContentTypeParameterEditor.cs | 34 +- .../MultiplePropertyGroupParameterEditor.cs | 38 +- .../MultiplePropertyTypeParameterEditor.cs | 38 +- .../PropertyGroupParameterEditor.cs | 38 +- .../PropertyTypeParameterEditor.cs | 38 +- .../PublishValueValueEditor.cs | 114 +- .../PublishValuesMultipleValueEditor.cs | 204 +- .../RadioButtonsPropertyEditor.cs | 52 +- .../RichTextPreValueController.cs | 264 +- .../PropertyEditors/RichTextPropertyEditor.cs | 190 +- .../PropertyEditors/RteEmbedController.cs | 156 +- .../PropertyEditors/SliderPropertyEditor.cs | 52 +- .../PropertyEditors/TagsPropertyEditor.cs | 148 +- .../PropertyEditors/TextAreaPropertyEditor.cs | 52 +- .../PropertyEditors/TextboxPropertyEditor.cs | 52 +- .../TrueFalsePropertyEditor.cs | 46 +- .../UploadFileTypeValidator.cs | 82 +- .../UserPickerPropertyEditor.cs | 36 +- .../MarkdownEditorValueConverter.cs | 86 +- .../RteMacroRenderingValueConverter.cs | 244 +- .../TextStringValueConverter.cs | 104 +- .../PublishedCache/IPublishedCache.cs | 430 +- .../PublishedCache/IPublishedContentCache.cs | 106 +- .../PublishedCache/IPublishedMediaCache.cs | 10 +- .../PublishedContentCache.cs | 1098 +- .../XmlPublishedCache/PublishedMediaCache.cs | 1450 +- .../XmlPublishedCache/RoutesCache.cs | 230 +- .../XmlPublishedCache/UmbracoContextCache.cs | 56 +- .../XmlPublishedCache/XmlPublishedContent.cs | 864 +- .../XmlPublishedCache/XmlPublishedProperty.cs | 154 +- src/Umbraco.Web/PublishedContentExtensions.cs | 2492 +- .../PublishedContentPropertyExtension.cs | 118 +- src/Umbraco.Web/PublishedContentQuery.cs | 606 +- src/Umbraco.Web/RenderFieldCaseType.cs | 26 +- src/Umbraco.Web/RenderFieldEncodingType.cs | 26 +- src/Umbraco.Web/RouteCollectionExtensions.cs | 270 +- src/Umbraco.Web/Routing/AliasUrlProvider.cs | 166 +- .../Routing/ContentFinderByIdPath.cs | 140 +- .../Routing/ContentFinderByLegacy404.cs | 182 +- .../Routing/ContentFinderByPageIdQuery.cs | 56 +- .../Routing/ContentFinderByUrlAlias.cs | 184 +- src/Umbraco.Web/Routing/DefaultUrlProvider.cs | 316 +- src/Umbraco.Web/Routing/DomainAndUri.cs | 98 +- src/Umbraco.Web/Routing/DomainHelper.cs | 644 +- .../Routing/EnsureRoutableOutcome.cs | 78 +- src/Umbraco.Web/Routing/IContentFinder.cs | 32 +- src/Umbraco.Web/Routing/ISiteDomainHelper.cs | 90 +- src/Umbraco.Web/Routing/IUrlProvider.cs | 84 +- .../Routing/LegacyRequestInitializer.cs | 72 +- .../PublishedContentNotFoundHandler.cs | 102 +- .../Routing/RoutableAttemptEventArgs.cs | 36 +- src/Umbraco.Web/Routing/SiteDomainHelper.cs | 622 +- .../Routing/UmbracoRequestEventArgs.cs | 40 +- src/Umbraco.Web/Routing/UrlProvider.cs | 494 +- .../Routing/UrlProviderExtensions.cs | 214 +- src/Umbraco.Web/Routing/UrlProviderMode.cs | 72 +- .../Routing/WebServicesRouteConstraint.cs | 52 +- src/Umbraco.Web/Search/ExamineIndexerModel.cs | 64 +- .../Search/ExamineSearcherModel.cs | 72 +- .../Search/LuceneIndexerExtensions.cs | 262 +- .../Providers/MembersMembershipProvider.cs | 230 +- .../Providers/UsersMembershipProvider.cs | 376 +- .../Security/ValidateRequestAttempt.cs | 28 +- src/Umbraco.Web/Security/WebSecurity.cs | 596 +- src/Umbraco.Web/TagQuery.cs | 436 +- src/Umbraco.Web/Templates/TemplateRenderer.cs | 470 +- .../Templates/TemplateUtilities.cs | 278 +- src/Umbraco.Web/Trees/ActionUrlMethod.cs | 22 +- .../Trees/ApplicationTreeController.cs | 360 +- .../Trees/ApplicationTreeExtensions.cs | 618 +- .../Trees/ContentBlueprintTreeController.cs | 236 +- .../Trees/ContentTreeController.cs | 530 +- .../Trees/ContentTreeControllerBase.cs | 888 +- src/Umbraco.Web/Trees/CoreTreeAttribute.cs | 32 +- .../Trees/DataTypeTreeController.cs | 272 +- src/Umbraco.Web/Trees/LegacyTreeController.cs | 224 +- .../Trees/LegacyTreeDataConverter.cs | 828 +- src/Umbraco.Web/Trees/LegacyTreeJavascript.cs | 176 +- src/Umbraco.Web/Trees/LegacyTreeParams.cs | 74 +- src/Umbraco.Web/Trees/MediaTreeController.cs | 320 +- .../Trees/MediaTypeTreeController.cs | 282 +- src/Umbraco.Web/Trees/MemberTreeController.cs | 398 +- .../Trees/MenuRenderingEventArgs.cs | 50 +- src/Umbraco.Web/Trees/TreeAttribute.cs | 116 +- src/Umbraco.Web/Trees/TreeController.cs | 80 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 910 +- .../Trees/TreeNodeRenderingEventArgs.cs | 32 +- .../Trees/TreeNodesRenderingEventArgs.cs | 32 +- .../Trees/TreeQueryStringParameters.cs | 28 +- .../Trees/TreeRenderingEventArgs.cs | 30 +- src/Umbraco.Web/Trees/UrlHelperExtensions.cs | 132 +- src/Umbraco.Web/UI/CdfLogger.cs | 94 +- .../UI/Controls/InsertMacroSplitButton.cs | 288 +- src/Umbraco.Web/UI/Controls/ProgressBar.cs | 34 +- src/Umbraco.Web/UI/Controls/UmbracoControl.cs | 136 +- .../UI/Controls/UmbracoUserControl.cs | 214 +- src/Umbraco.Web/UI/IAssignedApp.cs | 34 +- .../UI/JavaScript/CssInitialization.cs | 102 +- .../UI/JavaScript/JsInitialization.cs | 214 +- src/Umbraco.Web/UI/JavaScript/JsInitialize.js | 86 +- src/Umbraco.Web/UI/JavaScript/Main.js | 18 +- .../UI/JavaScript/Resources.Designer.cs | 248 +- src/Umbraco.Web/UI/JavaScript/Resources.resx | 258 +- .../UI/JavaScript/ServerVariables.js | 16 +- .../UI/JavaScript/ServerVariablesParser.cs | 66 +- src/Umbraco.Web/UI/Pages/BasePage.cs | 168 +- src/Umbraco.Web/UI/Pages/ClientTools.cs | 692 +- .../UI/Pages/UmbracoEnsuredPage.cs | 296 +- src/Umbraco.Web/UI/SpeechBubbleIcon.cs | 62 +- src/Umbraco.Web/Umbraco.Web.csproj | 3168 +-- src/Umbraco.Web/UmbracoApplication.cs | 32 +- src/Umbraco.Web/UmbracoContext.cs | 682 +- src/Umbraco.Web/UmbracoHelper.cs | 2184 +- src/Umbraco.Web/UmbracoModule.cs | 1316 +- src/Umbraco.Web/UriUtility.cs | 492 +- src/Umbraco.Web/UrlHelperExtensions.cs | 382 +- src/Umbraco.Web/UrlHelperRenderExtensions.cs | 776 +- .../org.umbraco.our/Reference.cs | 2090 +- .../org.umbraco.our/Reference.map | 12 +- .../org.umbraco.our/repository.disco | 10 +- .../org.umbraco.our/repository.wsdl | 1988 +- .../org.umbraco.update/Reference.cs | 570 +- .../org.umbraco.update/Reference.map | 12 +- .../UpgradeResult1.datasource | 18 +- .../org.umbraco.update/checkforupgrade.disco | 10 +- .../org.umbraco.update/checkforupgrade.wsdl | 282 +- .../WebApi/Binders/ContentItemBaseBinder.cs | 420 +- .../WebApi/Binders/ContentItemBinder.cs | 94 +- .../WebApi/Binders/MediaItemBinder.cs | 64 +- .../WebApi/Binders/MemberBinder.cs | 722 +- .../WebApi/CustomDateTimeConvertor.cs | 54 +- .../Filters/ContentItemValidationHelper.cs | 298 +- ...EnsureUserPermissionForContentAttribute.cs | 220 +- .../EnsureUserPermissionForMediaAttribute.cs | 268 +- .../FileUploadCleanupFilterAttribute.cs | 366 +- .../FilterAllowedOutgoingContentAttribute.cs | 236 +- .../Filters/HttpQueryStringFilterAttribute.cs | 86 +- .../OutgoingDateTimeFormatAttribute.cs | 90 +- .../UmbracoApplicationAuthorizeAttribute.cs | 90 +- .../UmbracoUserTimeoutFilterAttribute.cs | 70 +- .../Filters/ValidationFilterAttribute.cs | 54 +- .../WebApi/HttpRequestMessageExtensions.cs | 326 +- .../WebApi/MemberAuthorizeAttribute.cs | 172 +- .../WebApi/UmbracoApiController.cs | 20 +- .../WebApi/UmbracoAuthorizeAttribute.cs | 144 +- .../WebApi/UmbracoAuthorizedApiController.cs | 58 +- .../WebServices/BulkPublishController.cs | 202 +- .../WebServices/CoreStringsController.cs | 66 +- .../WebServices/DomainsApiController.cs | 380 +- .../WebServices/EmbedMediaService.cs | 164 +- .../ExamineManagementApiController.cs | 676 +- .../WebServices/SaveFileController.cs | 636 +- src/Umbraco.Web/WebServices/TagsController.cs | 156 +- .../UmbracoAuthorizedHttpHandler.cs | 202 +- .../UmbracoAuthorizedWebService.cs | 190 +- .../WebServices/UmbracoHttpHandler.cs | 150 +- .../WebServices/UmbracoWebService.cs | 152 +- src/Umbraco.Web/WebViewPageExtensions.cs | 60 +- .../AttributeCollectionAdapter.cs | 624 +- .../umbraco.presentation/MacroCacheContent.cs | 52 +- .../umbraco.presentation/default.aspx.cs | 368 +- src/Umbraco.Web/umbraco.presentation/item.cs | 462 +- .../umbraco.presentation/library.cs | 3120 +-- src/Umbraco.Web/umbraco.presentation/page.cs | 1178 +- .../umbraco/Default.aspx.cs | 78 +- .../umbraco/Trees/BaseTree.cs | 1142 +- .../umbraco/Trees/ITreeService.cs | 40 +- .../umbraco/Trees/NodeActionsEventArgs.cs | 36 +- .../umbraco/Trees/NullTree.cs | 124 +- .../umbraco/Trees/TreeDefinition.cs | 220 +- .../umbraco/Trees/TreeDefinitionCollection.cs | 374 +- .../umbraco/Trees/TreeDialogModes.cs | 34 +- .../umbraco/Trees/TreeEventArgs.cs | 52 +- .../umbraco/Trees/TreeRequestParams.cs | 270 +- .../umbraco/Trees/TreeService.cs | 264 +- .../umbraco/Trees/XmlTree.cs | 984 +- .../umbraco/Trees/loadPackager.cs | 316 +- .../umbraco/Trees/loadTranslationTasks.cs | 240 +- .../umbraco/Web/UI/ContentPage.cs | 50 +- .../umbraco/controls/ContentPicker.cs | 144 +- .../controls/Tree/CustomTreeControl.cs | 258 +- .../controls/Tree/CustomTreeService.cs | 306 +- .../umbraco/controls/Tree/JTreeContextMenu.cs | 118 +- .../controls/Tree/JTreeContextMenuItem.cs | 164 +- .../umbraco/controls/Tree/NodeInfo.cs | 64 +- .../umbraco/controls/Tree/TreeControl.ascx.cs | 1186 +- .../umbraco/controls/dualSelectBox.cs | 288 +- .../umbraco/create.aspx.cs | 98 +- .../umbraco/create/CreatedPackageTasks.cs | 86 +- .../umbraco/create/MemberGroupTasks.cs | 116 +- .../umbraco/create/dialogHandler_temp.cs | 80 +- .../umbraco/create/dictionaryTasks.cs | 114 +- .../umbraco/create/macroTasks.cs | 84 +- .../umbraco/create/simple.ascx.cs | 196 +- .../umbraco/dashboard/FeedProxy.aspx | 4 +- .../umbraco/dashboard/FeedProxy.aspx.cs | 124 +- .../dashboard/FeedProxy.aspx.designer.cs | 30 +- .../developer/Packages/editPackage.aspx | 464 +- .../developer/Packages/editPackage.aspx.cs | 910 +- .../Packages/editPackage.aspx.designer.cs | 1218 +- .../RelationTypes/EditRelationType.aspx | 290 +- .../RelationTypes/EditRelationType.aspx.cs | 568 +- .../EditRelationType.aspx.designer.cs | 498 +- .../RelationTypes/NewRelationType.aspx | 108 +- .../RelationTypes/NewRelationType.aspx.cs | 178 +- .../NewRelationType.aspx.designer.cs | 336 +- .../RelationTypesWebService.asmx | 2 +- .../RelationTypesWebService.asmx.cs | 70 +- .../TreeMenu/ActionDeleteRelationType.cs | 166 +- .../TreeMenu/ActionNewRelationType.cs | 166 +- .../umbraco/dialogs/AssignDomain2.aspx | 164 +- .../umbraco/dialogs/AssignDomain2.aspx.cs | 164 +- .../dialogs/AssignDomain2.aspx.designer.cs | 138 +- .../umbraco/dialogs/Preview.aspx | 36 +- .../umbraco/dialogs/Preview.aspx.cs | 60 +- .../umbraco/dialogs/Preview.aspx.designer.cs | 138 +- .../umbraco/dialogs/SendPublish.aspx | 26 +- .../umbraco/dialogs/SendPublish.aspx.cs | 74 +- .../dialogs/SendPublish.aspx.designer.cs | 32 +- .../umbraco/dialogs/create.aspx.cs | 320 +- .../umbraco/dialogs/empty.htm | 18 +- .../umbraco/dialogs/exportDocumenttype.aspx | 2 +- .../dialogs/exportDocumenttype.aspx.cs | 118 +- .../umbraco/dialogs/importDocumenttype.aspx | 98 +- .../dialogs/importDocumenttype.aspx.cs | 242 +- .../dialogs/insertMasterpageContent.aspx | 62 +- .../dialogs/insertMasterpageContent.aspx.cs | 104 +- .../insertMasterpageContent.aspx.designer.cs | 84 +- .../dialogs/insertMasterpagePlaceholder.aspx | 66 +- .../insertMasterpagePlaceholder.aspx.cs | 36 +- ...sertMasterpagePlaceholder.aspx.designer.cs | 52 +- .../umbraco/dialogs/notifications.aspx | 34 +- .../umbraco/dialogs/notifications.aspx.cs | 222 +- .../dialogs/notifications.aspx.designer.cs | 84 +- .../umbraco/dialogs/protectPage.aspx.cs | 1470 +- .../umbraco/dialogs/republish.aspx | 78 +- .../umbraco/dialogs/republish.aspx.cs | 136 +- .../dialogs/republish.aspx.designer.cs | 102 +- .../umbraco/dialogs/rollBack.aspx | 198 +- .../umbraco/dialogs/rollBack.aspx.cs | 312 +- .../umbraco/dialogs/rollBack.aspx.designer.cs | 300 +- .../umbraco/dialogs/sendToTranslation.aspx | 72 +- .../umbraco/dialogs/sendToTranslation.aspx.cs | 418 +- .../sendToTranslation.aspx.designer.cs | 264 +- .../umbraco/dialogs/sort.aspx.cs | 530 +- .../umbraco/dialogs/treePicker.aspx.cs | 94 +- .../umbraco/dialogs/umbracoField.aspx.cs | 502 +- .../umbraco/dialogs/viewAuditTrail.aspx | 144 +- .../umbraco/dialogs/viewAuditTrail.aspx.cs | 148 +- .../dialogs/viewAuditTrail.aspx.designer.cs | 48 +- .../umbraco/masterpages/default.Master.cs | 70 +- .../masterpages/umbracoDialog.Master.cs | 322 +- .../umbraco/masterpages/umbracoPage.Master.cs | 478 +- .../umbraco/settings/DictionaryItemList.aspx | 30 +- .../settings/DictionaryItemList.aspx.cs | 128 +- .../DictionaryItemList.aspx.designer.cs | 84 +- .../umbraco/settings/EditDictionaryItem.aspx | 30 +- .../settings/EditDictionaryItem.aspx.cs | 350 +- .../EditDictionaryItem.aspx.designer.cs | 48 +- .../umbraco/templateControls/ContentType.cs | 62 +- .../DisableEventValidation.cs | 54 +- .../umbraco/templateControls/Item.cs | 648 +- .../umbraco/templateControls/ItemRenderer.cs | 452 +- .../umbraco/templateControls/Macro.cs | 446 +- .../umbraco/translation/default.aspx | 110 +- .../umbraco/translation/default.aspx.cs | 420 +- .../translation/default.aspx.designer.cs | 174 +- .../umbraco/translation/details.aspx | 86 +- .../umbraco/translation/details.aspx.cs | 202 +- .../translation/details.aspx.designer.cs | 264 +- .../umbraco/translation/preview.aspx | 78 +- .../umbraco/translation/preview.aspx.cs | 84 +- .../translation/preview.aspx.designer.cs | 30 +- .../umbraco/translation/xml.aspx | 2 +- .../umbraco/translation/xml.aspx.cs | 214 +- .../umbraco/translation/xml.aspx.designer.cs | 50 +- .../urlRewriter/UrlRewriterFormWriter.cs | 138 +- .../umbraco/webservices/CheckForUpgrade.asmx | 2 +- .../webservices/CheckForUpgrade.asmx.cs | 218 +- .../umbraco/webservices/ajaxHelpers.cs | 50 +- .../umbraco/webservices/legacyAjaxCalls.asmx | 2 +- .../webservices/legacyAjaxCalls.asmx.cs | 276 +- .../umbraco/webservices/nodeSorter.asmx | 2 +- .../umbraco/webservices/nodeSorter.asmx.cs | 518 +- .../umbraco.presentation/umbracoPageHolder.cs | 228 +- src/umbraco.sln | 288 +- tools/ConfigTransformTool/readme.txt | 2 +- .../MSBuild.Community.Tasks.Targets | 294 +- .../MSBuild.Community.Tasks.xml | 20950 ++++++++-------- .../MSBuild.Community.Tasks.xsd | 12672 +++++----- tools/MSBuildCommunityTasks/Sample.proj | 304 +- ...teUmbracoSQLCEDatabase.vshost.exe.manifest | 22 +- .../Umbraco.MSBuild.Tasks.Targets | 24 +- 1616 files changed, 273322 insertions(+), 273322 deletions(-) diff --git a/build/NuSpecs/tools/Readme.txt b/build/NuSpecs/tools/Readme.txt index b6b55c1c4f..d89116775d 100644 --- a/build/NuSpecs/tools/Readme.txt +++ b/build/NuSpecs/tools/Readme.txt @@ -1,26 +1,26 @@ - - _ _ __ __ ____ _____ _____ ____ - | | | | \/ | _ \| __ \ /\ / ____/ __ \ - | | | | \ / | |_) | |__) | / \ | | | | | | - | | | | |\/| | _ <| _ / / /\ \| | | | | | - | |__| | | | | |_) | | \ \ / ____ | |___| |__| | - \____/|_| |_|____/|_| \_/_/ \_\_____\____/ - ----------------------------------------------------- - -Don't forget to build! - - -When upgrading your website using NuGet you should answer "No" to the questions to overwrite the Web.config -file (and config files in the config folder). - -This NuGet package includes build targets that extend the creation of a deploy package, which is generated by -Publishing from Visual Studio. The targets will only work once Publishing is configured, so if you don't use -Publish this won't affect you. -The following items will now be automatically included when creating a deploy package or publishing to the file -system: umbraco, umbraco_client, config\splashes and global.asax. - -Please read the release notes on our.umbraco.org: -http://our.umbraco.org/contribute/releases - + + _ _ __ __ ____ _____ _____ ____ + | | | | \/ | _ \| __ \ /\ / ____/ __ \ + | | | | \ / | |_) | |__) | / \ | | | | | | + | | | | |\/| | _ <| _ / / /\ \| | | | | | + | |__| | | | | |_) | | \ \ / ____ | |___| |__| | + \____/|_| |_|____/|_| \_/_/ \_\_____\____/ + +---------------------------------------------------- + +Don't forget to build! + + +When upgrading your website using NuGet you should answer "No" to the questions to overwrite the Web.config +file (and config files in the config folder). + +This NuGet package includes build targets that extend the creation of a deploy package, which is generated by +Publishing from Visual Studio. The targets will only work once Publishing is configured, so if you don't use +Publish this won't affect you. +The following items will now be automatically included when creating a deploy package or publishing to the file +system: umbraco, umbraco_client, config\splashes and global.asax. + +Please read the release notes on our.umbraco.org: +http://our.umbraco.org/contribute/releases + - Umbraco \ No newline at end of file diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index 592dc951e0..684739072c 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -1,162 +1,162 @@ -param($installPath, $toolsPath, $package, $project) - -Write-Host "installPath:" "${installPath}" -Write-Host "toolsPath:" "${toolsPath}" - -Write-Host " " - -if ($project) { - $dateTime = Get-Date -Format yyyyMMdd-HHmmss - - # Create paths and list them - $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName - Write-Host "projectPath:" "${projectPath}" - $backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime" - Write-Host "backupPath:" "${backupPath}" - $copyLogsPath = Join-Path $backupPath "CopyLogs" - Write-Host "copyLogsPath:" "${copyLogsPath}" - $webConfigSource = Join-Path $projectPath "Web.config" - Write-Host "webConfigSource:" "${webConfigSource}" - $configFolder = Join-Path $projectPath "Config" - Write-Host "configFolder:" "${configFolder}" - - # Create backup folder and logs folder if it doesn't exist yet - New-Item -ItemType Directory -Force -Path $backupPath - New-Item -ItemType Directory -Force -Path $copyLogsPath - - # Create a backup of original web.config - Copy-Item $webConfigSource $backupPath -Force - - # Backup config files folder - if(Test-Path $configFolder) { - $umbracoBackupPath = Join-Path $backupPath "Config" - New-Item -ItemType Directory -Force -Path $umbracoBackupPath - - robocopy $configFolder $umbracoBackupPath /e /LOG:$copyLogsPath\ConfigBackup.log - } - - # Copy umbraco and umbraco_files from package to project folder - $umbracoFolder = Join-Path $projectPath "Umbraco" - New-Item -ItemType Directory -Force -Path $umbracoFolder - $umbracoFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco" - $umbracoBackupPath = Join-Path $backupPath "Umbraco" - New-Item -ItemType Directory -Force -Path $umbracoBackupPath - robocopy $umbracoFolder $umbracoBackupPath /e /LOG:$copyLogsPath\UmbracoBackup.log - robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log - - $umbracoClientFolder = Join-Path $projectPath "Umbraco_Client" - New-Item -ItemType Directory -Force -Path $umbracoClientFolder - $umbracoClientFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco_Client" - $umbracoClientBackupPath = Join-Path $backupPath "Umbraco_Client" - New-Item -ItemType Directory -Force -Path $umbracoClientBackupPath - robocopy $umbracoClientFolder $umbracoClientBackupPath /e /LOG:$copyLogsPath\UmbracoClientBackup.log - robocopy $umbracoClientFolderSource $umbracoClientFolder /is /it /e /LOG:$copyLogsPath\UmbracoClientCopy.log - - $copyWebconfig = $true - $destinationWebConfig = Join-Path $projectPath "Web.config" - - if(Test-Path $destinationWebConfig) - { - Try - { - [xml]$config = Get-Content $destinationWebConfig - - $config.configuration.appSettings.ChildNodes | ForEach-Object { - if($_.key -eq "umbracoConfigurationStatus") - { - # The web.config has an umbraco-specific appSetting in it - # don't overwrite it and let config transforms do their thing - $copyWebconfig = $false - } - } - } - Catch { } - } - - if($copyWebconfig -eq $true) - { - $packageWebConfigSource = Join-Path $installPath "UmbracoFiles\Web.config" - Copy-Item $packageWebConfigSource $destinationWebConfig -Force - - # Copy files that don't get automatically copied for Website projects - # We do this here, when copyWebconfig is true because we only want to do it for new installs - # If this is an upgrade then the files should already be there - $splashesSource = Join-Path $installPath "UmbracoFiles\Config\splashes\*.*" - $splashesDestination = Join-Path $projectPath "Config\splashes\" - New-Item $splashesDestination -Type directory - Copy-Item $splashesSource $splashesDestination -Force - - $sqlCe64Source = Join-Path $installPath "UmbracoFiles\bin\amd64\*" - $sqlCe64Destination = Join-Path $projectPath "bin\amd64\" - Copy-Item $sqlCe64Source $sqlCe64Destination -Force - - $sqlCex86Source = Join-Path $installPath "UmbracoFiles\bin\x86\*" - $sqlCex86Destination = Join-Path $projectPath "bin\x86\" - Copy-Item $sqlCex86source $sqlCex86Destination -Force - - $umbracoUIXMLSource = Join-Path $installPath "UmbracoFiles\Umbraco\Config\Create\UI.xml" - $umbracoUIXMLDestination = Join-Path $projectPath "Umbraco\Config\Create\UI.xml" - Copy-Item $umbracoUIXMLSource $umbracoUIXMLDestination -Force - } else { - # This part only runs for upgrades - - $upgradeViewSource = Join-Path $umbracoFolderSource "Views\install\*" - $upgradeView = Join-Path $umbracoFolder "Views\install\" - Write-Host "Copying2 ${upgradeViewSource} to ${upgradeView}" - Copy-Item $upgradeViewSource $upgradeView -Force - - Try - { - # Disable tours for upgrades, presumably Umbraco experience is already available - $umbracoSettingsConfigPath = Join-Path $configFolder "umbracoSettings.config" - $content = (Get-Content $umbracoSettingsConfigPath).Replace('','') - # Saves with UTF-8 encoding without BOM which makes sure Umbraco can still read it - # Reference: https://stackoverflow.com/a/32951824/5018 - [IO.File]::WriteAllLines($umbracoSettingsConfigPath, $content) - } - Catch - { - # Not a big problem if this fails, let it go - } - - Try - { - $uiXmlConfigPath = Join-Path $umbracoFolder -ChildPath "Config" | Join-Path -ChildPath "create" | Join-Path -ChildPath "UI.xml" - $uiXmlFile = Join-Path $umbracoFolder -ChildPath "Config" | Join-Path -ChildPath "create" | Join-Path -ChildPath "UI.xml" - - $uiXml = New-Object System.Xml.XmlDocument - $uiXml.PreserveWhitespace = $true - - $uiXml.Load($uiXmlFile) - $createExists = $uiXml.SelectNodes("//nodeType[@alias='macros']/tasks/create") - - if($createExists.Count -eq 0) - { - $macrosTasksNode = $uiXml.SelectNodes("//nodeType[@alias='macros']/tasks") - - #Creating: - $createNode = $uiXml.CreateElement("create") - $createNode.SetAttribute("assembly", "umbraco") - $createNode.SetAttribute("type", "macroTasks") - $macrosTasksNode.AppendChild($createNode) - $uiXml.Save($uiXmlFile) - } - } - Catch { } - } - - $installFolder = Join-Path $projectPath "Install" - if(Test-Path $installFolder) { - Remove-Item $installFolder -Force -Recurse -Confirm:$false - } - - # Open appropriate readme - if($copyWebconfig -eq $true) - { - $DTE.ItemOperations.OpenFile($toolsPath + '\Readme.txt') - } - else - { - $DTE.ItemOperations.OpenFile($toolsPath + '\ReadmeUpgrade.txt') - } +param($installPath, $toolsPath, $package, $project) + +Write-Host "installPath:" "${installPath}" +Write-Host "toolsPath:" "${toolsPath}" + +Write-Host " " + +if ($project) { + $dateTime = Get-Date -Format yyyyMMdd-HHmmss + + # Create paths and list them + $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName + Write-Host "projectPath:" "${projectPath}" + $backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime" + Write-Host "backupPath:" "${backupPath}" + $copyLogsPath = Join-Path $backupPath "CopyLogs" + Write-Host "copyLogsPath:" "${copyLogsPath}" + $webConfigSource = Join-Path $projectPath "Web.config" + Write-Host "webConfigSource:" "${webConfigSource}" + $configFolder = Join-Path $projectPath "Config" + Write-Host "configFolder:" "${configFolder}" + + # Create backup folder and logs folder if it doesn't exist yet + New-Item -ItemType Directory -Force -Path $backupPath + New-Item -ItemType Directory -Force -Path $copyLogsPath + + # Create a backup of original web.config + Copy-Item $webConfigSource $backupPath -Force + + # Backup config files folder + if(Test-Path $configFolder) { + $umbracoBackupPath = Join-Path $backupPath "Config" + New-Item -ItemType Directory -Force -Path $umbracoBackupPath + + robocopy $configFolder $umbracoBackupPath /e /LOG:$copyLogsPath\ConfigBackup.log + } + + # Copy umbraco and umbraco_files from package to project folder + $umbracoFolder = Join-Path $projectPath "Umbraco" + New-Item -ItemType Directory -Force -Path $umbracoFolder + $umbracoFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco" + $umbracoBackupPath = Join-Path $backupPath "Umbraco" + New-Item -ItemType Directory -Force -Path $umbracoBackupPath + robocopy $umbracoFolder $umbracoBackupPath /e /LOG:$copyLogsPath\UmbracoBackup.log + robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log + + $umbracoClientFolder = Join-Path $projectPath "Umbraco_Client" + New-Item -ItemType Directory -Force -Path $umbracoClientFolder + $umbracoClientFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco_Client" + $umbracoClientBackupPath = Join-Path $backupPath "Umbraco_Client" + New-Item -ItemType Directory -Force -Path $umbracoClientBackupPath + robocopy $umbracoClientFolder $umbracoClientBackupPath /e /LOG:$copyLogsPath\UmbracoClientBackup.log + robocopy $umbracoClientFolderSource $umbracoClientFolder /is /it /e /LOG:$copyLogsPath\UmbracoClientCopy.log + + $copyWebconfig = $true + $destinationWebConfig = Join-Path $projectPath "Web.config" + + if(Test-Path $destinationWebConfig) + { + Try + { + [xml]$config = Get-Content $destinationWebConfig + + $config.configuration.appSettings.ChildNodes | ForEach-Object { + if($_.key -eq "umbracoConfigurationStatus") + { + # The web.config has an umbraco-specific appSetting in it + # don't overwrite it and let config transforms do their thing + $copyWebconfig = $false + } + } + } + Catch { } + } + + if($copyWebconfig -eq $true) + { + $packageWebConfigSource = Join-Path $installPath "UmbracoFiles\Web.config" + Copy-Item $packageWebConfigSource $destinationWebConfig -Force + + # Copy files that don't get automatically copied for Website projects + # We do this here, when copyWebconfig is true because we only want to do it for new installs + # If this is an upgrade then the files should already be there + $splashesSource = Join-Path $installPath "UmbracoFiles\Config\splashes\*.*" + $splashesDestination = Join-Path $projectPath "Config\splashes\" + New-Item $splashesDestination -Type directory + Copy-Item $splashesSource $splashesDestination -Force + + $sqlCe64Source = Join-Path $installPath "UmbracoFiles\bin\amd64\*" + $sqlCe64Destination = Join-Path $projectPath "bin\amd64\" + Copy-Item $sqlCe64Source $sqlCe64Destination -Force + + $sqlCex86Source = Join-Path $installPath "UmbracoFiles\bin\x86\*" + $sqlCex86Destination = Join-Path $projectPath "bin\x86\" + Copy-Item $sqlCex86source $sqlCex86Destination -Force + + $umbracoUIXMLSource = Join-Path $installPath "UmbracoFiles\Umbraco\Config\Create\UI.xml" + $umbracoUIXMLDestination = Join-Path $projectPath "Umbraco\Config\Create\UI.xml" + Copy-Item $umbracoUIXMLSource $umbracoUIXMLDestination -Force + } else { + # This part only runs for upgrades + + $upgradeViewSource = Join-Path $umbracoFolderSource "Views\install\*" + $upgradeView = Join-Path $umbracoFolder "Views\install\" + Write-Host "Copying2 ${upgradeViewSource} to ${upgradeView}" + Copy-Item $upgradeViewSource $upgradeView -Force + + Try + { + # Disable tours for upgrades, presumably Umbraco experience is already available + $umbracoSettingsConfigPath = Join-Path $configFolder "umbracoSettings.config" + $content = (Get-Content $umbracoSettingsConfigPath).Replace('','') + # Saves with UTF-8 encoding without BOM which makes sure Umbraco can still read it + # Reference: https://stackoverflow.com/a/32951824/5018 + [IO.File]::WriteAllLines($umbracoSettingsConfigPath, $content) + } + Catch + { + # Not a big problem if this fails, let it go + } + + Try + { + $uiXmlConfigPath = Join-Path $umbracoFolder -ChildPath "Config" | Join-Path -ChildPath "create" | Join-Path -ChildPath "UI.xml" + $uiXmlFile = Join-Path $umbracoFolder -ChildPath "Config" | Join-Path -ChildPath "create" | Join-Path -ChildPath "UI.xml" + + $uiXml = New-Object System.Xml.XmlDocument + $uiXml.PreserveWhitespace = $true + + $uiXml.Load($uiXmlFile) + $createExists = $uiXml.SelectNodes("//nodeType[@alias='macros']/tasks/create") + + if($createExists.Count -eq 0) + { + $macrosTasksNode = $uiXml.SelectNodes("//nodeType[@alias='macros']/tasks") + + #Creating: + $createNode = $uiXml.CreateElement("create") + $createNode.SetAttribute("assembly", "umbraco") + $createNode.SetAttribute("type", "macroTasks") + $macrosTasksNode.AppendChild($createNode) + $uiXml.Save($uiXmlFile) + } + } + Catch { } + } + + $installFolder = Join-Path $projectPath "Install" + if(Test-Path $installFolder) { + Remove-Item $installFolder -Force -Recurse -Confirm:$false + } + + # Open appropriate readme + if($copyWebconfig -eq $true) + { + $DTE.ItemOperations.OpenFile($toolsPath + '\Readme.txt') + } + else + { + $DTE.ItemOperations.OpenFile($toolsPath + '\ReadmeUpgrade.txt') + } } \ No newline at end of file diff --git a/build/RevertToCleanInstall.bat b/build/RevertToCleanInstall.bat index b21b33d8ff..f8482156de 100644 --- a/build/RevertToCleanInstall.bat +++ b/build/RevertToCleanInstall.bat @@ -1,127 +1,127 @@ -@ECHO OFF - -:choice -set /P c=WARNING! Are you sure you want to continue, this will remove all package files, view files, sqlce database, etc... Press 'Y' to auto-remove all files/folders, 'N' to cancel or 'C' to prompt for each folder removal? -if /I "%c%" EQU "C" goto :prompt -if /I "%c%" EQU "Y" goto :auto -if /I "%c%" EQU "N" goto :exit -goto :choice - - -:prompt - -echo Current folder: %CD% - -echo Removing sqlce database -del ..\src\Umbraco.Web.UI\App_Data\Umbraco.sdf - -echo Resetting installedPackages.config -echo ^^^ >..\src\Umbraco.Web.UI\App_Data\packages\installed\installedPackages.config - -echo Removing plugin cache files -del ..\src\Umbraco.Web.UI\App_Data\TEMP\PluginCache\*.* - -echo Removing cache files and examine index -del ..\src\Umbraco.Web.UI\App_Data\TEMP\*.* - -echo Removing log files -del ..\src\Umbraco.Web.UI\App_Data\Logs\*.* - -echo Removing packages -del ..\src\Umbraco.Web.UI\App_Data\packages\*.* - -echo Removing previews -del ..\src\Umbraco.Web.UI\App_Data\preview\*.* - -echo Removing app code files (typically added by starterkits) -del ..\src\Umbraco.Web.UI\App_Code\*.* - -echo Removing xslt files -del ..\src\Umbraco.Web.UI\xslt\*.* - -echo Removing user control files -del ..\src\Umbraco.Web.UI\UserControls\*.* - -echo Removing masterpage files -del ..\src\Umbraco.Web.UI\masterpages\*.* - -echo Removing razor files -del ..\src\Umbraco.Web.UI\macroScripts\*.* - -echo Removing media files -del ..\src\Umbraco.Web.UI\media\*.* - -echo Removing script files -del ..\src\Umbraco.Web.UI\scripts\*.* - -echo Removing css files -del ..\src\Umbraco.Web.UI\css\*.* - -echo "Umbraco install reverted to clean install" -pause -exit - - - -:auto - -echo Current folder: %CD% - -echo Removing sqlce database -del ..\src\Umbraco.Web.UI\App_Data\Umbraco.sdf - -echo Resetting installedPackages.config -echo ^^^ >..\src\Umbraco.Web.UI\App_Data\packages\installed\installedPackages.config - -echo Removing plugin cache files -FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\TEMP\PluginCache\*.*) DO DEL %%A - -echo Removing cache files and examine index -FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\TEMP\*.*) DO DEL %%A - -echo Removing log files -FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\Logs\*.*) DO DEL %%A - -echo Removing packages -FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\packages\*.*) DO DEL %%A - -echo Removing previews -FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\preview\*.*) DO DEL %%A - -echo Removing app code files (typically added by starterkits) -FOR %%A IN (..\src\Umbraco.Web.UI\App_Code\*.*) DO DEL %%A - -echo Removing xslt files -FOR %%A IN (..\src\Umbraco.Web.UI\xslt\*.*) DO DEL %%A - -echo Removing masterpage files -FOR %%A IN (..\src\Umbraco.Web.UI\masterpages\*.*) DO DEL %%A - -echo Removing user control files -FOR %%A IN (..\src\Umbraco.Web.UI\usercontrols\*.*) DO DEL %%A - -echo Removing view files -ATTRIB +H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S -FOR %%A IN (..\src\Umbraco.Web.UI\Views\) DO DEL /Q /S *.cshtml -H -ATTRIB -H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S - -echo Removing razor files -FOR %%A IN (..\src\Umbraco.Web.UI\macroScripts\*.*) DO DEL %%A - -echo Removing media files -FOR %%A IN (..\src\Umbraco.Web.UI\media\*.*) DO DEL %%A - -echo Removing script files -FOR %%A IN (..\src\Umbraco.Web.UI\scripts\*.*) DO DEL %%A - -echo Removing css files -FOR %%A IN (..\src\Umbraco.Web.UI\css\*.*) DO DEL %%A - -echo "Umbraco install reverted to clean install" -pause -exit - - - -:exit +@ECHO OFF + +:choice +set /P c=WARNING! Are you sure you want to continue, this will remove all package files, view files, sqlce database, etc... Press 'Y' to auto-remove all files/folders, 'N' to cancel or 'C' to prompt for each folder removal? +if /I "%c%" EQU "C" goto :prompt +if /I "%c%" EQU "Y" goto :auto +if /I "%c%" EQU "N" goto :exit +goto :choice + + +:prompt + +echo Current folder: %CD% + +echo Removing sqlce database +del ..\src\Umbraco.Web.UI\App_Data\Umbraco.sdf + +echo Resetting installedPackages.config +echo ^^^ >..\src\Umbraco.Web.UI\App_Data\packages\installed\installedPackages.config + +echo Removing plugin cache files +del ..\src\Umbraco.Web.UI\App_Data\TEMP\PluginCache\*.* + +echo Removing cache files and examine index +del ..\src\Umbraco.Web.UI\App_Data\TEMP\*.* + +echo Removing log files +del ..\src\Umbraco.Web.UI\App_Data\Logs\*.* + +echo Removing packages +del ..\src\Umbraco.Web.UI\App_Data\packages\*.* + +echo Removing previews +del ..\src\Umbraco.Web.UI\App_Data\preview\*.* + +echo Removing app code files (typically added by starterkits) +del ..\src\Umbraco.Web.UI\App_Code\*.* + +echo Removing xslt files +del ..\src\Umbraco.Web.UI\xslt\*.* + +echo Removing user control files +del ..\src\Umbraco.Web.UI\UserControls\*.* + +echo Removing masterpage files +del ..\src\Umbraco.Web.UI\masterpages\*.* + +echo Removing razor files +del ..\src\Umbraco.Web.UI\macroScripts\*.* + +echo Removing media files +del ..\src\Umbraco.Web.UI\media\*.* + +echo Removing script files +del ..\src\Umbraco.Web.UI\scripts\*.* + +echo Removing css files +del ..\src\Umbraco.Web.UI\css\*.* + +echo "Umbraco install reverted to clean install" +pause +exit + + + +:auto + +echo Current folder: %CD% + +echo Removing sqlce database +del ..\src\Umbraco.Web.UI\App_Data\Umbraco.sdf + +echo Resetting installedPackages.config +echo ^^^ >..\src\Umbraco.Web.UI\App_Data\packages\installed\installedPackages.config + +echo Removing plugin cache files +FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\TEMP\PluginCache\*.*) DO DEL %%A + +echo Removing cache files and examine index +FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\TEMP\*.*) DO DEL %%A + +echo Removing log files +FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\Logs\*.*) DO DEL %%A + +echo Removing packages +FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\packages\*.*) DO DEL %%A + +echo Removing previews +FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\preview\*.*) DO DEL %%A + +echo Removing app code files (typically added by starterkits) +FOR %%A IN (..\src\Umbraco.Web.UI\App_Code\*.*) DO DEL %%A + +echo Removing xslt files +FOR %%A IN (..\src\Umbraco.Web.UI\xslt\*.*) DO DEL %%A + +echo Removing masterpage files +FOR %%A IN (..\src\Umbraco.Web.UI\masterpages\*.*) DO DEL %%A + +echo Removing user control files +FOR %%A IN (..\src\Umbraco.Web.UI\usercontrols\*.*) DO DEL %%A + +echo Removing view files +ATTRIB +H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S +FOR %%A IN (..\src\Umbraco.Web.UI\Views\) DO DEL /Q /S *.cshtml -H +ATTRIB -H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S + +echo Removing razor files +FOR %%A IN (..\src\Umbraco.Web.UI\macroScripts\*.*) DO DEL %%A + +echo Removing media files +FOR %%A IN (..\src\Umbraco.Web.UI\media\*.*) DO DEL %%A + +echo Removing script files +FOR %%A IN (..\src\Umbraco.Web.UI\scripts\*.*) DO DEL %%A + +echo Removing css files +FOR %%A IN (..\src\Umbraco.Web.UI\css\*.*) DO DEL %%A + +echo "Umbraco install reverted to clean install" +pause +exit + + + +:exit exit \ No newline at end of file diff --git a/build/RevertToEmptyInstall.bat b/build/RevertToEmptyInstall.bat index aeb0cdac72..c0c6817e58 100644 --- a/build/RevertToEmptyInstall.bat +++ b/build/RevertToEmptyInstall.bat @@ -1,163 +1,163 @@ -@ECHO OFF - -:choice -set /P c=WARNING! Are you sure you want to continue, this will remove all package files, view files, sqlce database, etc... Press 'Y' to auto-remove all files/folders, 'N' to cancel or 'C' to prompt for each folder removal? -if /I "%c%" EQU "C" goto :prompt -if /I "%c%" EQU "Y" goto :auto -if /I "%c%" EQU "N" goto :exit -goto :choice - - -:prompt - -echo Current folder: %CD% - -echo Regenerating SQL CE database -SET buildfolder=%CD% -CD ..\tools\RegenerateUmbracoSQLCEDatabase\ -RegenerateUmbracoSQLCEDatabase.exe %CD%\..\..\src\Umbraco.Web.UI -CD %buildfolder% - -echo Removing bin files -del ..\src\Umbraco.Web.UI\bin\*.* - -echo Building solution -"%ProgramFiles(x86)%"\MSBuild\14.0\Bin\MSBuild.exe ..\src\umbraco.sln /t:Clean,Build - -echo Resetting installedPackages.config -echo ^^^ >..\src\Umbraco.Web.UI\App_Data\packages\installed\installedPackages.config - -echo Removing plugin cache files -del ..\src\Umbraco.Web.UI\App_Data\TEMP\PluginCache\*.* - -echo Removing cache files and examine index -del ..\src\Umbraco.Web.UI\App_Data\TEMP\*.* - -echo Removing log files -del ..\src\Umbraco.Web.UI\App_Data\Logs\*.* - -echo Removing packages -del ..\src\Umbraco.Web.UI\App_Data\packages\*.* - -echo Removing previews -del ..\src\Umbraco.Web.UI\App_Data\preview\*.* - -echo Removing app code files (typically added by starterkits) -del ..\src\Umbraco.Web.UI\App_Code\*.* - -echo Removing xslt files -del ..\src\Umbraco.Web.UI\xslt\*.* - -echo Removing user control files -del ..\src\Umbraco.Web.UI\UserControls\*.* - -echo Removing masterpage files -del ..\src\Umbraco.Web.UI\masterpages\*.* - -echo Removing razor files -del ..\src\Umbraco.Web.UI\macroScripts\*.* - -echo Removing media files -del ..\src\Umbraco.Web.UI\media\*.* - -echo Removing script files -del ..\src\Umbraco.Web.UI\scripts\*.* - -echo Removing css files -del ..\src\Umbraco.Web.UI\css\*.* - -echo "Umbraco install reverted to clean install" -pause -exit - - - -:auto - -echo Current folder: %CD% - -echo Regenerating SQL CE database -SET buildfolder=%CD% -CD ..\tools\RegenerateUmbracoSQLCEDatabase\ -RegenerateUmbracoSQLCEDatabase.exe %CD%\..\..\src\Umbraco.Web.UI -CD %buildfolder% - -echo Removing bin files - FOR %%A IN (..\src\Umbraco.Web.UI\bin\*.*) DO DEL %%A - -echo Building solution -"%ProgramFiles(x86)%"\MSBuild\14.0\Bin\MSBuild.exe ..\src\umbraco.sln /t:Clean,Build - -echo Resetting installedPackages.config -echo ^^^ >..\src\Umbraco.Web.UI\App_Data\packages\installed\installedPackages.config - -echo Removing plugin cache files -FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\TEMP\PluginCache\*.*) DO DEL %%A - -echo Removing cache files and examine index -FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\TEMP\*.*) DO DEL %%A - -echo Removing log files -FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\Logs\*.*) DO DEL %%A - -echo Removing packages -FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\packages\*.*) DO DEL %%A - -echo Removing previews -FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\preview\*.*) DO DEL %%A - -echo Removing app code files (typically added by starterkits) -FOR %%A IN (..\src\Umbraco.Web.UI\App_Code\*.*) DO DEL %%A - -echo Removing xslt files -FOR %%A IN (..\src\Umbraco.Web.UI\xslt\*.*) DO DEL %%A - -echo Removing masterpage files -FOR %%A IN (..\src\Umbraco.Web.UI\masterpages\*.*) DO DEL %%A - -echo Removing user control files -FOR %%A IN (..\src\Umbraco.Web.UI\usercontrols\*.*) DO DEL %%A - -echo Removing view files -ATTRIB +H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S -FOR %%A IN (..\src\Umbraco.Web.UI\Views\) DO DEL /Q /S *.cshtml -H -ATTRIB -H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S - -echo Removing razor files -FOR %%A IN (..\src\Umbraco.Web.UI\macroScripts\*.*) DO DEL %%A - -echo Removing media files -FOR %%A IN (..\src\Umbraco.Web.UI\media\*.*) DO DEL %%A - -echo Removing script files -FOR %%A IN (..\src\Umbraco.Web.UI\scripts\*.*) DO DEL %%A - -echo Removing css files -FOR %%A IN (..\src\Umbraco.Web.UI\css\*.*) DO DEL %%A - -echo Removing Courier files -del ..\src\Umbraco.Web.UI\config\courier.config -del ..\src\Umbraco.Web.UI\umbraco\images\tray\courier.jpg -rmdir "..\src\Umbraco.Web.UI\umbraco\plugins\courier\" /S /Q - -echo Removing Contour files -del ..\src\Umbraco.Web.UI\umbraco\images\tray\contour.png -FOR %%A IN (..\src\Umbraco.Web.UI\umbraco\images\umbraco\icon_*.*) DO DEL %%A -rmdir "..\src\Umbraco.Web.UI\umbraco\plugins\umbracoContour\" /S /Q -del ..\src\Umbraco.Web.UI\umbraco\xslt\templates\UmbracoContour*.* /S /Q -rmdir "..\src\Umbraco.Web.UI\usercontrols\umbracoContour\" /S /Q - -echo Start with a clean web.config -copy ..\src\Umbraco.Web.UI\web.Template.config ..\src\Umbraco.Web.UI\web.config /Y - -echo Start with a clean web.config -copy ..\src\Umbraco.Web.UI\web.Template.config ..\src\Umbraco.Web.UI\web.config /Y - -echo "Umbraco install reverted to clean install" -pause -exit - - - -:exit +@ECHO OFF + +:choice +set /P c=WARNING! Are you sure you want to continue, this will remove all package files, view files, sqlce database, etc... Press 'Y' to auto-remove all files/folders, 'N' to cancel or 'C' to prompt for each folder removal? +if /I "%c%" EQU "C" goto :prompt +if /I "%c%" EQU "Y" goto :auto +if /I "%c%" EQU "N" goto :exit +goto :choice + + +:prompt + +echo Current folder: %CD% + +echo Regenerating SQL CE database +SET buildfolder=%CD% +CD ..\tools\RegenerateUmbracoSQLCEDatabase\ +RegenerateUmbracoSQLCEDatabase.exe %CD%\..\..\src\Umbraco.Web.UI +CD %buildfolder% + +echo Removing bin files +del ..\src\Umbraco.Web.UI\bin\*.* + +echo Building solution +"%ProgramFiles(x86)%"\MSBuild\14.0\Bin\MSBuild.exe ..\src\umbraco.sln /t:Clean,Build + +echo Resetting installedPackages.config +echo ^^^ >..\src\Umbraco.Web.UI\App_Data\packages\installed\installedPackages.config + +echo Removing plugin cache files +del ..\src\Umbraco.Web.UI\App_Data\TEMP\PluginCache\*.* + +echo Removing cache files and examine index +del ..\src\Umbraco.Web.UI\App_Data\TEMP\*.* + +echo Removing log files +del ..\src\Umbraco.Web.UI\App_Data\Logs\*.* + +echo Removing packages +del ..\src\Umbraco.Web.UI\App_Data\packages\*.* + +echo Removing previews +del ..\src\Umbraco.Web.UI\App_Data\preview\*.* + +echo Removing app code files (typically added by starterkits) +del ..\src\Umbraco.Web.UI\App_Code\*.* + +echo Removing xslt files +del ..\src\Umbraco.Web.UI\xslt\*.* + +echo Removing user control files +del ..\src\Umbraco.Web.UI\UserControls\*.* + +echo Removing masterpage files +del ..\src\Umbraco.Web.UI\masterpages\*.* + +echo Removing razor files +del ..\src\Umbraco.Web.UI\macroScripts\*.* + +echo Removing media files +del ..\src\Umbraco.Web.UI\media\*.* + +echo Removing script files +del ..\src\Umbraco.Web.UI\scripts\*.* + +echo Removing css files +del ..\src\Umbraco.Web.UI\css\*.* + +echo "Umbraco install reverted to clean install" +pause +exit + + + +:auto + +echo Current folder: %CD% + +echo Regenerating SQL CE database +SET buildfolder=%CD% +CD ..\tools\RegenerateUmbracoSQLCEDatabase\ +RegenerateUmbracoSQLCEDatabase.exe %CD%\..\..\src\Umbraco.Web.UI +CD %buildfolder% + +echo Removing bin files + FOR %%A IN (..\src\Umbraco.Web.UI\bin\*.*) DO DEL %%A + +echo Building solution +"%ProgramFiles(x86)%"\MSBuild\14.0\Bin\MSBuild.exe ..\src\umbraco.sln /t:Clean,Build + +echo Resetting installedPackages.config +echo ^^^ >..\src\Umbraco.Web.UI\App_Data\packages\installed\installedPackages.config + +echo Removing plugin cache files +FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\TEMP\PluginCache\*.*) DO DEL %%A + +echo Removing cache files and examine index +FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\TEMP\*.*) DO DEL %%A + +echo Removing log files +FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\Logs\*.*) DO DEL %%A + +echo Removing packages +FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\packages\*.*) DO DEL %%A + +echo Removing previews +FOR %%A IN (..\src\Umbraco.Web.UI\App_Data\preview\*.*) DO DEL %%A + +echo Removing app code files (typically added by starterkits) +FOR %%A IN (..\src\Umbraco.Web.UI\App_Code\*.*) DO DEL %%A + +echo Removing xslt files +FOR %%A IN (..\src\Umbraco.Web.UI\xslt\*.*) DO DEL %%A + +echo Removing masterpage files +FOR %%A IN (..\src\Umbraco.Web.UI\masterpages\*.*) DO DEL %%A + +echo Removing user control files +FOR %%A IN (..\src\Umbraco.Web.UI\usercontrols\*.*) DO DEL %%A + +echo Removing view files +ATTRIB +H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S +FOR %%A IN (..\src\Umbraco.Web.UI\Views\) DO DEL /Q /S *.cshtml -H +ATTRIB -H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S + +echo Removing razor files +FOR %%A IN (..\src\Umbraco.Web.UI\macroScripts\*.*) DO DEL %%A + +echo Removing media files +FOR %%A IN (..\src\Umbraco.Web.UI\media\*.*) DO DEL %%A + +echo Removing script files +FOR %%A IN (..\src\Umbraco.Web.UI\scripts\*.*) DO DEL %%A + +echo Removing css files +FOR %%A IN (..\src\Umbraco.Web.UI\css\*.*) DO DEL %%A + +echo Removing Courier files +del ..\src\Umbraco.Web.UI\config\courier.config +del ..\src\Umbraco.Web.UI\umbraco\images\tray\courier.jpg +rmdir "..\src\Umbraco.Web.UI\umbraco\plugins\courier\" /S /Q + +echo Removing Contour files +del ..\src\Umbraco.Web.UI\umbraco\images\tray\contour.png +FOR %%A IN (..\src\Umbraco.Web.UI\umbraco\images\umbraco\icon_*.*) DO DEL %%A +rmdir "..\src\Umbraco.Web.UI\umbraco\plugins\umbracoContour\" /S /Q +del ..\src\Umbraco.Web.UI\umbraco\xslt\templates\UmbracoContour*.* /S /Q +rmdir "..\src\Umbraco.Web.UI\usercontrols\umbracoContour\" /S /Q + +echo Start with a clean web.config +copy ..\src\Umbraco.Web.UI\web.Template.config ..\src\Umbraco.Web.UI\web.config /Y + +echo Start with a clean web.config +copy ..\src\Umbraco.Web.UI\web.Template.config ..\src\Umbraco.Web.UI\web.config /Y + +echo "Umbraco install reverted to clean install" +pause +exit + + + +:exit exit \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 610549bb10..e7eebee6cd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,51 +1,51 @@ _You are browsing the Umbraco v8 branch._ _Looking for Umbraco version 7? [Click here](https://github.com/umbraco/Umbraco-CMS) to go to the v7 branch._ - -_Ready to try out Version 8? [See the quick start guide](V8_GETTING_STARTED.md)._ - -Umbraco CMS -=========== -The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 443,000 websites worldwide: [https://umbraco.com](https://umbraco.com) - -[![ScreenShot](img/vimeo.png)](https://vimeo.com/172382998/) - -## Umbraco CMS -Umbraco is a free open source Content Management System built on the ASP.NET platform. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social. - -## Watch an introduction video - -[![ScreenShot](https://shop.umbraco.com/images/whatisumbraco.png)](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/) - -## Umbraco - The Friendly CMS - -For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. - -Umbraco is not only loved by developers, but is a content editors dream. Enjoy intuitive editing tools, media management, responsive views and approval workflows to send your content live. - -Used by more than 443,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 220,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay. - -To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies) - -## Why Open Source? -As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible and community contributions and packages are available for all to use. - -## Trying out Umbraco CMS - -[Umbraco Cloud](https://umbraco.com) is the easiest and fastest way to use Umbraco yet with full support for all your custom .NET code and intergrations. You're up and running in less than a minute and your life will be made easier with automated upgrades and a built-in deployment engine. We offer a free 14 day trial, no credit card needed. - -If you want to DIY you can [download Umbraco](https://our.umbraco.org/download) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host yourself and handling deployments and upgrades is all down to you. - -## Community - -Our friendly community is available 24/7 at the community hub we call ["Our Umbraco"](https://our.umbraco.org). Our Umbraco feature forums for questions and answers, documentation, downloadable plugins for Umbraco and a rich collection of community resources. - -## Contribute to Umbraco - -Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](CONTRIBUTING.md). - -## Found a bug? - -Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://our.umbraco.org/contribute/report-an-issue-or-request-a-feature). - -To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org). + +_Ready to try out Version 8? [See the quick start guide](V8_GETTING_STARTED.md)._ + +Umbraco CMS +=========== +The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 443,000 websites worldwide: [https://umbraco.com](https://umbraco.com) + +[![ScreenShot](img/vimeo.png)](https://vimeo.com/172382998/) + +## Umbraco CMS +Umbraco is a free open source Content Management System built on the ASP.NET platform. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social. + +## Watch an introduction video + +[![ScreenShot](https://shop.umbraco.com/images/whatisumbraco.png)](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/) + +## Umbraco - The Friendly CMS + +For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. + +Umbraco is not only loved by developers, but is a content editors dream. Enjoy intuitive editing tools, media management, responsive views and approval workflows to send your content live. + +Used by more than 443,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 220,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay. + +To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies) + +## Why Open Source? +As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible and community contributions and packages are available for all to use. + +## Trying out Umbraco CMS + +[Umbraco Cloud](https://umbraco.com) is the easiest and fastest way to use Umbraco yet with full support for all your custom .NET code and intergrations. You're up and running in less than a minute and your life will be made easier with automated upgrades and a built-in deployment engine. We offer a free 14 day trial, no credit card needed. + +If you want to DIY you can [download Umbraco](https://our.umbraco.org/download) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host yourself and handling deployments and upgrades is all down to you. + +## Community + +Our friendly community is available 24/7 at the community hub we call ["Our Umbraco"](https://our.umbraco.org). Our Umbraco feature forums for questions and answers, documentation, downloadable plugins for Umbraco and a rich collection of community resources. + +## Contribute to Umbraco + +Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](CONTRIBUTING.md). + +## Found a bug? + +Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://our.umbraco.org/contribute/report-an-issue-or-request-a-feature). + +To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org). diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index d59bbf04f4..77bd21b673 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -1,22 +1,22 @@ -using System.Reflection; -using System.Resources; - -[assembly: AssemblyCompany("Umbraco")] -[assembly: AssemblyCopyright("Copyright © Umbraco 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: NeutralResourcesLanguage("en-US")] - -// versions -// read https://stackoverflow.com/questions/64602/what-are-differences-between-assemblyversion-assemblyfileversion-and-assemblyin +using System.Reflection; +using System.Resources; + +[assembly: AssemblyCompany("Umbraco")] +[assembly: AssemblyCopyright("Copyright © Umbraco 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: NeutralResourcesLanguage("en-US")] + +// versions +// read https://stackoverflow.com/questions/64602/what-are-differences-between-assemblyversion-assemblyfileversion-and-assemblyin // note: do NOT change anything here manually, use the build scripts - -// this is the ONLY ONE the CLR cares about for compatibility -// should change ONLY when "hard" breaking compatibility (manual change) -[assembly: AssemblyVersion("8.0.0")] - -// these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.0.0")] -[assembly: AssemblyInformationalVersion("8.0.0-alpha.44")] + +// this is the ONLY ONE the CLR cares about for compatibility +// should change ONLY when "hard" breaking compatibility (manual change) +[assembly: AssemblyVersion("8.0.0")] + +// these are FYI and changed automatically +[assembly: AssemblyFileVersion("8.0.0")] +[assembly: AssemblyInformationalVersion("8.0.0-alpha.44")] diff --git a/src/Umbraco.Core/AssemblyExtensions.cs b/src/Umbraco.Core/AssemblyExtensions.cs index 6b77a09b73..7bc0d79b22 100644 --- a/src/Umbraco.Core/AssemblyExtensions.cs +++ b/src/Umbraco.Core/AssemblyExtensions.cs @@ -1,72 +1,72 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; - -namespace Umbraco.Core -{ - internal static class AssemblyExtensions - { - /// - /// Returns the file used to load the assembly - /// - /// - /// - public static FileInfo GetAssemblyFile(this Assembly assembly) - { - var codeBase = assembly.CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - return new FileInfo(path); - } - - /// - /// Returns true if the assembly is the App_Code assembly - /// - /// - /// - public static bool IsAppCodeAssembly(this Assembly assembly) - { - if (assembly.FullName.StartsWith("App_Code")) - { - try - { - Assembly.Load("App_Code"); - return true; - } - catch (FileNotFoundException) - { - //this will occur if it cannot load the assembly - return false; - } - } - return false; - } - - /// - /// Returns true if the assembly is the compiled global asax. - /// - /// - /// - public static bool IsGlobalAsaxAssembly(this Assembly assembly) - { - //only way I can figure out how to test is by the name - return assembly.FullName.StartsWith("App_global.asax"); - } - - /// - /// Returns the file used to load the assembly - /// - /// - /// - public static FileInfo GetAssemblyFile(this AssemblyName assemblyName) - { - var codeBase = assemblyName.CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - return new FileInfo(path); - } - - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Umbraco.Core +{ + internal static class AssemblyExtensions + { + /// + /// Returns the file used to load the assembly + /// + /// + /// + public static FileInfo GetAssemblyFile(this Assembly assembly) + { + var codeBase = assembly.CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + return new FileInfo(path); + } + + /// + /// Returns true if the assembly is the App_Code assembly + /// + /// + /// + public static bool IsAppCodeAssembly(this Assembly assembly) + { + if (assembly.FullName.StartsWith("App_Code")) + { + try + { + Assembly.Load("App_Code"); + return true; + } + catch (FileNotFoundException) + { + //this will occur if it cannot load the assembly + return false; + } + } + return false; + } + + /// + /// Returns true if the assembly is the compiled global asax. + /// + /// + /// + public static bool IsGlobalAsaxAssembly(this Assembly assembly) + { + //only way I can figure out how to test is by the name + return assembly.FullName.StartsWith("App_global.asax"); + } + + /// + /// Returns the file used to load the assembly + /// + /// + /// + public static FileInfo GetAssemblyFile(this AssemblyName assemblyName) + { + var codeBase = assemblyName.CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + return new FileInfo(path); + } + + } +} diff --git a/src/Umbraco.Core/Attempt.cs b/src/Umbraco.Core/Attempt.cs index f1b6d2e636..5871626505 100644 --- a/src/Umbraco.Core/Attempt.cs +++ b/src/Umbraco.Core/Attempt.cs @@ -1,126 +1,126 @@ -using System; - -namespace Umbraco.Core -{ - /// - /// Provides ways to create attempts. - /// - public static class Attempt - { - // note: - // cannot rely on overloads only to differenciate between with/without status - // in some cases it will always be ambiguous, so be explicit w/ 'WithStatus' methods - - /// - /// Creates a successful attempt with a result. - /// - /// The type of the attempted operation result. - /// The result of the attempt. - /// The successful attempt. - public static Attempt Succeed(TResult result) - { - return Attempt.Succeed(result); - } - - /// - /// Creates a successful attempt with a result and a status. - /// - /// The type of the attempted operation result. - /// The type of the attempted operation status. - /// The status of the attempt. - /// The result of the attempt. - /// The successful attempt. - public static Attempt SucceedWithStatus(TStatus status, TResult result) - { - return Attempt.Succeed(status, result); - } - - /// - /// Creates a failed attempt. - /// - /// The type of the attempted operation result. - /// The failed attempt. - public static Attempt Fail() - { - return Attempt.Fail(); - } - - /// - /// Creates a failed attempt with a result. - /// - /// The type of the attempted operation result. - /// The result of the attempt. - /// The failed attempt. - public static Attempt Fail(TResult result) - { - return Attempt.Fail(result); - } - - /// - /// Creates a failed attempt with a result and a status. - /// - /// The type of the attempted operation result. - /// The type of the attempted operation status. - /// The status of the attempt. - /// The result of the attempt. - /// The failed attempt. - public static Attempt FailWithStatus(TStatus status, TResult result) - { - return Attempt.Fail(status, result); - } - - /// - /// Creates a failed attempt with a result and an exception. - /// - /// The type of the attempted operation result. - /// The result of the attempt. - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt Fail(TResult result, Exception exception) - { - return Attempt.Fail(result, exception); - } - - - /// - /// Creates a failed attempt with a result, an exception and a status. - /// - /// The type of the attempted operation result. - /// The type of the attempted operation status. - /// The status of the attempt. - /// The result of the attempt. - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt FailWithStatus(TStatus status, TResult result, Exception exception) - { - return Attempt.Fail(status, result, exception); - } - - /// - /// Creates a successful or a failed attempt, with a result. - /// - /// The type of the attempted operation result. - /// A value indicating whether the attempt is successful. - /// The result of the attempt. - /// The attempt. - public static Attempt If(bool condition, TResult result) - { - return Attempt.If(condition, result); - } - - /// - /// Creates a successful or a failed attempt, with a result. - /// - /// The type of the attempted operation result. - /// The type of the attempted operation status. - /// A value indicating whether the attempt is successful. - /// The status of the successful attempt. - /// The status of the failed attempt. - /// The result of the attempt. - /// The attempt. - public static Attempt IfWithStatus(bool condition, TStatus succStatus, TStatus failStatus, TResult result) - { - return Attempt.If(condition, succStatus, failStatus, result); - } - } -} +using System; + +namespace Umbraco.Core +{ + /// + /// Provides ways to create attempts. + /// + public static class Attempt + { + // note: + // cannot rely on overloads only to differenciate between with/without status + // in some cases it will always be ambiguous, so be explicit w/ 'WithStatus' methods + + /// + /// Creates a successful attempt with a result. + /// + /// The type of the attempted operation result. + /// The result of the attempt. + /// The successful attempt. + public static Attempt Succeed(TResult result) + { + return Attempt.Succeed(result); + } + + /// + /// Creates a successful attempt with a result and a status. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// The status of the attempt. + /// The result of the attempt. + /// The successful attempt. + public static Attempt SucceedWithStatus(TStatus status, TResult result) + { + return Attempt.Succeed(status, result); + } + + /// + /// Creates a failed attempt. + /// + /// The type of the attempted operation result. + /// The failed attempt. + public static Attempt Fail() + { + return Attempt.Fail(); + } + + /// + /// Creates a failed attempt with a result. + /// + /// The type of the attempted operation result. + /// The result of the attempt. + /// The failed attempt. + public static Attempt Fail(TResult result) + { + return Attempt.Fail(result); + } + + /// + /// Creates a failed attempt with a result and a status. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// The status of the attempt. + /// The result of the attempt. + /// The failed attempt. + public static Attempt FailWithStatus(TStatus status, TResult result) + { + return Attempt.Fail(status, result); + } + + /// + /// Creates a failed attempt with a result and an exception. + /// + /// The type of the attempted operation result. + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(TResult result, Exception exception) + { + return Attempt.Fail(result, exception); + } + + + /// + /// Creates a failed attempt with a result, an exception and a status. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// The status of the attempt. + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt FailWithStatus(TStatus status, TResult result, Exception exception) + { + return Attempt.Fail(status, result, exception); + } + + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// The type of the attempted operation result. + /// A value indicating whether the attempt is successful. + /// The result of the attempt. + /// The attempt. + public static Attempt If(bool condition, TResult result) + { + return Attempt.If(condition, result); + } + + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// A value indicating whether the attempt is successful. + /// The status of the successful attempt. + /// The status of the failed attempt. + /// The result of the attempt. + /// The attempt. + public static Attempt IfWithStatus(bool condition, TStatus succStatus, TStatus failStatus, TResult result) + { + return Attempt.If(condition, succStatus, failStatus, result); + } + } +} diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index bba5655b7a..1058fa6181 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -1,51 +1,51 @@ -using System; -using System.ComponentModel; -using Umbraco.Core.CodeAnnotations; - -namespace Umbraco.Core.Cache -{ - /// - /// Constants storing cache keys used in caching - /// - public static class CacheKeys - { - public const string ApplicationTreeCacheKey = "ApplicationTreeCache"; - public const string ApplicationsCacheKey = "ApplicationCache"; - - [Obsolete("This is no longer used and will be removed from the codebase in the future")] - [EditorBrowsable(EditorBrowsableState.Never)] - public const string UserTypeCacheKey = "UserTypeCache"; - - [Obsolete("This is no longer used and will be removed from the codebase in the future - it is referenced but no cache is stored against this key")] - [EditorBrowsable(EditorBrowsableState.Never)] - public const string ContentItemCacheKey = "contentItem"; - - [UmbracoWillObsolete("This cache key is only used for the legacy 'library' caching, remove in v8")] - public const string MediaCacheKey = "UL_GetMedia"; - - [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] - public const string MacroCacheKey = "UmbracoMacroCache"; - - public const string MacroContentCacheKey = "macroContent_"; // for macro contents - - [UmbracoWillObsolete("This cache key is only used for legacy 'library' member caching, remove in v8")] - public const string MemberLibraryCacheKey = "UL_GetMember"; - - [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] - public const string MemberBusinessLogicCacheKey = "MemberCacheItem_"; - - [UmbracoWillObsolete("This cache key is only used for legacy template business logic caching, remove in v8")] - public const string TemplateFrontEndCacheKey = "template"; - - public const string UserContextTimeoutCacheKey = "UmbracoUserContextTimeout"; - - [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] - public const string ContentTypeCacheKey = "UmbracoContentType"; - - [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] - public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:"; - - public const string IdToKeyCacheKey = "UI2K__"; - public const string KeyToIdCacheKey = "UK2I__"; - } -} +using System; +using System.ComponentModel; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Core.Cache +{ + /// + /// Constants storing cache keys used in caching + /// + public static class CacheKeys + { + public const string ApplicationTreeCacheKey = "ApplicationTreeCache"; + public const string ApplicationsCacheKey = "ApplicationCache"; + + [Obsolete("This is no longer used and will be removed from the codebase in the future")] + [EditorBrowsable(EditorBrowsableState.Never)] + public const string UserTypeCacheKey = "UserTypeCache"; + + [Obsolete("This is no longer used and will be removed from the codebase in the future - it is referenced but no cache is stored against this key")] + [EditorBrowsable(EditorBrowsableState.Never)] + public const string ContentItemCacheKey = "contentItem"; + + [UmbracoWillObsolete("This cache key is only used for the legacy 'library' caching, remove in v8")] + public const string MediaCacheKey = "UL_GetMedia"; + + [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] + public const string MacroCacheKey = "UmbracoMacroCache"; + + public const string MacroContentCacheKey = "macroContent_"; // for macro contents + + [UmbracoWillObsolete("This cache key is only used for legacy 'library' member caching, remove in v8")] + public const string MemberLibraryCacheKey = "UL_GetMember"; + + [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] + public const string MemberBusinessLogicCacheKey = "MemberCacheItem_"; + + [UmbracoWillObsolete("This cache key is only used for legacy template business logic caching, remove in v8")] + public const string TemplateFrontEndCacheKey = "template"; + + public const string UserContextTimeoutCacheKey = "UmbracoUserContextTimeout"; + + [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] + public const string ContentTypeCacheKey = "UmbracoContentType"; + + [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] + public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:"; + + public const string IdToKeyCacheKey = "UI2K__"; + public const string KeyToIdCacheKey = "UK2I__"; + } +} diff --git a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs index a3d55eb25f..e42cb4d9ad 100644 --- a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs +++ b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs @@ -1,70 +1,70 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Caching; - -namespace Umbraco.Core.Cache -{ - /// - /// Extensions for strongly typed access - /// - public static class CacheProviderExtensions - { - public static T GetCacheItem(this IRuntimeCacheProvider provider, - string cacheKey, - Func getCacheItem, - TimeSpan? timeout, - bool isSliding = false, - CacheItemPriority priority = CacheItemPriority.Normal, - CacheItemRemovedCallback removedCallback = null, - string[] dependentFiles = null) - { - var result = provider.GetCacheItem(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles); - return result == null ? default(T) : result.TryConvertTo().Result; - } - - public static void InsertCacheItem(this IRuntimeCacheProvider provider, - string cacheKey, - Func getCacheItem, - TimeSpan? timeout = null, - bool isSliding = false, - CacheItemPriority priority = CacheItemPriority.Normal, - CacheItemRemovedCallback removedCallback = null, - string[] dependentFiles = null) - { - provider.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles); - } - - public static IEnumerable GetCacheItemsByKeySearch(this ICacheProvider provider, string keyStartsWith) - { - var result = provider.GetCacheItemsByKeySearch(keyStartsWith); - return result.Select(x => x.TryConvertTo().Result); - } - - public static IEnumerable GetCacheItemsByKeyExpression(this ICacheProvider provider, string regexString) - { - var result = provider.GetCacheItemsByKeyExpression(regexString); - return result.Select(x => x.TryConvertTo().Result); - } - - public static T GetCacheItem(this ICacheProvider provider, string cacheKey) - { - var result = provider.GetCacheItem(cacheKey); - if (result == null) - { - return default(T); - } - return result.TryConvertTo().Result; - } - - public static T GetCacheItem(this ICacheProvider provider, string cacheKey, Func getCacheItem) - { - var result = provider.GetCacheItem(cacheKey, () => getCacheItem()); - if (result == null) - { - return default(T); - } - return result.TryConvertTo().Result; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Caching; + +namespace Umbraco.Core.Cache +{ + /// + /// Extensions for strongly typed access + /// + public static class CacheProviderExtensions + { + public static T GetCacheItem(this IRuntimeCacheProvider provider, + string cacheKey, + Func getCacheItem, + TimeSpan? timeout, + bool isSliding = false, + CacheItemPriority priority = CacheItemPriority.Normal, + CacheItemRemovedCallback removedCallback = null, + string[] dependentFiles = null) + { + var result = provider.GetCacheItem(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles); + return result == null ? default(T) : result.TryConvertTo().Result; + } + + public static void InsertCacheItem(this IRuntimeCacheProvider provider, + string cacheKey, + Func getCacheItem, + TimeSpan? timeout = null, + bool isSliding = false, + CacheItemPriority priority = CacheItemPriority.Normal, + CacheItemRemovedCallback removedCallback = null, + string[] dependentFiles = null) + { + provider.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles); + } + + public static IEnumerable GetCacheItemsByKeySearch(this ICacheProvider provider, string keyStartsWith) + { + var result = provider.GetCacheItemsByKeySearch(keyStartsWith); + return result.Select(x => x.TryConvertTo().Result); + } + + public static IEnumerable GetCacheItemsByKeyExpression(this ICacheProvider provider, string regexString) + { + var result = provider.GetCacheItemsByKeyExpression(regexString); + return result.Select(x => x.TryConvertTo().Result); + } + + public static T GetCacheItem(this ICacheProvider provider, string cacheKey) + { + var result = provider.GetCacheItem(cacheKey); + if (result == null) + { + return default(T); + } + return result.TryConvertTo().Result; + } + + public static T GetCacheItem(this ICacheProvider provider, string cacheKey, Func getCacheItem) + { + var result = provider.GetCacheItem(cacheKey, () => getCacheItem()); + if (result == null) + { + return default(T); + } + return result.TryConvertTo().Result; + } + } +} diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs index dbb0c6cc81..7242ab225e 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs @@ -1,120 +1,120 @@ -using System; -using Umbraco.Core.Events; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Sync; - -namespace Umbraco.Core.Cache -{ - /// - /// A base class for cache refreshers that handles events. - /// - /// The actual cache refresher type. - /// The actual cache refresher type is used for strongly typed events. - public abstract class CacheRefresherBase : ICacheRefresher - where TInstanceType : class, ICacheRefresher - { - /// - /// Initializes a new instance of the . - /// - /// A cache helper. - protected CacheRefresherBase(CacheHelper cacheHelper) - { - CacheHelper = cacheHelper; - } - - /// - /// Triggers when the cache is updated on the server. - /// - /// - /// Triggers on each server configured for an Umbraco project whenever a cache refresher is updated. - /// - public static event TypedEventHandler CacheUpdated; - - #region Define - - /// - /// Gets the typed 'this' for events. - /// - protected abstract TInstanceType This { get; } - - /// - /// Gets the unique identifier of the refresher. - /// - public abstract Guid RefresherUniqueId { get; } - - /// - /// Gets the name of the refresher. - /// - public abstract string Name { get; } - - #endregion - - #region Refresher - - /// - /// Refreshes all entities. - /// - public virtual void RefreshAll() - { - OnCacheUpdated(This, new CacheRefresherEventArgs(null, MessageType.RefreshAll)); - } - - /// - /// Refreshes an entity. - /// - /// The entity's identifier. - public virtual void Refresh(int id) - { - OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RefreshById)); - } - - /// - /// Refreshes an entity. - /// - /// The entity's identifier. - public virtual void Refresh(Guid id) - { - OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RefreshById)); - } - - /// - /// Removes an entity. - /// - /// The entity's identifier. - public virtual void Remove(int id) - { - OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RemoveById)); - } - - #endregion - - #region Protected - - /// - /// Gets the cache helper. - /// - protected CacheHelper CacheHelper { get; } - - /// - /// Clears the cache for all repository entities of a specified type. - /// - /// The type of the entities. - protected void ClearAllIsolatedCacheByEntityType() - where TEntity : class, IEntity - { - CacheHelper.IsolatedRuntimeCache.ClearCache(); - } - - /// - /// Raises the CacheUpdated event. - /// - /// The event sender. - /// The event arguments. - protected static void OnCacheUpdated(TInstanceType sender, CacheRefresherEventArgs args) - { - CacheUpdated?.Invoke(sender, args); - } - - #endregion - } -} +using System; +using Umbraco.Core.Events; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Sync; + +namespace Umbraco.Core.Cache +{ + /// + /// A base class for cache refreshers that handles events. + /// + /// The actual cache refresher type. + /// The actual cache refresher type is used for strongly typed events. + public abstract class CacheRefresherBase : ICacheRefresher + where TInstanceType : class, ICacheRefresher + { + /// + /// Initializes a new instance of the . + /// + /// A cache helper. + protected CacheRefresherBase(CacheHelper cacheHelper) + { + CacheHelper = cacheHelper; + } + + /// + /// Triggers when the cache is updated on the server. + /// + /// + /// Triggers on each server configured for an Umbraco project whenever a cache refresher is updated. + /// + public static event TypedEventHandler CacheUpdated; + + #region Define + + /// + /// Gets the typed 'this' for events. + /// + protected abstract TInstanceType This { get; } + + /// + /// Gets the unique identifier of the refresher. + /// + public abstract Guid RefresherUniqueId { get; } + + /// + /// Gets the name of the refresher. + /// + public abstract string Name { get; } + + #endregion + + #region Refresher + + /// + /// Refreshes all entities. + /// + public virtual void RefreshAll() + { + OnCacheUpdated(This, new CacheRefresherEventArgs(null, MessageType.RefreshAll)); + } + + /// + /// Refreshes an entity. + /// + /// The entity's identifier. + public virtual void Refresh(int id) + { + OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RefreshById)); + } + + /// + /// Refreshes an entity. + /// + /// The entity's identifier. + public virtual void Refresh(Guid id) + { + OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RefreshById)); + } + + /// + /// Removes an entity. + /// + /// The entity's identifier. + public virtual void Remove(int id) + { + OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RemoveById)); + } + + #endregion + + #region Protected + + /// + /// Gets the cache helper. + /// + protected CacheHelper CacheHelper { get; } + + /// + /// Clears the cache for all repository entities of a specified type. + /// + /// The type of the entities. + protected void ClearAllIsolatedCacheByEntityType() + where TEntity : class, IEntity + { + CacheHelper.IsolatedRuntimeCache.ClearCache(); + } + + /// + /// Raises the CacheUpdated event. + /// + /// The event sender. + /// The event arguments. + protected static void OnCacheUpdated(TInstanceType sender, CacheRefresherEventArgs args) + { + CacheUpdated?.Invoke(sender, args); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Cache/CacheRefresherEventArgs.cs b/src/Umbraco.Core/Cache/CacheRefresherEventArgs.cs index b7ae0477a6..7dea4229ab 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherEventArgs.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherEventArgs.cs @@ -1,19 +1,19 @@ -using System; -using Umbraco.Core.Sync; - -namespace Umbraco.Core.Cache -{ - /// - /// Event args for cache refresher updates - /// - public class CacheRefresherEventArgs : EventArgs - { - public CacheRefresherEventArgs(object msgObject, MessageType type) - { - MessageType = type; - MessageObject = msgObject; - } - public object MessageObject { get; private set; } - public MessageType MessageType { get; private set; } - } -} +using System; +using Umbraco.Core.Sync; + +namespace Umbraco.Core.Cache +{ + /// + /// Event args for cache refresher updates + /// + public class CacheRefresherEventArgs : EventArgs + { + public CacheRefresherEventArgs(object msgObject, MessageType type) + { + MessageType = type; + MessageObject = msgObject; + } + public object MessageObject { get; private set; } + public MessageType MessageType { get; private set; } + } +} diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs index 9da202b5aa..f556d47a8e 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs @@ -1,313 +1,313 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.ExceptionServices; -using System.Text.RegularExpressions; -using Umbraco.Core.Composing; - -namespace Umbraco.Core.Cache -{ - internal abstract class DictionaryCacheProviderBase : ICacheProvider - { - // prefix cache keys so we know which one are ours - protected const string CacheItemPrefix = "umbrtmche"; - - // an object that represent a value that has not been created yet - protected internal static readonly object ValueNotCreated = new object(); - - // manupulate the underlying cache entries - // these *must* be called from within the appropriate locks - // and use the full prefixed cache keys - protected abstract IEnumerable GetDictionaryEntries(); - protected abstract void RemoveEntry(string key); - protected abstract object GetEntry(string key); - - // read-write lock the underlying cache - //protected abstract IDisposable ReadLock { get; } - //protected abstract IDisposable WriteLock { get; } - - protected abstract void EnterReadLock(); - protected abstract void ExitReadLock(); - protected abstract void EnterWriteLock(); - protected abstract void ExitWriteLock(); - - protected string GetCacheKey(string key) - { - return string.Format("{0}-{1}", CacheItemPrefix, key); - } - - protected internal static Lazy GetSafeLazy(Func getCacheItem) - { - // try to generate the value and if it fails, - // wrap in an ExceptionHolder - would be much simpler - // to just use lazy.IsValueFaulted alas that field is - // internal - return new Lazy(() => - { - try - { - return getCacheItem(); - } - catch (Exception e) - { - return new ExceptionHolder(ExceptionDispatchInfo.Capture(e)); - } - }); - } - - protected internal static object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false) - { - // if onlyIfValueIsCreated, do not trigger value creation - // must return something, though, to differenciate from null values - if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated; - - // if execution has thrown then lazy.IsValueCreated is false - // and lazy.IsValueFaulted is true (but internal) so we use our - // own exception holder (see Lazy source code) to return null - if (lazy.Value is ExceptionHolder) return null; - - // we have a value and execution has not thrown so returning - // here does not throw - unless we're re-entering, take care of it - try - { - return lazy.Value; - } - catch (InvalidOperationException e) - { - throw new InvalidOperationException("The method that computes a value for the cache has tried to read that value from the cache.", e); - } - } - - internal class ExceptionHolder - { - public ExceptionHolder(ExceptionDispatchInfo e) - { - Exception = e; - } - - public ExceptionDispatchInfo Exception { get; } - } - - #region Clear - - public virtual void ClearAllCache() - { - try - { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries() - .ToArray()) - RemoveEntry((string) entry.Key); - } - finally - { - ExitWriteLock(); - } - } - - public virtual void ClearCacheItem(string key) - { - var cacheKey = GetCacheKey(key); - try - { - EnterWriteLock(); - RemoveEntry(cacheKey); - } - finally - { - ExitWriteLock(); - } - } - - public virtual void ClearCacheObjectTypes(string typeName) - { - var type = TypeFinder.GetTypeByName(typeName); - if (type == null) return; - var isInterface = type.IsInterface; - try - { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries() - .Where(x => - { - // entry.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // get non-created as NonCreatedValue & exceptions as null - var value = GetSafeLazyValue((Lazy) x.Value, true); - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); - }) - .ToArray()) - RemoveEntry((string) entry.Key); - } - finally - { - ExitWriteLock(); - } - } - - public virtual void ClearCacheObjectTypes() - { - var typeOfT = typeof(T); - var isInterface = typeOfT.IsInterface; - try - { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries() - .Where(x => - { - // entry.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // compare on exact type, don't use "is" - // get non-created as NonCreatedValue & exceptions as null - var value = GetSafeLazyValue((Lazy) x.Value, true); - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); - }) - .ToArray()) - RemoveEntry((string) entry.Key); - } - finally - { - ExitWriteLock(); - } - } - - public virtual void ClearCacheObjectTypes(Func predicate) - { - var typeOfT = typeof(T); - var isInterface = typeOfT.IsInterface; - var plen = CacheItemPrefix.Length + 1; - try - { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries() - .Where(x => - { - // entry.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // compare on exact type, don't use "is" - // get non-created as NonCreatedValue & exceptions as null - var value = GetSafeLazyValue((Lazy) x.Value, true); - if (value == null) return true; - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return (isInterface ? (value is T) : (value.GetType() == typeOfT)) - // run predicate on the 'public key' part only, ie without prefix - && predicate(((string) x.Key).Substring(plen), (T) value); - })) - RemoveEntry((string) entry.Key); - } - finally - { - ExitWriteLock(); - } - } - - public virtual void ClearCacheByKeySearch(string keyStartsWith) - { - var plen = CacheItemPrefix.Length + 1; - try - { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries() - .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) - .ToArray()) - RemoveEntry((string) entry.Key); - } - finally - { - ExitWriteLock(); - } - } - - public virtual void ClearCacheByKeyExpression(string regexString) - { - var plen = CacheItemPrefix.Length + 1; - try - { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries() - .Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString)) - .ToArray()) - RemoveEntry((string) entry.Key); - } - finally - { - ExitWriteLock(); - } - } - - #endregion - - #region Get - - public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - var plen = CacheItemPrefix.Length + 1; - IEnumerable entries; - try - { - EnterReadLock(); - entries = GetDictionaryEntries() - .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) - .ToArray(); // evaluate while locked - } - finally - { - ExitReadLock(); - } - - return entries - .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null - .Where(x => x != null); // backward compat, don't store null values in the cache - } - - public virtual IEnumerable GetCacheItemsByKeyExpression(string regexString) - { - const string prefix = CacheItemPrefix + "-"; - var plen = prefix.Length; - IEnumerable entries; - try - { - EnterReadLock(); - entries = GetDictionaryEntries() - .Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString)) - .ToArray(); // evaluate while locked - } - finally - { - ExitReadLock(); - } - return entries - .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null - .Where(x => x != null); // backward compat, don't store null values in the cache - } - - public virtual object GetCacheItem(string cacheKey) - { - cacheKey = GetCacheKey(cacheKey); - Lazy result; - try - { - EnterReadLock(); - result = GetEntry(cacheKey) as Lazy; // null if key not found - } - finally - { - ExitReadLock(); - } - return result == null ? null : GetSafeLazyValue(result); // return exceptions as null - } - - public abstract object GetCacheItem(string cacheKey, Func getCacheItem); - - #endregion - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.ExceptionServices; +using System.Text.RegularExpressions; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Cache +{ + internal abstract class DictionaryCacheProviderBase : ICacheProvider + { + // prefix cache keys so we know which one are ours + protected const string CacheItemPrefix = "umbrtmche"; + + // an object that represent a value that has not been created yet + protected internal static readonly object ValueNotCreated = new object(); + + // manupulate the underlying cache entries + // these *must* be called from within the appropriate locks + // and use the full prefixed cache keys + protected abstract IEnumerable GetDictionaryEntries(); + protected abstract void RemoveEntry(string key); + protected abstract object GetEntry(string key); + + // read-write lock the underlying cache + //protected abstract IDisposable ReadLock { get; } + //protected abstract IDisposable WriteLock { get; } + + protected abstract void EnterReadLock(); + protected abstract void ExitReadLock(); + protected abstract void EnterWriteLock(); + protected abstract void ExitWriteLock(); + + protected string GetCacheKey(string key) + { + return string.Format("{0}-{1}", CacheItemPrefix, key); + } + + protected internal static Lazy GetSafeLazy(Func getCacheItem) + { + // try to generate the value and if it fails, + // wrap in an ExceptionHolder - would be much simpler + // to just use lazy.IsValueFaulted alas that field is + // internal + return new Lazy(() => + { + try + { + return getCacheItem(); + } + catch (Exception e) + { + return new ExceptionHolder(ExceptionDispatchInfo.Capture(e)); + } + }); + } + + protected internal static object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false) + { + // if onlyIfValueIsCreated, do not trigger value creation + // must return something, though, to differenciate from null values + if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated; + + // if execution has thrown then lazy.IsValueCreated is false + // and lazy.IsValueFaulted is true (but internal) so we use our + // own exception holder (see Lazy source code) to return null + if (lazy.Value is ExceptionHolder) return null; + + // we have a value and execution has not thrown so returning + // here does not throw - unless we're re-entering, take care of it + try + { + return lazy.Value; + } + catch (InvalidOperationException e) + { + throw new InvalidOperationException("The method that computes a value for the cache has tried to read that value from the cache.", e); + } + } + + internal class ExceptionHolder + { + public ExceptionHolder(ExceptionDispatchInfo e) + { + Exception = e; + } + + public ExceptionDispatchInfo Exception { get; } + } + + #region Clear + + public virtual void ClearAllCache() + { + try + { + EnterWriteLock(); + foreach (var entry in GetDictionaryEntries() + .ToArray()) + RemoveEntry((string) entry.Key); + } + finally + { + ExitWriteLock(); + } + } + + public virtual void ClearCacheItem(string key) + { + var cacheKey = GetCacheKey(key); + try + { + EnterWriteLock(); + RemoveEntry(cacheKey); + } + finally + { + ExitWriteLock(); + } + } + + public virtual void ClearCacheObjectTypes(string typeName) + { + var type = TypeFinder.GetTypeByName(typeName); + if (type == null) return; + var isInterface = type.IsInterface; + try + { + EnterWriteLock(); + foreach (var entry in GetDictionaryEntries() + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // get non-created as NonCreatedValue & exceptions as null + var value = GetSafeLazyValue((Lazy) x.Value, true); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); + }) + .ToArray()) + RemoveEntry((string) entry.Key); + } + finally + { + ExitWriteLock(); + } + } + + public virtual void ClearCacheObjectTypes() + { + var typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; + try + { + EnterWriteLock(); + foreach (var entry in GetDictionaryEntries() + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // compare on exact type, don't use "is" + // get non-created as NonCreatedValue & exceptions as null + var value = GetSafeLazyValue((Lazy) x.Value, true); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); + }) + .ToArray()) + RemoveEntry((string) entry.Key); + } + finally + { + ExitWriteLock(); + } + } + + public virtual void ClearCacheObjectTypes(Func predicate) + { + var typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; + var plen = CacheItemPrefix.Length + 1; + try + { + EnterWriteLock(); + foreach (var entry in GetDictionaryEntries() + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // compare on exact type, don't use "is" + // get non-created as NonCreatedValue & exceptions as null + var value = GetSafeLazyValue((Lazy) x.Value, true); + if (value == null) return true; + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return (isInterface ? (value is T) : (value.GetType() == typeOfT)) + // run predicate on the 'public key' part only, ie without prefix + && predicate(((string) x.Key).Substring(plen), (T) value); + })) + RemoveEntry((string) entry.Key); + } + finally + { + ExitWriteLock(); + } + } + + public virtual void ClearCacheByKeySearch(string keyStartsWith) + { + var plen = CacheItemPrefix.Length + 1; + try + { + EnterWriteLock(); + foreach (var entry in GetDictionaryEntries() + .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) + .ToArray()) + RemoveEntry((string) entry.Key); + } + finally + { + ExitWriteLock(); + } + } + + public virtual void ClearCacheByKeyExpression(string regexString) + { + var plen = CacheItemPrefix.Length + 1; + try + { + EnterWriteLock(); + foreach (var entry in GetDictionaryEntries() + .Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString)) + .ToArray()) + RemoveEntry((string) entry.Key); + } + finally + { + ExitWriteLock(); + } + } + + #endregion + + #region Get + + public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + var plen = CacheItemPrefix.Length + 1; + IEnumerable entries; + try + { + EnterReadLock(); + entries = GetDictionaryEntries() + .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) + .ToArray(); // evaluate while locked + } + finally + { + ExitReadLock(); + } + + return entries + .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null); // backward compat, don't store null values in the cache + } + + public virtual IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + const string prefix = CacheItemPrefix + "-"; + var plen = prefix.Length; + IEnumerable entries; + try + { + EnterReadLock(); + entries = GetDictionaryEntries() + .Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString)) + .ToArray(); // evaluate while locked + } + finally + { + ExitReadLock(); + } + return entries + .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null); // backward compat, don't store null values in the cache + } + + public virtual object GetCacheItem(string cacheKey) + { + cacheKey = GetCacheKey(cacheKey); + Lazy result; + try + { + EnterReadLock(); + result = GetEntry(cacheKey) as Lazy; // null if key not found + } + finally + { + ExitReadLock(); + } + return result == null ? null : GetSafeLazyValue(result); // return exceptions as null + } + + public abstract object GetCacheItem(string cacheKey, Func getCacheItem); + + #endregion + } +} diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs index 5554655d83..52c230ff71 100644 --- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs @@ -1,161 +1,161 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Web; - -namespace Umbraco.Core.Cache -{ - /// - /// A cache provider that caches items in the HttpContext.Items - /// - /// - /// If the Items collection is null, then this provider has no effect - /// - internal class HttpRequestCacheProvider : DictionaryCacheProviderBase - { - // context provider - // the idea is that there is only one, application-wide HttpRequestCacheProvider instance, - // that is initialized with a method that returns the "current" context. - // NOTE - // but then it is initialized with () => new HttpContextWrapper(HttpContent.Current) - // which is higly inefficient because it creates a new wrapper each time we refer to _context() - // so replace it with _context1 and _context2 below + a way to get context.Items. - //private readonly Func _context; - - // NOTE - // and then in almost 100% cases _context2 will be () => HttpContext.Current - // so why not bring that logic in here and fallback on to HttpContext.Current when - // _context1 is null? - //private readonly HttpContextBase _context1; - //private readonly Func _context2; - private readonly HttpContextBase _context; - - private IDictionary ContextItems - { - //get { return _context1 != null ? _context1.Items : _context2().Items; } - get { return _context != null ? _context.Items : HttpContext.Current.Items; } - } - - private bool HasContextItems - { - get { return (_context != null && _context.Items != null) || HttpContext.Current != null; } - } - - // for unit tests - public HttpRequestCacheProvider(HttpContextBase context) - { - _context = context; - } - - // main constructor - // will use HttpContext.Current - public HttpRequestCacheProvider(/*Func context*/) - { - //_context2 = context; - } - - protected override IEnumerable GetDictionaryEntries() - { - const string prefix = CacheItemPrefix + "-"; - - if (HasContextItems == false) return Enumerable.Empty(); - - return ContextItems.Cast() - .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix)); - } - - protected override void RemoveEntry(string key) - { - if (HasContextItems == false) return; - - ContextItems.Remove(key); - } - - protected override object GetEntry(string key) - { - return HasContextItems ? ContextItems[key] : null; - } - - #region Lock - +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Umbraco.Core.Cache +{ + /// + /// A cache provider that caches items in the HttpContext.Items + /// + /// + /// If the Items collection is null, then this provider has no effect + /// + internal class HttpRequestCacheProvider : DictionaryCacheProviderBase + { + // context provider + // the idea is that there is only one, application-wide HttpRequestCacheProvider instance, + // that is initialized with a method that returns the "current" context. + // NOTE + // but then it is initialized with () => new HttpContextWrapper(HttpContent.Current) + // which is higly inefficient because it creates a new wrapper each time we refer to _context() + // so replace it with _context1 and _context2 below + a way to get context.Items. + //private readonly Func _context; + + // NOTE + // and then in almost 100% cases _context2 will be () => HttpContext.Current + // so why not bring that logic in here and fallback on to HttpContext.Current when + // _context1 is null? + //private readonly HttpContextBase _context1; + //private readonly Func _context2; + private readonly HttpContextBase _context; + + private IDictionary ContextItems + { + //get { return _context1 != null ? _context1.Items : _context2().Items; } + get { return _context != null ? _context.Items : HttpContext.Current.Items; } + } + + private bool HasContextItems + { + get { return (_context != null && _context.Items != null) || HttpContext.Current != null; } + } + + // for unit tests + public HttpRequestCacheProvider(HttpContextBase context) + { + _context = context; + } + + // main constructor + // will use HttpContext.Current + public HttpRequestCacheProvider(/*Func context*/) + { + //_context2 = context; + } + + protected override IEnumerable GetDictionaryEntries() + { + const string prefix = CacheItemPrefix + "-"; + + if (HasContextItems == false) return Enumerable.Empty(); + + return ContextItems.Cast() + .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix)); + } + + protected override void RemoveEntry(string key) + { + if (HasContextItems == false) return; + + ContextItems.Remove(key); + } + + protected override object GetEntry(string key) + { + return HasContextItems ? ContextItems[key] : null; + } + + #region Lock + private bool _entered; - - protected override void EnterReadLock() => EnterWriteLock(); - - protected override void EnterWriteLock() - { - if (HasContextItems) - { - System.Threading.Monitor.Enter(ContextItems.SyncRoot, ref _entered); - } - } - - protected override void ExitReadLock() => ExitWriteLock(); - - protected override void ExitWriteLock() - { - if (_entered) - { - _entered = false; - System.Threading.Monitor.Exit(ContextItems.SyncRoot); - } - } - - #endregion - - #region Get - - public override object GetCacheItem(string cacheKey, Func getCacheItem) - { - //no place to cache so just return the callback result - if (HasContextItems == false) return getCacheItem(); - - cacheKey = GetCacheKey(cacheKey); - - Lazy result; - - try - { - EnterWriteLock(); - result = ContextItems[cacheKey] as Lazy; // null if key not found - - // cannot create value within the lock, so if result.IsValueCreated is false, just - // do nothing here - means that if creation throws, a race condition could cause - // more than one thread to reach the return statement below and throw - accepted. - - if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null - { - result = GetSafeLazy(getCacheItem); - ContextItems[cacheKey] = result; - } - } - finally - { - ExitWriteLock(); + + protected override void EnterReadLock() => EnterWriteLock(); + + protected override void EnterWriteLock() + { + if (HasContextItems) + { + System.Threading.Monitor.Enter(ContextItems.SyncRoot, ref _entered); } - - // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache - // exceptions (but try again and again) and silently eat them - however at - // some point we have to report them - so need to re-throw here - - // this does not throw anymore - //return result.Value; - - var value = result.Value; // will not throw (safe lazy) - if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once! - return value; - } - - #endregion - - #region Insert - #endregion - - private class NoopLocker : DisposableObjectSlim - { - protected override void DisposeResources() - { } - } - } -} + } + + protected override void ExitReadLock() => ExitWriteLock(); + + protected override void ExitWriteLock() + { + if (_entered) + { + _entered = false; + System.Threading.Monitor.Exit(ContextItems.SyncRoot); + } + } + + #endregion + + #region Get + + public override object GetCacheItem(string cacheKey, Func getCacheItem) + { + //no place to cache so just return the callback result + if (HasContextItems == false) return getCacheItem(); + + cacheKey = GetCacheKey(cacheKey); + + Lazy result; + + try + { + EnterWriteLock(); + result = ContextItems[cacheKey] as Lazy; // null if key not found + + // cannot create value within the lock, so if result.IsValueCreated is false, just + // do nothing here - means that if creation throws, a race condition could cause + // more than one thread to reach the return statement below and throw - accepted. + + if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null + { + result = GetSafeLazy(getCacheItem); + ContextItems[cacheKey] = result; + } + } + finally + { + ExitWriteLock(); + } + + // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache + // exceptions (but try again and again) and silently eat them - however at + // some point we have to report them - so need to re-throw here + + // this does not throw anymore + //return result.Value; + + var value = result.Value; // will not throw (safe lazy) + if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once! + return value; + } + + #endregion + + #region Insert + #endregion + + private class NoopLocker : DisposableObjectSlim + { + protected override void DisposeResources() + { } + } + } +} diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs index 068bf3605e..835c5d1ee6 100644 --- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs @@ -1,241 +1,241 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Web.Caching; -using CacheItemPriority = System.Web.Caching.CacheItemPriority; - -namespace Umbraco.Core.Cache -{ - /// - /// A CacheProvider that wraps the logic of the HttpRuntime.Cache - /// - internal class HttpRuntimeCacheProvider : DictionaryCacheProviderBase, IRuntimeCacheProvider - { - // locker object that supports upgradeable read locking - // does not need to support recursion if we implement the cache correctly and ensure - // that methods cannot be reentrant, ie we do NOT create values while holding a lock. - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); - - private readonly System.Web.Caching.Cache _cache; - - /// - /// Used for debugging - /// - internal Guid InstanceId { get; private set; } - - public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache) - { - _cache = cache; - InstanceId = Guid.NewGuid(); - } - - protected override IEnumerable GetDictionaryEntries() - { - const string prefix = CacheItemPrefix + "-"; - return _cache.Cast() - .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix)); - } - - protected override void RemoveEntry(string key) - { - _cache.Remove(key); - } - - protected override object GetEntry(string key) - { - return _cache.Get(key); - } - - #region Lock - - protected override void EnterReadLock() - { - _locker.EnterReadLock(); - } - - protected override void EnterWriteLock() - { - _locker.EnterWriteLock();; - } - - protected override void ExitReadLock() - { +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Web.Caching; +using CacheItemPriority = System.Web.Caching.CacheItemPriority; + +namespace Umbraco.Core.Cache +{ + /// + /// A CacheProvider that wraps the logic of the HttpRuntime.Cache + /// + internal class HttpRuntimeCacheProvider : DictionaryCacheProviderBase, IRuntimeCacheProvider + { + // locker object that supports upgradeable read locking + // does not need to support recursion if we implement the cache correctly and ensure + // that methods cannot be reentrant, ie we do NOT create values while holding a lock. + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + + private readonly System.Web.Caching.Cache _cache; + + /// + /// Used for debugging + /// + internal Guid InstanceId { get; private set; } + + public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache) + { + _cache = cache; + InstanceId = Guid.NewGuid(); + } + + protected override IEnumerable GetDictionaryEntries() + { + const string prefix = CacheItemPrefix + "-"; + return _cache.Cast() + .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix)); + } + + protected override void RemoveEntry(string key) + { + _cache.Remove(key); + } + + protected override object GetEntry(string key) + { + return _cache.Get(key); + } + + #region Lock + + protected override void EnterReadLock() + { + _locker.EnterReadLock(); + } + + protected override void EnterWriteLock() + { + _locker.EnterWriteLock();; + } + + protected override void ExitReadLock() + { if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - - protected override void ExitWriteLock() - { + _locker.ExitReadLock(); + } + + protected override void ExitWriteLock() + { if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - } - - #endregion - - #region Get - - /// - /// Gets (and adds if necessary) an item from the cache with all of the default parameters - /// - /// - /// - /// - public override object GetCacheItem(string cacheKey, Func getCacheItem) - { - return GetCacheItem(cacheKey, getCacheItem, null, dependentFiles: null); - } - - /// - /// This overload is here for legacy purposes - /// - /// - /// - /// - /// - /// - /// - /// - /// - internal object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) - { - cacheKey = GetCacheKey(cacheKey); - - // NOTE - because we don't know what getCacheItem does, how long it will take and whether it will hang, - // getCacheItem should run OUTSIDE of the global application lock else we run into lock contention and - // nasty performance issues. - - // So.... we insert a Lazy in the cache while holding the global application lock, and then rely - // on the Lazy lock to ensure that getCacheItem runs once and everybody waits on it, while the global - // application lock has been released. - - // NOTE - // The Lazy value creation may produce a null value. - // Must make sure (for backward compatibility) that we pretend they are not in the cache. - // So if we find an entry in the cache that already has its value created and is null, - // pretend it was not there. If value is not already created, wait... and return null, that's - // what prior code did. - - // NOTE - // The Lazy value creation may throw. - - // So... the null value _will_ be in the cache but never returned - - Lazy result; - - // Fast! - // Only one thread can enter an UpgradeableReadLock at a time, but it does not prevent other - // threads to enter a ReadLock in the meantime -- only upgrading to WriteLock will prevent all - // reads. We first try with a normal ReadLock for maximum concurrency and take the penalty of - // having to re-lock in case there's no value. Would need to benchmark to figure out whether - // it's worth it, though... - try - { - _locker.EnterReadLock(); - result = _cache.Get(cacheKey) as Lazy; // null if key not found - } - finally - { + _locker.ExitWriteLock(); + } + + #endregion + + #region Get + + /// + /// Gets (and adds if necessary) an item from the cache with all of the default parameters + /// + /// + /// + /// + public override object GetCacheItem(string cacheKey, Func getCacheItem) + { + return GetCacheItem(cacheKey, getCacheItem, null, dependentFiles: null); + } + + /// + /// This overload is here for legacy purposes + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) + { + cacheKey = GetCacheKey(cacheKey); + + // NOTE - because we don't know what getCacheItem does, how long it will take and whether it will hang, + // getCacheItem should run OUTSIDE of the global application lock else we run into lock contention and + // nasty performance issues. + + // So.... we insert a Lazy in the cache while holding the global application lock, and then rely + // on the Lazy lock to ensure that getCacheItem runs once and everybody waits on it, while the global + // application lock has been released. + + // NOTE + // The Lazy value creation may produce a null value. + // Must make sure (for backward compatibility) that we pretend they are not in the cache. + // So if we find an entry in the cache that already has its value created and is null, + // pretend it was not there. If value is not already created, wait... and return null, that's + // what prior code did. + + // NOTE + // The Lazy value creation may throw. + + // So... the null value _will_ be in the cache but never returned + + Lazy result; + + // Fast! + // Only one thread can enter an UpgradeableReadLock at a time, but it does not prevent other + // threads to enter a ReadLock in the meantime -- only upgrading to WriteLock will prevent all + // reads. We first try with a normal ReadLock for maximum concurrency and take the penalty of + // having to re-lock in case there's no value. Would need to benchmark to figure out whether + // it's worth it, though... + try + { + _locker.EnterReadLock(); + result = _cache.Get(cacheKey) as Lazy; // null if key not found + } + finally + { if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - var value = result == null ? null : GetSafeLazyValue(result); - if (value != null) return value; - - using (var lck = new UpgradeableReadLock(_locker)) - { - result = _cache.Get(cacheKey) as Lazy; // null if key not found - - // cannot create value within the lock, so if result.IsValueCreated is false, just - // do nothing here - means that if creation throws, a race condition could cause - // more than one thread to reach the return statement below and throw - accepted. - - if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null - { - result = GetSafeLazy(getCacheItem); - var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); - var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); - - lck.UpgradeToWriteLock(); - //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); - } - } - - // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache - // exceptions (but try again and again) and silently eat them - however at - // some point we have to report them - so need to re-throw here - - // this does not throw anymore - //return result.Value; - - value = result.Value; // will not throw (safe lazy) - if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once! - return value; - } - - public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - CacheDependency dependency = null; - if (dependentFiles != null && dependentFiles.Any()) - { - dependency = new CacheDependency(dependentFiles); - } - return GetCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency); - } - - #endregion - - #region Insert - - /// - /// This overload is here for legacy purposes - /// - /// - /// - /// - /// - /// - /// - /// - internal void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) - { - // NOTE - here also we must insert a Lazy but we can evaluate it right now - // and make sure we don't store a null value. - - var result = GetSafeLazy(getCacheItem); - var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache - if (value == null) return; // do not store null values (backward compat) - - cacheKey = GetCacheKey(cacheKey); - - var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); - var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); - - try - { - _locker.EnterWriteLock(); - //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); - } - finally - { + _locker.ExitReadLock(); + } + var value = result == null ? null : GetSafeLazyValue(result); + if (value != null) return value; + + using (var lck = new UpgradeableReadLock(_locker)) + { + result = _cache.Get(cacheKey) as Lazy; // null if key not found + + // cannot create value within the lock, so if result.IsValueCreated is false, just + // do nothing here - means that if creation throws, a race condition could cause + // more than one thread to reach the return statement below and throw - accepted. + + if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null + { + result = GetSafeLazy(getCacheItem); + var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); + var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); + + lck.UpgradeToWriteLock(); + //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! + _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); + } + } + + // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache + // exceptions (but try again and again) and silently eat them - however at + // some point we have to report them - so need to re-throw here + + // this does not throw anymore + //return result.Value; + + value = result.Value; // will not throw (safe lazy) + if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once! + return value; + } + + public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + CacheDependency dependency = null; + if (dependentFiles != null && dependentFiles.Any()) + { + dependency = new CacheDependency(dependentFiles); + } + return GetCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency); + } + + #endregion + + #region Insert + + /// + /// This overload is here for legacy purposes + /// + /// + /// + /// + /// + /// + /// + /// + internal void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) + { + // NOTE - here also we must insert a Lazy but we can evaluate it right now + // and make sure we don't store a null value. + + var result = GetSafeLazy(getCacheItem); + var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + if (value == null) return; // do not store null values (backward compat) + + cacheKey = GetCacheKey(cacheKey); + + var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); + var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); + + try + { + _locker.EnterWriteLock(); + //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! + _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); + } + finally + { if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - } - } - - public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - CacheDependency dependency = null; - if (dependentFiles != null && dependentFiles.Any()) - { - dependency = new CacheDependency(dependentFiles); - } - InsertCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency); - } - - #endregion - } -} + _locker.ExitWriteLock(); + } + } + + public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + CacheDependency dependency = null; + if (dependentFiles != null && dependentFiles.Any()) + { + dependency = new CacheDependency(dependentFiles); + } + InsertCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Cache/ICacheProvider.cs b/src/Umbraco.Core/Cache/ICacheProvider.cs index da0c1cc918..177f7570c2 100644 --- a/src/Umbraco.Core/Cache/ICacheProvider.cs +++ b/src/Umbraco.Core/Cache/ICacheProvider.cs @@ -1,68 +1,68 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Cache -{ - /// - /// An interface for implementing a basic cache provider - /// - public interface ICacheProvider - { - /// - /// Removes all items from the cache. - /// - void ClearAllCache(); - - /// - /// Removes an item from the cache, identified by its key. - /// - /// The key of the item. - void ClearCacheItem(string key); - - /// - /// Removes items from the cache, of a specified type. - /// - /// The name of the type to remove. - /// - /// If the type is an interface, then all items of a type implementing that interface are - /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from - /// the specified type are not removed). - /// Performs a case-sensitive search. - /// - void ClearCacheObjectTypes(string typeName); - - /// - /// Removes items from the cache, of a specified type. - /// - /// The type of the items to remove. - /// If the type is an interface, then all items of a type implementing that interface are - /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from - /// the specified type are not removed). - void ClearCacheObjectTypes(); - - /// - /// Removes items from the cache, of a specified type, satisfying a predicate. - /// - /// The type of the items to remove. - /// The predicate to satisfy. - /// If the type is an interface, then all items of a type implementing that interface are - /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from - /// the specified type are not removed). - void ClearCacheObjectTypes(Func predicate); - - void ClearCacheByKeySearch(string keyStartsWith); - void ClearCacheByKeyExpression(string regexString); - - IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); - IEnumerable GetCacheItemsByKeyExpression(string regexString); - - /// - /// Returns an item with a given key - /// - /// - /// - object GetCacheItem(string cacheKey); - - object GetCacheItem(string cacheKey, Func getCacheItem); - } -} +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Cache +{ + /// + /// An interface for implementing a basic cache provider + /// + public interface ICacheProvider + { + /// + /// Removes all items from the cache. + /// + void ClearAllCache(); + + /// + /// Removes an item from the cache, identified by its key. + /// + /// The key of the item. + void ClearCacheItem(string key); + + /// + /// Removes items from the cache, of a specified type. + /// + /// The name of the type to remove. + /// + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). + /// Performs a case-sensitive search. + /// + void ClearCacheObjectTypes(string typeName); + + /// + /// Removes items from the cache, of a specified type. + /// + /// The type of the items to remove. + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). + void ClearCacheObjectTypes(); + + /// + /// Removes items from the cache, of a specified type, satisfying a predicate. + /// + /// The type of the items to remove. + /// The predicate to satisfy. + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). + void ClearCacheObjectTypes(Func predicate); + + void ClearCacheByKeySearch(string keyStartsWith); + void ClearCacheByKeyExpression(string regexString); + + IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); + IEnumerable GetCacheItemsByKeyExpression(string regexString); + + /// + /// Returns an item with a given key + /// + /// + /// + object GetCacheItem(string cacheKey); + + object GetCacheItem(string cacheKey, Func getCacheItem); + } +} diff --git a/src/Umbraco.Core/Cache/ICacheRefresher.cs b/src/Umbraco.Core/Cache/ICacheRefresher.cs index d254c770eb..96cb9d5418 100644 --- a/src/Umbraco.Core/Cache/ICacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ICacheRefresher.cs @@ -1,33 +1,33 @@ -using System; -using Umbraco.Core.Composing; - -namespace Umbraco.Core.Cache -{ - /// - /// The IcacheRefresher Interface is used for loadbalancing. - /// - /// - public interface ICacheRefresher : IDiscoverable - { - Guid RefresherUniqueId { get; } - string Name { get; } - void RefreshAll(); - void Refresh(int id); - void Remove(int id); - void Refresh(Guid id); - } - - /// - /// Strongly type cache refresher that is able to refresh cache of real instances of objects as well as IDs - /// - /// - /// - /// This is much better for performance when we're not running in a load balanced environment so we can refresh the cache - /// against a already resolved object instead of looking the object back up by id. - /// - interface ICacheRefresher : ICacheRefresher - { - void Refresh(T instance); - void Remove(T instance); - } -} +using System; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Cache +{ + /// + /// The IcacheRefresher Interface is used for loadbalancing. + /// + /// + public interface ICacheRefresher : IDiscoverable + { + Guid RefresherUniqueId { get; } + string Name { get; } + void RefreshAll(); + void Refresh(int id); + void Remove(int id); + void Refresh(Guid id); + } + + /// + /// Strongly type cache refresher that is able to refresh cache of real instances of objects as well as IDs + /// + /// + /// + /// This is much better for performance when we're not running in a load balanced environment so we can refresh the cache + /// against a already resolved object instead of looking the object back up by id. + /// + interface ICacheRefresher : ICacheRefresher + { + void Refresh(T instance); + void Remove(T instance); + } +} diff --git a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs index a9ecd6050c..29f1422a8f 100644 --- a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs @@ -1,14 +1,14 @@ -namespace Umbraco.Core.Cache -{ - /// - /// A cache refresher that supports refreshing or removing cache based on a custom Json payload - /// - interface IJsonCacheRefresher : ICacheRefresher - { - /// - /// Refreshes, clears, etc... any cache based on the information provided in the json - /// - /// - void Refresh(string json); - } -} +namespace Umbraco.Core.Cache +{ + /// + /// A cache refresher that supports refreshing or removing cache based on a custom Json payload + /// + interface IJsonCacheRefresher : ICacheRefresher + { + /// + /// Refreshes, clears, etc... any cache based on the information provided in the json + /// + /// + void Refresh(string json); + } +} diff --git a/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs index 202bcbaa9e..9f3d687e1d 100644 --- a/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs @@ -1,35 +1,35 @@ -using System; -using System.Runtime.Caching; -using System.Text; -using System.Web.Caching; -using CacheItemPriority = System.Web.Caching.CacheItemPriority; - -namespace Umbraco.Core.Cache -{ - /// - /// An abstract class for implementing a runtime cache provider - /// - /// - /// - public interface IRuntimeCacheProvider : ICacheProvider - { - object GetCacheItem( - string cacheKey, - Func getCacheItem, - TimeSpan? timeout, - bool isSliding = false, - CacheItemPriority priority = CacheItemPriority.Normal, - CacheItemRemovedCallback removedCallback = null, - string[] dependentFiles = null); - - void InsertCacheItem( - string cacheKey, - Func getCacheItem, - TimeSpan? timeout = null, - bool isSliding = false, - CacheItemPriority priority = CacheItemPriority.Normal, - CacheItemRemovedCallback removedCallback = null, - string[] dependentFiles = null); - - } -} +using System; +using System.Runtime.Caching; +using System.Text; +using System.Web.Caching; +using CacheItemPriority = System.Web.Caching.CacheItemPriority; + +namespace Umbraco.Core.Cache +{ + /// + /// An abstract class for implementing a runtime cache provider + /// + /// + /// + public interface IRuntimeCacheProvider : ICacheProvider + { + object GetCacheItem( + string cacheKey, + Func getCacheItem, + TimeSpan? timeout, + bool isSliding = false, + CacheItemPriority priority = CacheItemPriority.Normal, + CacheItemRemovedCallback removedCallback = null, + string[] dependentFiles = null); + + void InsertCacheItem( + string cacheKey, + Func getCacheItem, + TimeSpan? timeout = null, + bool isSliding = false, + CacheItemPriority priority = CacheItemPriority.Normal, + CacheItemRemovedCallback removedCallback = null, + string[] dependentFiles = null); + + } +} diff --git a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs index f2ffac4608..27302f0786 100644 --- a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs @@ -1,29 +1,29 @@ -using Umbraco.Core.Sync; - -namespace Umbraco.Core.Cache -{ - /// - /// A base class for "json" cache refreshers. - /// - /// The actual cache refresher type. - /// The actual cache refresher type is used for strongly typed events. - public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher - where TInstanceType : class, ICacheRefresher - { - /// - /// Initializes a new instance of the . - /// - /// A cache helper. - protected JsonCacheRefresherBase(CacheHelper cacheHelper) : base(cacheHelper) - { } - - /// - /// Refreshes as specified by a json payload. - /// - /// The json payload. - public virtual void Refresh(string json) - { - OnCacheUpdated(This, new CacheRefresherEventArgs(json, MessageType.RefreshByJson)); - } - } -} +using Umbraco.Core.Sync; + +namespace Umbraco.Core.Cache +{ + /// + /// A base class for "json" cache refreshers. + /// + /// The actual cache refresher type. + /// The actual cache refresher type is used for strongly typed events. + public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher + where TInstanceType : class, ICacheRefresher + { + /// + /// Initializes a new instance of the . + /// + /// A cache helper. + protected JsonCacheRefresherBase(CacheHelper cacheHelper) : base(cacheHelper) + { } + + /// + /// Refreshes as specified by a json payload. + /// + /// The json payload. + public virtual void Refresh(string json) + { + OnCacheUpdated(This, new CacheRefresherEventArgs(json, MessageType.RefreshByJson)); + } + } +} diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs index 1228feb350..78286f75e2 100644 --- a/src/Umbraco.Core/Cache/NullCacheProvider.cs +++ b/src/Umbraco.Core/Cache/NullCacheProvider.cs @@ -1,66 +1,66 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Caching; - -namespace Umbraco.Core.Cache -{ - /// - /// Represents a cache provider that does not cache anything. - /// - public class NullCacheProvider : IRuntimeCacheProvider - { - private NullCacheProvider() { } - - public static NullCacheProvider Instance { get; } = new NullCacheProvider(); - - public virtual void ClearAllCache() - { } - - public virtual void ClearCacheItem(string key) - { } - - public virtual void ClearCacheObjectTypes(string typeName) - { } - - public virtual void ClearCacheObjectTypes() - { } - - public virtual void ClearCacheObjectTypes(Func predicate) - { } - - public virtual void ClearCacheByKeySearch(string keyStartsWith) - { } - - public virtual void ClearCacheByKeyExpression(string regexString) - { } - - public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - return Enumerable.Empty(); - } - - public IEnumerable GetCacheItemsByKeyExpression(string regexString) - { - return Enumerable.Empty(); - } - - public virtual object GetCacheItem(string cacheKey) - { - return default(object); - } - - public virtual object GetCacheItem(string cacheKey, Func getCacheItem) - { - return getCacheItem(); - } - - public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - return getCacheItem(); - } - - public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Caching; + +namespace Umbraco.Core.Cache +{ + /// + /// Represents a cache provider that does not cache anything. + /// + public class NullCacheProvider : IRuntimeCacheProvider + { + private NullCacheProvider() { } + + public static NullCacheProvider Instance { get; } = new NullCacheProvider(); + + public virtual void ClearAllCache() + { } + + public virtual void ClearCacheItem(string key) + { } + + public virtual void ClearCacheObjectTypes(string typeName) + { } + + public virtual void ClearCacheObjectTypes() + { } + + public virtual void ClearCacheObjectTypes(Func predicate) + { } + + public virtual void ClearCacheByKeySearch(string keyStartsWith) + { } + + public virtual void ClearCacheByKeyExpression(string regexString) + { } + + public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + return Enumerable.Empty(); + } + + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + return Enumerable.Empty(); + } + + public virtual object GetCacheItem(string cacheKey) + { + return default(object); + } + + public virtual object GetCacheItem(string cacheKey, Func getCacheItem) + { + return getCacheItem(); + } + + public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + return getCacheItem(); + } + + public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { } + } +} diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs index b3a643dbfb..8a844bbc9b 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -1,359 +1,359 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Caching; -using System.Text.RegularExpressions; -using System.Threading; -using System.Web.Caching; -using Umbraco.Core.Composing; -using CacheItemPriority = System.Web.Caching.CacheItemPriority; - -namespace Umbraco.Core.Cache -{ - /// - /// Represents a cache provider that caches item in a . - /// A cache provider that wraps the logic of a System.Runtime.Caching.ObjectCache - /// - /// The is created with name "in-memory". That name is - /// used to retrieve configuration options. It does not identify the memory cache, i.e. - /// each instance of this class has its own, independent, memory cache. - public class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider - { - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - internal ObjectCache MemoryCache; - - /// - /// Used for debugging - /// - internal Guid InstanceId { get; private set; } - - public ObjectCacheRuntimeCacheProvider() - { - MemoryCache = new MemoryCache("in-memory"); - InstanceId = Guid.NewGuid(); - } - - #region Clear - - public virtual void ClearAllCache() - { - try - { - _locker.EnterWriteLock(); - MemoryCache.DisposeIfDisposable(); - MemoryCache = new MemoryCache("in-memory"); - } - finally - { +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Caching; +using System.Text.RegularExpressions; +using System.Threading; +using System.Web.Caching; +using Umbraco.Core.Composing; +using CacheItemPriority = System.Web.Caching.CacheItemPriority; + +namespace Umbraco.Core.Cache +{ + /// + /// Represents a cache provider that caches item in a . + /// A cache provider that wraps the logic of a System.Runtime.Caching.ObjectCache + /// + /// The is created with name "in-memory". That name is + /// used to retrieve configuration options. It does not identify the memory cache, i.e. + /// each instance of this class has its own, independent, memory cache. + public class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider + { + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + internal ObjectCache MemoryCache; + + /// + /// Used for debugging + /// + internal Guid InstanceId { get; private set; } + + public ObjectCacheRuntimeCacheProvider() + { + MemoryCache = new MemoryCache("in-memory"); + InstanceId = Guid.NewGuid(); + } + + #region Clear + + public virtual void ClearAllCache() + { + try + { + _locker.EnterWriteLock(); + MemoryCache.DisposeIfDisposable(); + MemoryCache = new MemoryCache("in-memory"); + } + finally + { if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - } - } - - public virtual void ClearCacheItem(string key) - { - try - { - _locker.EnterWriteLock(); - if (MemoryCache[key] == null) return; - MemoryCache.Remove(key); - } - finally - { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - } - } - - public virtual void ClearCacheObjectTypes(string typeName) - { - var type = TypeFinder.GetTypeByName(typeName); - if (type == null) return; - var isInterface = type.IsInterface; - try - { - _locker.EnterWriteLock(); - foreach (var key in MemoryCache - .Where(x => - { - // x.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // get non-created as NonCreatedValue & exceptions as null - var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); - }) - .Select(x => x.Key) - .ToArray()) // ToArray required to remove - MemoryCache.Remove(key); - } - finally - { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - } - } - - public virtual void ClearCacheObjectTypes() - { - try - { - _locker.EnterWriteLock(); - var typeOfT = typeof (T); - var isInterface = typeOfT.IsInterface; - foreach (var key in MemoryCache - .Where(x => - { - // x.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // get non-created as NonCreatedValue & exceptions as null - var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); - - }) - .Select(x => x.Key) - .ToArray()) // ToArray required to remove - MemoryCache.Remove(key); - } - finally - { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - } - } - - public virtual void ClearCacheObjectTypes(Func predicate) - { - try - { - _locker.EnterWriteLock(); - var typeOfT = typeof(T); - var isInterface = typeOfT.IsInterface; - foreach (var key in MemoryCache - .Where(x => - { - // x.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // get non-created as NonCreatedValue & exceptions as null - var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); - if (value == null) return true; - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return (isInterface ? (value is T) : (value.GetType() == typeOfT)) - && predicate(x.Key, (T)value); - }) - .Select(x => x.Key) - .ToArray()) // ToArray required to remove - MemoryCache.Remove(key); - } - finally - { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - } - } - - public virtual void ClearCacheByKeySearch(string keyStartsWith) - { - try - { - _locker.EnterWriteLock(); - foreach (var key in MemoryCache - .Where(x => x.Key.InvariantStartsWith(keyStartsWith)) - .Select(x => x.Key) - .ToArray()) // ToArray required to remove - MemoryCache.Remove(key); - } - finally - { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - } - } - - public virtual void ClearCacheByKeyExpression(string regexString) - { - try - { - _locker.EnterWriteLock(); - foreach (var key in MemoryCache - .Where(x => Regex.IsMatch(x.Key, regexString)) - .Select(x => x.Key) - .ToArray()) // ToArray required to remove - MemoryCache.Remove(key); - } - finally - { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - } - } - - #endregion - - #region Get - - public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - KeyValuePair[] entries; - try - { - _locker.EnterReadLock(); - entries = MemoryCache - .Where(x => x.Key.InvariantStartsWith(keyStartsWith)) - .ToArray(); // evaluate while locked - } - finally - { + _locker.ExitWriteLock(); + } + } + + public virtual void ClearCacheItem(string key) + { + try + { + _locker.EnterWriteLock(); + if (MemoryCache[key] == null) return; + MemoryCache.Remove(key); + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } + } + + public virtual void ClearCacheObjectTypes(string typeName) + { + var type = TypeFinder.GetTypeByName(typeName); + if (type == null) return; + var isInterface = type.IsInterface; + try + { + _locker.EnterWriteLock(); + foreach (var key in MemoryCache + .Where(x => + { + // x.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // get non-created as NonCreatedValue & exceptions as null + var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); + }) + .Select(x => x.Key) + .ToArray()) // ToArray required to remove + MemoryCache.Remove(key); + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } + } + + public virtual void ClearCacheObjectTypes() + { + try + { + _locker.EnterWriteLock(); + var typeOfT = typeof (T); + var isInterface = typeOfT.IsInterface; + foreach (var key in MemoryCache + .Where(x => + { + // x.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // get non-created as NonCreatedValue & exceptions as null + var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); + + }) + .Select(x => x.Key) + .ToArray()) // ToArray required to remove + MemoryCache.Remove(key); + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } + } + + public virtual void ClearCacheObjectTypes(Func predicate) + { + try + { + _locker.EnterWriteLock(); + var typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; + foreach (var key in MemoryCache + .Where(x => + { + // x.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // get non-created as NonCreatedValue & exceptions as null + var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true); + if (value == null) return true; + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return (isInterface ? (value is T) : (value.GetType() == typeOfT)) + && predicate(x.Key, (T)value); + }) + .Select(x => x.Key) + .ToArray()) // ToArray required to remove + MemoryCache.Remove(key); + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } + } + + public virtual void ClearCacheByKeySearch(string keyStartsWith) + { + try + { + _locker.EnterWriteLock(); + foreach (var key in MemoryCache + .Where(x => x.Key.InvariantStartsWith(keyStartsWith)) + .Select(x => x.Key) + .ToArray()) // ToArray required to remove + MemoryCache.Remove(key); + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } + } + + public virtual void ClearCacheByKeyExpression(string regexString) + { + try + { + _locker.EnterWriteLock(); + foreach (var key in MemoryCache + .Where(x => Regex.IsMatch(x.Key, regexString)) + .Select(x => x.Key) + .ToArray()) // ToArray required to remove + MemoryCache.Remove(key); + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } + } + + #endregion + + #region Get + + public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + KeyValuePair[] entries; + try + { + _locker.EnterReadLock(); + entries = MemoryCache + .Where(x => x.Key.InvariantStartsWith(keyStartsWith)) + .ToArray(); // evaluate while locked + } + finally + { if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - return entries - .Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null - .Where(x => x != null) // backward compat, don't store null values in the cache - .ToList(); - } - - public IEnumerable GetCacheItemsByKeyExpression(string regexString) - { - KeyValuePair[] entries; - try - { - _locker.EnterReadLock(); - entries = MemoryCache - .Where(x => Regex.IsMatch(x.Key, regexString)) - .ToArray(); // evaluate while locked - } - finally - { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - return entries - .Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null - .Where(x => x != null) // backward compat, don't store null values in the cache - .ToList(); - } - - public object GetCacheItem(string cacheKey) - { - Lazy result; - try - { - _locker.EnterReadLock(); - result = MemoryCache.Get(cacheKey) as Lazy; // null if key not found - } - finally - { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - return result == null ? null : DictionaryCacheProviderBase.GetSafeLazyValue(result); // return exceptions as null - } - - public object GetCacheItem(string cacheKey, Func getCacheItem) - { - return GetCacheItem(cacheKey, getCacheItem, null); - } - - public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - // see notes in HttpRuntimeCacheProvider - - Lazy result; - - using (var lck = new UpgradeableReadLock(_locker)) - { - result = MemoryCache.Get(cacheKey) as Lazy; - if (result == null || DictionaryCacheProviderBase.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null - { - result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); - var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); - - lck.UpgradeToWriteLock(); - //NOTE: This does an add or update - MemoryCache.Set(cacheKey, result, policy); - } - } - - //return result.Value; - - var value = result.Value; // will not throw (safe lazy) - if (value is DictionaryCacheProviderBase.ExceptionHolder eh) eh.Exception.Throw(); // throw once! - return value; - } - - #endregion - - #region Insert - - public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - // NOTE - here also we must insert a Lazy but we can evaluate it right now - // and make sure we don't store a null value. - - var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); - var value = result.Value; // force evaluation now - if (value == null) return; // do not store null values (backward compat) - - var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); - //NOTE: This does an add or update - MemoryCache.Set(cacheKey, result, policy); - } - - #endregion - - private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - var absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : (timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); - var sliding = isSliding == false ? ObjectCache.NoSlidingExpiration : (timeout ?? ObjectCache.NoSlidingExpiration); - - var policy = new CacheItemPolicy - { - AbsoluteExpiration = absolute, - SlidingExpiration = sliding - }; - - if (dependentFiles != null && dependentFiles.Any()) - { - policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependentFiles.ToList())); - } - - if (removedCallback != null) - { - policy.RemovedCallback = arguments => - { - //convert the reason - var reason = CacheItemRemovedReason.Removed; - switch (arguments.RemovedReason) - { - case CacheEntryRemovedReason.Removed: - reason = CacheItemRemovedReason.Removed; - break; - case CacheEntryRemovedReason.Expired: - reason = CacheItemRemovedReason.Expired; - break; - case CacheEntryRemovedReason.Evicted: - reason = CacheItemRemovedReason.Underused; - break; - case CacheEntryRemovedReason.ChangeMonitorChanged: - reason = CacheItemRemovedReason.Expired; - break; - case CacheEntryRemovedReason.CacheSpecificEviction: - reason = CacheItemRemovedReason.Underused; - break; - } - //call the callback - removedCallback(arguments.CacheItem.Key, arguments.CacheItem.Value, reason); - }; - } - return policy; - } - } -} + _locker.ExitReadLock(); + } + return entries + .Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null) // backward compat, don't store null values in the cache + .ToList(); + } + + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + KeyValuePair[] entries; + try + { + _locker.EnterReadLock(); + entries = MemoryCache + .Where(x => Regex.IsMatch(x.Key, regexString)) + .ToArray(); // evaluate while locked + } + finally + { + if (_locker.IsReadLockHeld) + _locker.ExitReadLock(); + } + return entries + .Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null) // backward compat, don't store null values in the cache + .ToList(); + } + + public object GetCacheItem(string cacheKey) + { + Lazy result; + try + { + _locker.EnterReadLock(); + result = MemoryCache.Get(cacheKey) as Lazy; // null if key not found + } + finally + { + if (_locker.IsReadLockHeld) + _locker.ExitReadLock(); + } + return result == null ? null : DictionaryCacheProviderBase.GetSafeLazyValue(result); // return exceptions as null + } + + public object GetCacheItem(string cacheKey, Func getCacheItem) + { + return GetCacheItem(cacheKey, getCacheItem, null); + } + + public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + // see notes in HttpRuntimeCacheProvider + + Lazy result; + + using (var lck = new UpgradeableReadLock(_locker)) + { + result = MemoryCache.Get(cacheKey) as Lazy; + if (result == null || DictionaryCacheProviderBase.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null + { + result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); + var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); + + lck.UpgradeToWriteLock(); + //NOTE: This does an add or update + MemoryCache.Set(cacheKey, result, policy); + } + } + + //return result.Value; + + var value = result.Value; // will not throw (safe lazy) + if (value is DictionaryCacheProviderBase.ExceptionHolder eh) eh.Exception.Throw(); // throw once! + return value; + } + + #endregion + + #region Insert + + public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + // NOTE - here also we must insert a Lazy but we can evaluate it right now + // and make sure we don't store a null value. + + var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); + var value = result.Value; // force evaluation now + if (value == null) return; // do not store null values (backward compat) + + var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); + //NOTE: This does an add or update + MemoryCache.Set(cacheKey, result, policy); + } + + #endregion + + private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + var absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : (timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); + var sliding = isSliding == false ? ObjectCache.NoSlidingExpiration : (timeout ?? ObjectCache.NoSlidingExpiration); + + var policy = new CacheItemPolicy + { + AbsoluteExpiration = absolute, + SlidingExpiration = sliding + }; + + if (dependentFiles != null && dependentFiles.Any()) + { + policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependentFiles.ToList())); + } + + if (removedCallback != null) + { + policy.RemovedCallback = arguments => + { + //convert the reason + var reason = CacheItemRemovedReason.Removed; + switch (arguments.RemovedReason) + { + case CacheEntryRemovedReason.Removed: + reason = CacheItemRemovedReason.Removed; + break; + case CacheEntryRemovedReason.Expired: + reason = CacheItemRemovedReason.Expired; + break; + case CacheEntryRemovedReason.Evicted: + reason = CacheItemRemovedReason.Underused; + break; + case CacheEntryRemovedReason.ChangeMonitorChanged: + reason = CacheItemRemovedReason.Expired; + break; + case CacheEntryRemovedReason.CacheSpecificEviction: + reason = CacheItemRemovedReason.Underused; + break; + } + //call the callback + removedCallback(arguments.CacheItem.Key, arguments.CacheItem.Value, reason); + }; + } + return policy; + } + } +} diff --git a/src/Umbraco.Core/Cache/StaticCacheProvider.cs b/src/Umbraco.Core/Cache/StaticCacheProvider.cs index bb63768b1e..1604add4d7 100644 --- a/src/Umbraco.Core/Cache/StaticCacheProvider.cs +++ b/src/Umbraco.Core/Cache/StaticCacheProvider.cs @@ -1,79 +1,79 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace Umbraco.Core.Cache -{ - /// - /// Represents a cache provider that statically caches item in a concurrent dictionary. - /// - public class StaticCacheProvider : ICacheProvider - { - internal readonly ConcurrentDictionary StaticCache = new ConcurrentDictionary(); - - public virtual void ClearAllCache() - { - StaticCache.Clear(); - } - - public virtual void ClearCacheItem(string key) - { - object val; - StaticCache.TryRemove(key, out val); - } - - public virtual void ClearCacheObjectTypes(string typeName) - { - StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType().ToString().InvariantEquals(typeName)); - } - - public virtual void ClearCacheObjectTypes() - { - var typeOfT = typeof(T); - StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT); - } - - public virtual void ClearCacheObjectTypes(Func predicate) - { - var typeOfT = typeof(T); - StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT && predicate(kvp.Key, (T)kvp.Value)); - } - - public virtual void ClearCacheByKeySearch(string keyStartsWith) - { - StaticCache.RemoveAll(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)); - } - - public virtual void ClearCacheByKeyExpression(string regexString) - { - StaticCache.RemoveAll(kvp => Regex.IsMatch(kvp.Key, regexString)); - } - - public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - return (from KeyValuePair c in StaticCache - where c.Key.InvariantStartsWith(keyStartsWith) - select c.Value).ToList(); - } - - public IEnumerable GetCacheItemsByKeyExpression(string regexString) - { - return (from KeyValuePair c in StaticCache - where Regex.IsMatch(c.Key, regexString) - select c.Value).ToList(); - } - - public virtual object GetCacheItem(string cacheKey) - { - var result = StaticCache[cacheKey]; - return result; - } - - public virtual object GetCacheItem(string cacheKey, Func getCacheItem) - { - return StaticCache.GetOrAdd(cacheKey, key => getCacheItem()); - } - } -} +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Umbraco.Core.Cache +{ + /// + /// Represents a cache provider that statically caches item in a concurrent dictionary. + /// + public class StaticCacheProvider : ICacheProvider + { + internal readonly ConcurrentDictionary StaticCache = new ConcurrentDictionary(); + + public virtual void ClearAllCache() + { + StaticCache.Clear(); + } + + public virtual void ClearCacheItem(string key) + { + object val; + StaticCache.TryRemove(key, out val); + } + + public virtual void ClearCacheObjectTypes(string typeName) + { + StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType().ToString().InvariantEquals(typeName)); + } + + public virtual void ClearCacheObjectTypes() + { + var typeOfT = typeof(T); + StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT); + } + + public virtual void ClearCacheObjectTypes(Func predicate) + { + var typeOfT = typeof(T); + StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT && predicate(kvp.Key, (T)kvp.Value)); + } + + public virtual void ClearCacheByKeySearch(string keyStartsWith) + { + StaticCache.RemoveAll(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)); + } + + public virtual void ClearCacheByKeyExpression(string regexString) + { + StaticCache.RemoveAll(kvp => Regex.IsMatch(kvp.Key, regexString)); + } + + public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + return (from KeyValuePair c in StaticCache + where c.Key.InvariantStartsWith(keyStartsWith) + select c.Value).ToList(); + } + + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + return (from KeyValuePair c in StaticCache + where Regex.IsMatch(c.Key, regexString) + select c.Value).ToList(); + } + + public virtual object GetCacheItem(string cacheKey) + { + var result = StaticCache[cacheKey]; + return result; + } + + public virtual object GetCacheItem(string cacheKey, Func getCacheItem) + { + return StaticCache.GetOrAdd(cacheKey, key => getCacheItem()); + } + } +} diff --git a/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs b/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs index e1070f8cc9..4defc15482 100644 --- a/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs @@ -1,36 +1,36 @@ -using Umbraco.Core.Sync; - -namespace Umbraco.Core.Cache -{ - /// - /// A base class for "typed" cache refreshers. - /// - /// The actual cache refresher type. - /// The entity type. - /// The actual cache refresher type is used for strongly typed events. - public abstract class TypedCacheRefresherBase : CacheRefresherBase, ICacheRefresher - where TInstanceType : class, ICacheRefresher - { - /// - /// Initializes a new instance of the . - /// - /// A cache helper. - protected TypedCacheRefresherBase(CacheHelper cacheHelper) - : base(cacheHelper) - { } - - #region Refresher - - public virtual void Refresh(TEntityType instance) - { - OnCacheUpdated(This, new CacheRefresherEventArgs(instance, MessageType.RefreshByInstance)); - } - - public virtual void Remove(TEntityType instance) - { - OnCacheUpdated(This, new CacheRefresherEventArgs(instance, MessageType.RemoveByInstance)); - } - - #endregion - } -} +using Umbraco.Core.Sync; + +namespace Umbraco.Core.Cache +{ + /// + /// A base class for "typed" cache refreshers. + /// + /// The actual cache refresher type. + /// The entity type. + /// The actual cache refresher type is used for strongly typed events. + public abstract class TypedCacheRefresherBase : CacheRefresherBase, ICacheRefresher + where TInstanceType : class, ICacheRefresher + { + /// + /// Initializes a new instance of the . + /// + /// A cache helper. + protected TypedCacheRefresherBase(CacheHelper cacheHelper) + : base(cacheHelper) + { } + + #region Refresher + + public virtual void Refresh(TEntityType instance) + { + OnCacheUpdated(This, new CacheRefresherEventArgs(instance, MessageType.RefreshByInstance)); + } + + public virtual void Remove(TEntityType instance) + { + OnCacheUpdated(This, new CacheRefresherEventArgs(instance, MessageType.RemoveByInstance)); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs b/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs index ae864e85be..98ff64462f 100644 --- a/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs @@ -1,35 +1,35 @@ -using System; - -namespace Umbraco.Core.CodeAnnotations -{ - /// - /// Attribute to add a Friendly Name string with an UmbracoObjectType enum value - /// - [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] - internal class FriendlyNameAttribute : Attribute - { - /// - /// friendly name value - /// - private readonly string _friendlyName; - - /// - /// Initializes a new instance of the FriendlyNameAttribute class - /// Sets the friendly name value - /// - /// attribute value - public FriendlyNameAttribute(string friendlyName) - { - this._friendlyName = friendlyName; - } - - /// - /// Gets the friendly name - /// - /// string of friendly name - public override string ToString() - { - return this._friendlyName; - } - } -} +using System; + +namespace Umbraco.Core.CodeAnnotations +{ + /// + /// Attribute to add a Friendly Name string with an UmbracoObjectType enum value + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] + internal class FriendlyNameAttribute : Attribute + { + /// + /// friendly name value + /// + private readonly string _friendlyName; + + /// + /// Initializes a new instance of the FriendlyNameAttribute class + /// Sets the friendly name value + /// + /// attribute value + public FriendlyNameAttribute(string friendlyName) + { + this._friendlyName = friendlyName; + } + + /// + /// Gets the friendly name + /// + /// string of friendly name + public override string ToString() + { + return this._friendlyName; + } + } +} diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoExperimentalFeatureAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoExperimentalFeatureAttribute.cs index e1377ff266..74be6ab397 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoExperimentalFeatureAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoExperimentalFeatureAttribute.cs @@ -1,35 +1,35 @@ -using System; - -namespace Umbraco.Core.CodeAnnotations -{ - /// - /// Marks the program elements that Umbraco is experimenting with and could become public. - /// - /// - /// Indicates that Umbraco is experimenting with code that potentially could become - /// public, but we're not sure, and the code is not stable and can be refactored at any time. - /// The issue tracker should contain more details, discussion, and planning. - /// - [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] - internal sealed class UmbracoExperimentalFeatureAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a description. - /// - /// The text string that describes what is intended. - public UmbracoExperimentalFeatureAttribute(string description) - { - - } - - /// - /// Initializes a new instance of the class with a tracker url and a description. - /// - /// The url of a tracker issue containing more details, discussion, and planning. - /// The text string that describes what is intended. - public UmbracoExperimentalFeatureAttribute(string trackerUrl, string description) - { - - } - } -} +using System; + +namespace Umbraco.Core.CodeAnnotations +{ + /// + /// Marks the program elements that Umbraco is experimenting with and could become public. + /// + /// + /// Indicates that Umbraco is experimenting with code that potentially could become + /// public, but we're not sure, and the code is not stable and can be refactored at any time. + /// The issue tracker should contain more details, discussion, and planning. + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] + internal sealed class UmbracoExperimentalFeatureAttribute : Attribute + { + /// + /// Initializes a new instance of the class with a description. + /// + /// The text string that describes what is intended. + public UmbracoExperimentalFeatureAttribute(string description) + { + + } + + /// + /// Initializes a new instance of the class with a tracker url and a description. + /// + /// The url of a tracker issue containing more details, discussion, and planning. + /// The text string that describes what is intended. + public UmbracoExperimentalFeatureAttribute(string trackerUrl, string description) + { + + } + } +} diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs index d2c431254f..6805038b2f 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs @@ -1,26 +1,26 @@ -using System; - -namespace Umbraco.Core.CodeAnnotations -{ - /// - /// Attribute to associate a GUID string and Type with an UmbracoObjectType Enum value - /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] - internal class UmbracoObjectTypeAttribute : Attribute - { - public UmbracoObjectTypeAttribute(string objectId) - { - ObjectId = new Guid(objectId); - } - - public UmbracoObjectTypeAttribute(string objectId, Type modelType) - { - ObjectId = new Guid(objectId); - ModelType = modelType; - } - - public Guid ObjectId { get; private set; } - - public Type ModelType { get; private set; } - } -} +using System; + +namespace Umbraco.Core.CodeAnnotations +{ + /// + /// Attribute to associate a GUID string and Type with an UmbracoObjectType Enum value + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] + internal class UmbracoObjectTypeAttribute : Attribute + { + public UmbracoObjectTypeAttribute(string objectId) + { + ObjectId = new Guid(objectId); + } + + public UmbracoObjectTypeAttribute(string objectId, Type modelType) + { + ObjectId = new Guid(objectId); + ModelType = modelType; + } + + public Guid ObjectId { get; private set; } + + public Type ModelType { get; private set; } + } +} diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoProposedPublicAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoProposedPublicAttribute.cs index 24d46224c5..1a65dc036e 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoProposedPublicAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoProposedPublicAttribute.cs @@ -1,36 +1,36 @@ -using System; - -namespace Umbraco.Core.CodeAnnotations -{ - /// - /// Marks the program elements that Umbraco is considering making public. - /// - /// - /// Indicates that Umbraco considers making the (currently internal) program element public - /// at some point in the future, but the decision is not fully made yet and is still pending reviews. - /// Note that it is not a commitment to make the program element public. It may not ever become public. - /// The issue tracker should contain more details, discussion, and planning. - /// - [AttributeUsage(AttributeTargets.All, AllowMultiple=false, Inherited=false)] - internal sealed class UmbracoProposedPublicAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a description. - /// - /// The text string that describes what is intended. - public UmbracoProposedPublicAttribute(string description) - { - - } - - /// - /// Initializes a new instance of the class with a tracker url and a description. - /// - /// The url of a tracker issue containing more details, discussion, and planning. - /// The text string that describes what is intended. - public UmbracoProposedPublicAttribute(string trackerUrl, string description) - { - - } - } -} +using System; + +namespace Umbraco.Core.CodeAnnotations +{ + /// + /// Marks the program elements that Umbraco is considering making public. + /// + /// + /// Indicates that Umbraco considers making the (currently internal) program element public + /// at some point in the future, but the decision is not fully made yet and is still pending reviews. + /// Note that it is not a commitment to make the program element public. It may not ever become public. + /// The issue tracker should contain more details, discussion, and planning. + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple=false, Inherited=false)] + internal sealed class UmbracoProposedPublicAttribute : Attribute + { + /// + /// Initializes a new instance of the class with a description. + /// + /// The text string that describes what is intended. + public UmbracoProposedPublicAttribute(string description) + { + + } + + /// + /// Initializes a new instance of the class with a tracker url and a description. + /// + /// The url of a tracker issue containing more details, discussion, and planning. + /// The text string that describes what is intended. + public UmbracoProposedPublicAttribute(string trackerUrl, string description) + { + + } + } +} diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoWillObsoleteAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoWillObsoleteAttribute.cs index 3d129a8a0a..7babe71469 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoWillObsoleteAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoWillObsoleteAttribute.cs @@ -1,37 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Core.CodeAnnotations -{ - /// - /// Marks the program elements that Umbraco will obsolete. - /// - /// - /// Indicates that Umbraco will obsolete the program element at some point in the future, but we do not want to - /// explicitely mark it [Obsolete] yet to avoid warning messages showing when developers compile Umbraco. - /// - [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] - internal sealed class UmbracoWillObsoleteAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a description. - /// - /// The text string that describes what is intended. - public UmbracoWillObsoleteAttribute(string description) - { - - } - - /// - /// Initializes a new instance of the class with a tracker url and a description. - /// - /// The url of a tracker issue containing more details, discussion, and planning. - /// The text string that describes what is intended. - public UmbracoWillObsoleteAttribute(string trackerUrl, string description) - { - - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.CodeAnnotations +{ + /// + /// Marks the program elements that Umbraco will obsolete. + /// + /// + /// Indicates that Umbraco will obsolete the program element at some point in the future, but we do not want to + /// explicitely mark it [Obsolete] yet to avoid warning messages showing when developers compile Umbraco. + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] + internal sealed class UmbracoWillObsoleteAttribute : Attribute + { + /// + /// Initializes a new instance of the class with a description. + /// + /// The text string that describes what is intended. + public UmbracoWillObsoleteAttribute(string description) + { + + } + + /// + /// Initializes a new instance of the class with a tracker url and a description. + /// + /// The url of a tracker issue containing more details, discussion, and planning. + /// The text string that describes what is intended. + public UmbracoWillObsoleteAttribute(string trackerUrl, string description) + { + + } + } +} diff --git a/src/Umbraco.Core/Configuration/CaseInsensitiveEnumConfigConverter.cs b/src/Umbraco.Core/Configuration/CaseInsensitiveEnumConfigConverter.cs index 9406619e46..f07e5133ef 100644 --- a/src/Umbraco.Core/Configuration/CaseInsensitiveEnumConfigConverter.cs +++ b/src/Umbraco.Core/Configuration/CaseInsensitiveEnumConfigConverter.cs @@ -1,32 +1,32 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using System.Configuration; - -namespace Umbraco.Core.Configuration -{ - /// - /// A case-insensitive configuration converter for enumerations. - /// - /// The type of the enumeration. - public class CaseInsensitiveEnumConfigConverter : ConfigurationConverterBase - where T : struct - { - public override object ConvertFrom(ITypeDescriptorContext ctx, CultureInfo ci, object data) - { - if (data == null) - throw new ArgumentNullException("data"); - - //return Enum.Parse(typeof(T), (string)data, true); - - T value; - if (Enum.TryParse((string)data, true, out value)) - return value; - - throw new Exception(string.Format("\"{0}\" is not valid {1} value. Valid values are: {2}.", - data, typeof(T).Name, - string.Join(", ", Enum.GetValues(typeof(T)).Cast()))); - } - } -} +using System; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Configuration; + +namespace Umbraco.Core.Configuration +{ + /// + /// A case-insensitive configuration converter for enumerations. + /// + /// The type of the enumeration. + public class CaseInsensitiveEnumConfigConverter : ConfigurationConverterBase + where T : struct + { + public override object ConvertFrom(ITypeDescriptorContext ctx, CultureInfo ci, object data) + { + if (data == null) + throw new ArgumentNullException("data"); + + //return Enum.Parse(typeof(T), (string)data, true); + + T value; + if (Enum.TryParse((string)data, true, out value)) + return value; + + throw new Exception(string.Format("\"{0}\" is not valid {1} value. Valid values are: {2}.", + data, typeof(T).Name, + string.Join(", ", Enum.GetValues(typeof(T)).Cast()))); + } + } +} diff --git a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs b/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs index 0dcef568ce..41076e5d91 100644 --- a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs +++ b/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs @@ -1,178 +1,178 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Web; -using System.Xml.Linq; -using ClientDependency.Core.CompositeFiles.Providers; -using ClientDependency.Core.Config; -using Semver; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Configuration -{ - /// - /// A utility class for working with CDF config and cache files - use sparingly! - /// - public class ClientDependencyConfiguration - { - private readonly ILogger _logger; - private readonly string _fileName; - - public ClientDependencyConfiguration(ILogger logger) - { - if (logger == null) throw new ArgumentNullException("logger"); - _logger = logger; - _fileName = IOHelper.MapPath(string.Format("{0}/ClientDependency.config", SystemDirectories.Config)); - } - - /// - /// Changes the version number in ClientDependency.config to a hashed value for the version and the DateTime.Day - /// - /// The version of Umbraco we're upgrading to - /// A date value to use in the hash to prevent this method from updating the version on each startup - /// Allows the developer to specify the date precision for the hash (i.e. "yyyyMMdd" would be a precision for the day) - /// Boolean to indicate succesful update of the ClientDependency.config file - public bool UpdateVersionNumber(SemVersion version, DateTime date, string dateFormat) - { - var byteContents = Encoding.Unicode.GetBytes(version + date.ToString(dateFormat)); - - //This is a way to convert a string to a long - //see https://www.codeproject.com/Articles/34309/Convert-String-to-bit-Integer - //We could much more easily use MD5 which would create us an INT but since that is not compliant with - //hashing standards we have to use SHA - int intHash; - using (var hash = SHA256.Create()) - { - var bytes = hash.ComputeHash(byteContents); - - var longResult = new[] { 0, 8, 16, 24 } - .Select(i => BitConverter.ToInt64(bytes, i)) - .Aggregate((x, y) => x ^ y); - - //CDF requires an INT, and although this isn't fail safe, it will work for our purposes. We are not hashing for crypto purposes - //so there could be some collisions with this conversion but it's not a problem for our purposes - //It's also important to note that the long.GetHashCode() implementation in .NET is this: return (int) this ^ (int) (this >> 32); - //which means that this value will not change per appdomain like some GetHashCode implementations. - intHash = longResult.GetHashCode(); - } - - try - { - var clientDependencyConfigXml = XDocument.Load(_fileName, LoadOptions.PreserveWhitespace); - if (clientDependencyConfigXml.Root != null) - { - - var versionAttribute = clientDependencyConfigXml.Root.Attribute("version"); - - //Set the new version to the hashcode of now - var oldVersion = versionAttribute.Value; - var newVersion = Math.Abs(intHash).ToString(); - - //don't update if it's the same version - if (oldVersion == newVersion) - return false; - - versionAttribute.SetValue(newVersion); - clientDependencyConfigXml.Save(_fileName, SaveOptions.DisableFormatting); - - _logger.Info(() => $"Updated version number from {oldVersion} to {newVersion}"); - return true; - } - } - catch (Exception ex) - { - _logger.Error("Couldn't update ClientDependency version number", ex); - } - - return false; - } - - /// - /// Changes the version number in ClientDependency.config to a random value to avoid stale caches - /// - /// - [Obsolete("Use the UpdateVersionNumber method specifying the version, date and dateFormat instead")] - public bool IncreaseVersionNumber() - { - try - { - var clientDependencyConfigXml = XDocument.Load(_fileName, LoadOptions.PreserveWhitespace); - if (clientDependencyConfigXml.Root != null) - { - - var versionAttribute = clientDependencyConfigXml.Root.Attribute("version"); - - //Set the new version to the hashcode of now - var oldVersion = versionAttribute.Value; - var newVersion = Math.Abs(DateTime.UtcNow.GetHashCode()); - - versionAttribute.SetValue(newVersion); - clientDependencyConfigXml.Save(_fileName, SaveOptions.DisableFormatting); - - _logger.Info(() => $"Updated version number from {oldVersion} to {newVersion}"); - return true; - } - } - catch (Exception ex) - { - _logger.Error("Couldn't update ClientDependency version number", ex); - } - - 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 - _logger.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 - _logger.Error("Could not clear temp files", ex); - success = false; - } - } - - return success; - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Web; +using System.Xml.Linq; +using ClientDependency.Core.CompositeFiles.Providers; +using ClientDependency.Core.Config; +using Semver; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Configuration +{ + /// + /// A utility class for working with CDF config and cache files - use sparingly! + /// + public class ClientDependencyConfiguration + { + private readonly ILogger _logger; + private readonly string _fileName; + + public ClientDependencyConfiguration(ILogger logger) + { + if (logger == null) throw new ArgumentNullException("logger"); + _logger = logger; + _fileName = IOHelper.MapPath(string.Format("{0}/ClientDependency.config", SystemDirectories.Config)); + } + + /// + /// Changes the version number in ClientDependency.config to a hashed value for the version and the DateTime.Day + /// + /// The version of Umbraco we're upgrading to + /// A date value to use in the hash to prevent this method from updating the version on each startup + /// Allows the developer to specify the date precision for the hash (i.e. "yyyyMMdd" would be a precision for the day) + /// Boolean to indicate succesful update of the ClientDependency.config file + public bool UpdateVersionNumber(SemVersion version, DateTime date, string dateFormat) + { + var byteContents = Encoding.Unicode.GetBytes(version + date.ToString(dateFormat)); + + //This is a way to convert a string to a long + //see https://www.codeproject.com/Articles/34309/Convert-String-to-bit-Integer + //We could much more easily use MD5 which would create us an INT but since that is not compliant with + //hashing standards we have to use SHA + int intHash; + using (var hash = SHA256.Create()) + { + var bytes = hash.ComputeHash(byteContents); + + var longResult = new[] { 0, 8, 16, 24 } + .Select(i => BitConverter.ToInt64(bytes, i)) + .Aggregate((x, y) => x ^ y); + + //CDF requires an INT, and although this isn't fail safe, it will work for our purposes. We are not hashing for crypto purposes + //so there could be some collisions with this conversion but it's not a problem for our purposes + //It's also important to note that the long.GetHashCode() implementation in .NET is this: return (int) this ^ (int) (this >> 32); + //which means that this value will not change per appdomain like some GetHashCode implementations. + intHash = longResult.GetHashCode(); + } + + try + { + var clientDependencyConfigXml = XDocument.Load(_fileName, LoadOptions.PreserveWhitespace); + if (clientDependencyConfigXml.Root != null) + { + + var versionAttribute = clientDependencyConfigXml.Root.Attribute("version"); + + //Set the new version to the hashcode of now + var oldVersion = versionAttribute.Value; + var newVersion = Math.Abs(intHash).ToString(); + + //don't update if it's the same version + if (oldVersion == newVersion) + return false; + + versionAttribute.SetValue(newVersion); + clientDependencyConfigXml.Save(_fileName, SaveOptions.DisableFormatting); + + _logger.Info(() => $"Updated version number from {oldVersion} to {newVersion}"); + return true; + } + } + catch (Exception ex) + { + _logger.Error("Couldn't update ClientDependency version number", ex); + } + + return false; + } + + /// + /// Changes the version number in ClientDependency.config to a random value to avoid stale caches + /// + /// + [Obsolete("Use the UpdateVersionNumber method specifying the version, date and dateFormat instead")] + public bool IncreaseVersionNumber() + { + try + { + var clientDependencyConfigXml = XDocument.Load(_fileName, LoadOptions.PreserveWhitespace); + if (clientDependencyConfigXml.Root != null) + { + + var versionAttribute = clientDependencyConfigXml.Root.Attribute("version"); + + //Set the new version to the hashcode of now + var oldVersion = versionAttribute.Value; + var newVersion = Math.Abs(DateTime.UtcNow.GetHashCode()); + + versionAttribute.SetValue(newVersion); + clientDependencyConfigXml.Save(_fileName, SaveOptions.DisableFormatting); + + _logger.Info(() => $"Updated version number from {oldVersion} to {newVersion}"); + return true; + } + } + catch (Exception ex) + { + _logger.Error("Couldn't update ClientDependency version number", ex); + } + + 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 + _logger.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 + _logger.Error("Could not clear temp files", ex); + success = false; + } + } + + return success; + } + } +} diff --git a/src/Umbraco.Core/Configuration/CommaDelimitedConfigurationElement.cs b/src/Umbraco.Core/Configuration/CommaDelimitedConfigurationElement.cs index 5015e9d8a2..716aa022a5 100644 --- a/src/Umbraco.Core/Configuration/CommaDelimitedConfigurationElement.cs +++ b/src/Umbraco.Core/Configuration/CommaDelimitedConfigurationElement.cs @@ -1,70 +1,70 @@ -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Configuration; - -namespace Umbraco.Core.Configuration -{ - /// - /// Defines a configuration section that contains inner text that is comma delimited - /// - internal class CommaDelimitedConfigurationElement : InnerTextConfigurationElement, IEnumerable - { - public override CommaDelimitedStringCollection Value - { - get - { - var converter = new CommaDelimitedStringCollectionConverter(); - return (CommaDelimitedStringCollection) converter.ConvertFrom(RawValue); - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return new InnerEnumerator(Value.GetEnumerator()); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return new InnerEnumerator(Value.GetEnumerator()); - } - - /// - /// A wrapper for StringEnumerator since it doesn't explicitly implement IEnumerable - /// - private class InnerEnumerator : IEnumerator - { - private readonly StringEnumerator _stringEnumerator; - - public InnerEnumerator(StringEnumerator stringEnumerator) - { - _stringEnumerator = stringEnumerator; - } - - public bool MoveNext() - { - return _stringEnumerator.MoveNext(); - } - - public void Reset() - { - _stringEnumerator.Reset(); - } - - string IEnumerator.Current - { - get { return _stringEnumerator.Current; } - } - - public object Current - { - get { return _stringEnumerator.Current; } - } - - public void Dispose() - { - ObjectExtensions.DisposeIfDisposable(_stringEnumerator); - } - } - } -} +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Configuration; + +namespace Umbraco.Core.Configuration +{ + /// + /// Defines a configuration section that contains inner text that is comma delimited + /// + internal class CommaDelimitedConfigurationElement : InnerTextConfigurationElement, IEnumerable + { + public override CommaDelimitedStringCollection Value + { + get + { + var converter = new CommaDelimitedStringCollectionConverter(); + return (CommaDelimitedStringCollection) converter.ConvertFrom(RawValue); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new InnerEnumerator(Value.GetEnumerator()); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new InnerEnumerator(Value.GetEnumerator()); + } + + /// + /// A wrapper for StringEnumerator since it doesn't explicitly implement IEnumerable + /// + private class InnerEnumerator : IEnumerator + { + private readonly StringEnumerator _stringEnumerator; + + public InnerEnumerator(StringEnumerator stringEnumerator) + { + _stringEnumerator = stringEnumerator; + } + + public bool MoveNext() + { + return _stringEnumerator.MoveNext(); + } + + public void Reset() + { + _stringEnumerator.Reset(); + } + + string IEnumerator.Current + { + get { return _stringEnumerator.Current; } + } + + public object Current + { + get { return _stringEnumerator.Current; } + } + + public void Dispose() + { + ObjectExtensions.DisposeIfDisposable(_stringEnumerator); + } + } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs index d83fc3ccfc..1642f23fc5 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs @@ -1,34 +1,34 @@ -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class AccessElement : RawXmlConfigurationElement, IAccess - { - public AccessElement() - { - - } - - public AccessElement(XElement rawXml) - :base(rawXml) - { - } - - public IEnumerable Rules - { - get - { - var result = new List(); - if (RawXml != null) - { - result.AddRange(RawXml.Elements("deny").Select(x => new AccessItem {Action = AccessType.Deny, Value = x.Value })); - result.AddRange(RawXml.Elements("grant").Select(x => new AccessItem { Action = AccessType.Grant, Value = x.Value })); - result.AddRange(RawXml.Elements("grantBySection").Select(x => new AccessItem { Action = AccessType.GrantBySection, Value = x.Value })); - } - return result; - } - } - } -} +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Umbraco.Core.Configuration.Dashboard +{ + internal class AccessElement : RawXmlConfigurationElement, IAccess + { + public AccessElement() + { + + } + + public AccessElement(XElement rawXml) + :base(rawXml) + { + } + + public IEnumerable Rules + { + get + { + var result = new List(); + if (RawXml != null) + { + result.AddRange(RawXml.Elements("deny").Select(x => new AccessItem {Action = AccessType.Deny, Value = x.Value })); + result.AddRange(RawXml.Elements("grant").Select(x => new AccessItem { Action = AccessType.Grant, Value = x.Value })); + result.AddRange(RawXml.Elements("grantBySection").Select(x => new AccessItem { Action = AccessType.GrantBySection, Value = x.Value })); + } + return result; + } + } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessItem.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessItem.cs index ec270bada5..37cf491536 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessItem.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AccessItem.cs @@ -1,15 +1,15 @@ -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class AccessItem : IAccessItem - { - /// - /// This can be grant, deny or grantBySection - /// - public AccessType Action { get; set; } - - /// - /// The value of the action - /// - public string Value { get; set; } - } -} +namespace Umbraco.Core.Configuration.Dashboard +{ + internal class AccessItem : IAccessItem + { + /// + /// This can be grant, deny or grantBySection + /// + public AccessType Action { get; set; } + + /// + /// The value of the action + /// + public string Value { get; set; } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessType.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessType.cs index 0d46b2e0e6..d72cac15d0 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessType.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AccessType.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Core.Configuration.Dashboard -{ - public enum AccessType - { - Grant, - Deny, - GrantBySection - } -} +namespace Umbraco.Core.Configuration.Dashboard +{ + public enum AccessType + { + Grant, + Deny, + GrantBySection + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AreaCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/AreaCollection.cs index 488e9a625b..31cc3eaec8 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AreaCollection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AreaCollection.cs @@ -1,32 +1,32 @@ -using System.Collections; -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class AreaCollection : ConfigurationElementCollection, IEnumerable - { - protected override ConfigurationElement CreateNewElement() - { - return new AreaElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((AreaElement) element).Value; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IArea; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +using System.Collections; +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.Dashboard +{ + internal class AreaCollection : ConfigurationElementCollection, IEnumerable + { + protected override ConfigurationElement CreateNewElement() + { + return new AreaElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((AreaElement) element).Value; + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as IArea; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AreaElement.cs b/src/Umbraco.Core/Configuration/Dashboard/AreaElement.cs index a688b69008..20393f89d8 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AreaElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AreaElement.cs @@ -1,12 +1,12 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class AreaElement : InnerTextConfigurationElement, IArea - { - string IArea.AreaName - { - get { return Value; } - } - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.Dashboard +{ + internal class AreaElement : InnerTextConfigurationElement, IArea + { + string IArea.AreaName + { + get { return Value; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AreasElement.cs b/src/Umbraco.Core/Configuration/Dashboard/AreasElement.cs index 549096f6d9..92e51c9b73 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AreasElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AreasElement.cs @@ -1,15 +1,15 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class AreasElement : ConfigurationElement - { - [ConfigurationCollection(typeof(SectionCollection), AddItemName = "area")] - [ConfigurationProperty("", IsDefaultCollection = true)] - public AreaCollection AreaCollection - { - get { return (AreaCollection)base[""]; } - set { base[""] = value; } - } - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.Dashboard +{ + internal class AreasElement : ConfigurationElement + { + [ConfigurationCollection(typeof(SectionCollection), AddItemName = "area")] + [ConfigurationProperty("", IsDefaultCollection = true)] + public AreaCollection AreaCollection + { + get { return (AreaCollection)base[""]; } + set { base[""] = value; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/ControlCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/ControlCollection.cs index a3fe9d9300..b8aa40da7f 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/ControlCollection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/ControlCollection.cs @@ -1,37 +1,37 @@ -using System.Collections; -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class ControlCollection : ConfigurationElementCollection, IEnumerable - { - internal void Add(ControlElement c) - { - BaseAdd(c); - } - - protected override ConfigurationElement CreateNewElement() - { - return new ControlElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((ControlElement)element).ControlPath; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IDashboardControl; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +using System.Collections; +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.Dashboard +{ + internal class ControlCollection : ConfigurationElementCollection, IEnumerable + { + internal void Add(ControlElement c) + { + BaseAdd(c); + } + + protected override ConfigurationElement CreateNewElement() + { + return new ControlElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((ControlElement)element).ControlPath; + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as IDashboardControl; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs b/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs index 680cebe2a6..0434eea47e 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs @@ -1,74 +1,74 @@ -using System; -using System.Configuration; -using System.Linq; -using System.Xml.Linq; - -namespace Umbraco.Core.Configuration.Dashboard -{ - - internal class ControlElement : RawXmlConfigurationElement, IDashboardControl - { - public bool ShowOnce - { - get - { - return RawXml.Attribute("showOnce") == null - ? false - : bool.Parse(RawXml.Attribute("showOnce").Value); - } - } - - public bool AddPanel - { - get - { - return RawXml.Attribute("addPanel") == null - ? true - : bool.Parse(RawXml.Attribute("addPanel").Value); - } - } - - public string PanelCaption - { - get - { - return RawXml.Attribute("panelCaption") == null - ? "" - : RawXml.Attribute("panelCaption").Value; - } - } - - public AccessElement Access - { - get - { - var access = RawXml.Element("access"); - if (access == null) - { - return new AccessElement(); - } - return new AccessElement(access); - } - } - - public string ControlPath - { - get - { - //we need to return the first (and only) text element of the children (wtf... who designed this configuration ! :P ) - var txt = RawXml.Nodes().OfType().FirstOrDefault(); - if (txt == null) - { - throw new ConfigurationErrorsException("The control element must contain a text node indicating the control path"); - } - return txt.Value.Trim(); - } - } - - - IAccess IDashboardControl.AccessRights - { - get { return Access; } - } - } -} +using System; +using System.Configuration; +using System.Linq; +using System.Xml.Linq; + +namespace Umbraco.Core.Configuration.Dashboard +{ + + internal class ControlElement : RawXmlConfigurationElement, IDashboardControl + { + public bool ShowOnce + { + get + { + return RawXml.Attribute("showOnce") == null + ? false + : bool.Parse(RawXml.Attribute("showOnce").Value); + } + } + + public bool AddPanel + { + get + { + return RawXml.Attribute("addPanel") == null + ? true + : bool.Parse(RawXml.Attribute("addPanel").Value); + } + } + + public string PanelCaption + { + get + { + return RawXml.Attribute("panelCaption") == null + ? "" + : RawXml.Attribute("panelCaption").Value; + } + } + + public AccessElement Access + { + get + { + var access = RawXml.Element("access"); + if (access == null) + { + return new AccessElement(); + } + return new AccessElement(access); + } + } + + public string ControlPath + { + get + { + //we need to return the first (and only) text element of the children (wtf... who designed this configuration ! :P ) + var txt = RawXml.Nodes().OfType().FirstOrDefault(); + if (txt == null) + { + throw new ConfigurationErrorsException("The control element must contain a text node indicating the control path"); + } + return txt.Value.Trim(); + } + } + + + IAccess IDashboardControl.AccessRights + { + get { return Access; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/DashboardSection.cs b/src/Umbraco.Core/Configuration/Dashboard/DashboardSection.cs index 4d78abcd6b..12bf0522e0 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/DashboardSection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/DashboardSection.cs @@ -1,24 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class DashboardSection : ConfigurationSection, IDashboardSection - { - [ConfigurationCollection(typeof(SectionCollection), AddItemName = "section")] - [ConfigurationProperty("", IsDefaultCollection = true)] - public SectionCollection SectionCollection - { - get { return (SectionCollection)base[""]; } - set { base[""] = value; } - } - - IEnumerable IDashboardSection.Sections - { - get { return SectionCollection; } - } - } -} +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Configuration.Dashboard +{ + internal class DashboardSection : ConfigurationSection, IDashboardSection + { + [ConfigurationCollection(typeof(SectionCollection), AddItemName = "section")] + [ConfigurationProperty("", IsDefaultCollection = true)] + public SectionCollection SectionCollection + { + get { return (SectionCollection)base[""]; } + set { base[""] = value; } + } + + IEnumerable IDashboardSection.Sections + { + get { return SectionCollection; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs b/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs index 477745ee1b..b7d8540a79 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IAccess - { - IEnumerable Rules { get; } - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Configuration.Dashboard +{ + public interface IAccess + { + IEnumerable Rules { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IAccessItem.cs b/src/Umbraco.Core/Configuration/Dashboard/IAccessItem.cs index e343a39bfb..8b18d50bb3 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IAccessItem.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IAccessItem.cs @@ -1,15 +1,15 @@ -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IAccessItem - { - /// - /// This can be grant, deny or grantBySection - /// - AccessType Action { get; set; } - - /// - /// The value of the action - /// - string Value { get; set; } - } -} +namespace Umbraco.Core.Configuration.Dashboard +{ + public interface IAccessItem + { + /// + /// This can be grant, deny or grantBySection + /// + AccessType Action { get; set; } + + /// + /// The value of the action + /// + string Value { get; set; } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IArea.cs b/src/Umbraco.Core/Configuration/Dashboard/IArea.cs index 71dbfdde75..25401db408 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IArea.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IArea.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IArea - { - string AreaName { get; } - } -} +namespace Umbraco.Core.Configuration.Dashboard +{ + public interface IArea + { + string AreaName { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs b/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs index ee766bef29..7dab542258 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs @@ -1,15 +1,15 @@ -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IDashboardControl - { - bool ShowOnce { get; } - - bool AddPanel { get; } - - string PanelCaption { get; } - - string ControlPath { get; } - - IAccess AccessRights { get; } - } -} +namespace Umbraco.Core.Configuration.Dashboard +{ + public interface IDashboardControl + { + bool ShowOnce { get; } + + bool AddPanel { get; } + + string PanelCaption { get; } + + string ControlPath { get; } + + IAccess AccessRights { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IDashboardSection.cs b/src/Umbraco.Core/Configuration/Dashboard/IDashboardSection.cs index 52b6e66d98..32dfc6653d 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IDashboardSection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IDashboardSection.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IDashboardSection - { - IEnumerable Sections { get; } - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Configuration.Dashboard +{ + public interface IDashboardSection + { + IEnumerable Sections { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IDashboardTab.cs b/src/Umbraco.Core/Configuration/Dashboard/IDashboardTab.cs index ad49d084d6..914b226265 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IDashboardTab.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IDashboardTab.cs @@ -1,13 +1,13 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IDashboardTab - { - string Caption { get; } - - IEnumerable Controls { get; } - - IAccess AccessRights { get; } - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Configuration.Dashboard +{ + public interface IDashboardTab + { + string Caption { get; } + + IEnumerable Controls { get; } + + IAccess AccessRights { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/ISection.cs b/src/Umbraco.Core/Configuration/Dashboard/ISection.cs index 319dcacc12..1005c0750d 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/ISection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/ISection.cs @@ -1,15 +1,15 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface ISection - { - string Alias { get; } - - IEnumerable Areas { get; } - - IEnumerable Tabs { get; } - - IAccess AccessRights { get; } - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Configuration.Dashboard +{ + public interface ISection + { + string Alias { get; } + + IEnumerable Areas { get; } + + IEnumerable Tabs { get; } + + IAccess AccessRights { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/SectionCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/SectionCollection.cs index c3ae6743a0..5717bd28c3 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/SectionCollection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/SectionCollection.cs @@ -1,37 +1,37 @@ -using System.Collections; -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class SectionCollection : ConfigurationElementCollection, IEnumerable - { - internal void Add(SectionElement c) - { - BaseAdd(c); - } - - protected override ConfigurationElement CreateNewElement() - { - return new SectionElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((SectionElement)element).Alias; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as ISection; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +using System.Collections; +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.Dashboard +{ + internal class SectionCollection : ConfigurationElementCollection, IEnumerable + { + internal void Add(SectionElement c) + { + BaseAdd(c); + } + + protected override ConfigurationElement CreateNewElement() + { + return new SectionElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((SectionElement)element).Alias; + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as ISection; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/SectionElement.cs b/src/Umbraco.Core/Configuration/Dashboard/SectionElement.cs index 55498f98c5..09049c13db 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/SectionElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/SectionElement.cs @@ -1,50 +1,50 @@ -using System.Collections.Generic; -using System.Configuration; -using System.Linq; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class SectionElement : ConfigurationElement, ISection - { - [ConfigurationProperty("alias", IsRequired = true)] - public string Alias - { - get { return (string) this["alias"]; } - } - - [ConfigurationProperty("areas", IsRequired = true)] - public AreasElement Areas - { - get { return (AreasElement)this["areas"]; } - } - - [ConfigurationProperty("access")] - public AccessElement Access - { - get { return (AccessElement)this["access"]; } - } - - [ConfigurationCollection(typeof(SectionCollection), AddItemName = "tab")] - [ConfigurationProperty("", IsDefaultCollection = true)] - public TabCollection TabCollection - { - get { return (TabCollection)base[""]; } - set { base[""] = value; } - } - - IEnumerable ISection.Tabs - { - get { return TabCollection; } - } - - IEnumerable ISection.Areas - { - get { return Areas.AreaCollection.Cast().Select(x => x.Value); } - } - - IAccess ISection.AccessRights - { - get { return Access; } - } - } -} +using System.Collections.Generic; +using System.Configuration; +using System.Linq; + +namespace Umbraco.Core.Configuration.Dashboard +{ + internal class SectionElement : ConfigurationElement, ISection + { + [ConfigurationProperty("alias", IsRequired = true)] + public string Alias + { + get { return (string) this["alias"]; } + } + + [ConfigurationProperty("areas", IsRequired = true)] + public AreasElement Areas + { + get { return (AreasElement)this["areas"]; } + } + + [ConfigurationProperty("access")] + public AccessElement Access + { + get { return (AccessElement)this["access"]; } + } + + [ConfigurationCollection(typeof(SectionCollection), AddItemName = "tab")] + [ConfigurationProperty("", IsDefaultCollection = true)] + public TabCollection TabCollection + { + get { return (TabCollection)base[""]; } + set { base[""] = value; } + } + + IEnumerable ISection.Tabs + { + get { return TabCollection; } + } + + IEnumerable ISection.Areas + { + get { return Areas.AreaCollection.Cast().Select(x => x.Value); } + } + + IAccess ISection.AccessRights + { + get { return Access; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/TabCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/TabCollection.cs index eed0f7af11..1b77ffd3fb 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/TabCollection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/TabCollection.cs @@ -1,37 +1,37 @@ -using System.Collections; -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class TabCollection : ConfigurationElementCollection, IEnumerable - { - internal void Add(TabElement c) - { - BaseAdd(c); - } - - protected override ConfigurationElement CreateNewElement() - { - return new TabElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((TabElement)element).Caption; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IDashboardTab; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +using System.Collections; +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.Dashboard +{ + internal class TabCollection : ConfigurationElementCollection, IEnumerable + { + internal void Add(TabElement c) + { + BaseAdd(c); + } + + protected override ConfigurationElement CreateNewElement() + { + return new TabElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((TabElement)element).Caption; + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as IDashboardTab; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/TabElement.cs b/src/Umbraco.Core/Configuration/Dashboard/TabElement.cs index 21dc953677..b92394596e 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/TabElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/TabElement.cs @@ -1,38 +1,38 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class TabElement : ConfigurationElement, IDashboardTab - { - [ConfigurationProperty("caption", IsRequired = true)] - public string Caption - { - get { return (string)this["caption"]; } - } - - [ConfigurationProperty("access")] - public AccessElement Access - { - get { return (AccessElement)this["access"]; } - } - - [ConfigurationCollection(typeof(ControlCollection), AddItemName = "control")] - [ConfigurationProperty("", IsDefaultCollection = true)] - public ControlCollection ControlCollection - { - get { return (ControlCollection)base[""]; } - set { base[""] = value; } - } - - IEnumerable IDashboardTab.Controls - { - get { return ControlCollection; } - } - - IAccess IDashboardTab.AccessRights - { - get { return Access; } - } - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.Dashboard +{ + internal class TabElement : ConfigurationElement, IDashboardTab + { + [ConfigurationProperty("caption", IsRequired = true)] + public string Caption + { + get { return (string)this["caption"]; } + } + + [ConfigurationProperty("access")] + public AccessElement Access + { + get { return (AccessElement)this["access"]; } + } + + [ConfigurationCollection(typeof(ControlCollection), AddItemName = "control")] + [ConfigurationProperty("", IsDefaultCollection = true)] + public ControlCollection ControlCollection + { + get { return (ControlCollection)base[""]; } + set { base[""] = value; } + } + + IEnumerable IDashboardTab.Controls + { + get { return ControlCollection; } + } + + IAccess IDashboardTab.AccessRights + { + get { return Access; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs b/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs index 144ddf3195..1085439b9e 100644 --- a/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs +++ b/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs @@ -1,67 +1,67 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Linq; -using System.Text; - -namespace Umbraco.Core.Configuration -{ - public class FileSystemProviderElement : ConfigurationElement, IFileSystemProviderElement - { - private const string ALIAS_KEY = "alias"; - private const string TYPE_KEY = "type"; - private const string PARAMETERS_KEY = "Parameters"; - - [ConfigurationProperty(ALIAS_KEY, IsKey = true, IsRequired = true)] - public string Alias - { - get - { - return ((string)(base[ALIAS_KEY])); - } - } - - [ConfigurationProperty(TYPE_KEY, IsKey = false, IsRequired = true)] - public string Type - { - get - { - return ((string)(base[TYPE_KEY])); - } - } - - [ConfigurationProperty(PARAMETERS_KEY, IsDefaultCollection = true, IsRequired = false)] - public KeyValueConfigurationCollection Parameters - { - get - { - 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; - } - } - } -} +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.Configuration +{ + public class FileSystemProviderElement : ConfigurationElement, IFileSystemProviderElement + { + private const string ALIAS_KEY = "alias"; + private const string TYPE_KEY = "type"; + private const string PARAMETERS_KEY = "Parameters"; + + [ConfigurationProperty(ALIAS_KEY, IsKey = true, IsRequired = true)] + public string Alias + { + get + { + return ((string)(base[ALIAS_KEY])); + } + } + + [ConfigurationProperty(TYPE_KEY, IsKey = false, IsRequired = true)] + public string Type + { + get + { + return ((string)(base[TYPE_KEY])); + } + } + + [ConfigurationProperty(PARAMETERS_KEY, IsDefaultCollection = true, IsRequired = false)] + public KeyValueConfigurationCollection Parameters + { + get + { + 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 26957dd68e..8d1fdb017e 100644 --- a/src/Umbraco.Core/Configuration/FileSystemProviderElementCollection.cs +++ b/src/Umbraco.Core/Configuration/FileSystemProviderElementCollection.cs @@ -1,43 +1,43 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Linq; -using System.Text; - -namespace Umbraco.Core.Configuration -{ - [ConfigurationCollection(typeof(FileSystemProviderElement), AddItemName = "Provider")] - public class FileSystemProviderElementCollection : ConfigurationElementCollection, IEnumerable - { - protected override ConfigurationElement CreateNewElement() - { - return new FileSystemProviderElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((FileSystemProviderElement)(element)).Alias; - } - - 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(); - } - } -} +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.Configuration +{ + [ConfigurationCollection(typeof(FileSystemProviderElement), AddItemName = "Provider")] + public class FileSystemProviderElementCollection : ConfigurationElementCollection, IEnumerable + { + protected override ConfigurationElement CreateNewElement() + { + return new FileSystemProviderElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((FileSystemProviderElement)(element)).Alias; + } + + 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 6829ed0cd6..7c6c552ddd 100644 --- a/src/Umbraco.Core/Configuration/FileSystemProvidersSection.cs +++ b/src/Umbraco.Core/Configuration/FileSystemProvidersSection.cs @@ -1,30 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Linq; -using System.Text; - -namespace Umbraco.Core.Configuration -{ - public class FileSystemProvidersSection : ConfigurationSection, IFileSystemProvidersSection - { - - [ConfigurationProperty("", IsDefaultCollection = true, IsRequired = true)] - public FileSystemProviderElementCollection Providers - { - 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; - } - } - } -} +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.Configuration +{ + public class FileSystemProvidersSection : ConfigurationSection, IFileSystemProvidersSection + { + + [ConfigurationProperty("", IsDefaultCollection = true, IsRequired = true)] + public FileSystemProviderElementCollection Providers + { + 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/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 2ee0177768..5f023b8dea 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -1,382 +1,382 @@ -using System; -using System.Configuration; -using System.Linq; -using System.Net.Configuration; -using System.Web; -using System.Web.Configuration; -using System.Web.Hosting; -using System.Web.Security; -using System.Xml; -using System.Xml.Linq; -using System.Xml.XPath; -using Umbraco.Core.Composing; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Security; - -namespace Umbraco.Core.Configuration -{ - //TODO: Replace checking for if the app settings exist and returning an empty string, instead return the defaults! - - /// - /// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information from web.config appsettings - /// - public class GlobalSettings : IGlobalSettings - { - - #region Private static fields - - - private static string _reservedPaths; - private static string _reservedUrls; - //ensure the built on (non-changeable) reserved paths are there at all times - internal const string StaticReservedPaths = "~/app_plugins/,~/install/,"; - internal const string StaticReservedUrls = "~/config/splashes/booting.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd,"; - #endregion - - /// - /// Used in unit testing to reset all config items that were set with property setters (i.e. did not come from config) - /// - private static void ResetInternal() - { - GlobalSettingsExtensions.Reset(); - _reservedPaths = null; - _reservedUrls = null; - HasSmtpServer = null; - } - - /// - /// Resets settings that were set programmatically, to their initial values. - /// - /// To be used in unit tests. - internal static void Reset() - { - ResetInternal(); - } +using System; +using System.Configuration; +using System.Linq; +using System.Net.Configuration; +using System.Web; +using System.Web.Configuration; +using System.Web.Hosting; +using System.Web.Security; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using Umbraco.Core.Composing; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Security; - //fixme should this go on the interface or some other helper? - public static bool HasSmtpServerConfigured(string appPath) - { - if (HasSmtpServer.HasValue) return HasSmtpServer.Value; - - var config = WebConfigurationManager.OpenWebConfiguration(appPath); - var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings"); - if (settings == null || settings.Smtp == null) return false; - if (settings.Smtp.SpecifiedPickupDirectory != null && string.IsNullOrEmpty(settings.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation) == false) - return true; - if (settings.Smtp.Network != null && string.IsNullOrEmpty(settings.Smtp.Network.Host) == false) - return true; - return false; - } - - /// - /// For testing only - /// - internal static bool? HasSmtpServer { get; set; } - - /// - /// Gets the reserved urls from web.config. - /// - /// The reserved urls. - public string ReservedUrls - { - get - { - if (_reservedUrls == null) - { - var urls = ConfigurationManager.AppSettings.ContainsKey("umbracoReservedUrls") - ? ConfigurationManager.AppSettings["umbracoReservedUrls"] - : string.Empty; - - //ensure the built on (non-changeable) reserved paths are there at all times - _reservedUrls = StaticReservedUrls + urls; - } - return _reservedUrls; - } - internal set { _reservedUrls = value; } - } - - /// - /// Gets the reserved paths from web.config - /// - /// The reserved paths. - public string ReservedPaths - { - get - { - if (_reservedPaths == null) - { - var reservedPaths = StaticReservedPaths; - //always add the umbraco path to the list - if (ConfigurationManager.AppSettings.ContainsKey("umbracoPath") - && !ConfigurationManager.AppSettings["umbracoPath"].IsNullOrWhiteSpace()) - { - reservedPaths += ConfigurationManager.AppSettings["umbracoPath"].EnsureEndsWith(','); - } - - var allPaths = ConfigurationManager.AppSettings.ContainsKey("umbracoReservedPaths") - ? ConfigurationManager.AppSettings["umbracoReservedPaths"] - : string.Empty; - - _reservedPaths = reservedPaths + allPaths; - } - return _reservedPaths; - } - } - - /// - /// Gets the name of the content XML file. - /// - /// The content XML. - /// - /// Defaults to ~/App_Data/umbraco.config - /// - public string ContentXmlFile - { - get - { - return ConfigurationManager.AppSettings.ContainsKey("umbracoContentXML") - ? ConfigurationManager.AppSettings["umbracoContentXML"] - : "~/App_Data/umbraco.config"; - } - } - - /// - /// Gets the path to umbraco's root directory (/umbraco by default). - /// - /// The path. - public string Path - { - get - { - return ConfigurationManager.AppSettings.ContainsKey("umbracoPath") - ? IOHelper.ResolveUrl(ConfigurationManager.AppSettings["umbracoPath"]) - : string.Empty; - } +namespace Umbraco.Core.Configuration +{ + //TODO: Replace checking for if the app settings exist and returning an empty string, instead return the defaults! + + /// + /// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information from web.config appsettings + /// + public class GlobalSettings : IGlobalSettings + { + + #region Private static fields + + + private static string _reservedPaths; + private static string _reservedUrls; + //ensure the built on (non-changeable) reserved paths are there at all times + internal const string StaticReservedPaths = "~/app_plugins/,~/install/,"; + internal const string StaticReservedUrls = "~/config/splashes/booting.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd,"; + #endregion + + /// + /// Used in unit testing to reset all config items that were set with property setters (i.e. did not come from config) + /// + private static void ResetInternal() + { + GlobalSettingsExtensions.Reset(); + _reservedPaths = null; + _reservedUrls = null; + HasSmtpServer = null; } - - /// - /// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance. - /// - /// The configuration status. - public string ConfigurationStatus - { - get - { - return ConfigurationManager.AppSettings.ContainsKey("umbracoConfigurationStatus") - ? ConfigurationManager.AppSettings["umbracoConfigurationStatus"] - : string.Empty; - } - set - { - SaveSetting("umbracoConfigurationStatus", value); - } - } - - /// - /// Saves a setting into the configuration file. - /// - /// Key of the setting to be saved. - /// Value of the setting to be saved. - internal static void SaveSetting(string key, string value) - { - var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root)); - var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); - - var appSettings = xml.Root.DescendantsAndSelf("appSettings").Single(); - - // Update appSetting if it exists, or else create a new appSetting for the given key and value - var setting = appSettings.Descendants("add").FirstOrDefault(s => s.Attribute("key").Value == key); - if (setting == null) - appSettings.Add(new XElement("add", new XAttribute("key", key), new XAttribute("value", value))); - else - setting.Attribute("value").Value = value; - - xml.Save(fileName, SaveOptions.DisableFormatting); - ConfigurationManager.RefreshSection("appSettings"); - } - - /// - /// Removes a setting from the configuration file. - /// - /// Key of the setting to be removed. - internal static void RemoveSetting(string key) - { - var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root)); - var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); - - var appSettings = xml.Root.DescendantsAndSelf("appSettings").Single(); - var setting = appSettings.Descendants("add").FirstOrDefault(s => s.Attribute("key").Value == key); - - if (setting != null) - { - setting.Remove(); - xml.Save(fileName, SaveOptions.DisableFormatting); - ConfigurationManager.RefreshSection("appSettings"); - } - } - - [Obsolete("Use IOHelper.GetRootDirectorySafe() instead")] - public static string FullPathToRoot => IOHelper.GetRootDirectorySafe(); - - /// - /// Gets a value indicating whether umbraco is running in [debug mode]. - /// - /// true if [debug mode]; otherwise, false. - //fixme surely thsi doesn't belong here and it's also a web request context thing - public static bool DebugMode - { - get - { - try - { - if (HttpContext.Current != null) - { - return HttpContext.Current.IsDebuggingEnabled; - } - //go and get it from config directly - var section = ConfigurationManager.GetSection("system.web/compilation") as CompilationSection; - return section != null && section.Debug; - } - catch - { - return false; - } - } - } - - /// - /// Gets the time out in minutes. - /// - /// The time out in minutes. - public int TimeOutInMinutes - { - get - { - try - { - return int.Parse(ConfigurationManager.AppSettings["umbracoTimeOutInMinutes"]); - } - catch - { - return 20; - } - } - } - - /// - /// Gets a value indicating whether umbraco uses directory urls. - /// - /// true if umbraco uses directory urls; otherwise, false. - public bool UseDirectoryUrls - { - get - { - try - { - return bool.Parse(ConfigurationManager.AppSettings["umbracoUseDirectoryUrls"]); - } - catch - { - return false; - } - } - } - - /// - /// Returns the number of days that should take place between version checks. - /// - /// The version check period in days (0 = never). - public int VersionCheckPeriod - { - get - { - try - { - return int.Parse(ConfigurationManager.AppSettings["umbracoVersionCheckPeriod"]); - } - catch - { - return 7; - } - } - } - - /// - /// This is the location type to store temporary files such as cache files or other localized files for a given machine - /// - /// - /// Currently used for the xml cache file and the plugin cache files - /// - public LocalTempStorage LocalTempStorageLocation - { - get - { - var setting = ConfigurationManager.AppSettings["umbracoLocalTempStorage"]; - if (!string.IsNullOrWhiteSpace(setting)) - return Enum.Parse(setting); - - return LocalTempStorage.Default; - } - } - - /// - /// Gets the default UI language. - /// - /// The default UI language. - // ReSharper disable once InconsistentNaming - public string DefaultUILanguage - { - get - { - return ConfigurationManager.AppSettings.ContainsKey("umbracoDefaultUILanguage") - ? ConfigurationManager.AppSettings["umbracoDefaultUILanguage"] - : string.Empty; - } - } - - /// - /// Gets a value indicating whether umbraco should hide top level nodes from generated urls. - /// - /// - /// true if umbraco hides top level nodes from urls; otherwise, false. - /// - public bool HideTopLevelNodeFromPath - { - get - { - try - { - return bool.Parse(ConfigurationManager.AppSettings["umbracoHideTopLevelNodeFromPath"]); - } - catch - { - return false; - } - } - } - - /// - /// Gets a value indicating whether umbraco should force a secure (https) connection to the backoffice. - /// - public bool UseHttps - { - get - { - try - { - return bool.Parse(ConfigurationManager.AppSettings["umbracoUseHttps"]); - } - catch - { - return false; - } - } - } - - } - - - - -} + + /// + /// Resets settings that were set programmatically, to their initial values. + /// + /// To be used in unit tests. + internal static void Reset() + { + ResetInternal(); + } + + //fixme should this go on the interface or some other helper? + public static bool HasSmtpServerConfigured(string appPath) + { + if (HasSmtpServer.HasValue) return HasSmtpServer.Value; + + var config = WebConfigurationManager.OpenWebConfiguration(appPath); + var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings"); + if (settings == null || settings.Smtp == null) return false; + if (settings.Smtp.SpecifiedPickupDirectory != null && string.IsNullOrEmpty(settings.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation) == false) + return true; + if (settings.Smtp.Network != null && string.IsNullOrEmpty(settings.Smtp.Network.Host) == false) + return true; + return false; + } + + /// + /// For testing only + /// + internal static bool? HasSmtpServer { get; set; } + + /// + /// Gets the reserved urls from web.config. + /// + /// The reserved urls. + public string ReservedUrls + { + get + { + if (_reservedUrls == null) + { + var urls = ConfigurationManager.AppSettings.ContainsKey("umbracoReservedUrls") + ? ConfigurationManager.AppSettings["umbracoReservedUrls"] + : string.Empty; + + //ensure the built on (non-changeable) reserved paths are there at all times + _reservedUrls = StaticReservedUrls + urls; + } + return _reservedUrls; + } + internal set { _reservedUrls = value; } + } + + /// + /// Gets the reserved paths from web.config + /// + /// The reserved paths. + public string ReservedPaths + { + get + { + if (_reservedPaths == null) + { + var reservedPaths = StaticReservedPaths; + //always add the umbraco path to the list + if (ConfigurationManager.AppSettings.ContainsKey("umbracoPath") + && !ConfigurationManager.AppSettings["umbracoPath"].IsNullOrWhiteSpace()) + { + reservedPaths += ConfigurationManager.AppSettings["umbracoPath"].EnsureEndsWith(','); + } + + var allPaths = ConfigurationManager.AppSettings.ContainsKey("umbracoReservedPaths") + ? ConfigurationManager.AppSettings["umbracoReservedPaths"] + : string.Empty; + + _reservedPaths = reservedPaths + allPaths; + } + return _reservedPaths; + } + } + + /// + /// Gets the name of the content XML file. + /// + /// The content XML. + /// + /// Defaults to ~/App_Data/umbraco.config + /// + public string ContentXmlFile + { + get + { + return ConfigurationManager.AppSettings.ContainsKey("umbracoContentXML") + ? ConfigurationManager.AppSettings["umbracoContentXML"] + : "~/App_Data/umbraco.config"; + } + } + + /// + /// Gets the path to umbraco's root directory (/umbraco by default). + /// + /// The path. + public string Path + { + get + { + return ConfigurationManager.AppSettings.ContainsKey("umbracoPath") + ? IOHelper.ResolveUrl(ConfigurationManager.AppSettings["umbracoPath"]) + : string.Empty; + } + } + + /// + /// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance. + /// + /// The configuration status. + public string ConfigurationStatus + { + get + { + return ConfigurationManager.AppSettings.ContainsKey("umbracoConfigurationStatus") + ? ConfigurationManager.AppSettings["umbracoConfigurationStatus"] + : string.Empty; + } + set + { + SaveSetting("umbracoConfigurationStatus", value); + } + } + + /// + /// Saves a setting into the configuration file. + /// + /// Key of the setting to be saved. + /// Value of the setting to be saved. + internal static void SaveSetting(string key, string value) + { + var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root)); + var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); + + var appSettings = xml.Root.DescendantsAndSelf("appSettings").Single(); + + // Update appSetting if it exists, or else create a new appSetting for the given key and value + var setting = appSettings.Descendants("add").FirstOrDefault(s => s.Attribute("key").Value == key); + if (setting == null) + appSettings.Add(new XElement("add", new XAttribute("key", key), new XAttribute("value", value))); + else + setting.Attribute("value").Value = value; + + xml.Save(fileName, SaveOptions.DisableFormatting); + ConfigurationManager.RefreshSection("appSettings"); + } + + /// + /// Removes a setting from the configuration file. + /// + /// Key of the setting to be removed. + internal static void RemoveSetting(string key) + { + var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root)); + var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); + + var appSettings = xml.Root.DescendantsAndSelf("appSettings").Single(); + var setting = appSettings.Descendants("add").FirstOrDefault(s => s.Attribute("key").Value == key); + + if (setting != null) + { + setting.Remove(); + xml.Save(fileName, SaveOptions.DisableFormatting); + ConfigurationManager.RefreshSection("appSettings"); + } + } + + [Obsolete("Use IOHelper.GetRootDirectorySafe() instead")] + public static string FullPathToRoot => IOHelper.GetRootDirectorySafe(); + + /// + /// Gets a value indicating whether umbraco is running in [debug mode]. + /// + /// true if [debug mode]; otherwise, false. + //fixme surely thsi doesn't belong here and it's also a web request context thing + public static bool DebugMode + { + get + { + try + { + if (HttpContext.Current != null) + { + return HttpContext.Current.IsDebuggingEnabled; + } + //go and get it from config directly + var section = ConfigurationManager.GetSection("system.web/compilation") as CompilationSection; + return section != null && section.Debug; + } + catch + { + return false; + } + } + } + + /// + /// Gets the time out in minutes. + /// + /// The time out in minutes. + public int TimeOutInMinutes + { + get + { + try + { + return int.Parse(ConfigurationManager.AppSettings["umbracoTimeOutInMinutes"]); + } + catch + { + return 20; + } + } + } + + /// + /// Gets a value indicating whether umbraco uses directory urls. + /// + /// true if umbraco uses directory urls; otherwise, false. + public bool UseDirectoryUrls + { + get + { + try + { + return bool.Parse(ConfigurationManager.AppSettings["umbracoUseDirectoryUrls"]); + } + catch + { + return false; + } + } + } + + /// + /// Returns the number of days that should take place between version checks. + /// + /// The version check period in days (0 = never). + public int VersionCheckPeriod + { + get + { + try + { + return int.Parse(ConfigurationManager.AppSettings["umbracoVersionCheckPeriod"]); + } + catch + { + return 7; + } + } + } + + /// + /// This is the location type to store temporary files such as cache files or other localized files for a given machine + /// + /// + /// Currently used for the xml cache file and the plugin cache files + /// + public LocalTempStorage LocalTempStorageLocation + { + get + { + var setting = ConfigurationManager.AppSettings["umbracoLocalTempStorage"]; + if (!string.IsNullOrWhiteSpace(setting)) + return Enum.Parse(setting); + + return LocalTempStorage.Default; + } + } + + /// + /// Gets the default UI language. + /// + /// The default UI language. + // ReSharper disable once InconsistentNaming + public string DefaultUILanguage + { + get + { + return ConfigurationManager.AppSettings.ContainsKey("umbracoDefaultUILanguage") + ? ConfigurationManager.AppSettings["umbracoDefaultUILanguage"] + : string.Empty; + } + } + + /// + /// Gets a value indicating whether umbraco should hide top level nodes from generated urls. + /// + /// + /// true if umbraco hides top level nodes from urls; otherwise, false. + /// + public bool HideTopLevelNodeFromPath + { + get + { + try + { + return bool.Parse(ConfigurationManager.AppSettings["umbracoHideTopLevelNodeFromPath"]); + } + catch + { + return false; + } + } + } + + /// + /// Gets a value indicating whether umbraco should force a secure (https) connection to the backoffice. + /// + public bool UseHttps + { + get + { + try + { + return bool.Parse(ConfigurationManager.AppSettings["umbracoUseHttps"]); + } + catch + { + return false; + } + } + } + + } + + + + +} diff --git a/src/Umbraco.Core/Configuration/InnerTextConfigurationElement.cs b/src/Umbraco.Core/Configuration/InnerTextConfigurationElement.cs index cfeec5ab1b..6a125f2c1b 100644 --- a/src/Umbraco.Core/Configuration/InnerTextConfigurationElement.cs +++ b/src/Umbraco.Core/Configuration/InnerTextConfigurationElement.cs @@ -1,69 +1,69 @@ -using System; -using System.Xml; -using System.Xml.Linq; - -namespace Umbraco.Core.Configuration -{ - /// - /// A full config section is required for any full element and we have some elements that are defined like this: - /// {element}MyValue{/element} instead of as attribute values. - /// - /// - internal class InnerTextConfigurationElement : RawXmlConfigurationElement - { - public InnerTextConfigurationElement() - { - } - - public InnerTextConfigurationElement(XElement rawXml) : base(rawXml) - { - } - - protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey) - { - base.DeserializeElement(reader, serializeCollectionKey); - //now validate and set the raw value - if (RawXml.HasElements) - throw new InvalidOperationException("An InnerTextConfigurationElement cannot contain any child elements, only attributes and a value"); - RawValue = RawXml.Value.Trim(); - - //RawValue = reader.ReadElementContentAsString(); - } - - public virtual T Value - { - get - { - var converted = RawValue.TryConvertTo(); - if (converted.Success == false) - throw new InvalidCastException("Could not convert value " + RawValue + " to type " + typeof(T)); - return converted.Result; - } - } - - /// - /// Exposes the raw string value - /// - internal string RawValue { get; set; } - - /// - /// Implicit operator so we don't need to use the 'Value' property explicitly - /// - /// - /// - public static implicit operator T(InnerTextConfigurationElement m) - { - return m.Value; - } - - /// - /// Return the string value of Value - /// - /// - public override string ToString() - { - return string.Format("{0}", Value); - } - - } -} +using System; +using System.Xml; +using System.Xml.Linq; + +namespace Umbraco.Core.Configuration +{ + /// + /// A full config section is required for any full element and we have some elements that are defined like this: + /// {element}MyValue{/element} instead of as attribute values. + /// + /// + internal class InnerTextConfigurationElement : RawXmlConfigurationElement + { + public InnerTextConfigurationElement() + { + } + + public InnerTextConfigurationElement(XElement rawXml) : base(rawXml) + { + } + + protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey) + { + base.DeserializeElement(reader, serializeCollectionKey); + //now validate and set the raw value + if (RawXml.HasElements) + throw new InvalidOperationException("An InnerTextConfigurationElement cannot contain any child elements, only attributes and a value"); + RawValue = RawXml.Value.Trim(); + + //RawValue = reader.ReadElementContentAsString(); + } + + public virtual T Value + { + get + { + var converted = RawValue.TryConvertTo(); + if (converted.Success == false) + throw new InvalidCastException("Could not convert value " + RawValue + " to type " + typeof(T)); + return converted.Result; + } + } + + /// + /// Exposes the raw string value + /// + internal string RawValue { get; set; } + + /// + /// Implicit operator so we don't need to use the 'Value' property explicitly + /// + /// + /// + public static implicit operator T(InnerTextConfigurationElement m) + { + return m.Value; + } + + /// + /// Return the string value of Value + /// + /// + public override string ToString() + { + return string.Format("{0}", Value); + } + + } +} diff --git a/src/Umbraco.Core/Configuration/OptionalCommaDelimitedConfigurationElement.cs b/src/Umbraco.Core/Configuration/OptionalCommaDelimitedConfigurationElement.cs index 8b5b21762f..610067d2db 100644 --- a/src/Umbraco.Core/Configuration/OptionalCommaDelimitedConfigurationElement.cs +++ b/src/Umbraco.Core/Configuration/OptionalCommaDelimitedConfigurationElement.cs @@ -1,43 +1,43 @@ -using System.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; - -namespace Umbraco.Core.Configuration -{ - /// - /// Used for specifying default values for comma delimited config - /// - internal class OptionalCommaDelimitedConfigurationElement : CommaDelimitedConfigurationElement - { - private readonly CommaDelimitedConfigurationElement _wrapped; - private readonly string[] _defaultValue; - - public OptionalCommaDelimitedConfigurationElement() - { - } - - public OptionalCommaDelimitedConfigurationElement(CommaDelimitedConfigurationElement wrapped, string[] defaultValue) - { - _wrapped = wrapped; - _defaultValue = defaultValue; - } - - public override CommaDelimitedStringCollection Value - { - get - { - if (_wrapped == null) - { - return base.Value; - } - - if (string.IsNullOrEmpty(_wrapped.RawValue)) - { - var val = new CommaDelimitedStringCollection(); - val.AddRange(_defaultValue); - return val; - } - return _wrapped.Value; - } - } - } -} +using System.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Core.Configuration +{ + /// + /// Used for specifying default values for comma delimited config + /// + internal class OptionalCommaDelimitedConfigurationElement : CommaDelimitedConfigurationElement + { + private readonly CommaDelimitedConfigurationElement _wrapped; + private readonly string[] _defaultValue; + + public OptionalCommaDelimitedConfigurationElement() + { + } + + public OptionalCommaDelimitedConfigurationElement(CommaDelimitedConfigurationElement wrapped, string[] defaultValue) + { + _wrapped = wrapped; + _defaultValue = defaultValue; + } + + public override CommaDelimitedStringCollection Value + { + get + { + if (_wrapped == null) + { + return base.Value; + } + + if (string.IsNullOrEmpty(_wrapped.RawValue)) + { + var val = new CommaDelimitedStringCollection(); + val.AddRange(_defaultValue); + return val; + } + return _wrapped.Value; + } + } + } +} diff --git a/src/Umbraco.Core/Configuration/OptionalInnerTextConfigurationElement.cs b/src/Umbraco.Core/Configuration/OptionalInnerTextConfigurationElement.cs index 8245811af0..b15e33019d 100644 --- a/src/Umbraco.Core/Configuration/OptionalInnerTextConfigurationElement.cs +++ b/src/Umbraco.Core/Configuration/OptionalInnerTextConfigurationElement.cs @@ -1,23 +1,23 @@ -namespace Umbraco.Core.Configuration -{ - /// - /// This is used to supply optional/default values when using InnerTextConfigurationElement - /// - /// - internal class OptionalInnerTextConfigurationElement : InnerTextConfigurationElement - { - private readonly InnerTextConfigurationElement _wrapped; - private readonly T _defaultValue; - - public OptionalInnerTextConfigurationElement(InnerTextConfigurationElement wrapped, T defaultValue) - { - _wrapped = wrapped; - _defaultValue = defaultValue; - } - - public override T Value - { - get { return string.IsNullOrEmpty(_wrapped.RawValue) ? _defaultValue : _wrapped.Value; } - } - } -} +namespace Umbraco.Core.Configuration +{ + /// + /// This is used to supply optional/default values when using InnerTextConfigurationElement + /// + /// + internal class OptionalInnerTextConfigurationElement : InnerTextConfigurationElement + { + private readonly InnerTextConfigurationElement _wrapped; + private readonly T _defaultValue; + + public OptionalInnerTextConfigurationElement(InnerTextConfigurationElement wrapped, T defaultValue) + { + _wrapped = wrapped; + _defaultValue = defaultValue; + } + + public override T Value + { + get { return string.IsNullOrEmpty(_wrapped.RawValue) ? _defaultValue : _wrapped.Value; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/RawXmlConfigurationElement.cs b/src/Umbraco.Core/Configuration/RawXmlConfigurationElement.cs index 1b09b80d68..5bc6ad3d32 100644 --- a/src/Umbraco.Core/Configuration/RawXmlConfigurationElement.cs +++ b/src/Umbraco.Core/Configuration/RawXmlConfigurationElement.cs @@ -1,30 +1,30 @@ -using System.Configuration; -using System.Xml; -using System.Xml.Linq; - -namespace Umbraco.Core.Configuration -{ - /// - /// A configuration section that simply exposes the entire raw xml of the section itself which inheritors can use - /// to do with as they please. - /// - internal abstract class RawXmlConfigurationElement : ConfigurationElement - { - protected RawXmlConfigurationElement() - { - - } - - protected RawXmlConfigurationElement(XElement rawXml) - { - RawXml = rawXml; - } - - protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey) - { - RawXml = (XElement)XNode.ReadFrom(reader); - } - - protected XElement RawXml { get; private set; } - } -} +using System.Configuration; +using System.Xml; +using System.Xml.Linq; + +namespace Umbraco.Core.Configuration +{ + /// + /// A configuration section that simply exposes the entire raw xml of the section itself which inheritors can use + /// to do with as they please. + /// + internal abstract class RawXmlConfigurationElement : ConfigurationElement + { + protected RawXmlConfigurationElement() + { + + } + + protected RawXmlConfigurationElement(XElement rawXml) + { + RawXml = rawXml; + } + + protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey) + { + RawXml = (XElement)XNode.ReadFrom(reader); + } + + protected XElement RawXml { get; private set; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoConfig.cs b/src/Umbraco.Core/Configuration/UmbracoConfig.cs index f16faab06e..248736d0fe 100644 --- a/src/Umbraco.Core/Configuration/UmbracoConfig.cs +++ b/src/Umbraco.Core/Configuration/UmbracoConfig.cs @@ -1,196 +1,196 @@ -using System; -using System.Configuration; -using System.IO; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.Dashboard; -using Umbraco.Core.Configuration.Grid; -using Umbraco.Core.Configuration.HealthChecks; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Configuration -{ - /// - /// The gateway to all umbraco configuration - /// - public class UmbracoConfig - { - #region Singleton - - private static readonly Lazy Lazy = new Lazy(() => new UmbracoConfig()); - - public static UmbracoConfig For => Lazy.Value; - - #endregion - - /// - /// Default constructor - /// - private UmbracoConfig() - { - // note: need to use SafeCallContext here because ConfigurationManager.GetSection ends up getting AppDomain.Evidence - // which will want to serialize the call context including anything that is in there - what a mess! - - if (_umbracoSettings == null) - { - IUmbracoSettingsSection umbracoSettings; - using (new SafeCallContext()) - { - umbracoSettings = ConfigurationManager.GetSection("umbracoConfiguration/settings") as IUmbracoSettingsSection; - } - SetUmbracoSettings(umbracoSettings); - } - - if (_dashboardSection == null) - { - IDashboardSection dashboardConfig; - using (new SafeCallContext()) - { - dashboardConfig = ConfigurationManager.GetSection("umbracoConfiguration/dashBoard") as IDashboardSection; - } - SetDashboardSettings(dashboardConfig); - } - - if (_healthChecks == null) - { - var healthCheckConfig = ConfigurationManager.GetSection("umbracoConfiguration/HealthChecks") as IHealthChecks; - SetHealthCheckSettings(healthCheckConfig); - } - } - - /// - /// Constructor - can be used for testing - /// - /// - /// - /// - /// - public UmbracoConfig(IUmbracoSettingsSection umbracoSettings, IDashboardSection dashboardSettings, IHealthChecks healthChecks, IGlobalSettings globalSettings) - { - SetHealthCheckSettings(healthChecks); - SetUmbracoSettings(umbracoSettings); - SetDashboardSettings(dashboardSettings); - SetGlobalConfig(globalSettings); - } - - private IHealthChecks _healthChecks; - private IDashboardSection _dashboardSection; - private IUmbracoSettingsSection _umbracoSettings; - private IGridConfig _gridConfig; - private IGlobalSettings _globalSettings; - - /// - /// Gets the IHealthCheck config - /// - public IHealthChecks HealthCheck() - { - if (_healthChecks == null) - { - var ex = new ConfigurationErrorsException("Could not load the " + typeof(IHealthChecks) + " from config file, ensure the web.config and healthchecks.config files are formatted correctly"); - Current.Logger.Error("Config error", ex); - throw ex; - } - - return _healthChecks; - } - - /// - /// Gets the IDashboardSection - /// - public IDashboardSection DashboardSettings() - { - if (_dashboardSection == null) - { - var ex = new ConfigurationErrorsException("Could not load the " + typeof(IDashboardSection) + " from config file, ensure the web.config and Dashboard.config files are formatted correctly"); - Current.Logger.Error("Config error", ex); - throw ex; - } - - return _dashboardSection; - } - - /// - /// Only for testing - /// - /// - public void SetDashboardSettings(IDashboardSection value) - { - _dashboardSection = value; - } - - /// - /// Only for testing - /// - /// - public void SetHealthCheckSettings(IHealthChecks value) - { - _healthChecks = value; - } - - /// - /// Only for testing - /// - /// - public void SetUmbracoSettings(IUmbracoSettingsSection value) - { - _umbracoSettings = value; - } - - /// - /// Only for testing - /// - /// - public void SetGlobalConfig(IGlobalSettings value) - { - _globalSettings = value; - } - - /// - /// Gets the IGlobalSettings - /// - public IGlobalSettings GlobalSettings() - { - return _globalSettings ?? (_globalSettings = new GlobalSettings()); +using System; +using System.Configuration; +using System.IO; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration.Dashboard; +using Umbraco.Core.Configuration.Grid; +using Umbraco.Core.Configuration.HealthChecks; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Configuration +{ + /// + /// The gateway to all umbraco configuration + /// + public class UmbracoConfig + { + #region Singleton + + private static readonly Lazy Lazy = new Lazy(() => new UmbracoConfig()); + + public static UmbracoConfig For => Lazy.Value; + + #endregion + + /// + /// Default constructor + /// + private UmbracoConfig() + { + // note: need to use SafeCallContext here because ConfigurationManager.GetSection ends up getting AppDomain.Evidence + // which will want to serialize the call context including anything that is in there - what a mess! + + if (_umbracoSettings == null) + { + IUmbracoSettingsSection umbracoSettings; + using (new SafeCallContext()) + { + umbracoSettings = ConfigurationManager.GetSection("umbracoConfiguration/settings") as IUmbracoSettingsSection; + } + SetUmbracoSettings(umbracoSettings); + } + + if (_dashboardSection == null) + { + IDashboardSection dashboardConfig; + using (new SafeCallContext()) + { + dashboardConfig = ConfigurationManager.GetSection("umbracoConfiguration/dashBoard") as IDashboardSection; + } + SetDashboardSettings(dashboardConfig); + } + + if (_healthChecks == null) + { + var healthCheckConfig = ConfigurationManager.GetSection("umbracoConfiguration/HealthChecks") as IHealthChecks; + SetHealthCheckSettings(healthCheckConfig); + } } - - /// - /// Gets the IUmbracoSettings - /// - public IUmbracoSettingsSection UmbracoSettings() - { - if (_umbracoSettings == null) - { - var ex = new ConfigurationErrorsException("Could not load the " + typeof (IUmbracoSettingsSection) + " from config file, ensure the web.config and umbracoSettings.config files are formatted correctly"); - Current.Logger.Error("Config error", ex); - throw ex; - } - - return _umbracoSettings; - } - - /// - /// Only for testing - /// - /// - public void SetGridConfig(IGridConfig value) - { - _gridConfig = value; - } - - /// - /// Gets the IGridConfig - /// - public IGridConfig GridConfig(ILogger logger, IRuntimeCacheProvider runtimeCache, DirectoryInfo appPlugins, DirectoryInfo configFolder, bool isDebug) - { - if (_gridConfig == null) - { - _gridConfig = new GridConfig(logger, runtimeCache, appPlugins, configFolder, isDebug); - } - - return _gridConfig; - } - - //TODO: Add other configurations here ! - } -} + + /// + /// Constructor - can be used for testing + /// + /// + /// + /// + /// + public UmbracoConfig(IUmbracoSettingsSection umbracoSettings, IDashboardSection dashboardSettings, IHealthChecks healthChecks, IGlobalSettings globalSettings) + { + SetHealthCheckSettings(healthChecks); + SetUmbracoSettings(umbracoSettings); + SetDashboardSettings(dashboardSettings); + SetGlobalConfig(globalSettings); + } + + private IHealthChecks _healthChecks; + private IDashboardSection _dashboardSection; + private IUmbracoSettingsSection _umbracoSettings; + private IGridConfig _gridConfig; + private IGlobalSettings _globalSettings; + + /// + /// Gets the IHealthCheck config + /// + public IHealthChecks HealthCheck() + { + if (_healthChecks == null) + { + var ex = new ConfigurationErrorsException("Could not load the " + typeof(IHealthChecks) + " from config file, ensure the web.config and healthchecks.config files are formatted correctly"); + Current.Logger.Error("Config error", ex); + throw ex; + } + + return _healthChecks; + } + + /// + /// Gets the IDashboardSection + /// + public IDashboardSection DashboardSettings() + { + if (_dashboardSection == null) + { + var ex = new ConfigurationErrorsException("Could not load the " + typeof(IDashboardSection) + " from config file, ensure the web.config and Dashboard.config files are formatted correctly"); + Current.Logger.Error("Config error", ex); + throw ex; + } + + return _dashboardSection; + } + + /// + /// Only for testing + /// + /// + public void SetDashboardSettings(IDashboardSection value) + { + _dashboardSection = value; + } + + /// + /// Only for testing + /// + /// + public void SetHealthCheckSettings(IHealthChecks value) + { + _healthChecks = value; + } + + /// + /// Only for testing + /// + /// + public void SetUmbracoSettings(IUmbracoSettingsSection value) + { + _umbracoSettings = value; + } + + /// + /// Only for testing + /// + /// + public void SetGlobalConfig(IGlobalSettings value) + { + _globalSettings = value; + } + + /// + /// Gets the IGlobalSettings + /// + public IGlobalSettings GlobalSettings() + { + return _globalSettings ?? (_globalSettings = new GlobalSettings()); + } + + /// + /// Gets the IUmbracoSettings + /// + public IUmbracoSettingsSection UmbracoSettings() + { + if (_umbracoSettings == null) + { + var ex = new ConfigurationErrorsException("Could not load the " + typeof (IUmbracoSettingsSection) + " from config file, ensure the web.config and umbracoSettings.config files are formatted correctly"); + Current.Logger.Error("Config error", ex); + throw ex; + } + + return _umbracoSettings; + } + + /// + /// Only for testing + /// + /// + public void SetGridConfig(IGridConfig value) + { + _gridConfig = value; + } + + /// + /// Gets the IGridConfig + /// + public IGridConfig GridConfig(ILogger logger, IRuntimeCacheProvider runtimeCache, DirectoryInfo appPlugins, DirectoryInfo configFolder, bool isDebug) + { + if (_gridConfig == null) + { + _gridConfig = new GridConfig(logger, runtimeCache, appPlugins, configFolder, isDebug); + } + + return _gridConfig; + } + + //TODO: Add other configurations here ! + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs b/src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs index 930d73c5f5..bde25652f9 100644 --- a/src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs @@ -1,32 +1,32 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration -{ - /// - /// Represents an Umbraco configuration section which can be used to pass to UmbracoConfiguration.For{T} - /// - public interface IUmbracoConfigurationSection - { - - } - - /// - /// Represents an Umbraco section within the configuration file. - /// - /// - /// The requirement for these sections is to be read-only. - /// However for unit tests purposes it is internally possible to override some values, and - /// then calling >ResetSection should cancel these changes and bring the section back to - /// what it was originally. - /// The UmbracoSettings.For{T} method will return a section, either one that - /// is in the configuration file, or a section that was created with default values. - /// - public abstract class UmbracoConfigurationSection : ConfigurationSection, IUmbracoConfigurationSection - { - /// - /// Gets a value indicating whether the section actually is in the configuration file. - /// - protected bool IsPresent { get { return ElementInformation.IsPresent; } } - - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration +{ + /// + /// Represents an Umbraco configuration section which can be used to pass to UmbracoConfiguration.For{T} + /// + public interface IUmbracoConfigurationSection + { + + } + + /// + /// Represents an Umbraco section within the configuration file. + /// + /// + /// The requirement for these sections is to be read-only. + /// However for unit tests purposes it is internally possible to override some values, and + /// then calling >ResetSection should cancel these changes and bring the section back to + /// what it was originally. + /// The UmbracoSettings.For{T} method will return a section, either one that + /// is in the configuration file, or a section that was created with default values. + /// + public abstract class UmbracoConfigurationSection : ConfigurationSection, IUmbracoConfigurationSection + { + /// + /// Gets a value indicating whether the section actually is in the configuration file. + /// + protected bool IsPresent { get { return ElementInformation.IsPresent; } } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsCollection.cs index f602389f1f..06051b54e0 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsCollection.cs @@ -1,36 +1,36 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class AppCodeFileExtensionsCollection : ConfigurationElementCollection, IEnumerable - { - internal void Add(FileExtensionElement element) - { - base.BaseAdd(element); - } - - protected override ConfigurationElement CreateNewElement() - { - return new FileExtensionElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((FileExtensionElement)element).Value; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IFileExtension; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class AppCodeFileExtensionsCollection : ConfigurationElementCollection, IEnumerable + { + internal void Add(FileExtensionElement element) + { + base.BaseAdd(element); + } + + protected override ConfigurationElement CreateNewElement() + { + return new FileExtensionElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((FileExtensionElement)element).Value; + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as IFileExtension; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsElement.cs index 3642cc576e..decddad8ed 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsElement.cs @@ -1,17 +1,17 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class AppCodeFileExtensionsElement : ConfigurationElement - { - [ConfigurationCollection(typeof(AppCodeFileExtensionsCollection), AddItemName = "ext")] - [ConfigurationProperty("", IsDefaultCollection = true)] - internal AppCodeFileExtensionsCollection AppCodeFileExtensionsCollection - { - get { return (AppCodeFileExtensionsCollection)base[""]; } - set { base[""] = value; } - } - - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class AppCodeFileExtensionsElement : ConfigurationElement + { + [ConfigurationCollection(typeof(AppCodeFileExtensionsCollection), AddItemName = "ext")] + [ConfigurationProperty("", IsDefaultCollection = true)] + internal AppCodeFileExtensionsCollection AppCodeFileExtensionsCollection + { + get { return (AppCodeFileExtensionsCollection)base[""]; } + set { base[""] = value; } + } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/CharCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/CharCollection.cs index 8ce65f95cb..7b62fcc123 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/CharCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/CharCollection.cs @@ -1,36 +1,36 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class CharCollection : ConfigurationElementCollection, IEnumerable - { - internal void Add(CharElement c) - { - BaseAdd(c); - } - - protected override ConfigurationElement CreateNewElement() - { - return new CharElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((CharElement)element).Char; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IChar; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class CharCollection : ConfigurationElementCollection, IEnumerable + { + internal void Add(CharElement c) + { + BaseAdd(c); + } + + protected override ConfigurationElement CreateNewElement() + { + return new CharElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((CharElement)element).Char; + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as IChar; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/CharElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/CharElement.cs index 6252ef7888..7a13d76aaf 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/CharElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/CharElement.cs @@ -1,32 +1,32 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class CharElement : InnerTextConfigurationElement, IChar - { - private string _char; - private string _replacement; - - internal string Char - { - get { return _char ?? (_char = (string)RawXml.Attribute("org")); } - set { _char = value; } - } - - internal string Replacement - { - get { return _replacement ?? (_replacement = Value); } - set { _replacement = value; } - } - - string IChar.Char - { - get { return Char; } - - } - - string IChar.Replacement - { - get { return Replacement; } - - } - } -} +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class CharElement : InnerTextConfigurationElement, IChar + { + private string _char; + private string _replacement; + + internal string Char + { + get { return _char ?? (_char = (string)RawXml.Attribute("org")); } + set { _char = value; } + } + + internal string Replacement + { + get { return _replacement ?? (_replacement = Value); } + set { _replacement = value; } + } + + string IChar.Char + { + get { return Char; } + + } + + string IChar.Replacement + { + get { return Replacement; } + + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 4a00aeb3ee..2a3a6efca2 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -1,146 +1,146 @@ -using System.Collections.Generic; -using System.Configuration; -using Umbraco.Core.Macros; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ContentElement : UmbracoConfigurationElement, IContentSection - { - private const string DefaultPreviewBadge = @"In Preview Mode - click to end"; - - [ConfigurationProperty("imaging")] - internal ContentImagingElement Imaging => (ContentImagingElement) this["imaging"]; - - [ConfigurationProperty("scripteditor")] - internal ContentScriptEditorElement ScriptEditor => (ContentScriptEditorElement) this["scripteditor"]; - - [ConfigurationProperty("ResolveUrlsFromTextString")] - internal InnerTextConfigurationElement ResolveUrlsFromTextString => GetOptionalTextElement("ResolveUrlsFromTextString", false); - - [ConfigurationProperty("UploadAllowDirectories")] - internal InnerTextConfigurationElement UploadAllowDirectories => GetOptionalTextElement("UploadAllowDirectories", true); - - public IEnumerable Error404Collection => Errors.Error404Collection; - - [ConfigurationProperty("errors", IsRequired = true)] - internal ContentErrorsElement Errors => (ContentErrorsElement) base["errors"]; - - [ConfigurationProperty("notifications", IsRequired = true)] - internal NotificationsElement Notifications => (NotificationsElement) base["notifications"]; - - [ConfigurationProperty("ensureUniqueNaming")] - internal InnerTextConfigurationElement EnsureUniqueNaming => GetOptionalTextElement("ensureUniqueNaming", true); - - [ConfigurationProperty("XmlCacheEnabled")] - internal InnerTextConfigurationElement XmlCacheEnabled => GetOptionalTextElement("XmlCacheEnabled", true); - - [ConfigurationProperty("ContinouslyUpdateXmlDiskCache")] - internal InnerTextConfigurationElement ContinouslyUpdateXmlDiskCache => GetOptionalTextElement("ContinouslyUpdateXmlDiskCache", true); - - [ConfigurationProperty("XmlContentCheckForDiskChanges")] - internal InnerTextConfigurationElement XmlContentCheckForDiskChanges => GetOptionalTextElement("XmlContentCheckForDiskChanges", false); - - [ConfigurationProperty("EnableSplashWhileLoading")] - internal InnerTextConfigurationElement EnableSplashWhileLoading => GetOptionalTextElement("EnableSplashWhileLoading", false); - - [ConfigurationProperty("PropertyContextHelpOption")] - internal InnerTextConfigurationElement PropertyContextHelpOption => GetOptionalTextElement("PropertyContextHelpOption", "text"); - - [ConfigurationProperty("ForceSafeAliases")] - internal InnerTextConfigurationElement ForceSafeAliases => GetOptionalTextElement("ForceSafeAliases", true); - - [ConfigurationProperty("PreviewBadge")] - internal InnerTextConfigurationElement PreviewBadge => GetOptionalTextElement("PreviewBadge", DefaultPreviewBadge); - - [ConfigurationProperty("UmbracoLibraryCacheDuration")] - internal InnerTextConfigurationElement UmbracoLibraryCacheDuration => GetOptionalTextElement("UmbracoLibraryCacheDuration", 1800); - - [ConfigurationProperty("MacroErrors")] - internal InnerTextConfigurationElement MacroErrors => GetOptionalTextElement("MacroErrors", MacroErrorBehaviour.Inline); - - [ConfigurationProperty("disallowedUploadFiles")] - internal CommaDelimitedConfigurationElement DisallowedUploadFiles => GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd"}); - - [ConfigurationProperty("allowedUploadFiles")] - internal CommaDelimitedConfigurationElement AllowedUploadFiles => GetOptionalDelimitedElement("allowedUploadFiles", new string[0]); - - [ConfigurationProperty("cloneXmlContent")] - internal InnerTextConfigurationElement CloneXmlContent => GetOptionalTextElement("cloneXmlContent", true); - - [ConfigurationProperty("GlobalPreviewStorageEnabled")] - internal InnerTextConfigurationElement GlobalPreviewStorageEnabled => GetOptionalTextElement("GlobalPreviewStorageEnabled", false); - - [ConfigurationProperty("defaultDocumentTypeProperty")] - internal InnerTextConfigurationElement DefaultDocumentTypeProperty => GetOptionalTextElement("defaultDocumentTypeProperty", "Textstring"); - - [ConfigurationProperty("showDeprecatedPropertyEditors")] - internal InnerTextConfigurationElement ShowDeprecatedPropertyEditors => GetOptionalTextElement("showDeprecatedPropertyEditors", false); - - [ConfigurationProperty("EnableInheritedDocumentTypes")] - internal InnerTextConfigurationElement EnableInheritedDocumentTypes => GetOptionalTextElement("EnableInheritedDocumentTypes", true); - - [ConfigurationProperty("EnableInheritedMediaTypes")] - internal InnerTextConfigurationElement EnableInheritedMediaTypes => GetOptionalTextElement("EnableInheritedMediaTypes", true); - - [ConfigurationProperty("loginBackgroundImage")] - internal InnerTextConfigurationElement LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty); - - string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress; - - bool IContentSection.DisableHtmlEmail => Notifications.DisableHtmlEmail; - - IEnumerable IContentSection.ImageFileTypes => Imaging.ImageFileTypes; - - IEnumerable IContentSection.ImageTagAllowedAttributes => Imaging.ImageTagAllowedAttributes; - - IEnumerable IContentSection.ImageAutoFillProperties => Imaging.ImageAutoFillProperties; - - bool IContentSection.ScriptEditorDisable => ScriptEditor.ScriptEditorDisable; - - string IContentSection.ScriptFolderPath => ScriptEditor.ScriptFolderPath; - - IEnumerable IContentSection.ScriptFileTypes => ScriptEditor.ScriptFileTypes; - - bool IContentSection.ResolveUrlsFromTextString => ResolveUrlsFromTextString; - - bool IContentSection.UploadAllowDirectories => UploadAllowDirectories; - - bool IContentSection.EnsureUniqueNaming => EnsureUniqueNaming; - - bool IContentSection.XmlCacheEnabled => XmlCacheEnabled; - - bool IContentSection.ContinouslyUpdateXmlDiskCache => ContinouslyUpdateXmlDiskCache; - - bool IContentSection.XmlContentCheckForDiskChanges => XmlContentCheckForDiskChanges; - - bool IContentSection.EnableSplashWhileLoading => EnableSplashWhileLoading; - - string IContentSection.PropertyContextHelpOption => PropertyContextHelpOption; - - bool IContentSection.ForceSafeAliases => ForceSafeAliases; - - string IContentSection.PreviewBadge => PreviewBadge; - - int IContentSection.UmbracoLibraryCacheDuration => UmbracoLibraryCacheDuration; - - MacroErrorBehaviour IContentSection.MacroErrorBehaviour => MacroErrors; - - IEnumerable IContentSection.DisallowedUploadFiles => DisallowedUploadFiles; - - IEnumerable IContentSection.AllowedUploadFiles => AllowedUploadFiles; - - bool IContentSection.CloneXmlContent => CloneXmlContent; - - bool IContentSection.GlobalPreviewStorageEnabled => GlobalPreviewStorageEnabled; - - string IContentSection.DefaultDocumentTypeProperty => DefaultDocumentTypeProperty; - - bool IContentSection.ShowDeprecatedPropertyEditors => ShowDeprecatedPropertyEditors; - - bool IContentSection.EnableInheritedDocumentTypes => EnableInheritedDocumentTypes; - - bool IContentSection.EnableInheritedMediaTypes => EnableInheritedMediaTypes; - - string IContentSection.LoginBackgroundImage => LoginBackgroundImage; - } -} +using System.Collections.Generic; +using System.Configuration; +using Umbraco.Core.Macros; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ContentElement : UmbracoConfigurationElement, IContentSection + { + private const string DefaultPreviewBadge = @"In Preview Mode - click to end"; + + [ConfigurationProperty("imaging")] + internal ContentImagingElement Imaging => (ContentImagingElement) this["imaging"]; + + [ConfigurationProperty("scripteditor")] + internal ContentScriptEditorElement ScriptEditor => (ContentScriptEditorElement) this["scripteditor"]; + + [ConfigurationProperty("ResolveUrlsFromTextString")] + internal InnerTextConfigurationElement ResolveUrlsFromTextString => GetOptionalTextElement("ResolveUrlsFromTextString", false); + + [ConfigurationProperty("UploadAllowDirectories")] + internal InnerTextConfigurationElement UploadAllowDirectories => GetOptionalTextElement("UploadAllowDirectories", true); + + public IEnumerable Error404Collection => Errors.Error404Collection; + + [ConfigurationProperty("errors", IsRequired = true)] + internal ContentErrorsElement Errors => (ContentErrorsElement) base["errors"]; + + [ConfigurationProperty("notifications", IsRequired = true)] + internal NotificationsElement Notifications => (NotificationsElement) base["notifications"]; + + [ConfigurationProperty("ensureUniqueNaming")] + internal InnerTextConfigurationElement EnsureUniqueNaming => GetOptionalTextElement("ensureUniqueNaming", true); + + [ConfigurationProperty("XmlCacheEnabled")] + internal InnerTextConfigurationElement XmlCacheEnabled => GetOptionalTextElement("XmlCacheEnabled", true); + + [ConfigurationProperty("ContinouslyUpdateXmlDiskCache")] + internal InnerTextConfigurationElement ContinouslyUpdateXmlDiskCache => GetOptionalTextElement("ContinouslyUpdateXmlDiskCache", true); + + [ConfigurationProperty("XmlContentCheckForDiskChanges")] + internal InnerTextConfigurationElement XmlContentCheckForDiskChanges => GetOptionalTextElement("XmlContentCheckForDiskChanges", false); + + [ConfigurationProperty("EnableSplashWhileLoading")] + internal InnerTextConfigurationElement EnableSplashWhileLoading => GetOptionalTextElement("EnableSplashWhileLoading", false); + + [ConfigurationProperty("PropertyContextHelpOption")] + internal InnerTextConfigurationElement PropertyContextHelpOption => GetOptionalTextElement("PropertyContextHelpOption", "text"); + + [ConfigurationProperty("ForceSafeAliases")] + internal InnerTextConfigurationElement ForceSafeAliases => GetOptionalTextElement("ForceSafeAliases", true); + + [ConfigurationProperty("PreviewBadge")] + internal InnerTextConfigurationElement PreviewBadge => GetOptionalTextElement("PreviewBadge", DefaultPreviewBadge); + + [ConfigurationProperty("UmbracoLibraryCacheDuration")] + internal InnerTextConfigurationElement UmbracoLibraryCacheDuration => GetOptionalTextElement("UmbracoLibraryCacheDuration", 1800); + + [ConfigurationProperty("MacroErrors")] + internal InnerTextConfigurationElement MacroErrors => GetOptionalTextElement("MacroErrors", MacroErrorBehaviour.Inline); + + [ConfigurationProperty("disallowedUploadFiles")] + internal CommaDelimitedConfigurationElement DisallowedUploadFiles => GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd"}); + + [ConfigurationProperty("allowedUploadFiles")] + internal CommaDelimitedConfigurationElement AllowedUploadFiles => GetOptionalDelimitedElement("allowedUploadFiles", new string[0]); + + [ConfigurationProperty("cloneXmlContent")] + internal InnerTextConfigurationElement CloneXmlContent => GetOptionalTextElement("cloneXmlContent", true); + + [ConfigurationProperty("GlobalPreviewStorageEnabled")] + internal InnerTextConfigurationElement GlobalPreviewStorageEnabled => GetOptionalTextElement("GlobalPreviewStorageEnabled", false); + + [ConfigurationProperty("defaultDocumentTypeProperty")] + internal InnerTextConfigurationElement DefaultDocumentTypeProperty => GetOptionalTextElement("defaultDocumentTypeProperty", "Textstring"); + + [ConfigurationProperty("showDeprecatedPropertyEditors")] + internal InnerTextConfigurationElement ShowDeprecatedPropertyEditors => GetOptionalTextElement("showDeprecatedPropertyEditors", false); + + [ConfigurationProperty("EnableInheritedDocumentTypes")] + internal InnerTextConfigurationElement EnableInheritedDocumentTypes => GetOptionalTextElement("EnableInheritedDocumentTypes", true); + + [ConfigurationProperty("EnableInheritedMediaTypes")] + internal InnerTextConfigurationElement EnableInheritedMediaTypes => GetOptionalTextElement("EnableInheritedMediaTypes", true); + + [ConfigurationProperty("loginBackgroundImage")] + internal InnerTextConfigurationElement LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty); + + string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress; + + bool IContentSection.DisableHtmlEmail => Notifications.DisableHtmlEmail; + + IEnumerable IContentSection.ImageFileTypes => Imaging.ImageFileTypes; + + IEnumerable IContentSection.ImageTagAllowedAttributes => Imaging.ImageTagAllowedAttributes; + + IEnumerable IContentSection.ImageAutoFillProperties => Imaging.ImageAutoFillProperties; + + bool IContentSection.ScriptEditorDisable => ScriptEditor.ScriptEditorDisable; + + string IContentSection.ScriptFolderPath => ScriptEditor.ScriptFolderPath; + + IEnumerable IContentSection.ScriptFileTypes => ScriptEditor.ScriptFileTypes; + + bool IContentSection.ResolveUrlsFromTextString => ResolveUrlsFromTextString; + + bool IContentSection.UploadAllowDirectories => UploadAllowDirectories; + + bool IContentSection.EnsureUniqueNaming => EnsureUniqueNaming; + + bool IContentSection.XmlCacheEnabled => XmlCacheEnabled; + + bool IContentSection.ContinouslyUpdateXmlDiskCache => ContinouslyUpdateXmlDiskCache; + + bool IContentSection.XmlContentCheckForDiskChanges => XmlContentCheckForDiskChanges; + + bool IContentSection.EnableSplashWhileLoading => EnableSplashWhileLoading; + + string IContentSection.PropertyContextHelpOption => PropertyContextHelpOption; + + bool IContentSection.ForceSafeAliases => ForceSafeAliases; + + string IContentSection.PreviewBadge => PreviewBadge; + + int IContentSection.UmbracoLibraryCacheDuration => UmbracoLibraryCacheDuration; + + MacroErrorBehaviour IContentSection.MacroErrorBehaviour => MacroErrors; + + IEnumerable IContentSection.DisallowedUploadFiles => DisallowedUploadFiles; + + IEnumerable IContentSection.AllowedUploadFiles => AllowedUploadFiles; + + bool IContentSection.CloneXmlContent => CloneXmlContent; + + bool IContentSection.GlobalPreviewStorageEnabled => GlobalPreviewStorageEnabled; + + string IContentSection.DefaultDocumentTypeProperty => DefaultDocumentTypeProperty; + + bool IContentSection.ShowDeprecatedPropertyEditors => ShowDeprecatedPropertyEditors; + + bool IContentSection.EnableInheritedDocumentTypes => EnableInheritedDocumentTypes; + + bool IContentSection.EnableInheritedMediaTypes => EnableInheritedMediaTypes; + + string IContentSection.LoginBackgroundImage => LoginBackgroundImage; + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentError404Collection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentError404Collection.cs index cd1b9722de..bdbcb27b4c 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentError404Collection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentError404Collection.cs @@ -1,37 +1,37 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ContentError404Collection : ConfigurationElementCollection, IEnumerable - { - internal void Add(ContentErrorPageElement element) - { - BaseAdd(element); - } - - protected override ConfigurationElement CreateNewElement() - { - return new ContentErrorPageElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((ContentErrorPageElement)element).Culture - + ((ContentErrorPageElement)element).Value; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as ContentErrorPageElement; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ContentError404Collection : ConfigurationElementCollection, IEnumerable + { + internal void Add(ContentErrorPageElement element) + { + BaseAdd(element); + } + + protected override ConfigurationElement CreateNewElement() + { + return new ContentErrorPageElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((ContentErrorPageElement)element).Culture + + ((ContentErrorPageElement)element).Value; + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as ContentErrorPageElement; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorPageElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorPageElement.cs index 5cedee59dc..c12dbdddd4 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorPageElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorPageElement.cs @@ -1,65 +1,65 @@ -using System; -using System.Xml.Linq; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ContentErrorPageElement : InnerTextConfigurationElement, IContentErrorPage - { - public ContentErrorPageElement(XElement rawXml) - : base(rawXml) - { - } - - public ContentErrorPageElement() - { - - } - - public bool HasContentId - { - get { return ContentId != int.MinValue; } - } - - public bool HasContentKey - { - get { return ContentKey != Guid.Empty; } - } - - public int ContentId - { - get - { - int parsed; - if (int.TryParse(Value, out parsed)) - { - return parsed; - } - return int.MinValue; - } - } - - public Guid ContentKey - { - get - { - Guid parsed; - if (Guid.TryParse(Value, out parsed)) - { - return parsed; - } - return Guid.Empty; - } - } - - public string ContentXPath - { - get { return Value; } - } - - public string Culture - { - get { return (string) RawXml.Attribute("culture"); } - set { RawXml.Attribute("culture").Value = value; } - } - } -} +using System; +using System.Xml.Linq; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ContentErrorPageElement : InnerTextConfigurationElement, IContentErrorPage + { + public ContentErrorPageElement(XElement rawXml) + : base(rawXml) + { + } + + public ContentErrorPageElement() + { + + } + + public bool HasContentId + { + get { return ContentId != int.MinValue; } + } + + public bool HasContentKey + { + get { return ContentKey != Guid.Empty; } + } + + public int ContentId + { + get + { + int parsed; + if (int.TryParse(Value, out parsed)) + { + return parsed; + } + return int.MinValue; + } + } + + public Guid ContentKey + { + get + { + Guid parsed; + if (Guid.TryParse(Value, out parsed)) + { + return parsed; + } + return Guid.Empty; + } + } + + public string ContentXPath + { + get { return Value; } + } + + public string Culture + { + get { return (string) RawXml.Attribute("culture"); } + set { RawXml.Attribute("culture").Value = value; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorsElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorsElement.cs index 8066306bf0..5b5b54380d 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorsElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorsElement.cs @@ -1,44 +1,44 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ContentErrorsElement : RawXmlConfigurationElement - { - - public IEnumerable Error404Collection - { - get - { - var result = new ContentError404Collection(); - if (RawXml != null) - { - var e404 = RawXml.Elements("error404").First(); - var ePages = e404.Elements("errorPage").ToArray(); - if (ePages.Any()) - { - //there are multiple - foreach (var e in ePages) - { - result.Add(new ContentErrorPageElement(e) - { - Culture = (string)e.Attribute("culture"), - RawValue = e.Value - }); - } - } - else - { - //there's only one defined - result.Add(new ContentErrorPageElement(e404) - { - RawValue = e404.Value - }); - } - } - return result; - } - } - - } -} +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ContentErrorsElement : RawXmlConfigurationElement + { + + public IEnumerable Error404Collection + { + get + { + var result = new ContentError404Collection(); + if (RawXml != null) + { + var e404 = RawXml.Elements("error404").First(); + var ePages = e404.Elements("errorPage").ToArray(); + if (ePages.Any()) + { + //there are multiple + foreach (var e in ePages) + { + result.Add(new ContentErrorPageElement(e) + { + Culture = (string)e.Attribute("culture"), + RawValue = e.Value + }); + } + } + else + { + //there's only one defined + result.Add(new ContentErrorPageElement(e404) + { + RawValue = e404.Value + }); + } + } + return result; + } + } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentImagingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentImagingElement.cs index 61406ed7b9..93537fe870 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentImagingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentImagingElement.cs @@ -1,81 +1,81 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ContentImagingElement : ConfigurationElement - { - - [ConfigurationProperty("imageFileTypes")] - internal CommaDelimitedConfigurationElement ImageFileTypes - { - get - { - return new OptionalCommaDelimitedConfigurationElement( - (CommaDelimitedConfigurationElement)this["imageFileTypes"], - //set the default - GetDefaultImageFileTypes()); - } - } - - internal static string[] GetDefaultImageFileTypes() - { - return new[] {"jpeg", "jpg", "gif", "bmp", "png", "tiff", "tif"}; - } - - [ConfigurationProperty("allowedAttributes")] - internal CommaDelimitedConfigurationElement ImageTagAllowedAttributes - { - get - { - return new OptionalCommaDelimitedConfigurationElement( - (CommaDelimitedConfigurationElement)this["allowedAttributes"], - //set the default - new[] { "src", "alt", "border", "class", "style", "align", "id", "name", "onclick", "usemap" }); - } - } - - private ImagingAutoFillPropertiesCollection _defaultImageAutoFill; - - [ConfigurationCollection(typeof(ImagingAutoFillPropertiesCollection), AddItemName = "uploadField")] - [ConfigurationProperty("autoFillImageProperties", IsDefaultCollection = true)] - internal ImagingAutoFillPropertiesCollection ImageAutoFillProperties - { - get - { - if (_defaultImageAutoFill != null) - { - return _defaultImageAutoFill; - } - - //here we need to check if this element is defined, if it is not then we'll setup the defaults - var prop = Properties["autoFillImageProperties"]; - var autoFill = this[prop] as ConfigurationElement; - if (autoFill != null && autoFill.ElementInformation.IsPresent == false) - { - _defaultImageAutoFill = new ImagingAutoFillPropertiesCollection - { - new ImagingAutoFillUploadFieldElement - { - Alias = "umbracoFile" - } - }; - return _defaultImageAutoFill; - } - - return (ImagingAutoFillPropertiesCollection) base["autoFillImageProperties"]; - } - } - - internal static ImagingAutoFillPropertiesCollection GetDefaultImageAutoFillProperties() - { - return new ImagingAutoFillPropertiesCollection - { - new ImagingAutoFillUploadFieldElement - { - Alias = "umbracoFile" - } - }; - } - - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ContentImagingElement : ConfigurationElement + { + + [ConfigurationProperty("imageFileTypes")] + internal CommaDelimitedConfigurationElement ImageFileTypes + { + get + { + return new OptionalCommaDelimitedConfigurationElement( + (CommaDelimitedConfigurationElement)this["imageFileTypes"], + //set the default + GetDefaultImageFileTypes()); + } + } + + internal static string[] GetDefaultImageFileTypes() + { + return new[] {"jpeg", "jpg", "gif", "bmp", "png", "tiff", "tif"}; + } + + [ConfigurationProperty("allowedAttributes")] + internal CommaDelimitedConfigurationElement ImageTagAllowedAttributes + { + get + { + return new OptionalCommaDelimitedConfigurationElement( + (CommaDelimitedConfigurationElement)this["allowedAttributes"], + //set the default + new[] { "src", "alt", "border", "class", "style", "align", "id", "name", "onclick", "usemap" }); + } + } + + private ImagingAutoFillPropertiesCollection _defaultImageAutoFill; + + [ConfigurationCollection(typeof(ImagingAutoFillPropertiesCollection), AddItemName = "uploadField")] + [ConfigurationProperty("autoFillImageProperties", IsDefaultCollection = true)] + internal ImagingAutoFillPropertiesCollection ImageAutoFillProperties + { + get + { + if (_defaultImageAutoFill != null) + { + return _defaultImageAutoFill; + } + + //here we need to check if this element is defined, if it is not then we'll setup the defaults + var prop = Properties["autoFillImageProperties"]; + var autoFill = this[prop] as ConfigurationElement; + if (autoFill != null && autoFill.ElementInformation.IsPresent == false) + { + _defaultImageAutoFill = new ImagingAutoFillPropertiesCollection + { + new ImagingAutoFillUploadFieldElement + { + Alias = "umbracoFile" + } + }; + return _defaultImageAutoFill; + } + + return (ImagingAutoFillPropertiesCollection) base["autoFillImageProperties"]; + } + } + + internal static ImagingAutoFillPropertiesCollection GetDefaultImageAutoFillProperties() + { + return new ImagingAutoFillPropertiesCollection + { + new ImagingAutoFillUploadFieldElement + { + Alias = "umbracoFile" + } + }; + } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs index f0c6400799..c218138e31 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs @@ -1,27 +1,27 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ContentScriptEditorElement : UmbracoConfigurationElement - { - [ConfigurationProperty("scriptFolderPath")] - internal InnerTextConfigurationElement ScriptFolderPath - { - get { return GetOptionalTextElement("scriptFolderPath", "/scripts"); } - } - - [ConfigurationProperty("scriptFileTypes")] - internal OptionalCommaDelimitedConfigurationElement ScriptFileTypes - { - get { return GetOptionalDelimitedElement("scriptFileTypes", new[] {"js", "xml"}); } - } - - [ConfigurationProperty("scriptDisableEditor")] - internal InnerTextConfigurationElement ScriptEditorDisable - { - get { return GetOptionalTextElement("scriptDisableEditor", false); } - } - - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ContentScriptEditorElement : UmbracoConfigurationElement + { + [ConfigurationProperty("scriptFolderPath")] + internal InnerTextConfigurationElement ScriptFolderPath + { + get { return GetOptionalTextElement("scriptFolderPath", "/scripts"); } + } + + [ConfigurationProperty("scriptFileTypes")] + internal OptionalCommaDelimitedConfigurationElement ScriptFileTypes + { + get { return GetOptionalDelimitedElement("scriptFileTypes", new[] {"js", "xml"}); } + } + + [ConfigurationProperty("scriptDisableEditor")] + internal InnerTextConfigurationElement ScriptEditorDisable + { + get { return GetOptionalTextElement("scriptDisableEditor", false); } + } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/DisabledLogTypesCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/DisabledLogTypesCollection.cs index 1674274990..4d79685ff2 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/DisabledLogTypesCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/DisabledLogTypesCollection.cs @@ -1,31 +1,31 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class DisabledLogTypesCollection : ConfigurationElementCollection, IEnumerable - { - protected override ConfigurationElement CreateNewElement() - { - return new LogTypeElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((LogTypeElement)element).Value; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as ILogType; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class DisabledLogTypesCollection : ConfigurationElementCollection, IEnumerable + { + protected override ConfigurationElement CreateNewElement() + { + return new LogTypeElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((LogTypeElement)element).Value; + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as ILogType; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ExternalLoggerElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ExternalLoggerElement.cs index cf6e785325..00cf29a751 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ExternalLoggerElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ExternalLoggerElement.cs @@ -1,25 +1,25 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ExternalLoggerElement : ConfigurationElement - { - [ConfigurationProperty("assembly")] - internal string Assembly - { - get { return (string)base["assembly"]; } - } - - [ConfigurationProperty("type")] - internal string Type - { - get { return (string)base["type"]; } - } - - [ConfigurationProperty("logAuditTrail")] - internal bool LogAuditTrail - { - get { return (bool)base["logAuditTrail"]; } - } - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ExternalLoggerElement : ConfigurationElement + { + [ConfigurationProperty("assembly")] + internal string Assembly + { + get { return (string)base["assembly"]; } + } + + [ConfigurationProperty("type")] + internal string Type + { + get { return (string)base["type"]; } + } + + [ConfigurationProperty("logAuditTrail")] + internal bool LogAuditTrail + { + get { return (bool)base["logAuditTrail"]; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/FileExtensionElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/FileExtensionElement.cs index d0f566093f..225a7d7aed 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/FileExtensionElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/FileExtensionElement.cs @@ -1,21 +1,21 @@ -using System.Xml.Linq; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class FileExtensionElement : InnerTextConfigurationElement, IFileExtension - { - public FileExtensionElement() - { - } - - internal FileExtensionElement(XElement rawXml) - : base(rawXml) - { - } - - string IFileExtension.Extension - { - get { return Value; } - } - } -} +using System.Xml.Linq; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class FileExtensionElement : InnerTextConfigurationElement, IFileExtension + { + public FileExtensionElement() + { + } + + internal FileExtensionElement(XElement rawXml) + : base(rawXml) + { + } + + string IFileExtension.Extension + { + get { return Value; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs index 0b3f5cb404..bd33472ea9 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IChar - { - string Char { get; } - string Replacement { get; } - } -} +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IChar + { + string Char { get; } + string Replacement { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentErrorPage.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentErrorPage.cs index 9761727be8..3ea00d7c74 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentErrorPage.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentErrorPage.cs @@ -1,14 +1,14 @@ -using System; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IContentErrorPage - { - int ContentId { get; } - Guid ContentKey { get; } - string ContentXPath { get; } - bool HasContentId { get; } - bool HasContentKey { get; } - string Culture { get; set; } - } -} +using System; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IContentErrorPage + { + int ContentId { get; } + Guid ContentKey { get; } + string ContentXPath { get; } + bool HasContentId { get; } + bool HasContentKey { get; } + string Culture { get; set; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index 6929646114..176b53fdcb 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -1,72 +1,72 @@ -using System.Collections.Generic; -using Umbraco.Core.Macros; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IContentSection : IUmbracoConfigurationSection - { - string NotificationEmailAddress { get; } - - bool DisableHtmlEmail { get; } - - IEnumerable ImageFileTypes { get; } - - IEnumerable ImageTagAllowedAttributes { get; } - - IEnumerable ImageAutoFillProperties { get; } - - string ScriptFolderPath { get; } - - IEnumerable ScriptFileTypes { get; } - - bool ScriptEditorDisable { get; } - - bool ResolveUrlsFromTextString { get; } - +using System.Collections.Generic; +using Umbraco.Core.Macros; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IContentSection : IUmbracoConfigurationSection + { + string NotificationEmailAddress { get; } + + bool DisableHtmlEmail { get; } + + IEnumerable ImageFileTypes { get; } + + IEnumerable ImageTagAllowedAttributes { get; } + + IEnumerable ImageAutoFillProperties { get; } + + string ScriptFolderPath { get; } + + IEnumerable ScriptFileTypes { get; } + + bool ScriptEditorDisable { get; } + + bool ResolveUrlsFromTextString { get; } + bool UploadAllowDirectories { get; } - IEnumerable Error404Collection { get; } - - bool EnsureUniqueNaming { get; } - - bool XmlCacheEnabled { get; } - - bool ContinouslyUpdateXmlDiskCache { get; } - - bool XmlContentCheckForDiskChanges { get; } - - bool EnableSplashWhileLoading { get; } - - string PropertyContextHelpOption { get; } - - bool ForceSafeAliases { get; } - - string PreviewBadge { get; } - - int UmbracoLibraryCacheDuration { get; } - - MacroErrorBehaviour MacroErrorBehaviour { get; } - - IEnumerable DisallowedUploadFiles { get; } - - IEnumerable AllowedUploadFiles { get; } - - bool CloneXmlContent { get; } - - bool GlobalPreviewStorageEnabled { get; } - - string DefaultDocumentTypeProperty { get; } - - /// - /// Gets a value indicating whether to show deprecated property editors in - /// a datatype list of available editors. - /// - bool ShowDeprecatedPropertyEditors { get; } - - bool EnableInheritedDocumentTypes { get; } - - bool EnableInheritedMediaTypes { get; } - - string LoginBackgroundImage { get; } - } -} + IEnumerable Error404Collection { get; } + + bool EnsureUniqueNaming { get; } + + bool XmlCacheEnabled { get; } + + bool ContinouslyUpdateXmlDiskCache { get; } + + bool XmlContentCheckForDiskChanges { get; } + + bool EnableSplashWhileLoading { get; } + + string PropertyContextHelpOption { get; } + + bool ForceSafeAliases { get; } + + string PreviewBadge { get; } + + int UmbracoLibraryCacheDuration { get; } + + MacroErrorBehaviour MacroErrorBehaviour { get; } + + IEnumerable DisallowedUploadFiles { get; } + + IEnumerable AllowedUploadFiles { get; } + + bool CloneXmlContent { get; } + + bool GlobalPreviewStorageEnabled { get; } + + string DefaultDocumentTypeProperty { get; } + + /// + /// Gets a value indicating whether to show deprecated property editors in + /// a datatype list of available editors. + /// + bool ShowDeprecatedPropertyEditors { get; } + + bool EnableInheritedDocumentTypes { get; } + + bool EnableInheritedMediaTypes { get; } + + string LoginBackgroundImage { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IFileExtension.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IFileExtension.cs index 08ae1d3699..0431fcc205 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IFileExtension.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IFileExtension.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IFileExtension - { - string Extension { get; } - } -} +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IFileExtension + { + string Extension { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs index 320cf641be..11b5e42e78 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs @@ -1,18 +1,18 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IImagingAutoFillUploadField - { - /// - /// Allow setting internally so we can create a default - /// - string Alias { get; } - - string WidthFieldAlias { get; } - - string HeightFieldAlias { get; } - - string LengthFieldAlias { get; } - - string ExtensionFieldAlias { get; } - } -} +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IImagingAutoFillUploadField + { + /// + /// Allow setting internally so we can create a default + /// + string Alias { get; } + + string WidthFieldAlias { get; } + + string HeightFieldAlias { get; } + + string LengthFieldAlias { get; } + + string ExtensionFieldAlias { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ILogType.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ILogType.cs index bf1a9306da..f3624736fe 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ILogType.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ILogType.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface ILogType - { - string LogTypeAlias { get; } - } -} +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface ILogType + { + string LogTypeAlias { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSection.cs index b5b1a0dc37..220f4b9d5b 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSection.cs @@ -1,17 +1,17 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface ILoggingSection : IUmbracoConfigurationSection - { - bool AutoCleanLogs { get; } - - bool EnableLogging { get; } - - int CleaningMiliseconds { get; } - - int MaxLogAge { get; } - - IEnumerable DisabledLogTypes { get; } - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface ILoggingSection : IUmbracoConfigurationSection + { + bool AutoCleanLogs { get; } + + bool EnableLogging { get; } + + int CleaningMiliseconds { get; } + + int MaxLogAge { get; } + + IEnumerable DisabledLogTypes { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IProvidersSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IProvidersSection.cs index 52e4405847..6fdac94af1 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IProvidersSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IProvidersSection.cs @@ -1,9 +1,9 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IProvidersSection : IUmbracoConfigurationSection - { - string DefaultBackOfficeUserProvider { get; } - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IProvidersSection : IUmbracoConfigurationSection + { + string DefaultBackOfficeUserProvider { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSection.cs index c1151b24d2..f6d78eecf2 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSection.cs @@ -1,19 +1,19 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IRequestHandlerSection : IUmbracoConfigurationSection - { - bool UseDomainPrefixes { get; } - - bool AddTrailingSlash { get; } - - bool RemoveDoubleDashes { get; } - - bool ConvertUrlsToAscii { get; } - - bool TryConvertUrlsToAscii { get; } - - IEnumerable CharCollection { get; } - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IRequestHandlerSection : IUmbracoConfigurationSection + { + bool UseDomainPrefixes { get; } + + bool AddTrailingSlash { get; } + + bool RemoveDoubleDashes { get; } + + bool ConvertUrlsToAscii { get; } + + bool TryConvertUrlsToAscii { get; } + + IEnumerable CharCollection { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTask.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTask.cs index fd448897ee..22fa0288bc 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTask.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTask.cs @@ -1,13 +1,13 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IScheduledTask - { - string Alias { get; } - - bool Log { get; } - - int Interval { get; } - - string Url { get; } - } -} +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IScheduledTask + { + string Alias { get; } + + bool Log { get; } + + int Interval { get; } + + string Url { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTasksSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTasksSection.cs index 8da6446675..e4a52e1fb1 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTasksSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTasksSection.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IScheduledTasksSection : IUmbracoConfigurationSection - { - IEnumerable Tasks { get; } - - string BaseUrl { get; } - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IScheduledTasksSection : IUmbracoConfigurationSection + { + IEnumerable Tasks { get; } + + string BaseUrl { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs index 11eab07ec2..b8b8c86f8d 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs @@ -1,27 +1,27 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface ISecuritySection : IUmbracoConfigurationSection - { - 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; } - - /// - /// 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; } - } -} +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface ISecuritySection : IUmbracoConfigurationSection + { + 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; } + + /// + /// 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; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ITemplatesSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ITemplatesSection.cs index e156b46d6c..67fd58030b 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ITemplatesSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ITemplatesSection.cs @@ -1,9 +1,9 @@ -using System; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface ITemplatesSection : IUmbracoConfigurationSection - { - RenderingEngine DefaultRenderingEngine { get; } - } -} +using System; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface ITemplatesSection : IUmbracoConfigurationSection + { + RenderingEngine DefaultRenderingEngine { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs index 09cc698756..28da03ff2d 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs @@ -1,27 +1,27 @@ -using System; -using System.ComponentModel; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IUmbracoSettingsSection : IUmbracoConfigurationSection - { +using System; +using System.ComponentModel; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IUmbracoSettingsSection : IUmbracoConfigurationSection + { IBackOfficeSection BackOffice { get; } - - IContentSection Content { get; } - - ISecuritySection Security { get; } - - IRequestHandlerSection RequestHandler { get; } - - ITemplatesSection Templates { get; } - - ILoggingSection Logging { get; } - - IScheduledTasksSection ScheduledTasks { get; } - - IProvidersSection Providers { get; } - - IWebRoutingSection WebRouting { get; } - - } -} + + IContentSection Content { get; } + + ISecuritySection Security { get; } + + IRequestHandlerSection RequestHandler { get; } + + ITemplatesSection Templates { get; } + + ILoggingSection Logging { get; } + + IScheduledTasksSection ScheduledTasks { get; } + + IProvidersSection Providers { get; } + + IWebRoutingSection WebRouting { get; } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs index c7b7a07206..1ce937d31c 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs @@ -1,20 +1,20 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IWebRoutingSection : IUmbracoConfigurationSection - { - bool TrySkipIisCustomErrors { get; } - - bool InternalRedirectPreservesTemplate { get; } - - bool DisableAlternativeTemplates { get; } - - bool DisableFindContentByIdPath { get; } - - bool DisableRedirectUrlTracking { get; } - - string UrlProviderMode { get; } - - string UmbracoApplicationUrl { get; } - } - -} +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IWebRoutingSection : IUmbracoConfigurationSection + { + bool TrySkipIisCustomErrors { get; } + + bool InternalRedirectPreservesTemplate { get; } + + bool DisableAlternativeTemplates { get; } + + bool DisableFindContentByIdPath { get; } + + bool DisableRedirectUrlTracking { get; } + + string UrlProviderMode { get; } + + string UmbracoApplicationUrl { get; } + } + +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillPropertiesCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillPropertiesCollection.cs index 60d68252e2..0bac9721a3 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillPropertiesCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillPropertiesCollection.cs @@ -1,37 +1,37 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ImagingAutoFillPropertiesCollection : ConfigurationElementCollection, IEnumerable - { - - protected override ConfigurationElement CreateNewElement() - { - return new ImagingAutoFillUploadFieldElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((ImagingAutoFillUploadFieldElement)element).Alias; - } - - internal void Add(ImagingAutoFillUploadFieldElement item) - { - BaseAdd(item); - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IImagingAutoFillUploadField; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ImagingAutoFillPropertiesCollection : ConfigurationElementCollection, IEnumerable + { + + protected override ConfigurationElement CreateNewElement() + { + return new ImagingAutoFillUploadFieldElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((ImagingAutoFillUploadFieldElement)element).Alias; + } + + internal void Add(ImagingAutoFillUploadFieldElement item) + { + BaseAdd(item); + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as IImagingAutoFillUploadField; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs index 77b9faec8e..0573cb4efe 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs @@ -1,67 +1,67 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ImagingAutoFillUploadFieldElement : UmbracoConfigurationElement, IImagingAutoFillUploadField - { - /// - /// Allow setting internally so we can create a default - /// - [ConfigurationProperty("alias", IsKey = true, IsRequired = true)] - public string Alias - { - get { return (string)this["alias"]; } - set { this["alias"] = value; } - } - - [ConfigurationProperty("widthFieldAlias")] - internal InnerTextConfigurationElement WidthFieldAlias - { - get { return GetOptionalTextElement("widthFieldAlias", "umbracoWidth"); } - } - - [ConfigurationProperty("heightFieldAlias")] - internal InnerTextConfigurationElement HeightFieldAlias - { - get { return GetOptionalTextElement("heightFieldAlias", "umbracoHeight"); } - } - - [ConfigurationProperty("lengthFieldAlias")] - internal InnerTextConfigurationElement LengthFieldAlias - { - get { return GetOptionalTextElement("lengthFieldAlias", "umbracoBytes"); } - } - - [ConfigurationProperty("extensionFieldAlias")] - internal InnerTextConfigurationElement ExtensionFieldAlias - { - get { return GetOptionalTextElement("extensionFieldAlias", "umbracoExtension"); } - } - - string IImagingAutoFillUploadField.Alias - { - get { return Alias; } - - } - - string IImagingAutoFillUploadField.WidthFieldAlias - { - get { return WidthFieldAlias; } - } - - string IImagingAutoFillUploadField.HeightFieldAlias - { - get { return HeightFieldAlias; } - } - - string IImagingAutoFillUploadField.LengthFieldAlias - { - get { return LengthFieldAlias; } - } - - string IImagingAutoFillUploadField.ExtensionFieldAlias - { - get { return ExtensionFieldAlias; } - } - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ImagingAutoFillUploadFieldElement : UmbracoConfigurationElement, IImagingAutoFillUploadField + { + /// + /// Allow setting internally so we can create a default + /// + [ConfigurationProperty("alias", IsKey = true, IsRequired = true)] + public string Alias + { + get { return (string)this["alias"]; } + set { this["alias"] = value; } + } + + [ConfigurationProperty("widthFieldAlias")] + internal InnerTextConfigurationElement WidthFieldAlias + { + get { return GetOptionalTextElement("widthFieldAlias", "umbracoWidth"); } + } + + [ConfigurationProperty("heightFieldAlias")] + internal InnerTextConfigurationElement HeightFieldAlias + { + get { return GetOptionalTextElement("heightFieldAlias", "umbracoHeight"); } + } + + [ConfigurationProperty("lengthFieldAlias")] + internal InnerTextConfigurationElement LengthFieldAlias + { + get { return GetOptionalTextElement("lengthFieldAlias", "umbracoBytes"); } + } + + [ConfigurationProperty("extensionFieldAlias")] + internal InnerTextConfigurationElement ExtensionFieldAlias + { + get { return GetOptionalTextElement("extensionFieldAlias", "umbracoExtension"); } + } + + string IImagingAutoFillUploadField.Alias + { + get { return Alias; } + + } + + string IImagingAutoFillUploadField.WidthFieldAlias + { + get { return WidthFieldAlias; } + } + + string IImagingAutoFillUploadField.HeightFieldAlias + { + get { return HeightFieldAlias; } + } + + string IImagingAutoFillUploadField.LengthFieldAlias + { + get { return LengthFieldAlias; } + } + + string IImagingAutoFillUploadField.ExtensionFieldAlias + { + get { return ExtensionFieldAlias; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/LogTypeElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/LogTypeElement.cs index ff92d9c173..772391da90 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/LogTypeElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/LogTypeElement.cs @@ -1,11 +1,11 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class LogTypeElement : InnerTextConfigurationElement, ILogType - { - - string ILogType.LogTypeAlias - { - get { return Value; } - } - } -} +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class LogTypeElement : InnerTextConfigurationElement, ILogType + { + + string ILogType.LogTypeAlias + { + get { return Value; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs index 3f9e93ed86..7632f241e1 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs @@ -1,92 +1,92 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class LoggingElement : UmbracoConfigurationElement, ILoggingSection - { - - [ConfigurationProperty("autoCleanLogs")] - internal InnerTextConfigurationElement AutoCleanLogs - { - get { return GetOptionalTextElement("autoCleanLogs", false); } - } - - [ConfigurationProperty("enableLogging")] - internal InnerTextConfigurationElement EnableLogging - { - get { return GetOptionalTextElement("enableLogging", true); } - } - - [ConfigurationProperty("enableAsyncLogging")] - internal InnerTextConfigurationElement EnableAsyncLogging - { - get { return GetOptionalTextElement("enableAsyncLogging", true); } - } - - [ConfigurationProperty("cleaningMiliseconds")] - internal InnerTextConfigurationElement CleaningMiliseconds - { - get { return GetOptionalTextElement("cleaningMiliseconds", -1); } - } - - [ConfigurationProperty("maxLogAge")] - internal InnerTextConfigurationElement MaxLogAge - { - get { return GetOptionalTextElement("maxLogAge", -1); } - } - - [ConfigurationCollection(typeof(DisabledLogTypesCollection), AddItemName = "logTypeAlias")] - [ConfigurationProperty("disabledLogTypes", IsDefaultCollection = true)] - internal DisabledLogTypesCollection DisabledLogTypes - { - get { return (DisabledLogTypesCollection)base["disabledLogTypes"]; } - } - - [ConfigurationProperty("externalLogger", IsRequired = false)] - internal ExternalLoggerElement ExternalLogger - { - get { return (ExternalLoggerElement) base["externalLogger"]; } - } - - public bool ExternalLoggerIsConfigured - { - get - { - var externalLoggerProperty = Properties["externalLogger"]; - var externalLogger = this[externalLoggerProperty] as ConfigurationElement; - if (externalLogger != null && externalLogger.ElementInformation.IsPresent) - { - return true; - } - return false; - } - } - - bool ILoggingSection.AutoCleanLogs - { - get { return AutoCleanLogs; } - } - - bool ILoggingSection.EnableLogging - { - get { return EnableLogging; } - } - - int ILoggingSection.CleaningMiliseconds - { - get { return CleaningMiliseconds; } - } - - int ILoggingSection.MaxLogAge - { - get { return MaxLogAge; } - } - - IEnumerable ILoggingSection.DisabledLogTypes - { - get { return DisabledLogTypes; } - } - - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class LoggingElement : UmbracoConfigurationElement, ILoggingSection + { + + [ConfigurationProperty("autoCleanLogs")] + internal InnerTextConfigurationElement AutoCleanLogs + { + get { return GetOptionalTextElement("autoCleanLogs", false); } + } + + [ConfigurationProperty("enableLogging")] + internal InnerTextConfigurationElement EnableLogging + { + get { return GetOptionalTextElement("enableLogging", true); } + } + + [ConfigurationProperty("enableAsyncLogging")] + internal InnerTextConfigurationElement EnableAsyncLogging + { + get { return GetOptionalTextElement("enableAsyncLogging", true); } + } + + [ConfigurationProperty("cleaningMiliseconds")] + internal InnerTextConfigurationElement CleaningMiliseconds + { + get { return GetOptionalTextElement("cleaningMiliseconds", -1); } + } + + [ConfigurationProperty("maxLogAge")] + internal InnerTextConfigurationElement MaxLogAge + { + get { return GetOptionalTextElement("maxLogAge", -1); } + } + + [ConfigurationCollection(typeof(DisabledLogTypesCollection), AddItemName = "logTypeAlias")] + [ConfigurationProperty("disabledLogTypes", IsDefaultCollection = true)] + internal DisabledLogTypesCollection DisabledLogTypes + { + get { return (DisabledLogTypesCollection)base["disabledLogTypes"]; } + } + + [ConfigurationProperty("externalLogger", IsRequired = false)] + internal ExternalLoggerElement ExternalLogger + { + get { return (ExternalLoggerElement) base["externalLogger"]; } + } + + public bool ExternalLoggerIsConfigured + { + get + { + var externalLoggerProperty = Properties["externalLogger"]; + var externalLogger = this[externalLoggerProperty] as ConfigurationElement; + if (externalLogger != null && externalLogger.ElementInformation.IsPresent) + { + return true; + } + return false; + } + } + + bool ILoggingSection.AutoCleanLogs + { + get { return AutoCleanLogs; } + } + + bool ILoggingSection.EnableLogging + { + get { return EnableLogging; } + } + + int ILoggingSection.CleaningMiliseconds + { + get { return CleaningMiliseconds; } + } + + int ILoggingSection.MaxLogAge + { + get { return MaxLogAge; } + } + + IEnumerable ILoggingSection.DisabledLogTypes + { + get { return DisabledLogTypes; } + } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs index 0facef0f87..534e4012ff 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs @@ -1,20 +1,20 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class NotificationsElement : UmbracoConfigurationElement - { - [ConfigurationProperty("email")] - internal InnerTextConfigurationElement NotificationEmailAddress - { - get { return (InnerTextConfigurationElement)this["email"]; } - } - - [ConfigurationProperty("disableHtmlEmail")] - internal InnerTextConfigurationElement DisableHtmlEmail - { - get { return GetOptionalTextElement("disableHtmlEmail", false); } - } - - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class NotificationsElement : UmbracoConfigurationElement + { + [ConfigurationProperty("email")] + internal InnerTextConfigurationElement NotificationEmailAddress + { + get { return (InnerTextConfigurationElement)this["email"]; } + } + + [ConfigurationProperty("disableHtmlEmail")] + internal InnerTextConfigurationElement DisableHtmlEmail + { + get { return GetOptionalTextElement("disableHtmlEmail", false); } + } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ProvidersElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ProvidersElement.cs index 2a02ceaa80..87876b1e0a 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ProvidersElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ProvidersElement.cs @@ -1,18 +1,18 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ProvidersElement : ConfigurationElement, IProvidersSection - { - [ConfigurationProperty("users")] - public UserProviderElement Users - { - get { return (UserProviderElement)base["users"]; } - } - - public string DefaultBackOfficeUserProvider - { - get { return Users.DefaultBackOfficeProvider; } - } - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ProvidersElement : ConfigurationElement, IProvidersSection + { + [ConfigurationProperty("users")] + public UserProviderElement Users + { + get { return (UserProviderElement)base["users"]; } + } + + public string DefaultBackOfficeUserProvider + { + get { return Users.DefaultBackOfficeProvider; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs index cf16a31889..b05a31537b 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs @@ -1,127 +1,127 @@ -using System; -using System.Configuration; -using System.Globalization; -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class RequestHandlerElement : UmbracoConfigurationElement, IRequestHandlerSection - { - [ConfigurationProperty("useDomainPrefixes")] - public InnerTextConfigurationElement UseDomainPrefixes - { - get { return GetOptionalTextElement("useDomainPrefixes", false); } - } - - [ConfigurationProperty("addTrailingSlash")] - public InnerTextConfigurationElement AddTrailingSlash - { - get { return GetOptionalTextElement("addTrailingSlash", true); } - } - - private UrlReplacingElement _defaultUrlReplacing; - [ConfigurationProperty("urlReplacing")] - public UrlReplacingElement UrlReplacing - { - get - { - if (_defaultUrlReplacing != null) - { - return _defaultUrlReplacing; - } - - //here we need to check if this element is defined, if it is not then we'll setup the defaults - var prop = Properties["urlReplacing"]; - var urls = this[prop] as ConfigurationElement; - if (urls != null && urls.ElementInformation.IsPresent == false) - { - _defaultUrlReplacing = new UrlReplacingElement() - { - CharCollection = GetDefaultCharReplacements() - }; - - return _defaultUrlReplacing; - } - - return (UrlReplacingElement)this["urlReplacing"]; - } - } - - internal static CharCollection GetDefaultCharReplacements() - { - var dictionary = new Dictionary() - { - {' ',"-"}, - {'\"',""}, - {'\'',""}, - {'%',""}, - {'.',""}, - {';',""}, - {'/',""}, - {'\\',""}, - {':',""}, - {'#',""}, - {'+',"plus"}, - {'*',"star"}, - {'&',""}, - {'?',""}, - {'æ',"ae"}, - {'ø',"oe"}, - {'å',"aa"}, - {'ä',"ae"}, - {'ö',"oe"}, - {'ü',"ue"}, - {'ß',"ss"}, - {'Ä',"ae"}, - {'Ö',"oe"}, - {'|',"-"}, - {'<',""}, - {'>',""} - }; - - //const string chars = @" ,"",',%,.,;,/,\,:,#,+,*,&,?,æ,ø,å,ä,ö,ü,ß,Ä,Ö,|,<,>"; - - var collection = new CharCollection(); - foreach (var c in dictionary) - { - collection.Add(new CharElement - { - Char = c.Key.ToString(CultureInfo.InvariantCulture), - Replacement = c.Value.ToString(CultureInfo.InvariantCulture) - }); - } - - return collection; - } - - bool IRequestHandlerSection.UseDomainPrefixes - { - get { return UseDomainPrefixes; } - } - - bool IRequestHandlerSection.AddTrailingSlash - { - get { return AddTrailingSlash; } - } - - bool IRequestHandlerSection.RemoveDoubleDashes - { - get { return UrlReplacing.RemoveDoubleDashes; } - } - - bool IRequestHandlerSection.ConvertUrlsToAscii - { - get { return UrlReplacing.ConvertUrlsToAscii.InvariantEquals("true"); } - } - - bool IRequestHandlerSection.TryConvertUrlsToAscii - { - get { return UrlReplacing.ConvertUrlsToAscii.InvariantEquals("try"); } - } - - IEnumerable IRequestHandlerSection.CharCollection - { - get { return UrlReplacing.CharCollection; } - } - } -} +using System; +using System.Configuration; +using System.Globalization; +using System.Collections.Generic; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class RequestHandlerElement : UmbracoConfigurationElement, IRequestHandlerSection + { + [ConfigurationProperty("useDomainPrefixes")] + public InnerTextConfigurationElement UseDomainPrefixes + { + get { return GetOptionalTextElement("useDomainPrefixes", false); } + } + + [ConfigurationProperty("addTrailingSlash")] + public InnerTextConfigurationElement AddTrailingSlash + { + get { return GetOptionalTextElement("addTrailingSlash", true); } + } + + private UrlReplacingElement _defaultUrlReplacing; + [ConfigurationProperty("urlReplacing")] + public UrlReplacingElement UrlReplacing + { + get + { + if (_defaultUrlReplacing != null) + { + return _defaultUrlReplacing; + } + + //here we need to check if this element is defined, if it is not then we'll setup the defaults + var prop = Properties["urlReplacing"]; + var urls = this[prop] as ConfigurationElement; + if (urls != null && urls.ElementInformation.IsPresent == false) + { + _defaultUrlReplacing = new UrlReplacingElement() + { + CharCollection = GetDefaultCharReplacements() + }; + + return _defaultUrlReplacing; + } + + return (UrlReplacingElement)this["urlReplacing"]; + } + } + + internal static CharCollection GetDefaultCharReplacements() + { + var dictionary = new Dictionary() + { + {' ',"-"}, + {'\"',""}, + {'\'',""}, + {'%',""}, + {'.',""}, + {';',""}, + {'/',""}, + {'\\',""}, + {':',""}, + {'#',""}, + {'+',"plus"}, + {'*',"star"}, + {'&',""}, + {'?',""}, + {'æ',"ae"}, + {'ø',"oe"}, + {'å',"aa"}, + {'ä',"ae"}, + {'ö',"oe"}, + {'ü',"ue"}, + {'ß',"ss"}, + {'Ä',"ae"}, + {'Ö',"oe"}, + {'|',"-"}, + {'<',""}, + {'>',""} + }; + + //const string chars = @" ,"",',%,.,;,/,\,:,#,+,*,&,?,æ,ø,å,ä,ö,ü,ß,Ä,Ö,|,<,>"; + + var collection = new CharCollection(); + foreach (var c in dictionary) + { + collection.Add(new CharElement + { + Char = c.Key.ToString(CultureInfo.InvariantCulture), + Replacement = c.Value.ToString(CultureInfo.InvariantCulture) + }); + } + + return collection; + } + + bool IRequestHandlerSection.UseDomainPrefixes + { + get { return UseDomainPrefixes; } + } + + bool IRequestHandlerSection.AddTrailingSlash + { + get { return AddTrailingSlash; } + } + + bool IRequestHandlerSection.RemoveDoubleDashes + { + get { return UrlReplacing.RemoveDoubleDashes; } + } + + bool IRequestHandlerSection.ConvertUrlsToAscii + { + get { return UrlReplacing.ConvertUrlsToAscii.InvariantEquals("true"); } + } + + bool IRequestHandlerSection.TryConvertUrlsToAscii + { + get { return UrlReplacing.ConvertUrlsToAscii.InvariantEquals("try"); } + } + + IEnumerable IRequestHandlerSection.CharCollection + { + get { return UrlReplacing.CharCollection; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTaskElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTaskElement.cs index 1ba844f6e1..a7ecd6f84e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTaskElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTaskElement.cs @@ -1,31 +1,31 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ScheduledTaskElement : ConfigurationElement, IScheduledTask - { - [ConfigurationProperty("alias")] - public string Alias - { - get { return (string)base["alias"]; } - } - - [ConfigurationProperty("log")] - public bool Log - { - get { return (bool)base["log"]; } - } - - [ConfigurationProperty("interval")] - public int Interval - { - get { return (int)base["interval"]; } - } - - [ConfigurationProperty("url")] - public string Url - { - get { return (string)base["url"]; } - } - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ScheduledTaskElement : ConfigurationElement, IScheduledTask + { + [ConfigurationProperty("alias")] + public string Alias + { + get { return (string)base["alias"]; } + } + + [ConfigurationProperty("log")] + public bool Log + { + get { return (bool)base["log"]; } + } + + [ConfigurationProperty("interval")] + public int Interval + { + get { return (int)base["interval"]; } + } + + [ConfigurationProperty("url")] + public string Url + { + get { return (string)base["url"]; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksCollection.cs index 5ff35ccf63..9e9a037c0f 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksCollection.cs @@ -1,31 +1,31 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ScheduledTasksCollection : ConfigurationElementCollection, IEnumerable - { - protected override ConfigurationElement CreateNewElement() - { - return new ScheduledTaskElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((ScheduledTaskElement)element).Alias; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IScheduledTask; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ScheduledTasksCollection : ConfigurationElementCollection, IEnumerable + { + protected override ConfigurationElement CreateNewElement() + { + return new ScheduledTaskElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((ScheduledTaskElement)element).Alias; + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as IScheduledTask; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksElement.cs index 7829259ab8..5c83939a7f 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksElement.cs @@ -1,26 +1,26 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ScheduledTasksElement : ConfigurationElement, IScheduledTasksSection - { - [ConfigurationCollection(typeof(ScheduledTasksCollection), AddItemName = "task")] - [ConfigurationProperty("", IsDefaultCollection = true)] - internal ScheduledTasksCollection Tasks - { - get { return (ScheduledTasksCollection)base[""]; } - } - - IEnumerable IScheduledTasksSection.Tasks - { - get { return Tasks; } - } - - [ConfigurationProperty("baseUrl", IsRequired = false, DefaultValue = null)] - public string BaseUrl - { - get { return (string)base["baseUrl"]; } - } - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class ScheduledTasksElement : ConfigurationElement, IScheduledTasksSection + { + [ConfigurationCollection(typeof(ScheduledTasksCollection), AddItemName = "task")] + [ConfigurationProperty("", IsDefaultCollection = true)] + internal ScheduledTasksCollection Tasks + { + get { return (ScheduledTasksCollection)base[""]; } + } + + IEnumerable IScheduledTasksSection.Tasks + { + get { return Tasks; } + } + + [ConfigurationProperty("baseUrl", IsRequired = false, DefaultValue = null)] + public string BaseUrl + { + get { return (string)base["baseUrl"]; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs index 9ce88a8f75..6bd5be3824 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs @@ -1,93 +1,93 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class SecurityElement : UmbracoConfigurationElement, ISecuritySection - { - [ConfigurationProperty("keepUserLoggedIn")] - internal InnerTextConfigurationElement KeepUserLoggedIn - { - get { return GetOptionalTextElement("keepUserLoggedIn", true); } - } - - [ConfigurationProperty("hideDisabledUsersInBackoffice")] - internal InnerTextConfigurationElement HideDisabledUsersInBackoffice - { - 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 - { - get { return GetOptionalTextElement("authCookieName", "UMB_UCONTEXT"); } - } - - [ConfigurationProperty("authCookieDomain")] - internal InnerTextConfigurationElement AuthCookieDomain - { - get { return GetOptionalTextElement("authCookieDomain", null); } - } - - bool ISecuritySection.KeepUserLoggedIn - { - get { return KeepUserLoggedIn; } - } - - bool ISecuritySection.HideDisabledUsersInBackoffice - { - 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; } - } - - string ISecuritySection.AuthCookieDomain - { - get { return AuthCookieDomain; } - } - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class SecurityElement : UmbracoConfigurationElement, ISecuritySection + { + [ConfigurationProperty("keepUserLoggedIn")] + internal InnerTextConfigurationElement KeepUserLoggedIn + { + get { return GetOptionalTextElement("keepUserLoggedIn", true); } + } + + [ConfigurationProperty("hideDisabledUsersInBackoffice")] + internal InnerTextConfigurationElement HideDisabledUsersInBackoffice + { + 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 + { + get { return GetOptionalTextElement("authCookieName", "UMB_UCONTEXT"); } + } + + [ConfigurationProperty("authCookieDomain")] + internal InnerTextConfigurationElement AuthCookieDomain + { + get { return GetOptionalTextElement("authCookieDomain", null); } + } + + bool ISecuritySection.KeepUserLoggedIn + { + get { return KeepUserLoggedIn; } + } + + bool ISecuritySection.HideDisabledUsersInBackoffice + { + 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; } + } + + string ISecuritySection.AuthCookieDomain + { + get { return AuthCookieDomain; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs index d4d0f230ce..8c929b02d8 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs @@ -1,20 +1,20 @@ -using System; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class TemplatesElement : UmbracoConfigurationElement, ITemplatesSection - { - [ConfigurationProperty("defaultRenderingEngine", IsRequired = true)] - internal InnerTextConfigurationElement DefaultRenderingEngine - { - get { return GetOptionalTextElement("defaultRenderingEngine", RenderingEngine.Mvc); } - } - - RenderingEngine ITemplatesSection.DefaultRenderingEngine - { - get { return DefaultRenderingEngine; } - } - - } -} +using System; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class TemplatesElement : UmbracoConfigurationElement, ITemplatesSection + { + [ConfigurationProperty("defaultRenderingEngine", IsRequired = true)] + internal InnerTextConfigurationElement DefaultRenderingEngine + { + get { return GetOptionalTextElement("defaultRenderingEngine", RenderingEngine.Mvc); } + } + + RenderingEngine ITemplatesSection.DefaultRenderingEngine + { + get { return DefaultRenderingEngine; } + } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs index 7a08ec3b18..d36410f317 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs @@ -1,111 +1,111 @@ -using System; -using System.ComponentModel; -using System.Configuration; -using System.Linq; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - - public class UmbracoSettingsSection : ConfigurationSection, IUmbracoSettingsSection - { - [ConfigurationProperty("backOffice")] - internal BackOfficeElement BackOffice - { - get { return (BackOfficeElement)this["backOffice"]; } - } - - [ConfigurationProperty("content")] - internal ContentElement Content - { - get { return (ContentElement)this["content"]; } - } - - [ConfigurationProperty("security")] - internal SecurityElement Security - { - get { return (SecurityElement)this["security"]; } - } - - [ConfigurationProperty("requestHandler")] - internal RequestHandlerElement RequestHandler - { - get { return (RequestHandlerElement)this["requestHandler"]; } - } - - [ConfigurationProperty("templates")] - internal TemplatesElement Templates - { - get { return (TemplatesElement)this["templates"]; } - } - - [ConfigurationProperty("logging")] - internal LoggingElement Logging - { - get { return (LoggingElement)this["logging"]; } - } - - [ConfigurationProperty("scheduledTasks")] - internal ScheduledTasksElement ScheduledTasks - { - get { return (ScheduledTasksElement)this["scheduledTasks"]; } - } - - [ConfigurationProperty("providers")] - internal ProvidersElement Providers - { - get { return (ProvidersElement)this["providers"]; } - } - - [ConfigurationProperty("web.routing")] - internal WebRoutingElement WebRouting - { - get { return (WebRoutingElement)this["web.routing"]; } - } - - IContentSection IUmbracoSettingsSection.Content - { - get { return Content; } - } - - ISecuritySection IUmbracoSettingsSection.Security - { - get { return Security; } - } - - IRequestHandlerSection IUmbracoSettingsSection.RequestHandler - { - get { return RequestHandler; } - } - - ITemplatesSection IUmbracoSettingsSection.Templates - { - get { return Templates; } - } - - IBackOfficeSection IUmbracoSettingsSection.BackOffice - { - get { return BackOffice; } - } - - ILoggingSection IUmbracoSettingsSection.Logging - { - get { return Logging; } - } - - IScheduledTasksSection IUmbracoSettingsSection.ScheduledTasks - { - get { return ScheduledTasks; } - } - - IProvidersSection IUmbracoSettingsSection.Providers - { - get { return Providers; } - } - - IWebRoutingSection IUmbracoSettingsSection.WebRouting - { - get { return WebRouting; } - } - - } -} +using System; +using System.ComponentModel; +using System.Configuration; +using System.Linq; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + + public class UmbracoSettingsSection : ConfigurationSection, IUmbracoSettingsSection + { + [ConfigurationProperty("backOffice")] + internal BackOfficeElement BackOffice + { + get { return (BackOfficeElement)this["backOffice"]; } + } + + [ConfigurationProperty("content")] + internal ContentElement Content + { + get { return (ContentElement)this["content"]; } + } + + [ConfigurationProperty("security")] + internal SecurityElement Security + { + get { return (SecurityElement)this["security"]; } + } + + [ConfigurationProperty("requestHandler")] + internal RequestHandlerElement RequestHandler + { + get { return (RequestHandlerElement)this["requestHandler"]; } + } + + [ConfigurationProperty("templates")] + internal TemplatesElement Templates + { + get { return (TemplatesElement)this["templates"]; } + } + + [ConfigurationProperty("logging")] + internal LoggingElement Logging + { + get { return (LoggingElement)this["logging"]; } + } + + [ConfigurationProperty("scheduledTasks")] + internal ScheduledTasksElement ScheduledTasks + { + get { return (ScheduledTasksElement)this["scheduledTasks"]; } + } + + [ConfigurationProperty("providers")] + internal ProvidersElement Providers + { + get { return (ProvidersElement)this["providers"]; } + } + + [ConfigurationProperty("web.routing")] + internal WebRoutingElement WebRouting + { + get { return (WebRoutingElement)this["web.routing"]; } + } + + IContentSection IUmbracoSettingsSection.Content + { + get { return Content; } + } + + ISecuritySection IUmbracoSettingsSection.Security + { + get { return Security; } + } + + IRequestHandlerSection IUmbracoSettingsSection.RequestHandler + { + get { return RequestHandler; } + } + + ITemplatesSection IUmbracoSettingsSection.Templates + { + get { return Templates; } + } + + IBackOfficeSection IUmbracoSettingsSection.BackOffice + { + get { return BackOffice; } + } + + ILoggingSection IUmbracoSettingsSection.Logging + { + get { return Logging; } + } + + IScheduledTasksSection IUmbracoSettingsSection.ScheduledTasks + { + get { return ScheduledTasks; } + } + + IProvidersSection IUmbracoSettingsSection.Providers + { + get { return Providers; } + } + + IWebRoutingSection IUmbracoSettingsSection.WebRouting + { + get { return WebRouting; } + } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/UrlReplacingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/UrlReplacingElement.cs index 6d76f45a8c..cdfa95e780 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/UrlReplacingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/UrlReplacingElement.cs @@ -1,29 +1,29 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class UrlReplacingElement : ConfigurationElement - { - [ConfigurationProperty("removeDoubleDashes", DefaultValue = true)] - internal bool RemoveDoubleDashes - { - get { return (bool) base["removeDoubleDashes"]; } - } - - [ConfigurationProperty("toAscii", DefaultValue = "false")] - internal string ConvertUrlsToAscii - { - get { return (string) base["toAscii"]; } - } - - [ConfigurationCollection(typeof(CharCollection), AddItemName = "char")] - [ConfigurationProperty("", IsDefaultCollection = true)] - internal CharCollection CharCollection - { - get { return (CharCollection)base[""]; } - set { base[""] = value; } - } - - } -} +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class UrlReplacingElement : ConfigurationElement + { + [ConfigurationProperty("removeDoubleDashes", DefaultValue = true)] + internal bool RemoveDoubleDashes + { + get { return (bool) base["removeDoubleDashes"]; } + } + + [ConfigurationProperty("toAscii", DefaultValue = "false")] + internal string ConvertUrlsToAscii + { + get { return (string) base["toAscii"]; } + } + + [ConfigurationCollection(typeof(CharCollection), AddItemName = "char")] + [ConfigurationProperty("", IsDefaultCollection = true)] + internal CharCollection CharCollection + { + get { return (CharCollection)base[""]; } + set { base[""] = value; } + } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/UserProviderElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/UserProviderElement.cs index 60da143b58..eedb7b4ccf 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/UserProviderElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/UserProviderElement.cs @@ -1,21 +1,21 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class UserProviderElement : ConfigurationElement - { - [ConfigurationProperty("DefaultBackofficeProvider")] - internal InnerTextConfigurationElement DefaultBackOfficeProvider - { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["DefaultBackofficeProvider"], - //set the default - "UsersMembershipProvider"); - } - } - - - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class UserProviderElement : ConfigurationElement + { + [ConfigurationProperty("DefaultBackofficeProvider")] + internal InnerTextConfigurationElement DefaultBackOfficeProvider + { + get + { + return new OptionalInnerTextConfigurationElement( + (InnerTextConfigurationElement)this["DefaultBackofficeProvider"], + //set the default + "UsersMembershipProvider"); + } + } + + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs index 393aac4f50..9f82d15f7d 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs @@ -1,48 +1,48 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class WebRoutingElement : ConfigurationElement, IWebRoutingSection - { - [ConfigurationProperty("trySkipIisCustomErrors", DefaultValue = "false")] - public bool TrySkipIisCustomErrors - { - get { return (bool) base["trySkipIisCustomErrors"]; } - } - - [ConfigurationProperty("internalRedirectPreservesTemplate", DefaultValue = "false")] - public bool InternalRedirectPreservesTemplate - { - get { return (bool) base["internalRedirectPreservesTemplate"]; } - } - - [ConfigurationProperty("disableAlternativeTemplates", DefaultValue = "false")] - public bool DisableAlternativeTemplates - { - get { return (bool) base["disableAlternativeTemplates"]; } - } - [ConfigurationProperty("disableFindContentByIdPath", DefaultValue = "false")] - public bool DisableFindContentByIdPath - { - get { return (bool) base["disableFindContentByIdPath"]; } - } - - [ConfigurationProperty("disableRedirectUrlTracking", DefaultValue = "false")] - public bool DisableRedirectUrlTracking - { - get { return (bool) base["disableRedirectUrlTracking"]; } - } - - [ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")] - public string UrlProviderMode - { - get { return (string) base["urlProviderMode"]; } - } - - [ConfigurationProperty("umbracoApplicationUrl", DefaultValue = null)] - public string UmbracoApplicationUrl - { - get { return (string)base["umbracoApplicationUrl"]; } - } - } -} +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class WebRoutingElement : ConfigurationElement, IWebRoutingSection + { + [ConfigurationProperty("trySkipIisCustomErrors", DefaultValue = "false")] + public bool TrySkipIisCustomErrors + { + get { return (bool) base["trySkipIisCustomErrors"]; } + } + + [ConfigurationProperty("internalRedirectPreservesTemplate", DefaultValue = "false")] + public bool InternalRedirectPreservesTemplate + { + get { return (bool) base["internalRedirectPreservesTemplate"]; } + } + + [ConfigurationProperty("disableAlternativeTemplates", DefaultValue = "false")] + public bool DisableAlternativeTemplates + { + get { return (bool) base["disableAlternativeTemplates"]; } + } + [ConfigurationProperty("disableFindContentByIdPath", DefaultValue = "false")] + public bool DisableFindContentByIdPath + { + get { return (bool) base["disableFindContentByIdPath"]; } + } + + [ConfigurationProperty("disableRedirectUrlTracking", DefaultValue = "false")] + public bool DisableRedirectUrlTracking + { + get { return (bool) base["disableRedirectUrlTracking"]; } + } + + [ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")] + public string UrlProviderMode + { + get { return (string) base["urlProviderMode"]; } + } + + [ConfigurationProperty("umbracoApplicationUrl", DefaultValue = null)] + public string UmbracoApplicationUrl + { + get { return (string)base["umbracoApplicationUrl"]; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 96418d4536..9c0ad4c7c4 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -1,71 +1,71 @@ -using System; -using System.Configuration; -using System.Reflection; -using Semver; - -namespace Umbraco.Core.Configuration -{ - /// - /// Represents the version of the executing code. - /// - public static class UmbracoVersion - { - // BEWARE! - // This class is parsed and updated by the build scripts. - // Do NOT modify it unless you understand what you are doing. - - /// - /// Gets the version of the executing code. - /// - public static Version Current { get; } = new Version("8.0.0"); - - /// - /// Gets the version comment of the executing code (eg "beta"). - /// - public static string CurrentComment => "alpha.44"; - - /// - /// Gets the assembly version of Umbraco.Code.dll. - /// - /// Get it by looking at a class in that dll, due to medium trust issues, - /// see http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx, - /// however fixme we don't support medium trust anymore? - public static string AssemblyVersion => new AssemblyName(typeof(UmbracoVersion).Assembly.FullName).Version.ToString(); - - /// - /// Gets the semantic version of the executing code. - /// - public static SemVersion SemanticVersion { get; } = new SemVersion( - Current.Major, - Current.Minor, - Current.Build, - CurrentComment.IsNullOrWhiteSpace() ? null : CurrentComment, - Current.Revision > 0 ? Current.Revision.ToInvariantString() : null); - - /// - /// Gets the "local" version of the site. - /// - /// - /// Three things have a version, really: the executing code, the database model, - /// and the site/files. The database model version is entirely managed via migrations, - /// and changes during an upgrade. The executing code version changes when new code is - /// deployed. The site/files version changes during an upgrade. - /// - public static SemVersion Local - { - get - { - try - { - // fixme - this should live in its own independent file! NOT web.config! - var value = ConfigurationManager.AppSettings["umbracoConfigurationStatus"]; - return value.IsNullOrWhiteSpace() ? null : SemVersion.TryParse(value, out var semver) ? semver : null; - } - catch - { - return null; - } - } - } - } -} +using System; +using System.Configuration; +using System.Reflection; +using Semver; + +namespace Umbraco.Core.Configuration +{ + /// + /// Represents the version of the executing code. + /// + public static class UmbracoVersion + { + // BEWARE! + // This class is parsed and updated by the build scripts. + // Do NOT modify it unless you understand what you are doing. + + /// + /// Gets the version of the executing code. + /// + public static Version Current { get; } = new Version("8.0.0"); + + /// + /// Gets the version comment of the executing code (eg "beta"). + /// + public static string CurrentComment => "alpha.44"; + + /// + /// Gets the assembly version of Umbraco.Code.dll. + /// + /// Get it by looking at a class in that dll, due to medium trust issues, + /// see http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx, + /// however fixme we don't support medium trust anymore? + public static string AssemblyVersion => new AssemblyName(typeof(UmbracoVersion).Assembly.FullName).Version.ToString(); + + /// + /// Gets the semantic version of the executing code. + /// + public static SemVersion SemanticVersion { get; } = new SemVersion( + Current.Major, + Current.Minor, + Current.Build, + CurrentComment.IsNullOrWhiteSpace() ? null : CurrentComment, + Current.Revision > 0 ? Current.Revision.ToInvariantString() : null); + + /// + /// Gets the "local" version of the site. + /// + /// + /// Three things have a version, really: the executing code, the database model, + /// and the site/files. The database model version is entirely managed via migrations, + /// and changes during an upgrade. The executing code version changes when new code is + /// deployed. The site/files version changes during an upgrade. + /// + public static SemVersion Local + { + get + { + try + { + // fixme - this should live in its own independent file! NOT web.config! + var value = ConfigurationManager.AppSettings["umbracoConfigurationStatus"]; + return value.IsNullOrWhiteSpace() ? null : SemVersion.TryParse(value, out var semver) ? semver : null; + } + catch + { + return null; + } + } + } + } +} diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index b23efd6bb8..c4d73558c6 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -1,151 +1,151 @@ -namespace Umbraco.Core -{ - public static partial class Constants - { - /// - /// Defines the alias identifiers for Umbraco's core application sections. - /// - public static class Applications - { - /// - /// Application alias for the content section. - /// - public const string Content = "content"; - - /// - /// Application alias for the developer section. - /// - public const string Developer = "developer"; - - /// - /// Application alias for the media section. - /// - public const string Media = "media"; - - /// - /// Application alias for the members section. - /// - public const string Members = "member"; - - /// - /// Application alias for the settings section. - /// - public const string Settings = "settings"; - - /// - /// Application alias for the translation section. - /// - public const string Translation = "translation"; - - /// - /// Application alias for the users section. - /// - public const string Users = "users"; - - /// - /// Application alias for the forms section. - /// - public const string Forms = "forms"; - } - - /// - /// Defines the alias identifiers for Umbraco's core trees. - /// - public static class Trees - { - /// - /// alias for the content tree. - /// - public const string Content = "content"; - - /// - /// alias for the content blueprint tree. - /// - public const string ContentBlueprints = "contentBlueprints"; - - /// - /// alias for the member tree. - /// - public const string Members = "member"; - - /// - /// alias for the media tree. - /// - public const string Media = "media"; - - /// - /// alias for the macro tree. - /// - public const string Macros = "macros"; - - /// - /// alias for the datatype tree. - /// - public const string DataTypes = "dataTypes"; - - /// - /// alias for the packages tree - /// - public const string Packages = "packages"; - - /// - /// alias for the dictionary tree. - /// - public const string Dictionary = "dictionary"; - - public const string Stylesheets = "stylesheets"; - - /// - /// alias for the document type tree. - /// - public const string DocumentTypes = "documentTypes"; - - /// - /// alias for the media type tree. - /// - public const string MediaTypes = "mediaTypes"; - - /// - /// alias for the member type tree. - /// - public const string MemberTypes = "memberTypes"; - - /// - /// alias for the member group tree. - /// - public const string MemberGroups = "memberGroups"; - - /// - /// alias for the template tree. - /// - public const string Templates = "templates"; - - public const string RelationTypes = "relationTypes"; - - public const string Languages = "languages"; - - /// - /// alias for the user types tree. - /// - public const string UserTypes = "userTypes"; - - /// - /// alias for the user permissions tree. - /// - public const string UserPermissions = "userPermissions"; - - /// - /// alias for the users tree. - /// - public const string Users = "users"; - - public const string Scripts = "scripts"; - - public const string PartialViews = "partialViews"; - - public const string PartialViewMacros = "partialViewMacros"; - - //TODO: Fill in the rest! - } - } -} +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the alias identifiers for Umbraco's core application sections. + /// + public static class Applications + { + /// + /// Application alias for the content section. + /// + public const string Content = "content"; + + /// + /// Application alias for the developer section. + /// + public const string Developer = "developer"; + + /// + /// Application alias for the media section. + /// + public const string Media = "media"; + + /// + /// Application alias for the members section. + /// + public const string Members = "member"; + + /// + /// Application alias for the settings section. + /// + public const string Settings = "settings"; + + /// + /// Application alias for the translation section. + /// + public const string Translation = "translation"; + + /// + /// Application alias for the users section. + /// + public const string Users = "users"; + + /// + /// Application alias for the forms section. + /// + public const string Forms = "forms"; + } + + /// + /// Defines the alias identifiers for Umbraco's core trees. + /// + public static class Trees + { + /// + /// alias for the content tree. + /// + public const string Content = "content"; + + /// + /// alias for the content blueprint tree. + /// + public const string ContentBlueprints = "contentBlueprints"; + + /// + /// alias for the member tree. + /// + public const string Members = "member"; + + /// + /// alias for the media tree. + /// + public const string Media = "media"; + + /// + /// alias for the macro tree. + /// + public const string Macros = "macros"; + + /// + /// alias for the datatype tree. + /// + public const string DataTypes = "dataTypes"; + + /// + /// alias for the packages tree + /// + public const string Packages = "packages"; + + /// + /// alias for the dictionary tree. + /// + public const string Dictionary = "dictionary"; + + public const string Stylesheets = "stylesheets"; + + /// + /// alias for the document type tree. + /// + public const string DocumentTypes = "documentTypes"; + + /// + /// alias for the media type tree. + /// + public const string MediaTypes = "mediaTypes"; + + /// + /// alias for the member type tree. + /// + public const string MemberTypes = "memberTypes"; + + /// + /// alias for the member group tree. + /// + public const string MemberGroups = "memberGroups"; + + /// + /// alias for the template tree. + /// + public const string Templates = "templates"; + + public const string RelationTypes = "relationTypes"; + + public const string Languages = "languages"; + + /// + /// alias for the user types tree. + /// + public const string UserTypes = "userTypes"; + + /// + /// alias for the user permissions tree. + /// + public const string UserPermissions = "userPermissions"; + + /// + /// alias for the users tree. + /// + public const string Users = "users"; + + public const string Scripts = "scripts"; + + public const string PartialViews = "partialViews"; + + public const string PartialViewMacros = "partialViewMacros"; + + //TODO: Fill in the rest! + } + } +} diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 4e49933585..ed3e7d682c 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -1,346 +1,346 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core -{ - public static partial class Constants - { - /// - /// Defines the identifiers for property-type alias conventions that are used within the Umbraco core. - /// - public static class Conventions - { - internal static class PermissionCategories - { - public const string ContentCategory = "content"; - public const string AdministrationCategory = "administration"; - public const string StructureCategory = "structure"; - public const string OtherCategory = "other"; - } - - public static class PublicAccess - { - public const string MemberUsernameRuleType = "MemberUsername"; - public const string MemberRoleRuleType = "MemberRole"; - - [Obsolete("No longer supported, this is here for backwards compatibility only")] - public const string MemberIdRuleType = "MemberId"; - [Obsolete("No longer supported, this is here for backwards compatibility only")] - public const string MemberGroupIdRuleType = "MemberGroupId"; - } - - public static class Localization - { - /// - /// The root id for all top level dictionary items - /// - [Obsolete("There is no dictionary root item id anymore, it is simply null")] - public const string DictionaryItemRootId = "41c7638d-f529-4bff-853e-59a0c2fb1bde"; - } - - public static class DataTypes - { - public const string ListViewPrefix = "List View - "; - } - - public static class PropertyGroups - { - public const string ListViewGroupName = "umbContainerView"; - } - - /// - /// Constants for Umbraco Content property aliases. - /// - public static class Content - { - /// - /// Property alias for the Content's Url (internal) redirect. - /// - public const string InternalRedirectId = "umbracoInternalRedirectId"; - - /// - /// Property alias for the Content's navigational hide, (not actually used in core code). - /// - public const string NaviHide = "umbracoNaviHide"; - - /// - /// Property alias for the Content's Url redirect. - /// - public const string Redirect = "umbracoRedirect"; - - /// - /// Property alias for the Content's Url alias. - /// - public const string UrlAlias = "umbracoUrlAlias"; - - /// - /// Property alias for the Content's Url name. - /// - public const string UrlName = "umbracoUrlName"; - } - - /// - /// Constants for Umbraco Media property aliases. - /// - public static class Media - { - /// - /// Property alias for the Media's file name. - /// - public const string File = "umbracoFile"; - - /// - /// Property alias for the Media's width. - /// - public const string Width = "umbracoWidth"; - - /// - /// Property alias for the Media's height. - /// - public const string Height = "umbracoHeight"; - - /// - /// Property alias for the Media's file size (in bytes). - /// - public const string Bytes = "umbracoBytes"; - - /// - /// Property alias for the Media's file extension. - /// - public const string Extension = "umbracoExtension"; - } - - /// - /// Defines the alias identifiers for Umbraco media types. - /// - public static class MediaTypes - { - /// - /// MediaType alias for a file. - /// - public const string File = "File"; - - /// - /// MediaType alias for a folder. - /// - public const string Folder = "Folder"; - - /// - /// MediaType alias for an image. - /// - public const string Image = "Image"; - - /// - /// MediaType alias indicating allowing auto-selection. - /// - public const string AutoSelect = "umbracoAutoSelect"; - } - - /// - /// Constants for Umbraco Member property aliases. - /// - public static class Member - { - /// - /// if a role starts with __umbracoRole we won't show it as it's an internal role used for public access - /// - public static readonly string InternalRolePrefix = "__umbracoRole"; - - public static readonly string UmbracoMemberProviderName = "UmbracoMembershipProvider"; - - public static readonly string UmbracoRoleProviderName = "UmbracoRoleProvider"; - - /// - /// Property alias for a Members Password Question - /// - public const string PasswordQuestion = "umbracoMemberPasswordRetrievalQuestion"; - - public const string PasswordQuestionLabel = "Password Question"; - - /// - /// Property alias for Members Password Answer - /// - public const string PasswordAnswer = "umbracoMemberPasswordRetrievalAnswer"; - - public const string PasswordAnswerLabel = "Password Answer"; - - /// - /// Property alias for the Comments on a Member - /// - public const string Comments = "umbracoMemberComments"; - - public const string CommentsLabel = "Comments"; - - /// - /// Property alias for the Approved boolean of a Member - /// - public const string IsApproved = "umbracoMemberApproved"; - - public const string IsApprovedLabel = "Is Approved"; - - /// - /// Property alias for the Locked out boolean of a Member - /// - public const string IsLockedOut = "umbracoMemberLockedOut"; - - public const string IsLockedOutLabel = "Is Locked Out"; - - /// - /// Property alias for the last date the Member logged in - /// - public const string LastLoginDate = "umbracoMemberLastLogin"; - - public const string LastLoginDateLabel = "Last Login Date"; - - /// - /// Property alias for the last date a Member changed its password - /// - public const string LastPasswordChangeDate = "umbracoMemberLastPasswordChangeDate"; - - public const string LastPasswordChangeDateLabel = "Last Password Change Date"; - - /// - /// Property alias for the last date a Member was locked out - /// - public const string LastLockoutDate = "umbracoMemberLastLockoutDate"; - - public const string LastLockoutDateLabel = "Last Lockout Date"; - - /// - /// Property alias for the number of failed login attemps - /// - public const string FailedPasswordAttempts = "umbracoMemberFailedPasswordAttempts"; - - public const string FailedPasswordAttemptsLabel = "Failed Password Attempts"; - - /// - /// Group name to put the membership properties on - /// - internal const string StandardPropertiesGroupName = "Membership"; - - public static Dictionary GetStandardPropertyTypeStubs() - { - return new Dictionary - { - { - Comments, - new PropertyType(PropertyEditors.Aliases.TextArea, ValueStorageType.Ntext, true, Comments) - { - Name = CommentsLabel - } - }, - { - FailedPasswordAttempts, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer, true, FailedPasswordAttempts) - { - Name = FailedPasswordAttemptsLabel - } - }, - { - IsApproved, - new PropertyType(PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, true, IsApproved) - { - Name = IsApprovedLabel - } - }, - { - IsLockedOut, - new PropertyType(PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, true, IsLockedOut) - { - Name = IsLockedOutLabel - } - }, - { - LastLockoutDate, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, true, LastLockoutDate) - { - Name = LastLockoutDateLabel - } - }, - { - LastLoginDate, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, true, LastLoginDate) - { - Name = LastLoginDateLabel - } - }, - { - LastPasswordChangeDate, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, true, LastPasswordChangeDate) - { - Name = LastPasswordChangeDateLabel - } - }, - { - PasswordAnswer, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar, true, PasswordAnswer) - { - Name = PasswordAnswerLabel - } - }, - { - PasswordQuestion, - new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar, true, PasswordQuestion) - { - Name = PasswordQuestionLabel - } - } - }; - } - } - - /// - /// Defines the alias identifiers for Umbraco member types. - /// - public static class MemberTypes - { - /// - /// MemberType alias for default member type. - /// - public const string DefaultAlias = "Member"; - - public const string SystemDefaultProtectType = "_umbracoSystemDefaultProtectType"; - - public const string AllMembersListId = "all-members"; - } - - /// - /// Constants for Umbraco URLs/Querystrings. - /// - public static class Url - { - /// - /// Querystring parameter name used for Umbraco's alternative template functionality. - /// - public const string AltTemplate = "altTemplate"; - } - - /// - /// Defines the alias identifiers for built-in Umbraco relation types. - /// - public static class RelationTypes - { - /// - /// ContentType name for default relation type "Relate Document On Copy". - /// - public const string RelateDocumentOnCopyName = "Relate Document On Copy"; - - /// - /// ContentType alias for default relation type "Relate Document On Copy". - /// - public const string RelateDocumentOnCopyAlias = "relateDocumentOnCopy"; - - /// - /// ContentType name for default relation type "Relate Parent Document On Delete". - /// - public const string RelateParentDocumentOnDeleteName = "Relate Parent Document On Delete"; - - /// - /// ContentType alias for default relation type "Relate Parent Document On Delete". - /// - public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete"; - } - } - } -} +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the identifiers for property-type alias conventions that are used within the Umbraco core. + /// + public static class Conventions + { + internal static class PermissionCategories + { + public const string ContentCategory = "content"; + public const string AdministrationCategory = "administration"; + public const string StructureCategory = "structure"; + public const string OtherCategory = "other"; + } + + public static class PublicAccess + { + public const string MemberUsernameRuleType = "MemberUsername"; + public const string MemberRoleRuleType = "MemberRole"; + + [Obsolete("No longer supported, this is here for backwards compatibility only")] + public const string MemberIdRuleType = "MemberId"; + [Obsolete("No longer supported, this is here for backwards compatibility only")] + public const string MemberGroupIdRuleType = "MemberGroupId"; + } + + public static class Localization + { + /// + /// The root id for all top level dictionary items + /// + [Obsolete("There is no dictionary root item id anymore, it is simply null")] + public const string DictionaryItemRootId = "41c7638d-f529-4bff-853e-59a0c2fb1bde"; + } + + public static class DataTypes + { + public const string ListViewPrefix = "List View - "; + } + + public static class PropertyGroups + { + public const string ListViewGroupName = "umbContainerView"; + } + + /// + /// Constants for Umbraco Content property aliases. + /// + public static class Content + { + /// + /// Property alias for the Content's Url (internal) redirect. + /// + public const string InternalRedirectId = "umbracoInternalRedirectId"; + + /// + /// Property alias for the Content's navigational hide, (not actually used in core code). + /// + public const string NaviHide = "umbracoNaviHide"; + + /// + /// Property alias for the Content's Url redirect. + /// + public const string Redirect = "umbracoRedirect"; + + /// + /// Property alias for the Content's Url alias. + /// + public const string UrlAlias = "umbracoUrlAlias"; + + /// + /// Property alias for the Content's Url name. + /// + public const string UrlName = "umbracoUrlName"; + } + + /// + /// Constants for Umbraco Media property aliases. + /// + public static class Media + { + /// + /// Property alias for the Media's file name. + /// + public const string File = "umbracoFile"; + + /// + /// Property alias for the Media's width. + /// + public const string Width = "umbracoWidth"; + + /// + /// Property alias for the Media's height. + /// + public const string Height = "umbracoHeight"; + + /// + /// Property alias for the Media's file size (in bytes). + /// + public const string Bytes = "umbracoBytes"; + + /// + /// Property alias for the Media's file extension. + /// + public const string Extension = "umbracoExtension"; + } + + /// + /// Defines the alias identifiers for Umbraco media types. + /// + public static class MediaTypes + { + /// + /// MediaType alias for a file. + /// + public const string File = "File"; + + /// + /// MediaType alias for a folder. + /// + public const string Folder = "Folder"; + + /// + /// MediaType alias for an image. + /// + public const string Image = "Image"; + + /// + /// MediaType alias indicating allowing auto-selection. + /// + public const string AutoSelect = "umbracoAutoSelect"; + } + + /// + /// Constants for Umbraco Member property aliases. + /// + public static class Member + { + /// + /// if a role starts with __umbracoRole we won't show it as it's an internal role used for public access + /// + public static readonly string InternalRolePrefix = "__umbracoRole"; + + public static readonly string UmbracoMemberProviderName = "UmbracoMembershipProvider"; + + public static readonly string UmbracoRoleProviderName = "UmbracoRoleProvider"; + + /// + /// Property alias for a Members Password Question + /// + public const string PasswordQuestion = "umbracoMemberPasswordRetrievalQuestion"; + + public const string PasswordQuestionLabel = "Password Question"; + + /// + /// Property alias for Members Password Answer + /// + public const string PasswordAnswer = "umbracoMemberPasswordRetrievalAnswer"; + + public const string PasswordAnswerLabel = "Password Answer"; + + /// + /// Property alias for the Comments on a Member + /// + public const string Comments = "umbracoMemberComments"; + + public const string CommentsLabel = "Comments"; + + /// + /// Property alias for the Approved boolean of a Member + /// + public const string IsApproved = "umbracoMemberApproved"; + + public const string IsApprovedLabel = "Is Approved"; + + /// + /// Property alias for the Locked out boolean of a Member + /// + public const string IsLockedOut = "umbracoMemberLockedOut"; + + public const string IsLockedOutLabel = "Is Locked Out"; + + /// + /// Property alias for the last date the Member logged in + /// + public const string LastLoginDate = "umbracoMemberLastLogin"; + + public const string LastLoginDateLabel = "Last Login Date"; + + /// + /// Property alias for the last date a Member changed its password + /// + public const string LastPasswordChangeDate = "umbracoMemberLastPasswordChangeDate"; + + public const string LastPasswordChangeDateLabel = "Last Password Change Date"; + + /// + /// Property alias for the last date a Member was locked out + /// + public const string LastLockoutDate = "umbracoMemberLastLockoutDate"; + + public const string LastLockoutDateLabel = "Last Lockout Date"; + + /// + /// Property alias for the number of failed login attemps + /// + public const string FailedPasswordAttempts = "umbracoMemberFailedPasswordAttempts"; + + public const string FailedPasswordAttemptsLabel = "Failed Password Attempts"; + + /// + /// Group name to put the membership properties on + /// + internal const string StandardPropertiesGroupName = "Membership"; + + public static Dictionary GetStandardPropertyTypeStubs() + { + return new Dictionary + { + { + Comments, + new PropertyType(PropertyEditors.Aliases.TextArea, ValueStorageType.Ntext, true, Comments) + { + Name = CommentsLabel + } + }, + { + FailedPasswordAttempts, + new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer, true, FailedPasswordAttempts) + { + Name = FailedPasswordAttemptsLabel + } + }, + { + IsApproved, + new PropertyType(PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, true, IsApproved) + { + Name = IsApprovedLabel + } + }, + { + IsLockedOut, + new PropertyType(PropertyEditors.Aliases.Boolean, ValueStorageType.Integer, true, IsLockedOut) + { + Name = IsLockedOutLabel + } + }, + { + LastLockoutDate, + new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, true, LastLockoutDate) + { + Name = LastLockoutDateLabel + } + }, + { + LastLoginDate, + new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, true, LastLoginDate) + { + Name = LastLoginDateLabel + } + }, + { + LastPasswordChangeDate, + new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, true, LastPasswordChangeDate) + { + Name = LastPasswordChangeDateLabel + } + }, + { + PasswordAnswer, + new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar, true, PasswordAnswer) + { + Name = PasswordAnswerLabel + } + }, + { + PasswordQuestion, + new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar, true, PasswordQuestion) + { + Name = PasswordQuestionLabel + } + } + }; + } + } + + /// + /// Defines the alias identifiers for Umbraco member types. + /// + public static class MemberTypes + { + /// + /// MemberType alias for default member type. + /// + public const string DefaultAlias = "Member"; + + public const string SystemDefaultProtectType = "_umbracoSystemDefaultProtectType"; + + public const string AllMembersListId = "all-members"; + } + + /// + /// Constants for Umbraco URLs/Querystrings. + /// + public static class Url + { + /// + /// Querystring parameter name used for Umbraco's alternative template functionality. + /// + public const string AltTemplate = "altTemplate"; + } + + /// + /// Defines the alias identifiers for built-in Umbraco relation types. + /// + public static class RelationTypes + { + /// + /// ContentType name for default relation type "Relate Document On Copy". + /// + public const string RelateDocumentOnCopyName = "Relate Document On Copy"; + + /// + /// ContentType alias for default relation type "Relate Document On Copy". + /// + public const string RelateDocumentOnCopyAlias = "relateDocumentOnCopy"; + + /// + /// ContentType name for default relation type "Relate Parent Document On Delete". + /// + public const string RelateParentDocumentOnDeleteName = "Relate Parent Document On Delete"; + + /// + /// ContentType alias for default relation type "Relate Parent Document On Delete". + /// + public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete"; + } + } + } +} diff --git a/src/Umbraco.Core/Constants-Examine.cs b/src/Umbraco.Core/Constants-Examine.cs index 2f52e289c6..ddc3500066 100644 --- a/src/Umbraco.Core/Constants-Examine.cs +++ b/src/Umbraco.Core/Constants-Examine.cs @@ -1,24 +1,24 @@ -namespace Umbraco.Core -{ - public static partial class Constants - { - public static class Examine - { - /// - /// The alias of the internal member indexer - /// - public const string InternalMemberIndexer = "InternalMemberIndexer"; - - /// - /// The alias of the internal content indexer - /// - public const string InternalIndexer = "InternalIndexer"; - - /// - /// The alias of the external content indexer - /// - public const string ExternalIndexer = "ExternalIndexer"; - - } - } -} +namespace Umbraco.Core +{ + public static partial class Constants + { + public static class Examine + { + /// + /// The alias of the internal member indexer + /// + public const string InternalMemberIndexer = "InternalMemberIndexer"; + + /// + /// The alias of the internal content indexer + /// + public const string InternalIndexer = "InternalIndexer"; + + /// + /// The alias of the external content indexer + /// + public const string ExternalIndexer = "ExternalIndexer"; + + } + } +} diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index c303068a24..fb629d382e 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -1,137 +1,137 @@ -using System; - -namespace Umbraco.Core -{ - public static partial class Constants - { - /// - /// Defines the Umbraco object type unique identifiers. - /// - public static class ObjectTypes - { - /// - /// Defines the Umbraco object type unique identifiers as string. - /// - /// Should be used only when it's not possible to use the corresponding - /// readonly Guid value, e.g. in attributes (where only consts can be used). - public static class Strings - { - // ReSharper disable MemberHidesStaticFromOuterClass - - public const string DataTypeContainer = "521231E3-8B37-469C-9F9D-51AFC91FEB7B"; - - public const string DocumentTypeContainer = "2F7A2769-6B0B-4468-90DD-AF42D64F7F16"; - - public const string MediaTypeContainer = "42AEF799-B288-4744-9B10-BE144B73CDC4"; - - public const string ContentItem = "10E2B09F-C28B-476D-B77A-AA686435E44A"; - - public const string ContentItemType = "7A333C54-6F43-40A4-86A2-18688DC7E532"; - - public const string ContentRecycleBin = "01BB7FF2-24DC-4C0C-95A2-C24EF72BBAC8"; - - public const string DataType = "30A2A501-1978-4DDB-A57B-F7EFED43BA3C"; - - public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972"; - - public const string DocumentBlueprint = "6EBEF410-03AA-48CF-A792-E1C1CB087ACA"; - - public const string DocumentType = "A2CB7800-F571-4787-9638-BC48539A0EFB"; - - public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C"; - - public const string MediaRecycleBin = "CF3D8E34-1C1C-41e9-AE56-878B57B32113"; - - public const string MediaType = "4EA4382B-2F5A-4C2B-9587-AE9B3CF3602E"; - - public const string Member = "39EB0F98-B348-42A1-8662-E7EB18487560"; - - public const string MemberGroup = "366E63B9-880F-4E13-A61C-98069B029728"; - - public const string MemberType = "9B5416FB-E72F-45A9-A07B-5A9A2709CE43"; - - public const string SystemRoot = "EA7D8624-4CFE-4578-A871-24AA946BF34D"; - - public const string Template = "6FBDE604-4178-42CE-A10B-8A2600A2F07D"; - - public const string LockObject = "87A9F1FF-B1E4-4A25-BABB-465A4A47EC41"; - - public const string RelationType = "B1988FAD-8675-4F47-915A-B3A602BC5D8D"; - - public const string FormsForm = "F5A9F787-6593-46F0-B8FF-BFD9BCA9F6BB"; - - public const string FormsPreValue = "42D7BF9B-A362-4FEE-B45A-674D5C064B70"; - - public const string FormsDataSource = "CFED6CE4-9359-443E-9977-9956FEB1D867"; - - public const string Language = "6B05D05B-EC78-49BE-A4E4-79E274F07A77"; - - public const string IdReservation = "92849B1E-3904-4713-9356-F646F87C25F4"; - - [Obsolete("This no longer exists in the database")] - internal const string Stylesheet = "9F68DA4F-A3A8-44C2-8226-DCBD125E4840"; - - [Obsolete("This no longer exists in the database")] - internal const string StylesheetProperty = "5555da4f-a123-42b2-4488-dcdfb25e4111"; - - // ReSharper restore MemberHidesStaticFromOuterClass - } - - public static readonly Guid SystemRoot = new Guid(Strings.SystemRoot); - - public static readonly Guid ContentRecycleBin = new Guid(Strings.ContentRecycleBin); - - public static readonly Guid MediaRecycleBin = new Guid(Strings.MediaRecycleBin); - - public static readonly Guid DataTypeContainer = new Guid(Strings.DataTypeContainer); - - public static readonly Guid DocumentTypeContainer = new Guid(Strings.DocumentTypeContainer); - - public static readonly Guid MediaTypeContainer = new Guid(Strings.MediaTypeContainer); - - public static readonly Guid DataType = new Guid(Strings.DataType); - - public static readonly Guid Document = new Guid(Strings.Document); - - public static readonly Guid DocumentBlueprint = new Guid(Strings.DocumentBlueprint); - - public static readonly Guid DocumentType = new Guid(Strings.DocumentType); - - public static readonly Guid Media = new Guid(Strings.Media); - - public static readonly Guid MediaType = new Guid(Strings.MediaType); - - public static readonly Guid Member = new Guid(Strings.Member); - - public static readonly Guid MemberGroup = new Guid(Strings.MemberGroup); - - public static readonly Guid MemberType = new Guid(Strings.MemberType); - - public static readonly Guid TemplateType = new Guid(Strings.Template); - - public static readonly Guid LockObject = new Guid(Strings.LockObject); - - public static readonly Guid RelationType = new Guid(Strings.RelationType); - - public static readonly Guid FormsForm = new Guid(Strings.FormsForm); - - public static readonly Guid FormsPreValue = new Guid(Strings.FormsPreValue); - - public static readonly Guid FormsDataSource = new Guid(Strings.FormsDataSource); - - public static readonly Guid Language = new Guid(Strings.Language); - - public static readonly Guid IdReservation = new Guid(Strings.IdReservation); - - public static readonly Guid Template = new Guid(Strings.Template); - - public static readonly Guid ContentItem = new Guid(Strings.ContentItem); - - [Obsolete("This no longer exists in the database")] - internal static readonly Guid Stylesheet = new Guid(Strings.Stylesheet); - - [Obsolete("This no longer exists in the database")] - internal static readonly Guid StylesheetProperty = new Guid(Strings.StylesheetProperty); - } - } -} +using System; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the Umbraco object type unique identifiers. + /// + public static class ObjectTypes + { + /// + /// Defines the Umbraco object type unique identifiers as string. + /// + /// Should be used only when it's not possible to use the corresponding + /// readonly Guid value, e.g. in attributes (where only consts can be used). + public static class Strings + { + // ReSharper disable MemberHidesStaticFromOuterClass + + public const string DataTypeContainer = "521231E3-8B37-469C-9F9D-51AFC91FEB7B"; + + public const string DocumentTypeContainer = "2F7A2769-6B0B-4468-90DD-AF42D64F7F16"; + + public const string MediaTypeContainer = "42AEF799-B288-4744-9B10-BE144B73CDC4"; + + public const string ContentItem = "10E2B09F-C28B-476D-B77A-AA686435E44A"; + + public const string ContentItemType = "7A333C54-6F43-40A4-86A2-18688DC7E532"; + + public const string ContentRecycleBin = "01BB7FF2-24DC-4C0C-95A2-C24EF72BBAC8"; + + public const string DataType = "30A2A501-1978-4DDB-A57B-F7EFED43BA3C"; + + public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972"; + + public const string DocumentBlueprint = "6EBEF410-03AA-48CF-A792-E1C1CB087ACA"; + + public const string DocumentType = "A2CB7800-F571-4787-9638-BC48539A0EFB"; + + public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C"; + + public const string MediaRecycleBin = "CF3D8E34-1C1C-41e9-AE56-878B57B32113"; + + public const string MediaType = "4EA4382B-2F5A-4C2B-9587-AE9B3CF3602E"; + + public const string Member = "39EB0F98-B348-42A1-8662-E7EB18487560"; + + public const string MemberGroup = "366E63B9-880F-4E13-A61C-98069B029728"; + + public const string MemberType = "9B5416FB-E72F-45A9-A07B-5A9A2709CE43"; + + public const string SystemRoot = "EA7D8624-4CFE-4578-A871-24AA946BF34D"; + + public const string Template = "6FBDE604-4178-42CE-A10B-8A2600A2F07D"; + + public const string LockObject = "87A9F1FF-B1E4-4A25-BABB-465A4A47EC41"; + + public const string RelationType = "B1988FAD-8675-4F47-915A-B3A602BC5D8D"; + + public const string FormsForm = "F5A9F787-6593-46F0-B8FF-BFD9BCA9F6BB"; + + public const string FormsPreValue = "42D7BF9B-A362-4FEE-B45A-674D5C064B70"; + + public const string FormsDataSource = "CFED6CE4-9359-443E-9977-9956FEB1D867"; + + public const string Language = "6B05D05B-EC78-49BE-A4E4-79E274F07A77"; + + public const string IdReservation = "92849B1E-3904-4713-9356-F646F87C25F4"; + + [Obsolete("This no longer exists in the database")] + internal const string Stylesheet = "9F68DA4F-A3A8-44C2-8226-DCBD125E4840"; + + [Obsolete("This no longer exists in the database")] + internal const string StylesheetProperty = "5555da4f-a123-42b2-4488-dcdfb25e4111"; + + // ReSharper restore MemberHidesStaticFromOuterClass + } + + public static readonly Guid SystemRoot = new Guid(Strings.SystemRoot); + + public static readonly Guid ContentRecycleBin = new Guid(Strings.ContentRecycleBin); + + public static readonly Guid MediaRecycleBin = new Guid(Strings.MediaRecycleBin); + + public static readonly Guid DataTypeContainer = new Guid(Strings.DataTypeContainer); + + public static readonly Guid DocumentTypeContainer = new Guid(Strings.DocumentTypeContainer); + + public static readonly Guid MediaTypeContainer = new Guid(Strings.MediaTypeContainer); + + public static readonly Guid DataType = new Guid(Strings.DataType); + + public static readonly Guid Document = new Guid(Strings.Document); + + public static readonly Guid DocumentBlueprint = new Guid(Strings.DocumentBlueprint); + + public static readonly Guid DocumentType = new Guid(Strings.DocumentType); + + public static readonly Guid Media = new Guid(Strings.Media); + + public static readonly Guid MediaType = new Guid(Strings.MediaType); + + public static readonly Guid Member = new Guid(Strings.Member); + + public static readonly Guid MemberGroup = new Guid(Strings.MemberGroup); + + public static readonly Guid MemberType = new Guid(Strings.MemberType); + + public static readonly Guid TemplateType = new Guid(Strings.Template); + + public static readonly Guid LockObject = new Guid(Strings.LockObject); + + public static readonly Guid RelationType = new Guid(Strings.RelationType); + + public static readonly Guid FormsForm = new Guid(Strings.FormsForm); + + public static readonly Guid FormsPreValue = new Guid(Strings.FormsPreValue); + + public static readonly Guid FormsDataSource = new Guid(Strings.FormsDataSource); + + public static readonly Guid Language = new Guid(Strings.Language); + + public static readonly Guid IdReservation = new Guid(Strings.IdReservation); + + public static readonly Guid Template = new Guid(Strings.Template); + + public static readonly Guid ContentItem = new Guid(Strings.ContentItem); + + [Obsolete("This no longer exists in the database")] + internal static readonly Guid Stylesheet = new Guid(Strings.Stylesheet); + + [Obsolete("This no longer exists in the database")] + internal static readonly Guid StylesheetProperty = new Guid(Strings.StylesheetProperty); + } + } +} diff --git a/src/Umbraco.Core/Constants-Packaging.cs b/src/Umbraco.Core/Constants-Packaging.cs index e88639ebbd..37f2c23fa3 100644 --- a/src/Umbraco.Core/Constants-Packaging.cs +++ b/src/Umbraco.Core/Constants-Packaging.cs @@ -1,55 +1,55 @@ -namespace Umbraco.Core -{ - public static partial class Constants - { - /// - /// Defines the constants used for Umbraco packages in the package.config xml - /// - public static class Packaging - { - public const string UmbPackageNodeName = "umbPackage"; - public const string DataTypesNodeName = "DataTypes"; - public const string PackageXmlFileName = "package.xml"; - public const string UmbracoPackageExtention = ".umb"; - public const string DataTypeNodeName = "DataType"; - public const string LanguagesNodeName = "Languages"; - public const string FilesNodeName = "files"; - public const string StylesheetsNodeName = "Stylesheets"; - public const string TemplatesNodeName = "Templates"; - public const string NameNodeName = "Name"; - public const string TemplateNodeName = "Template"; - public const string AliasNodeNameSmall = "alias"; - public const string AliasNodeNameCapital = "Alias"; - public const string DictionaryItemsNodeName = "DictionaryItems"; - public const string DictionaryItemNodeName = "DictionaryItem"; - public const string MacrosNodeName = "Macros"; - public const string DocumentsNodeName = "Documents"; - public const string DocumentSetNodeName = "DocumentSet"; - public const string DocumentTypesNodeName = "DocumentTypes"; - public const string DocumentTypeNodeName = "DocumentType"; - public const string FileNodeName = "file"; - public const string OrgNameNodeName = "orgName"; - public const string OrgPathNodeName = "orgPath"; - public const string GuidNodeName = "guid"; - public const string StylesheetNodeName = "styleSheet"; - public const string MacroNodeName = "macro"; - public const string InfoNodeName = "info"; - public const string PackageRequirementsMajorXpath = "./package/requirements/major"; - public const string PackageRequirementsMinorXpath = "./package/requirements/minor"; - public const string PackageRequirementsPatchXpath = "./package/requirements/patch"; - public const string PackageNameXpath = "./package/name"; - public const string PackageVersionXpath = "./package/version"; - public const string PackageUrlXpath = "./package/url"; - public const string PackageLicenseXpath = "./package/license"; - public const string PackageLicenseXpathUrlAttribute = "url"; - public const string AuthorNameXpath = "./author/name"; - public const string AuthorWebsiteXpath = "./author/website"; - public const string ReadmeXpath = "./readme"; - public const string ControlNodeName = "control"; - public const string ActionNodeName = "Action"; - public const string ActionsNodeName = "Actions"; - public const string UndoNodeAttribute = "undo"; - public const string RunatNodeAttribute = "runat"; - } - } -} +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the constants used for Umbraco packages in the package.config xml + /// + public static class Packaging + { + public const string UmbPackageNodeName = "umbPackage"; + public const string DataTypesNodeName = "DataTypes"; + public const string PackageXmlFileName = "package.xml"; + public const string UmbracoPackageExtention = ".umb"; + public const string DataTypeNodeName = "DataType"; + public const string LanguagesNodeName = "Languages"; + public const string FilesNodeName = "files"; + public const string StylesheetsNodeName = "Stylesheets"; + public const string TemplatesNodeName = "Templates"; + public const string NameNodeName = "Name"; + public const string TemplateNodeName = "Template"; + public const string AliasNodeNameSmall = "alias"; + public const string AliasNodeNameCapital = "Alias"; + public const string DictionaryItemsNodeName = "DictionaryItems"; + public const string DictionaryItemNodeName = "DictionaryItem"; + public const string MacrosNodeName = "Macros"; + public const string DocumentsNodeName = "Documents"; + public const string DocumentSetNodeName = "DocumentSet"; + public const string DocumentTypesNodeName = "DocumentTypes"; + public const string DocumentTypeNodeName = "DocumentType"; + public const string FileNodeName = "file"; + public const string OrgNameNodeName = "orgName"; + public const string OrgPathNodeName = "orgPath"; + public const string GuidNodeName = "guid"; + public const string StylesheetNodeName = "styleSheet"; + public const string MacroNodeName = "macro"; + public const string InfoNodeName = "info"; + public const string PackageRequirementsMajorXpath = "./package/requirements/major"; + public const string PackageRequirementsMinorXpath = "./package/requirements/minor"; + public const string PackageRequirementsPatchXpath = "./package/requirements/patch"; + public const string PackageNameXpath = "./package/name"; + public const string PackageVersionXpath = "./package/version"; + public const string PackageUrlXpath = "./package/url"; + public const string PackageLicenseXpath = "./package/license"; + public const string PackageLicenseXpathUrlAttribute = "url"; + public const string AuthorNameXpath = "./author/name"; + public const string AuthorWebsiteXpath = "./author/website"; + public const string ReadmeXpath = "./readme"; + public const string ControlNodeName = "control"; + public const string ActionNodeName = "Action"; + public const string ActionsNodeName = "Actions"; + public const string UndoNodeAttribute = "undo"; + public const string RunatNodeAttribute = "runat"; + } + } +} diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index f6fa5a443c..126613cdb3 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -1,216 +1,216 @@ -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core -{ - public static partial class Constants - { - /// - /// Defines property editors constants. - /// - public static class PropertyEditors - { - /// - /// Used to prefix generic properties that are internal content properties - /// - public const string InternalGenericPropertiesPrefix = "_umb_"; - - /// - /// Defines Umbraco built-in property editor aliases. - /// - public static class Aliases - { - /// - /// CheckBox List. - /// - public const string CheckBoxList = "Umbraco.CheckBoxList"; - - /// - /// Color Picker. - /// - public const string ColorPicker = "Umbraco.ColorPicker"; - - /// - /// Content Picker. - /// - public const string ContentPicker = "Umbraco.ContentPicker"; - - /// - /// Date. - /// - public const string Date = "Umbraco.Date"; - - /// - /// DateTime. - /// - public const string DateTime = "Umbraco.DateTime"; - - /// - /// DropDown List. - /// - public const string DropDownList = "Umbraco.DropDown"; - - /// - /// DropDown List, Publish Keys. - /// - public const string DropdownlistPublishKeys = "Umbraco.DropdownlistPublishingKeys"; - - /// - /// DropDown List Multiple. - /// - public const string DropDownListMultiple = "Umbraco.DropDownMultiple"; - - /// - /// DropDown List Multiple, Publish Keys. - /// - public const string DropdownlistMultiplePublishKeys = "Umbraco.DropdownlistMultiplePublishKeys"; - - /// - /// DropDown List. - /// - public const string DropDownListFlexible = "Umbraco.DropDown.Flexible"; - - /// - /// Grid. - /// - public const string Grid = "Umbraco.Grid"; - - /// - /// Image Cropper. - /// - public const string ImageCropper = "Umbraco.ImageCropper"; - - /// - /// Integer. - /// - public const string Integer = "Umbraco.Integer"; - - /// - /// Decimal. - /// - public const string Decimal = "Umbraco.Decimal"; - - /// - /// ListView. - /// - public const string ListView = "Umbraco.ListView"; - - /// - /// Macro Container. - /// - public const string MacroContainer = "Umbraco.MacroContainer"; - - /// - /// Media Picker. - /// - public const string MediaPicker = "Umbraco.MediaPicker"; - - /// - /// Member Picker. - /// - public const string MemberPicker = "Umbraco.MemberPicker"; - - /// - /// Member Group Picker. - /// - public const string MemberGroupPicker = "Umbraco.MemberGroupPicker"; - - /// - /// MultiNode Tree Picker. - /// - public const string MultiNodeTreePicker = "Umbraco.MultiNodeTreePicker"; - - /// - /// Multiple TextString. - /// - public const string MultipleTextstring = "Umbraco.MultipleTextstring"; - - /// - /// NoEdit. - /// - public const string NoEdit = "Umbraco.NoEdit"; - - /// - /// Picker Relations. - /// - public const string PickerRelations = "Umbraco.PickerRelations"; - - /// - /// RadioButton list. - /// - public const string RadioButtonList = "Umbraco.RadioButtonList"; - - /// - /// Related Links. - /// - public const string RelatedLinks = "Umbraco.RelatedLinks"; - - /// - /// Slider. - /// - public const string Slider = "Umbraco.Slider"; - - /// - /// Tags. - /// - public const string Tags = "Umbraco.Tags"; - - /// - /// Textbox. - /// - public const string TextBox = "Umbraco.TextBox"; - - /// - /// Textbox Multiple. - /// - public const string TextArea = "Umbraco.TextArea"; - - /// - /// TinyMCE - /// - public const string TinyMce = "Umbraco.TinyMCEv3"; - - /// - /// Boolean. - /// - public const string Boolean = "Umbraco.TrueFalse"; - - /// - /// Markdown Editor. - /// - public const string MarkdownEditor = "Umbraco.MarkdownEditor"; - - /// - /// User Picker. - /// - public const string UserPicker = "Umbraco.UserPicker"; - - /// - /// Upload Field. - /// - public const string UploadField = "Umbraco.UploadField"; - - /// - /// Email Address. - /// - public const string EmailAddress = "Umbraco.EmailAddress"; - - /// - /// Nested Content. - /// - public const string NestedContent = "Umbraco.NestedContent"; - } - - /// - /// Defines Umbraco build-in datatype configuration keys. - /// - public static class ConfigurationKeys - { - /// - /// The value type of property data (i.e., string, integer, etc) - /// - /// Must be a valid value. - public const string DataValueType = "umbracoDataValueType"; - } - } - } -} +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines property editors constants. + /// + public static class PropertyEditors + { + /// + /// Used to prefix generic properties that are internal content properties + /// + public const string InternalGenericPropertiesPrefix = "_umb_"; + + /// + /// Defines Umbraco built-in property editor aliases. + /// + public static class Aliases + { + /// + /// CheckBox List. + /// + public const string CheckBoxList = "Umbraco.CheckBoxList"; + + /// + /// Color Picker. + /// + public const string ColorPicker = "Umbraco.ColorPicker"; + + /// + /// Content Picker. + /// + public const string ContentPicker = "Umbraco.ContentPicker"; + + /// + /// Date. + /// + public const string Date = "Umbraco.Date"; + + /// + /// DateTime. + /// + public const string DateTime = "Umbraco.DateTime"; + + /// + /// DropDown List. + /// + public const string DropDownList = "Umbraco.DropDown"; + + /// + /// DropDown List, Publish Keys. + /// + public const string DropdownlistPublishKeys = "Umbraco.DropdownlistPublishingKeys"; + + /// + /// DropDown List Multiple. + /// + public const string DropDownListMultiple = "Umbraco.DropDownMultiple"; + + /// + /// DropDown List Multiple, Publish Keys. + /// + public const string DropdownlistMultiplePublishKeys = "Umbraco.DropdownlistMultiplePublishKeys"; + + /// + /// DropDown List. + /// + public const string DropDownListFlexible = "Umbraco.DropDown.Flexible"; + + /// + /// Grid. + /// + public const string Grid = "Umbraco.Grid"; + + /// + /// Image Cropper. + /// + public const string ImageCropper = "Umbraco.ImageCropper"; + + /// + /// Integer. + /// + public const string Integer = "Umbraco.Integer"; + + /// + /// Decimal. + /// + public const string Decimal = "Umbraco.Decimal"; + + /// + /// ListView. + /// + public const string ListView = "Umbraco.ListView"; + + /// + /// Macro Container. + /// + public const string MacroContainer = "Umbraco.MacroContainer"; + + /// + /// Media Picker. + /// + public const string MediaPicker = "Umbraco.MediaPicker"; + + /// + /// Member Picker. + /// + public const string MemberPicker = "Umbraco.MemberPicker"; + + /// + /// Member Group Picker. + /// + public const string MemberGroupPicker = "Umbraco.MemberGroupPicker"; + + /// + /// MultiNode Tree Picker. + /// + public const string MultiNodeTreePicker = "Umbraco.MultiNodeTreePicker"; + + /// + /// Multiple TextString. + /// + public const string MultipleTextstring = "Umbraco.MultipleTextstring"; + + /// + /// NoEdit. + /// + public const string NoEdit = "Umbraco.NoEdit"; + + /// + /// Picker Relations. + /// + public const string PickerRelations = "Umbraco.PickerRelations"; + + /// + /// RadioButton list. + /// + public const string RadioButtonList = "Umbraco.RadioButtonList"; + + /// + /// Related Links. + /// + public const string RelatedLinks = "Umbraco.RelatedLinks"; + + /// + /// Slider. + /// + public const string Slider = "Umbraco.Slider"; + + /// + /// Tags. + /// + public const string Tags = "Umbraco.Tags"; + + /// + /// Textbox. + /// + public const string TextBox = "Umbraco.TextBox"; + + /// + /// Textbox Multiple. + /// + public const string TextArea = "Umbraco.TextArea"; + + /// + /// TinyMCE + /// + public const string TinyMce = "Umbraco.TinyMCEv3"; + + /// + /// Boolean. + /// + public const string Boolean = "Umbraco.TrueFalse"; + + /// + /// Markdown Editor. + /// + public const string MarkdownEditor = "Umbraco.MarkdownEditor"; + + /// + /// User Picker. + /// + public const string UserPicker = "Umbraco.UserPicker"; + + /// + /// Upload Field. + /// + public const string UploadField = "Umbraco.UploadField"; + + /// + /// Email Address. + /// + public const string EmailAddress = "Umbraco.EmailAddress"; + + /// + /// Nested Content. + /// + public const string NestedContent = "Umbraco.NestedContent"; + } + + /// + /// Defines Umbraco build-in datatype configuration keys. + /// + public static class ConfigurationKeys + { + /// + /// The value type of property data (i.e., string, integer, etc) + /// + /// Must be a valid value. + public const string DataValueType = "umbracoDataValueType"; + } + } + } +} diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index 9fad62c347..eaceff275e 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -1,65 +1,65 @@ -namespace Umbraco.Core -{ - public static partial class Constants - { - /// - /// Defines the identifiers for Umbraco system nodes. - /// - public static class System - { - /// - /// The integer identifier for global system root node. - /// - public const int Root = -1; - - /// - /// The string identifier for global system root node. - /// - /// Use this instead of re-creating the string everywhere. - public const string RootString = "-1"; - - /// - /// The integer identifier for content's recycle bin. - /// - public const int RecycleBinContent = -20; - - /// - /// The string identifier for content's recycle bin. - /// - /// Use this instead of re-creating the string everywhere. - public const string RecycleBinContentString = "-20"; - - /// - /// The string path prefix of the content's recycle bin. - /// - /// - /// Everything that is in the content recycle bin, has a path that starts with the prefix. - /// Use this instead of re-creating the string everywhere. - /// - public const string RecycleBinContentPathPrefix = "-1,-20,"; - - /// - /// The integer identifier for media's recycle bin. - /// - public const int RecycleBinMedia = -21; - - /// - /// The string identifier for media's recycle bin. - /// - /// Use this instead of re-creating the string everywhere. - public const string RecycleBinMediaString = "-21"; - - /// - /// The string path prefix of the media's recycle bin. - /// - /// - /// Everything that is in the media recycle bin, has a path that starts with the prefix. - /// Use this instead of re-creating the string everywhere. - /// - public const string RecycleBinMediaPathPrefix = "-1,-21,"; - - public const string UmbracoConnectionName = "umbracoDbDSN"; - public const string UmbracoUpgradePlanName = "Umbraco.Core"; - } - } -} +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the identifiers for Umbraco system nodes. + /// + public static class System + { + /// + /// The integer identifier for global system root node. + /// + public const int Root = -1; + + /// + /// The string identifier for global system root node. + /// + /// Use this instead of re-creating the string everywhere. + public const string RootString = "-1"; + + /// + /// The integer identifier for content's recycle bin. + /// + public const int RecycleBinContent = -20; + + /// + /// The string identifier for content's recycle bin. + /// + /// Use this instead of re-creating the string everywhere. + public const string RecycleBinContentString = "-20"; + + /// + /// The string path prefix of the content's recycle bin. + /// + /// + /// Everything that is in the content recycle bin, has a path that starts with the prefix. + /// Use this instead of re-creating the string everywhere. + /// + public const string RecycleBinContentPathPrefix = "-1,-20,"; + + /// + /// The integer identifier for media's recycle bin. + /// + public const int RecycleBinMedia = -21; + + /// + /// The string identifier for media's recycle bin. + /// + /// Use this instead of re-creating the string everywhere. + public const string RecycleBinMediaString = "-21"; + + /// + /// The string path prefix of the media's recycle bin. + /// + /// + /// Everything that is in the media recycle bin, has a path that starts with the prefix. + /// Use this instead of re-creating the string everywhere. + /// + public const string RecycleBinMediaPathPrefix = "-1,-21,"; + + public const string UmbracoConnectionName = "umbracoDbDSN"; + public const string UmbracoUpgradePlanName = "Umbraco.Core"; + } + } +} diff --git a/src/Umbraco.Core/Constants.cs b/src/Umbraco.Core/Constants.cs index a1e77bc919..ba12d11197 100644 --- a/src/Umbraco.Core/Constants.cs +++ b/src/Umbraco.Core/Constants.cs @@ -1,10 +1,10 @@ -namespace Umbraco.Core -{ - /// - /// Constants all the identifiers within the Umbraco core. - /// - public static partial class Constants - { - // generic constants can go here - } -} +namespace Umbraco.Core +{ + /// + /// Constants all the identifiers within the Umbraco core. + /// + public static partial class Constants + { + // generic constants can go here + } +} diff --git a/src/Umbraco.Core/CustomBooleanTypeConverter.cs b/src/Umbraco.Core/CustomBooleanTypeConverter.cs index f96f8ef460..c5c8896a07 100644 --- a/src/Umbraco.Core/CustomBooleanTypeConverter.cs +++ b/src/Umbraco.Core/CustomBooleanTypeConverter.cs @@ -1,32 +1,32 @@ -using System; -using System.ComponentModel; - -namespace Umbraco.Core -{ - /// - /// Allows for converting string representations of 0 and 1 to boolean - /// - internal class CustomBooleanTypeConverter : BooleanConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) - { - if (value is string) - { - var str = (string)value; - if (str == null || str.Length == 0 || str == "0") return false; - if (str == "1") return true; - } - - return base.ConvertFrom(context, culture, value); - } - } -} +using System; +using System.ComponentModel; + +namespace Umbraco.Core +{ + /// + /// Allows for converting string representations of 0 and 1 to boolean + /// + internal class CustomBooleanTypeConverter : BooleanConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + { + return true; + } + return base.CanConvertFrom(context, sourceType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + { + if (value is string) + { + var str = (string)value; + if (str == null || str.Length == 0 || str == "0") return false; + if (str == "1") return true; + } + + return base.ConvertFrom(context, culture, value); + } + } +} diff --git a/src/Umbraco.Core/DataTableExtensions.cs b/src/Umbraco.Core/DataTableExtensions.cs index 272fc064c4..a0539ee114 100644 --- a/src/Umbraco.Core/DataTableExtensions.cs +++ b/src/Umbraco.Core/DataTableExtensions.cs @@ -1,119 +1,119 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Text; -using System.Threading; - -namespace Umbraco.Core -{ - - - /// - /// Static and extension methods for the DataTable object - /// - internal static class DataTableExtensions - { - - - /// - /// Creates a DataTable with the specified alias and columns and uses a callback to populate the headers. - /// - /// - /// - /// - /// - /// - /// This has been migrated from the Node class and uses proper locking now. It is now used by the Node class and the - /// DynamicPublishedContent extensions for legacy reasons. - /// - public static DataTable GenerateDataTable( - string tableAlias, - Func>> getHeaders, - Func>, IEnumerable>>>> rowData) - { - var dt = new DataTable(tableAlias); - - //get all row data - var tableData = rowData().ToArray(); - - //get all headers - var propertyHeaders = GetPropertyHeaders(tableAlias, getHeaders); - foreach(var h in propertyHeaders) - { - dt.Columns.Add(new DataColumn(h.Value)); - } - - //add row data - foreach(var r in tableData) - { - dt.PopulateRow( - propertyHeaders, - r.Item1, - r.Item2); - } - - return dt; - } - - /// - /// Helper method to return this ugly object - /// - /// - /// - /// This is for legacy code, I didn't want to go creating custom classes for these - /// - internal static List>, IEnumerable>>> CreateTableData() - { - return new List>, IEnumerable>>>(); - } - - /// - /// Helper method to deal with these ugly objects - /// - /// - /// - /// - /// - /// This is for legacy code, I didn't want to go creating custom classes for these - /// - internal static void AddRowData( - List>, IEnumerable>>> rowData, - IEnumerable> standardVals, - IEnumerable> userVals) - { - rowData.Add(new System.Tuple>, IEnumerable>>( - standardVals, - userVals - )); - } - - private static IDictionary GetPropertyHeaders(string alias, Func>> getHeaders) - { - var headers = getHeaders(alias); - var def = headers.ToDictionary(pt => pt.Key, pt => pt.Value); - return def; - } - - private static void PopulateRow( - this DataTable dt, - IDictionary aliasesToNames, - IEnumerable> standardVals, - IEnumerable> userPropertyVals) - { - var dr = dt.NewRow(); - foreach (var r in standardVals) - { - dr[r.Key] = r.Value; - } - foreach (var p in userPropertyVals.Where(p => p.Value != null)) - { - dr[aliasesToNames[p.Key]] = p.Value; - } - dt.Rows.Add(dr); - } - - } -} +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Umbraco.Core +{ + + + /// + /// Static and extension methods for the DataTable object + /// + internal static class DataTableExtensions + { + + + /// + /// Creates a DataTable with the specified alias and columns and uses a callback to populate the headers. + /// + /// + /// + /// + /// + /// + /// This has been migrated from the Node class and uses proper locking now. It is now used by the Node class and the + /// DynamicPublishedContent extensions for legacy reasons. + /// + public static DataTable GenerateDataTable( + string tableAlias, + Func>> getHeaders, + Func>, IEnumerable>>>> rowData) + { + var dt = new DataTable(tableAlias); + + //get all row data + var tableData = rowData().ToArray(); + + //get all headers + var propertyHeaders = GetPropertyHeaders(tableAlias, getHeaders); + foreach(var h in propertyHeaders) + { + dt.Columns.Add(new DataColumn(h.Value)); + } + + //add row data + foreach(var r in tableData) + { + dt.PopulateRow( + propertyHeaders, + r.Item1, + r.Item2); + } + + return dt; + } + + /// + /// Helper method to return this ugly object + /// + /// + /// + /// This is for legacy code, I didn't want to go creating custom classes for these + /// + internal static List>, IEnumerable>>> CreateTableData() + { + return new List>, IEnumerable>>>(); + } + + /// + /// Helper method to deal with these ugly objects + /// + /// + /// + /// + /// + /// This is for legacy code, I didn't want to go creating custom classes for these + /// + internal static void AddRowData( + List>, IEnumerable>>> rowData, + IEnumerable> standardVals, + IEnumerable> userVals) + { + rowData.Add(new System.Tuple>, IEnumerable>>( + standardVals, + userVals + )); + } + + private static IDictionary GetPropertyHeaders(string alias, Func>> getHeaders) + { + var headers = getHeaders(alias); + var def = headers.ToDictionary(pt => pt.Key, pt => pt.Value); + return def; + } + + private static void PopulateRow( + this DataTable dt, + IDictionary aliasesToNames, + IEnumerable> standardVals, + IEnumerable> userPropertyVals) + { + var dr = dt.NewRow(); + foreach (var r in standardVals) + { + dr[r.Key] = r.Value; + } + foreach (var p in userPropertyVals.Where(p => p.Value != null)) + { + dr[aliasesToNames[p.Key]] = p.Value; + } + dt.Rows.Add(dr); + } + + } +} diff --git a/src/Umbraco.Core/DateTimeExtensions.cs b/src/Umbraco.Core/DateTimeExtensions.cs index b3babd2d07..66b788f9c8 100644 --- a/src/Umbraco.Core/DateTimeExtensions.cs +++ b/src/Umbraco.Core/DateTimeExtensions.cs @@ -1,83 +1,83 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Core -{ - public static class DateTimeExtensions - { - - /// - /// Returns the DateTime as an ISO formatted string that is globally expectable - /// - /// - /// - public static string ToIsoString(this DateTime dt) - { - return dt.ToString("yyyy-MM-dd HH:mm:ss"); - } - - public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo) - { - if (truncateTo == DateTruncate.Year) - return new DateTime(dt.Year, 1, 1); - if (truncateTo == DateTruncate.Month) - return new DateTime(dt.Year, dt.Month, 1); - if (truncateTo == DateTruncate.Day) - return new DateTime(dt.Year, dt.Month, dt.Day); - if (truncateTo == DateTruncate.Hour) - return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0); - if (truncateTo == DateTruncate.Minute) - return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0); - return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second); - } - - public enum DateTruncate - { - Year, - Month, - Day, - Hour, - Minute, - Second - } - - /// - /// Calculates the number of minutes from a date time, on a rolling daily basis (so if - /// date time is before the time, calculate onto next day) - /// - /// Date to start from - /// Time to compare against (in Hmm form, e.g. 330, 2200) - /// - public static int PeriodicMinutesFrom(this DateTime fromDateTime, string scheduledTime) - { - // Ensure time provided is 4 digits long - if (scheduledTime.Length == 3) - { - scheduledTime = "0" + scheduledTime; - } - - var scheduledHour = int.Parse(scheduledTime.Substring(0, 2)); - var scheduledMinute = int.Parse(scheduledTime.Substring(2)); - - DateTime scheduledDateTime; - if (IsScheduledInRemainingDay(fromDateTime, scheduledHour, scheduledMinute)) - { - scheduledDateTime = new DateTime(fromDateTime.Year, fromDateTime.Month, fromDateTime.Day, scheduledHour, scheduledMinute, 0); - } - else - { - var nextDay = fromDateTime.AddDays(1); - scheduledDateTime = new DateTime(nextDay.Year, nextDay.Month, nextDay.Day, scheduledHour, scheduledMinute, 0); - } - - return (int)(scheduledDateTime - fromDateTime).TotalMinutes; - } - - private static bool IsScheduledInRemainingDay(DateTime fromDateTime, int scheduledHour, int scheduledMinute) - { - return scheduledHour > fromDateTime.Hour || (scheduledHour == fromDateTime.Hour && scheduledMinute >= fromDateTime.Minute); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core +{ + public static class DateTimeExtensions + { + + /// + /// Returns the DateTime as an ISO formatted string that is globally expectable + /// + /// + /// + public static string ToIsoString(this DateTime dt) + { + return dt.ToString("yyyy-MM-dd HH:mm:ss"); + } + + public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo) + { + if (truncateTo == DateTruncate.Year) + return new DateTime(dt.Year, 1, 1); + if (truncateTo == DateTruncate.Month) + return new DateTime(dt.Year, dt.Month, 1); + if (truncateTo == DateTruncate.Day) + return new DateTime(dt.Year, dt.Month, dt.Day); + if (truncateTo == DateTruncate.Hour) + return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0); + if (truncateTo == DateTruncate.Minute) + return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0); + return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second); + } + + public enum DateTruncate + { + Year, + Month, + Day, + Hour, + Minute, + Second + } + + /// + /// Calculates the number of minutes from a date time, on a rolling daily basis (so if + /// date time is before the time, calculate onto next day) + /// + /// Date to start from + /// Time to compare against (in Hmm form, e.g. 330, 2200) + /// + public static int PeriodicMinutesFrom(this DateTime fromDateTime, string scheduledTime) + { + // Ensure time provided is 4 digits long + if (scheduledTime.Length == 3) + { + scheduledTime = "0" + scheduledTime; + } + + var scheduledHour = int.Parse(scheduledTime.Substring(0, 2)); + var scheduledMinute = int.Parse(scheduledTime.Substring(2)); + + DateTime scheduledDateTime; + if (IsScheduledInRemainingDay(fromDateTime, scheduledHour, scheduledMinute)) + { + scheduledDateTime = new DateTime(fromDateTime.Year, fromDateTime.Month, fromDateTime.Day, scheduledHour, scheduledMinute, 0); + } + else + { + var nextDay = fromDateTime.AddDays(1); + scheduledDateTime = new DateTime(nextDay.Year, nextDay.Month, nextDay.Day, scheduledHour, scheduledMinute, 0); + } + + return (int)(scheduledDateTime - fromDateTime).TotalMinutes; + } + + private static bool IsScheduledInRemainingDay(DateTime fromDateTime, int scheduledHour, int scheduledMinute) + { + return scheduledHour > fromDateTime.Hour || (scheduledHour == fromDateTime.Hour && scheduledMinute >= fromDateTime.Minute); + } + } +} diff --git a/src/Umbraco.Core/DelegateEqualityComparer.cs b/src/Umbraco.Core/DelegateEqualityComparer.cs index d9c4f14435..dae990c635 100644 --- a/src/Umbraco.Core/DelegateEqualityComparer.cs +++ b/src/Umbraco.Core/DelegateEqualityComparer.cs @@ -1,60 +1,60 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core -{ - /// - /// A custom equality comparer that excepts a delegate to do the comparison operation - /// - /// - public class DelegateEqualityComparer : IEqualityComparer - { - private readonly Func _equals; - private readonly Func _getHashcode; - - #region Implementation of IEqualityComparer - - public DelegateEqualityComparer(Func equals, Func getHashcode) - { - _getHashcode = getHashcode; - _equals = equals; - } - - public static DelegateEqualityComparer CompareMember(Func memberExpression) where TMember : IEquatable - { - return new DelegateEqualityComparer( - (x, y) => memberExpression.Invoke(x).Equals((TMember)memberExpression.Invoke(y)), - x => - { - var invoked = memberExpression.Invoke(x); - return !ReferenceEquals(invoked, default(TMember)) ? invoked.GetHashCode() : 0; - }); - } - - /// - /// Determines whether the specified objects are equal. - /// - /// - /// true if the specified objects are equal; otherwise, false. - /// - /// The first object of type to compare.The second object of type to compare. - public bool Equals(T x, T y) - { - return _equals.Invoke(x, y); - } - - /// - /// Returns a hash code for the specified object. - /// - /// - /// A hash code for the specified object. - /// - /// The for which a hash code is to be returned.The type of is a reference type and is null. - public int GetHashCode(T obj) - { - return _getHashcode.Invoke(obj); - } - - #endregion - } -} +using System; +using System.Collections.Generic; + +namespace Umbraco.Core +{ + /// + /// A custom equality comparer that excepts a delegate to do the comparison operation + /// + /// + public class DelegateEqualityComparer : IEqualityComparer + { + private readonly Func _equals; + private readonly Func _getHashcode; + + #region Implementation of IEqualityComparer + + public DelegateEqualityComparer(Func equals, Func getHashcode) + { + _getHashcode = getHashcode; + _equals = equals; + } + + public static DelegateEqualityComparer CompareMember(Func memberExpression) where TMember : IEquatable + { + return new DelegateEqualityComparer( + (x, y) => memberExpression.Invoke(x).Equals((TMember)memberExpression.Invoke(y)), + x => + { + var invoked = memberExpression.Invoke(x); + return !ReferenceEquals(invoked, default(TMember)) ? invoked.GetHashCode() : 0; + }); + } + + /// + /// Determines whether the specified objects are equal. + /// + /// + /// true if the specified objects are equal; otherwise, false. + /// + /// The first object of type to compare.The second object of type to compare. + public bool Equals(T x, T y) + { + return _equals.Invoke(x, y); + } + + /// + /// Returns a hash code for the specified object. + /// + /// + /// A hash code for the specified object. + /// + /// The for which a hash code is to be returned.The type of is a reference type and is null. + public int GetHashCode(T obj) + { + return _getHashcode.Invoke(obj); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Dictionary/ICultureDictionary.cs b/src/Umbraco.Core/Dictionary/ICultureDictionary.cs index f8a6e49973..d03a5dfa73 100644 --- a/src/Umbraco.Core/Dictionary/ICultureDictionary.cs +++ b/src/Umbraco.Core/Dictionary/ICultureDictionary.cs @@ -1,31 +1,31 @@ -using System; -using System.Collections.Generic; -using System.Globalization; - -namespace Umbraco.Core.Dictionary -{ - /// - /// Represents a dictionary based on a specific culture - /// - public interface ICultureDictionary - { - /// - /// Returns the dictionary value based on the key supplied - /// - /// - /// - string this[string key] { get; } - - /// - /// Returns the current culture - /// - CultureInfo Culture { get; } - - /// - /// Returns the child dictionary entries for a given key - /// - /// - /// - IDictionary GetChildren(string key); - } -} +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace Umbraco.Core.Dictionary +{ + /// + /// Represents a dictionary based on a specific culture + /// + public interface ICultureDictionary + { + /// + /// Returns the dictionary value based on the key supplied + /// + /// + /// + string this[string key] { get; } + + /// + /// Returns the current culture + /// + CultureInfo Culture { get; } + + /// + /// Returns the child dictionary entries for a given key + /// + /// + /// + IDictionary GetChildren(string key); + } +} diff --git a/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs b/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs index ce896a5ef7..8f94de4394 100644 --- a/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs +++ b/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Core.Dictionary -{ - public interface ICultureDictionaryFactory - { - ICultureDictionary CreateDictionary(); - } -} +namespace Umbraco.Core.Dictionary +{ + public interface ICultureDictionaryFactory + { + ICultureDictionary CreateDictionary(); + } +} diff --git a/src/Umbraco.Core/DictionaryExtensions.cs b/src/Umbraco.Core/DictionaryExtensions.cs index 8c618a06ac..fb44ec7dcd 100644 --- a/src/Umbraco.Core/DictionaryExtensions.cs +++ b/src/Umbraco.Core/DictionaryExtensions.cs @@ -1,284 +1,284 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Text; -using System.Web; - -namespace Umbraco.Core -{ - /// - /// Extension methods for dictionary & concurrentdictionary - /// - internal static class DictionaryExtensions - { - - /// - /// Method to Get a value by the key. If the key doesn't exist it will create a new TVal object for the key and return it. - /// - /// - /// - /// - /// - /// - public static TVal GetOrCreate(this IDictionary dict, TKey key) - where TVal : class, new() - { - if (dict.ContainsKey(key) == false) - { - dict.Add(key, new TVal()); - } - return dict[key]; - } - - /// - /// Updates an item with the specified key with the specified value - /// - /// - /// - /// - /// - /// - /// - /// - /// Taken from: http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression - /// - /// If there is an item in the dictionary with the key, it will keep trying to update it until it can - /// - public static bool TryUpdate(this ConcurrentDictionary dict, TKey key, Func updateFactory) - { - TValue curValue; - while (dict.TryGetValue(key, out curValue)) - { - if (dict.TryUpdate(key, updateFactory(curValue), curValue)) - return true; - //if we're looping either the key was removed by another thread, or another thread - //changed the value, so we start again. - } - return false; - } - - /// - /// Updates an item with the specified key with the specified value - /// - /// - /// - /// - /// - /// - /// - /// - /// Taken from: http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression - /// - /// WARNING: If the value changes after we've retreived it, then the item will not be updated - /// - public static bool TryUpdateOptimisitic(this ConcurrentDictionary dict, TKey key, Func updateFactory) - { - TValue curValue; - if (!dict.TryGetValue(key, out curValue)) - return false; - dict.TryUpdate(key, updateFactory(curValue), curValue); - return true;//note we return true whether we succeed or not, see explanation below. - } - - /// - /// Converts a dictionary to another type by only using direct casting - /// - /// - /// - /// - /// - public static IDictionary ConvertTo(this IDictionary d) - { - var result = new Dictionary(); - foreach (DictionaryEntry v in d) - { - result.Add((TKeyOut)v.Key, (TValOut)v.Value); - } - return result; - } - - /// - /// Converts a dictionary to another type using the specified converters - /// - /// - /// - /// - /// - /// - /// - public static IDictionary ConvertTo(this IDictionary d, Func keyConverter, Func valConverter) - { - var result = new Dictionary(); - foreach (DictionaryEntry v in d) - { - result.Add(keyConverter(v.Key), valConverter(v.Value)); - } - return result; - } - - /// - /// Converts a dictionary to a NameValueCollection - /// - /// - /// - public static NameValueCollection ToNameValueCollection(this IDictionary d) - { - var n = new NameValueCollection(); - foreach (var i in d) - { - n.Add(i.Key, i.Value); - } - return n; - } - - - /// - /// Merges all key/values from the sources dictionaries into the destination dictionary - /// - /// - /// - /// - /// The source dictionary to merge other dictionaries into - /// - /// By default all values will be retained in the destination if the same keys exist in the sources but - /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the destination. Note that - /// it will just use the last found key/value if this is true. - /// - /// The other dictionaries to merge values from - public static void MergeLeft(this T destination, IEnumerable> sources, bool overwrite = false) - where T : IDictionary - { - foreach (var p in sources.SelectMany(src => src).Where(p => overwrite || destination.ContainsKey(p.Key) == false)) - { - destination[p.Key] = p.Value; - } - } - - /// - /// Merges all key/values from the sources dictionaries into the destination dictionary - /// - /// - /// - /// - /// The source dictionary to merge other dictionaries into - /// - /// By default all values will be retained in the destination if the same keys exist in the sources but - /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the destination. Note that - /// it will just use the last found key/value if this is true. - /// - /// The other dictionary to merge values from - public static void MergeLeft(this T destination, IDictionary source, bool overwrite = false) - where T : IDictionary - { - destination.MergeLeft(new[] {source}, overwrite); - } - - /// - /// Returns the value of the key value based on the key, if the key is not found, a null value is returned - /// - /// The type of the key. - /// The type of the val. - /// The d. - /// The key. - /// The default value. - /// - public static TVal GetValue(this IDictionary d, TKey key, TVal defaultValue = default(TVal)) - { - if (d.ContainsKey(key)) - { - return d[key]; - } - return defaultValue; - } - - /// - /// Returns the value of the key value based on the key as it's string value, if the key is not found, then an empty string is returned - /// - /// - /// - /// - public static string GetValueAsString(this IDictionary d, TKey key) - { - if (d.ContainsKey(key)) - { - return d[key].ToString(); - } - return String.Empty; - } - - /// - /// Returns the value of the key value based on the key as it's string value, if the key is not found or is an empty string, then the provided default value is returned - /// - /// - /// - /// - /// - public static string GetValueAsString(this IDictionary d, TKey key, string defaultValue) - { - if (d.ContainsKey(key)) - { - var value = d[key].ToString(); - if (value != string.Empty) - return value; - } - - return defaultValue; - } - - /// contains key ignore case. - /// The dictionary. - /// The key. - /// Value Type - /// The contains key ignore case. - public static bool ContainsKeyIgnoreCase(this IDictionary dictionary, string key) - { - return dictionary.Keys.InvariantContains(key); - } - - /// - /// Converts a dictionary object to a query string representation such as: - /// firstname=shannon&lastname=deminick - /// - /// - /// - public static string ToQueryString(this IDictionary d) - { - if (!d.Any()) return ""; - - var builder = new StringBuilder(); - foreach (var i in d) - { - builder.Append(String.Format("{0}={1}&", HttpUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : HttpUtility.UrlEncode(i.Value.ToString()))); - } - return builder.ToString().TrimEnd('&'); - } - - /// The get entry ignore case. - /// The dictionary. - /// The key. - /// The type - /// The entry - public static TValue GetValueIgnoreCase(this IDictionary dictionary, string key) - { - return dictionary.GetValueIgnoreCase(key, default(TValue)); - } - - /// The get entry ignore case. - /// The dictionary. - /// The key. - /// The default value. - /// The type - /// The entry - public static TValue GetValueIgnoreCase(this IDictionary dictionary, string key, TValue defaultValue) - { - key = dictionary.Keys.FirstOrDefault(i => i.InvariantEquals(key)); - - return key.IsNullOrWhiteSpace() == false - ? dictionary[key] - : defaultValue; - } - } -} +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Web; + +namespace Umbraco.Core +{ + /// + /// Extension methods for dictionary & concurrentdictionary + /// + internal static class DictionaryExtensions + { + + /// + /// Method to Get a value by the key. If the key doesn't exist it will create a new TVal object for the key and return it. + /// + /// + /// + /// + /// + /// + public static TVal GetOrCreate(this IDictionary dict, TKey key) + where TVal : class, new() + { + if (dict.ContainsKey(key) == false) + { + dict.Add(key, new TVal()); + } + return dict[key]; + } + + /// + /// Updates an item with the specified key with the specified value + /// + /// + /// + /// + /// + /// + /// + /// + /// Taken from: http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression + /// + /// If there is an item in the dictionary with the key, it will keep trying to update it until it can + /// + public static bool TryUpdate(this ConcurrentDictionary dict, TKey key, Func updateFactory) + { + TValue curValue; + while (dict.TryGetValue(key, out curValue)) + { + if (dict.TryUpdate(key, updateFactory(curValue), curValue)) + return true; + //if we're looping either the key was removed by another thread, or another thread + //changed the value, so we start again. + } + return false; + } + + /// + /// Updates an item with the specified key with the specified value + /// + /// + /// + /// + /// + /// + /// + /// + /// Taken from: http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression + /// + /// WARNING: If the value changes after we've retreived it, then the item will not be updated + /// + public static bool TryUpdateOptimisitic(this ConcurrentDictionary dict, TKey key, Func updateFactory) + { + TValue curValue; + if (!dict.TryGetValue(key, out curValue)) + return false; + dict.TryUpdate(key, updateFactory(curValue), curValue); + return true;//note we return true whether we succeed or not, see explanation below. + } + + /// + /// Converts a dictionary to another type by only using direct casting + /// + /// + /// + /// + /// + public static IDictionary ConvertTo(this IDictionary d) + { + var result = new Dictionary(); + foreach (DictionaryEntry v in d) + { + result.Add((TKeyOut)v.Key, (TValOut)v.Value); + } + return result; + } + + /// + /// Converts a dictionary to another type using the specified converters + /// + /// + /// + /// + /// + /// + /// + public static IDictionary ConvertTo(this IDictionary d, Func keyConverter, Func valConverter) + { + var result = new Dictionary(); + foreach (DictionaryEntry v in d) + { + result.Add(keyConverter(v.Key), valConverter(v.Value)); + } + return result; + } + + /// + /// Converts a dictionary to a NameValueCollection + /// + /// + /// + public static NameValueCollection ToNameValueCollection(this IDictionary d) + { + var n = new NameValueCollection(); + foreach (var i in d) + { + n.Add(i.Key, i.Value); + } + return n; + } + + + /// + /// Merges all key/values from the sources dictionaries into the destination dictionary + /// + /// + /// + /// + /// The source dictionary to merge other dictionaries into + /// + /// By default all values will be retained in the destination if the same keys exist in the sources but + /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the destination. Note that + /// it will just use the last found key/value if this is true. + /// + /// The other dictionaries to merge values from + public static void MergeLeft(this T destination, IEnumerable> sources, bool overwrite = false) + where T : IDictionary + { + foreach (var p in sources.SelectMany(src => src).Where(p => overwrite || destination.ContainsKey(p.Key) == false)) + { + destination[p.Key] = p.Value; + } + } + + /// + /// Merges all key/values from the sources dictionaries into the destination dictionary + /// + /// + /// + /// + /// The source dictionary to merge other dictionaries into + /// + /// By default all values will be retained in the destination if the same keys exist in the sources but + /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the destination. Note that + /// it will just use the last found key/value if this is true. + /// + /// The other dictionary to merge values from + public static void MergeLeft(this T destination, IDictionary source, bool overwrite = false) + where T : IDictionary + { + destination.MergeLeft(new[] {source}, overwrite); + } + + /// + /// Returns the value of the key value based on the key, if the key is not found, a null value is returned + /// + /// The type of the key. + /// The type of the val. + /// The d. + /// The key. + /// The default value. + /// + public static TVal GetValue(this IDictionary d, TKey key, TVal defaultValue = default(TVal)) + { + if (d.ContainsKey(key)) + { + return d[key]; + } + return defaultValue; + } + + /// + /// Returns the value of the key value based on the key as it's string value, if the key is not found, then an empty string is returned + /// + /// + /// + /// + public static string GetValueAsString(this IDictionary d, TKey key) + { + if (d.ContainsKey(key)) + { + return d[key].ToString(); + } + return String.Empty; + } + + /// + /// Returns the value of the key value based on the key as it's string value, if the key is not found or is an empty string, then the provided default value is returned + /// + /// + /// + /// + /// + public static string GetValueAsString(this IDictionary d, TKey key, string defaultValue) + { + if (d.ContainsKey(key)) + { + var value = d[key].ToString(); + if (value != string.Empty) + return value; + } + + return defaultValue; + } + + /// contains key ignore case. + /// The dictionary. + /// The key. + /// Value Type + /// The contains key ignore case. + public static bool ContainsKeyIgnoreCase(this IDictionary dictionary, string key) + { + return dictionary.Keys.InvariantContains(key); + } + + /// + /// Converts a dictionary object to a query string representation such as: + /// firstname=shannon&lastname=deminick + /// + /// + /// + public static string ToQueryString(this IDictionary d) + { + if (!d.Any()) return ""; + + var builder = new StringBuilder(); + foreach (var i in d) + { + builder.Append(String.Format("{0}={1}&", HttpUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : HttpUtility.UrlEncode(i.Value.ToString()))); + } + return builder.ToString().TrimEnd('&'); + } + + /// The get entry ignore case. + /// The dictionary. + /// The key. + /// The type + /// The entry + public static TValue GetValueIgnoreCase(this IDictionary dictionary, string key) + { + return dictionary.GetValueIgnoreCase(key, default(TValue)); + } + + /// The get entry ignore case. + /// The dictionary. + /// The key. + /// The default value. + /// The type + /// The entry + public static TValue GetValueIgnoreCase(this IDictionary dictionary, string key, TValue defaultValue) + { + key = dictionary.Keys.FirstOrDefault(i => i.InvariantEquals(key)); + + return key.IsNullOrWhiteSpace() == false + ? dictionary[key] + : defaultValue; + } + } +} diff --git a/src/Umbraco.Core/DisposableObject.cs b/src/Umbraco.Core/DisposableObject.cs index ecdc149f6e..b2c150f96c 100644 --- a/src/Umbraco.Core/DisposableObject.cs +++ b/src/Umbraco.Core/DisposableObject.cs @@ -1,64 +1,64 @@ -using System; - -namespace Umbraco.Core -{ - /// - /// Abstract implementation of IDisposable. - /// - /// - /// This is for objects that DO have unmanaged resources. Use - /// for objects that do NOT have unmanaged resources, and avoid creating a finalizer. - /// - /// Can also be used as a pattern for when inheriting is not possible. - /// - /// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx - /// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/ - /// - /// Note: if an object's ctor throws, it will never be disposed, and so if that ctor - /// has allocated disposable objects, it should take care of disposing them. - /// - public abstract class DisposableObject : IDisposable - { - private readonly object _locko = new object(); - - // gets a value indicating whether this instance is disposed. - // for internal tests only (not thread safe) - public bool Disposed { get; private set; } - - // implements IDisposable - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - // finalizer - ~DisposableObject() - { - Dispose(false); - } - - private void Dispose(bool disposing) - { - // can happen if the object construction failed - if (_locko == null) - return; - - lock (_locko) - { - if (Disposed) return; - Disposed = true; - } - - DisposeUnmanagedResources(); - - if (disposing) - DisposeResources(); - } - - protected abstract void DisposeResources(); - - protected virtual void DisposeUnmanagedResources() - { } - } -} +using System; + +namespace Umbraco.Core +{ + /// + /// Abstract implementation of IDisposable. + /// + /// + /// This is for objects that DO have unmanaged resources. Use + /// for objects that do NOT have unmanaged resources, and avoid creating a finalizer. + /// + /// Can also be used as a pattern for when inheriting is not possible. + /// + /// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx + /// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/ + /// + /// Note: if an object's ctor throws, it will never be disposed, and so if that ctor + /// has allocated disposable objects, it should take care of disposing them. + /// + public abstract class DisposableObject : IDisposable + { + private readonly object _locko = new object(); + + // gets a value indicating whether this instance is disposed. + // for internal tests only (not thread safe) + public bool Disposed { get; private set; } + + // implements IDisposable + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + // finalizer + ~DisposableObject() + { + Dispose(false); + } + + private void Dispose(bool disposing) + { + // can happen if the object construction failed + if (_locko == null) + return; + + lock (_locko) + { + if (Disposed) return; + Disposed = true; + } + + DisposeUnmanagedResources(); + + if (disposing) + DisposeResources(); + } + + protected abstract void DisposeResources(); + + protected virtual void DisposeUnmanagedResources() + { } + } +} diff --git a/src/Umbraco.Core/DisposableTimer.cs b/src/Umbraco.Core/DisposableTimer.cs index 6ded588be6..58382a50e9 100644 --- a/src/Umbraco.Core/DisposableTimer.cs +++ b/src/Umbraco.Core/DisposableTimer.cs @@ -1,111 +1,111 @@ -using System; -using System.Diagnostics; -using Umbraco.Core.Logging; - -namespace Umbraco.Core -{ - /// - /// Starts the timer and invokes a callback upon disposal. Provides a simple way of timing an operation by wrapping it in a using (C#) statement. - /// - public class DisposableTimer : DisposableObjectSlim - { - private readonly ILogger _logger; - private readonly LogType? _logType; - private readonly Type _loggerType; - private readonly int _thresholdMilliseconds; - private readonly IDisposable _profilerStep; - private readonly string _endMessage; - private string _failMessage; - private Exception _failException; - private bool _failed; - - internal enum LogType - { - Debug, Info - } - - // internal - created by profiling logger - internal DisposableTimer(ILogger logger, LogType logType, IProfiler profiler, Type loggerType, - string startMessage, string endMessage, string failMessage = null, - int thresholdMilliseconds = 0) - { - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (loggerType == null) throw new ArgumentNullException(nameof(loggerType)); - - _logger = logger; - _logType = logType; - _loggerType = loggerType; - _endMessage = endMessage; - _failMessage = failMessage; - _thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds; - - if (thresholdMilliseconds == 0) - { - switch (logType) - { - case LogType.Debug: - logger.Debug(loggerType, startMessage); - break; - case LogType.Info: - logger.Info(loggerType, startMessage); - break; - default: - throw new ArgumentOutOfRangeException(nameof(logType)); - } - } - - // else aren't logging the start message, this is output to the profiler but not the log, - // we just want the log to contain the result if it's more than the minimum ms threshold. - - _profilerStep = profiler?.Step(loggerType, startMessage); - } - - /// - /// Reports a failure. - /// - /// The fail message. - /// The exception. - /// Completion of the timer will be reported as an error, with the specified message and exception. - public void Fail(string failMessage = null, Exception exception = null) - { - _failed = true; - _failMessage = failMessage ?? _failMessage ?? "Failed."; - _failException = exception; - } - - public Stopwatch Stopwatch { get; } = Stopwatch.StartNew(); - - /// - ///Disposes resources. - /// - /// Overrides abstract class which handles required locking. - protected override void DisposeResources() - { - Stopwatch.Stop(); - - _profilerStep?.Dispose(); - - if ((Stopwatch.ElapsedMilliseconds >= _thresholdMilliseconds || _failed) - && _logType.HasValue && _loggerType != null && _logger != null - && (_endMessage.IsNullOrWhiteSpace() == false || _failed)) - { - if (_failed) - { - _logger.Error(_loggerType, $"{_failMessage} ({Stopwatch.ElapsedMilliseconds}ms)", _failException); - } - else switch (_logType) - { - case LogType.Debug: - _logger.Debug(_loggerType, () => $"{_endMessage} ({Stopwatch.ElapsedMilliseconds}ms)"); - break; - case LogType.Info: - _logger.Info(_loggerType, () => $"{_endMessage} ({Stopwatch.ElapsedMilliseconds}ms)"); - break; - // filtered in the ctor - //default: - // throw new Exception(); - } - } - } - } -} +using System; +using System.Diagnostics; +using Umbraco.Core.Logging; + +namespace Umbraco.Core +{ + /// + /// Starts the timer and invokes a callback upon disposal. Provides a simple way of timing an operation by wrapping it in a using (C#) statement. + /// + public class DisposableTimer : DisposableObjectSlim + { + private readonly ILogger _logger; + private readonly LogType? _logType; + private readonly Type _loggerType; + private readonly int _thresholdMilliseconds; + private readonly IDisposable _profilerStep; + private readonly string _endMessage; + private string _failMessage; + private Exception _failException; + private bool _failed; + + internal enum LogType + { + Debug, Info + } + + // internal - created by profiling logger + internal DisposableTimer(ILogger logger, LogType logType, IProfiler profiler, Type loggerType, + string startMessage, string endMessage, string failMessage = null, + int thresholdMilliseconds = 0) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (loggerType == null) throw new ArgumentNullException(nameof(loggerType)); + + _logger = logger; + _logType = logType; + _loggerType = loggerType; + _endMessage = endMessage; + _failMessage = failMessage; + _thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds; + + if (thresholdMilliseconds == 0) + { + switch (logType) + { + case LogType.Debug: + logger.Debug(loggerType, startMessage); + break; + case LogType.Info: + logger.Info(loggerType, startMessage); + break; + default: + throw new ArgumentOutOfRangeException(nameof(logType)); + } + } + + // else aren't logging the start message, this is output to the profiler but not the log, + // we just want the log to contain the result if it's more than the minimum ms threshold. + + _profilerStep = profiler?.Step(loggerType, startMessage); + } + + /// + /// Reports a failure. + /// + /// The fail message. + /// The exception. + /// Completion of the timer will be reported as an error, with the specified message and exception. + public void Fail(string failMessage = null, Exception exception = null) + { + _failed = true; + _failMessage = failMessage ?? _failMessage ?? "Failed."; + _failException = exception; + } + + public Stopwatch Stopwatch { get; } = Stopwatch.StartNew(); + + /// + ///Disposes resources. + /// + /// Overrides abstract class which handles required locking. + protected override void DisposeResources() + { + Stopwatch.Stop(); + + _profilerStep?.Dispose(); + + if ((Stopwatch.ElapsedMilliseconds >= _thresholdMilliseconds || _failed) + && _logType.HasValue && _loggerType != null && _logger != null + && (_endMessage.IsNullOrWhiteSpace() == false || _failed)) + { + if (_failed) + { + _logger.Error(_loggerType, $"{_failMessage} ({Stopwatch.ElapsedMilliseconds}ms)", _failException); + } + else switch (_logType) + { + case LogType.Debug: + _logger.Debug(_loggerType, () => $"{_endMessage} ({Stopwatch.ElapsedMilliseconds}ms)"); + break; + case LogType.Info: + _logger.Info(_loggerType, () => $"{_endMessage} ({Stopwatch.ElapsedMilliseconds}ms)"); + break; + // filtered in the ctor + //default: + // throw new Exception(); + } + } + } + } +} diff --git a/src/Umbraco.Core/Enum.cs b/src/Umbraco.Core/Enum.cs index 5bc70c637d..36fa3898cb 100644 --- a/src/Umbraco.Core/Enum.cs +++ b/src/Umbraco.Core/Enum.cs @@ -1,109 +1,109 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Core -{ - /// - /// Provides utility methods for handling enumerations. - /// - /// - /// Taken from http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net-3-5 - /// - public static class Enum - where T : struct - { - private static readonly List Values; - private static readonly Dictionary InsensitiveNameToValue; - private static readonly Dictionary SensitiveNameToValue; - private static readonly Dictionary IntToValue; - private static readonly Dictionary ValueToName; - - static Enum() - { - Values = Enum.GetValues(typeof(T)).Cast().ToList(); - - IntToValue = new Dictionary(); - ValueToName = new Dictionary(); - SensitiveNameToValue = new Dictionary(); - InsensitiveNameToValue = new Dictionary(); - - foreach (var value in Values) - { - var name = value.ToString(); - - IntToValue[Convert.ToInt32(value)] = value; - ValueToName[value] = name; - SensitiveNameToValue[name] = value; - InsensitiveNameToValue[name.ToLowerInvariant()] = value; - } - } - - public static bool IsDefined(T value) - { - return ValueToName.Keys.Contains(value); - } - - public static bool IsDefined(string value) - { - return SensitiveNameToValue.Keys.Contains(value); - } - - public static bool IsDefined(int value) - { - return IntToValue.Keys.Contains(value); - } - - public static IEnumerable GetValues() - { - return Values; - } - - public static string[] GetNames() - { - return ValueToName.Values.ToArray(); - } - - public static string GetName(T value) - { - return ValueToName.TryGetValue(value, out var name) ? name : null; - } - - public static T Parse(string value, bool ignoreCase = false) - { - var names = ignoreCase ? InsensitiveNameToValue : SensitiveNameToValue; - if (ignoreCase) value = value.ToLowerInvariant(); - - if (names.TryGetValue(value, out var parsed)) - return parsed; - - throw new ArgumentException($"Value \"{value}\"is not a valid {typeof(T).Name} enumeration value.", nameof(value)); - } - - public static bool TryParse(string value, out T returnValue, bool ignoreCase = false) - { - var names = ignoreCase ? InsensitiveNameToValue : SensitiveNameToValue; - if (ignoreCase) value = value.ToLowerInvariant(); - return names.TryGetValue(value, out returnValue); - } - - public static T? ParseOrNull(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return null; - - if (InsensitiveNameToValue.TryGetValue(value.ToLowerInvariant(), out var parsed)) - return parsed; - - return null; - } - - public static T? CastOrNull(int value) - { - if (IntToValue.TryGetValue(value, out var foundValue)) - return foundValue; - - return null; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core +{ + /// + /// Provides utility methods for handling enumerations. + /// + /// + /// Taken from http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net-3-5 + /// + public static class Enum + where T : struct + { + private static readonly List Values; + private static readonly Dictionary InsensitiveNameToValue; + private static readonly Dictionary SensitiveNameToValue; + private static readonly Dictionary IntToValue; + private static readonly Dictionary ValueToName; + + static Enum() + { + Values = Enum.GetValues(typeof(T)).Cast().ToList(); + + IntToValue = new Dictionary(); + ValueToName = new Dictionary(); + SensitiveNameToValue = new Dictionary(); + InsensitiveNameToValue = new Dictionary(); + + foreach (var value in Values) + { + var name = value.ToString(); + + IntToValue[Convert.ToInt32(value)] = value; + ValueToName[value] = name; + SensitiveNameToValue[name] = value; + InsensitiveNameToValue[name.ToLowerInvariant()] = value; + } + } + + public static bool IsDefined(T value) + { + return ValueToName.Keys.Contains(value); + } + + public static bool IsDefined(string value) + { + return SensitiveNameToValue.Keys.Contains(value); + } + + public static bool IsDefined(int value) + { + return IntToValue.Keys.Contains(value); + } + + public static IEnumerable GetValues() + { + return Values; + } + + public static string[] GetNames() + { + return ValueToName.Values.ToArray(); + } + + public static string GetName(T value) + { + return ValueToName.TryGetValue(value, out var name) ? name : null; + } + + public static T Parse(string value, bool ignoreCase = false) + { + var names = ignoreCase ? InsensitiveNameToValue : SensitiveNameToValue; + if (ignoreCase) value = value.ToLowerInvariant(); + + if (names.TryGetValue(value, out var parsed)) + return parsed; + + throw new ArgumentException($"Value \"{value}\"is not a valid {typeof(T).Name} enumeration value.", nameof(value)); + } + + public static bool TryParse(string value, out T returnValue, bool ignoreCase = false) + { + var names = ignoreCase ? InsensitiveNameToValue : SensitiveNameToValue; + if (ignoreCase) value = value.ToLowerInvariant(); + return names.TryGetValue(value, out returnValue); + } + + public static T? ParseOrNull(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + + if (InsensitiveNameToValue.TryGetValue(value.ToLowerInvariant(), out var parsed)) + return parsed; + + return null; + } + + public static T? CastOrNull(int value) + { + if (IntToValue.TryGetValue(value, out var foundValue)) + return foundValue; + + return null; + } + } +} diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index 6ae96c9d53..0d199d1d0d 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -1,345 +1,345 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using Umbraco.Core.Logging; - -namespace Umbraco.Core -{ - /// - /// Extensions for enumerable sources - /// - public static class EnumerableExtensions - { - public static IEnumerable> InGroupsOf(this IEnumerable source, int groupSize) - { - if (source == null) - throw new ArgumentNullException("source"); - if (groupSize <= 0) - throw new ArgumentException("Must be greater than zero.", "groupSize"); - - - // following code derived from MoreLinq and does not allocate bazillions of tuples - - T[] temp = null; - var count = 0; - - foreach (var item in source) - { - if (temp == null) temp = new T[groupSize]; - temp[count++] = item; - if (count != groupSize) continue; - yield return temp/*.Select(x => x)*/; - temp = null; - count = 0; - } - - if (temp != null && count > 0) - yield return temp.Take(count); - } - - public static IEnumerable SelectByGroups(this IEnumerable source, Func, IEnumerable> selector, int groupSize) - { - // don't want to use a SelectMany(x => x) here - isn't this better? - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var resultGroup in source.InGroupsOf(groupSize).Select(selector)) - foreach (var result in resultGroup) - yield return result; - } - - /// The distinct by. - /// The source. - /// The key selector. - /// Source type - /// Key type - /// the unique list - public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) - where TKey : IEquatable - { - return source.Distinct(DelegateEqualityComparer.CompareMember(keySelector)); - } - - /// - /// Returns a sequence of length whose elements are the result of invoking . - /// - /// - /// The factory. - /// The count. - /// - public static IEnumerable Range(Func factory, int count) - { - for (int i = 1; i <= count; i++) - { - yield return factory.Invoke(i - 1); - } - } - - /// The if not null. - /// The items. - /// The action. - /// The type - public static void IfNotNull(this IEnumerable items, Action action) where TItem : class - { - if (items != null) - { - foreach (TItem item in items) - { - item.IfNotNull(action); - } - } - } - - /// The flatten list. - /// The items. - /// The select child. - /// Item type - /// list of TItem - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Do not use, use SelectRecursive instead which has far less potential of re-iterating an iterator which may cause significantly more SQL queries")] - public static IEnumerable FlattenList(this IEnumerable e, Func> f) - { - return e.SelectMany(c => f(c).FlattenList(f)).Concat(e); - } - - /// - /// Returns true if all items in the other collection exist in this collection - /// - /// - /// - /// - /// - public static bool ContainsAll(this IEnumerable source, IEnumerable other) - { - if (source == null) throw new ArgumentNullException("source"); - if (other == null) throw new ArgumentNullException("other"); - - return other.Except(source).Any() == false; - } - - /// - /// Returns true if the source contains any of the items in the other list - /// - /// - /// - /// - /// - public static bool ContainsAny(this IEnumerable source, IEnumerable other) - { - return other.Any(source.Contains); - } - - /// - /// Removes all matching items from an . - /// - /// - /// The list. - /// The predicate. - /// - public static void RemoveAll(this IList list, Func predicate) - { - for (var i = 0; i < list.Count; i++) - { - if (predicate(list[i])) - { - list.RemoveAt(i--); - } - } - } - - /// - /// Removes all matching items from an . - /// - /// - /// The list. - /// The predicate. - /// - public static void RemoveAll(this ICollection list, Func predicate) - { - var matches = list.Where(predicate).ToArray(); - foreach (var match in matches) - { - list.Remove(match); - } - } - - public static IEnumerable SelectRecursive( - this IEnumerable source, - Func> recursiveSelector, int maxRecusionDepth = 100) - { - var stack = new Stack>(); - stack.Push(source.GetEnumerator()); - - try - { - while (stack.Count > 0) - { - if (stack.Count > maxRecusionDepth) - throw new InvalidOperationException("Maximum recursion depth reached of " + maxRecusionDepth); - - if (stack.Peek().MoveNext()) - { - var current = stack.Peek().Current; - - yield return current; - - stack.Push(recursiveSelector(current).GetEnumerator()); - } - else - { - stack.Pop().Dispose(); - } - } - } - finally - { - while (stack.Count > 0) - { - stack.Pop().Dispose(); - } - } - } - - /// - /// Filters a sequence of values to ignore those which are null. - /// - /// - /// The coll. - /// - /// - public static IEnumerable WhereNotNull(this IEnumerable coll) where T : class - { - return coll.Where(x => x != null); - } - - public static IEnumerable ForAllThatAre(this IEnumerable sequence, Action projection) - where TActual : class - { - return sequence.Select( - x => - { - if (x is TActual) - { - var casted = x as TActual; - projection.Invoke(casted); - } - return x; - }); - } - - /// - /// Finds the index of the first item matching an expression in an enumerable. - /// - /// The type of the enumerated objects. - /// The enumerable to search. - /// The expression to test the items against. - /// The index of the first matching item, or -1. - public static int FindIndex(this IEnumerable items, Func predicate) - { - return FindIndex(items, 0, predicate); - } - - /// - /// Finds the index of the first item matching an expression in an enumerable. - /// - /// The type of the enumerated objects. - /// The enumerable to search. - /// The index to start at. - /// The expression to test the items against. - /// The index of the first matching item, or -1. - public static int FindIndex(this IEnumerable items, int startIndex, Func predicate) - { - if (items == null) throw new ArgumentNullException("items"); - if (predicate == null) throw new ArgumentNullException("predicate"); - if (startIndex < 0) throw new ArgumentOutOfRangeException("startIndex"); - - var index = startIndex; - if (index > 0) - items = items.Skip(index); - - foreach (var item in items) - { - if (predicate(item)) return index; - index++; - } - - return -1; - } - - ///Finds the index of the first occurence of an item in an enumerable. - ///The enumerable to search. - ///The item to find. - ///The index of the first matching item, or -1 if the item was not found. - public static int IndexOf(this IEnumerable items, T item) - { - return items.FindIndex(i => EqualityComparer.Default.Equals(item, i)); - } - - /// - /// Determines if 2 lists have equal elements within them regardless of how they are sorted - /// - /// - /// - /// - /// - /// - /// The logic for this is taken from: - /// http://stackoverflow.com/questions/4576723/test-whether-two-ienumerablet-have-the-same-values-with-the-same-frequencies - /// - /// There's a few answers, this one seems the best for it's simplicity and based on the comment of Eamon - /// - public static bool UnsortedSequenceEqual(this IEnumerable source, IEnumerable other) - { - if (source == null && other == null) return true; - if (source == null || other == null) return false; - - var list1Groups = source.ToLookup(i => i); - var list2Groups = other.ToLookup(i => i); - return list1Groups.Count == list2Groups.Count - && list1Groups.All(g => g.Count() == list2Groups[g.Key].Count()); - } - - /// - /// Transforms an enumerable. - /// - /// - /// - /// - /// - public static IEnumerable Transform(this IEnumerable source, Func, IEnumerable> transform) - { - return transform(source); - } - - /// - /// Gets a null IEnumerable as an empty IEnumerable. - /// - /// - /// - /// - public static IEnumerable EmptyNull(this IEnumerable items) - { - return items ?? Enumerable.Empty(); - } - - // the .OfType() filter is nice when there's only one type - // this is to support filtering with multiple types - public static IEnumerable OfTypes(this IEnumerable contents, params Type[] types) - { - 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; - } - } - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using Umbraco.Core.Logging; + +namespace Umbraco.Core +{ + /// + /// Extensions for enumerable sources + /// + public static class EnumerableExtensions + { + public static IEnumerable> InGroupsOf(this IEnumerable source, int groupSize) + { + if (source == null) + throw new ArgumentNullException("source"); + if (groupSize <= 0) + throw new ArgumentException("Must be greater than zero.", "groupSize"); + + + // following code derived from MoreLinq and does not allocate bazillions of tuples + + T[] temp = null; + var count = 0; + + foreach (var item in source) + { + if (temp == null) temp = new T[groupSize]; + temp[count++] = item; + if (count != groupSize) continue; + yield return temp/*.Select(x => x)*/; + temp = null; + count = 0; + } + + if (temp != null && count > 0) + yield return temp.Take(count); + } + + public static IEnumerable SelectByGroups(this IEnumerable source, Func, IEnumerable> selector, int groupSize) + { + // don't want to use a SelectMany(x => x) here - isn't this better? + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var resultGroup in source.InGroupsOf(groupSize).Select(selector)) + foreach (var result in resultGroup) + yield return result; + } + + /// The distinct by. + /// The source. + /// The key selector. + /// Source type + /// Key type + /// the unique list + public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) + where TKey : IEquatable + { + return source.Distinct(DelegateEqualityComparer.CompareMember(keySelector)); + } + + /// + /// Returns a sequence of length whose elements are the result of invoking . + /// + /// + /// The factory. + /// The count. + /// + public static IEnumerable Range(Func factory, int count) + { + for (int i = 1; i <= count; i++) + { + yield return factory.Invoke(i - 1); + } + } + + /// The if not null. + /// The items. + /// The action. + /// The type + public static void IfNotNull(this IEnumerable items, Action action) where TItem : class + { + if (items != null) + { + foreach (TItem item in items) + { + item.IfNotNull(action); + } + } + } + + /// The flatten list. + /// The items. + /// The select child. + /// Item type + /// list of TItem + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Do not use, use SelectRecursive instead which has far less potential of re-iterating an iterator which may cause significantly more SQL queries")] + public static IEnumerable FlattenList(this IEnumerable e, Func> f) + { + return e.SelectMany(c => f(c).FlattenList(f)).Concat(e); + } + + /// + /// Returns true if all items in the other collection exist in this collection + /// + /// + /// + /// + /// + public static bool ContainsAll(this IEnumerable source, IEnumerable other) + { + if (source == null) throw new ArgumentNullException("source"); + if (other == null) throw new ArgumentNullException("other"); + + return other.Except(source).Any() == false; + } + + /// + /// Returns true if the source contains any of the items in the other list + /// + /// + /// + /// + /// + public static bool ContainsAny(this IEnumerable source, IEnumerable other) + { + return other.Any(source.Contains); + } + + /// + /// Removes all matching items from an . + /// + /// + /// The list. + /// The predicate. + /// + public static void RemoveAll(this IList list, Func predicate) + { + for (var i = 0; i < list.Count; i++) + { + if (predicate(list[i])) + { + list.RemoveAt(i--); + } + } + } + + /// + /// Removes all matching items from an . + /// + /// + /// The list. + /// The predicate. + /// + public static void RemoveAll(this ICollection list, Func predicate) + { + var matches = list.Where(predicate).ToArray(); + foreach (var match in matches) + { + list.Remove(match); + } + } + + public static IEnumerable SelectRecursive( + this IEnumerable source, + Func> recursiveSelector, int maxRecusionDepth = 100) + { + var stack = new Stack>(); + stack.Push(source.GetEnumerator()); + + try + { + while (stack.Count > 0) + { + if (stack.Count > maxRecusionDepth) + throw new InvalidOperationException("Maximum recursion depth reached of " + maxRecusionDepth); + + if (stack.Peek().MoveNext()) + { + var current = stack.Peek().Current; + + yield return current; + + stack.Push(recursiveSelector(current).GetEnumerator()); + } + else + { + stack.Pop().Dispose(); + } + } + } + finally + { + while (stack.Count > 0) + { + stack.Pop().Dispose(); + } + } + } + + /// + /// Filters a sequence of values to ignore those which are null. + /// + /// + /// The coll. + /// + /// + public static IEnumerable WhereNotNull(this IEnumerable coll) where T : class + { + return coll.Where(x => x != null); + } + + public static IEnumerable ForAllThatAre(this IEnumerable sequence, Action projection) + where TActual : class + { + return sequence.Select( + x => + { + if (x is TActual) + { + var casted = x as TActual; + projection.Invoke(casted); + } + return x; + }); + } + + /// + /// Finds the index of the first item matching an expression in an enumerable. + /// + /// The type of the enumerated objects. + /// The enumerable to search. + /// The expression to test the items against. + /// The index of the first matching item, or -1. + public static int FindIndex(this IEnumerable items, Func predicate) + { + return FindIndex(items, 0, predicate); + } + + /// + /// Finds the index of the first item matching an expression in an enumerable. + /// + /// The type of the enumerated objects. + /// The enumerable to search. + /// The index to start at. + /// The expression to test the items against. + /// The index of the first matching item, or -1. + public static int FindIndex(this IEnumerable items, int startIndex, Func predicate) + { + if (items == null) throw new ArgumentNullException("items"); + if (predicate == null) throw new ArgumentNullException("predicate"); + if (startIndex < 0) throw new ArgumentOutOfRangeException("startIndex"); + + var index = startIndex; + if (index > 0) + items = items.Skip(index); + + foreach (var item in items) + { + if (predicate(item)) return index; + index++; + } + + return -1; + } + + ///Finds the index of the first occurence of an item in an enumerable. + ///The enumerable to search. + ///The item to find. + ///The index of the first matching item, or -1 if the item was not found. + public static int IndexOf(this IEnumerable items, T item) + { + return items.FindIndex(i => EqualityComparer.Default.Equals(item, i)); + } + + /// + /// Determines if 2 lists have equal elements within them regardless of how they are sorted + /// + /// + /// + /// + /// + /// + /// The logic for this is taken from: + /// http://stackoverflow.com/questions/4576723/test-whether-two-ienumerablet-have-the-same-values-with-the-same-frequencies + /// + /// There's a few answers, this one seems the best for it's simplicity and based on the comment of Eamon + /// + public static bool UnsortedSequenceEqual(this IEnumerable source, IEnumerable other) + { + if (source == null && other == null) return true; + if (source == null || other == null) return false; + + var list1Groups = source.ToLookup(i => i); + var list2Groups = other.ToLookup(i => i); + return list1Groups.Count == list2Groups.Count + && list1Groups.All(g => g.Count() == list2Groups[g.Key].Count()); + } + + /// + /// Transforms an enumerable. + /// + /// + /// + /// + /// + public static IEnumerable Transform(this IEnumerable source, Func, IEnumerable> transform) + { + return transform(source); + } + + /// + /// Gets a null IEnumerable as an empty IEnumerable. + /// + /// + /// + /// + public static IEnumerable EmptyNull(this IEnumerable items) + { + return items ?? Enumerable.Empty(); + } + + // the .OfType() filter is nice when there's only one type + // this is to support filtering with multiple types + public static IEnumerable OfTypes(this IEnumerable contents, params Type[] types) + { + 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 6f88fdcce4..e0eb24c579 100644 --- a/src/Umbraco.Core/Events/CancellableEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs @@ -1,142 +1,142 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Security.Permissions; - -namespace Umbraco.Core.Events -{ - /// - /// Event args for that can support cancellation - /// - [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - 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) - { - CanCancel = canCancel; - Messages = messages; - AdditionalData = new ReadOnlyDictionary(additionalData); - } - - public CancellableEventArgs(bool canCancel, EventMessages eventMessages) - { - if (eventMessages == null) throw new ArgumentNullException("eventMessages"); - CanCancel = canCancel; - Messages = eventMessages; - AdditionalData = EmptyAdditionalData; - } - - public CancellableEventArgs(bool canCancel) - { - CanCancel = canCancel; - //create a standalone messages - Messages = new EventMessages(); - AdditionalData = EmptyAdditionalData; - } - - public CancellableEventArgs(EventMessages eventMessages) - : this(true, eventMessages) - { } - - public CancellableEventArgs() - : this(true) - { } - - /// - /// Flag to determine if this instance will support being cancellable - /// - public bool CanCancel { get; set; } - - /// - /// If this instance supports cancellation, this gets/sets the cancel value - /// - public bool Cancel - { - get - { - if (CanCancel == false) - { - throw new InvalidOperationException("This event argument class does not support cancelling."); - } - return _cancel; - } - set - { - if (CanCancel == false) - { - throw new InvalidOperationException("This event argument class does not support cancelling."); - } - _cancel = value; - } - } - - /// - /// if this instance supports cancellation, this will set Cancel to true with an affiliated cancellation message - /// - /// - public void CancelOperation(EventMessage cancelationMessage) - { - Cancel = true; - cancelationMessage.IsDefaultEventMessage = true; - Messages.Add(cancelationMessage); - } - - /// - /// Returns the EventMessages object which is used to add messages to the message collection for this event - /// - public EventMessages Messages { get; private set; } - - /// - /// In some cases raised evens might need to contain additional arbitrary readonly data which can be read by event subscribers - /// - /// - /// This allows for a bit of flexibility in our event raising - it's not pretty but we need to maintain backwards compatibility - /// so we cannot change the strongly typed nature for some 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; - if (ReferenceEquals(this, other)) return true; - return Equals(AdditionalData, other.AdditionalData); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((CancellableEventArgs) obj); - } - - public override int GetHashCode() - { - return AdditionalData != null ? AdditionalData.GetHashCode() : 0; - } - - public static bool operator ==(CancellableEventArgs left, CancellableEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right) - { - return Equals(left, right) == false; - } - } -} +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Security.Permissions; + +namespace Umbraco.Core.Events +{ + /// + /// Event args for that can support cancellation + /// + [HostProtection(SecurityAction.LinkDemand, SharedState = true)] + 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) + { + CanCancel = canCancel; + Messages = messages; + AdditionalData = new ReadOnlyDictionary(additionalData); + } + + public CancellableEventArgs(bool canCancel, EventMessages eventMessages) + { + if (eventMessages == null) throw new ArgumentNullException("eventMessages"); + CanCancel = canCancel; + Messages = eventMessages; + AdditionalData = EmptyAdditionalData; + } + + public CancellableEventArgs(bool canCancel) + { + CanCancel = canCancel; + //create a standalone messages + Messages = new EventMessages(); + AdditionalData = EmptyAdditionalData; + } + + public CancellableEventArgs(EventMessages eventMessages) + : this(true, eventMessages) + { } + + public CancellableEventArgs() + : this(true) + { } + + /// + /// Flag to determine if this instance will support being cancellable + /// + public bool CanCancel { get; set; } + + /// + /// If this instance supports cancellation, this gets/sets the cancel value + /// + public bool Cancel + { + get + { + if (CanCancel == false) + { + throw new InvalidOperationException("This event argument class does not support cancelling."); + } + return _cancel; + } + set + { + if (CanCancel == false) + { + throw new InvalidOperationException("This event argument class does not support cancelling."); + } + _cancel = value; + } + } + + /// + /// if this instance supports cancellation, this will set Cancel to true with an affiliated cancellation message + /// + /// + public void CancelOperation(EventMessage cancelationMessage) + { + Cancel = true; + cancelationMessage.IsDefaultEventMessage = true; + Messages.Add(cancelationMessage); + } + + /// + /// Returns the EventMessages object which is used to add messages to the message collection for this event + /// + public EventMessages Messages { get; private set; } + + /// + /// In some cases raised evens might need to contain additional arbitrary readonly data which can be read by event subscribers + /// + /// + /// This allows for a bit of flexibility in our event raising - it's not pretty but we need to maintain backwards compatibility + /// so we cannot change the strongly typed nature for some 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; + if (ReferenceEquals(this, other)) return true; + return Equals(AdditionalData, other.AdditionalData); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((CancellableEventArgs) obj); + } + + public override int GetHashCode() + { + return AdditionalData != null ? AdditionalData.GetHashCode() : 0; + } + + public static bool operator ==(CancellableEventArgs left, CancellableEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right) + { + return Equals(left, right) == false; + } + } +} diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs index 8c8362251a..a64a399249 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs @@ -1,176 +1,176 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Security.Permissions; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Events -{ - /// - /// Used as a base class for the generic type CancellableObjectEventArgs{T} so that we can get direct 'object' access to the underlying EventObject - /// - [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - public abstract class CancellableObjectEventArgs : CancellableEventArgs - { - protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(canCancel, messages, additionalData) - { - EventObject = eventObject; - } - - protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages eventMessages) - : base(canCancel, eventMessages) - { - EventObject = eventObject; - } - - protected CancellableObjectEventArgs(object eventObject, EventMessages eventMessages) - : this(eventObject, true, eventMessages) - { - } - - protected CancellableObjectEventArgs(object eventObject, bool canCancel) - : base(canCancel) - { - EventObject = eventObject; - } - - protected CancellableObjectEventArgs(object eventObject) - : this(eventObject, true) - { - } - - /// - /// Returns the object relating to the event - /// - /// - /// This is protected so that inheritors can expose it with their own name - /// - internal object EventObject { get; set; } - - } - - /// - /// Event args for a strongly typed object that can support cancellation - /// - /// - [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - public class CancellableObjectEventArgs : CancellableObjectEventArgs, IEquatable> - { - public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(eventObject, canCancel, messages, additionalData) - { - } - - public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { - } - - public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { - } - - public CancellableObjectEventArgs(T eventObject, bool canCancel) - : base(eventObject, canCancel) - { - } - - public CancellableObjectEventArgs(T eventObject) - : base(eventObject) - { - } - - /// - /// Returns the object relating to the event - /// - /// - /// This is protected so that inheritors can expose it with their own name - /// - protected new T EventObject - { - get { return (T) base.EventObject; } - set { base.EventObject = value; } - } - - public bool Equals(CancellableObjectEventArgs other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && EqualityComparer.Default.Equals(EventObject, other.EventObject); - } - - public override bool Equals(object obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((CancellableObjectEventArgs)obj); - } - - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject); - } - } - - public static bool operator ==(CancellableObjectEventArgs left, CancellableObjectEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(CancellableObjectEventArgs left, CancellableObjectEventArgs right) - { - return !Equals(left, right); - } - } - - [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - public class CancellableEnumerableObjectEventArgs : CancellableObjectEventArgs>, IEquatable> - { - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(eventObject, canCancel, messages, additionalData) - { } - - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { } - - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { } - - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel) - : base(eventObject, canCancel) - { } - - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject) - : base(eventObject) - { } - - public bool Equals(CancellableEnumerableObjectEventArgs other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - - return EventObject.SequenceEqual(other.EventObject); - } - - public override bool Equals(object obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((CancellableEnumerableObjectEventArgs)obj); - } - - public override int GetHashCode() - { - return HashCodeHelper.GetHashCode(EventObject); - } - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Security.Permissions; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Events +{ + /// + /// Used as a base class for the generic type CancellableObjectEventArgs{T} so that we can get direct 'object' access to the underlying EventObject + /// + [HostProtection(SecurityAction.LinkDemand, SharedState = true)] + public abstract class CancellableObjectEventArgs : CancellableEventArgs + { + protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + : base(canCancel, messages, additionalData) + { + EventObject = eventObject; + } + + protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages eventMessages) + : base(canCancel, eventMessages) + { + EventObject = eventObject; + } + + protected CancellableObjectEventArgs(object eventObject, EventMessages eventMessages) + : this(eventObject, true, eventMessages) + { + } + + protected CancellableObjectEventArgs(object eventObject, bool canCancel) + : base(canCancel) + { + EventObject = eventObject; + } + + protected CancellableObjectEventArgs(object eventObject) + : this(eventObject, true) + { + } + + /// + /// Returns the object relating to the event + /// + /// + /// This is protected so that inheritors can expose it with their own name + /// + internal object EventObject { get; set; } + + } + + /// + /// Event args for a strongly typed object that can support cancellation + /// + /// + [HostProtection(SecurityAction.LinkDemand, SharedState = true)] + public class CancellableObjectEventArgs : CancellableObjectEventArgs, IEquatable> + { + public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + : base(eventObject, canCancel, messages, additionalData) + { + } + + public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + } + + public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } + + public CancellableObjectEventArgs(T eventObject, bool canCancel) + : base(eventObject, canCancel) + { + } + + public CancellableObjectEventArgs(T eventObject) + : base(eventObject) + { + } + + /// + /// Returns the object relating to the event + /// + /// + /// This is protected so that inheritors can expose it with their own name + /// + protected new T EventObject + { + get { return (T) base.EventObject; } + set { base.EventObject = value; } + } + + public bool Equals(CancellableObjectEventArgs other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && EqualityComparer.Default.Equals(EventObject, other.EventObject); + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CancellableObjectEventArgs)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject); + } + } + + public static bool operator ==(CancellableObjectEventArgs left, CancellableObjectEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(CancellableObjectEventArgs left, CancellableObjectEventArgs right) + { + return !Equals(left, right); + } + } + + [HostProtection(SecurityAction.LinkDemand, SharedState = true)] + public class CancellableEnumerableObjectEventArgs : CancellableObjectEventArgs>, IEquatable> + { + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + : base(eventObject, canCancel, messages, additionalData) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel) + : base(eventObject, canCancel) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject) + : base(eventObject) + { } + + public bool Equals(CancellableEnumerableObjectEventArgs other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + + return EventObject.SequenceEqual(other.EventObject); + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CancellableEnumerableObjectEventArgs)obj); + } + + public override int GetHashCode() + { + return HashCodeHelper.GetHashCode(EventObject); + } + } +} diff --git a/src/Umbraco.Core/Events/ContentCacheEventArgs.cs b/src/Umbraco.Core/Events/ContentCacheEventArgs.cs index b177f25674..fd0adb4570 100644 --- a/src/Umbraco.Core/Events/ContentCacheEventArgs.cs +++ b/src/Umbraco.Core/Events/ContentCacheEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Events -{ - public class ContentCacheEventArgs : System.ComponentModel.CancelEventArgs { } -} +namespace Umbraco.Core.Events +{ + public class ContentCacheEventArgs : System.ComponentModel.CancelEventArgs { } +} diff --git a/src/Umbraco.Core/Events/CopyEventArgs.cs b/src/Umbraco.Core/Events/CopyEventArgs.cs index c697dcbc06..d2d39e0ba9 100644 --- a/src/Umbraco.Core/Events/CopyEventArgs.cs +++ b/src/Umbraco.Core/Events/CopyEventArgs.cs @@ -1,87 +1,87 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Events -{ - public class CopyEventArgs : CancellableObjectEventArgs, IEquatable> - { - public CopyEventArgs(TEntity original, TEntity copy, bool canCancel, int parentId) - : base(original, canCancel) - { - Copy = copy; - ParentId = parentId; - } - - public CopyEventArgs(TEntity eventObject, TEntity copy, int parentId) - : base(eventObject) - { - Copy = copy; - ParentId = parentId; - } - - public CopyEventArgs(TEntity eventObject, TEntity copy, bool canCancel, int parentId, bool relateToOriginal) - : base(eventObject, canCancel) - { - Copy = copy; - ParentId = parentId; - RelateToOriginal = relateToOriginal; - } - - /// - /// The copied entity - /// - public TEntity Copy { get; set; } - - /// - /// The original entity - /// - public TEntity Original - { - get { return EventObject; } - } - - /// - /// Gets or Sets the Id of the objects new parent. - /// - public int ParentId { get; private set; } - - public bool RelateToOriginal { get; set; } - - public bool Equals(CopyEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && EqualityComparer.Default.Equals(Copy, other.Copy) && ParentId == other.ParentId && RelateToOriginal == other.RelateToOriginal; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((CopyEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Copy); - hashCode = (hashCode * 397) ^ ParentId; - hashCode = (hashCode * 397) ^ RelateToOriginal.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(CopyEventArgs left, CopyEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(CopyEventArgs left, CopyEventArgs right) - { - return !Equals(left, right); - } - } -} +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Events +{ + public class CopyEventArgs : CancellableObjectEventArgs, IEquatable> + { + public CopyEventArgs(TEntity original, TEntity copy, bool canCancel, int parentId) + : base(original, canCancel) + { + Copy = copy; + ParentId = parentId; + } + + public CopyEventArgs(TEntity eventObject, TEntity copy, int parentId) + : base(eventObject) + { + Copy = copy; + ParentId = parentId; + } + + public CopyEventArgs(TEntity eventObject, TEntity copy, bool canCancel, int parentId, bool relateToOriginal) + : base(eventObject, canCancel) + { + Copy = copy; + ParentId = parentId; + RelateToOriginal = relateToOriginal; + } + + /// + /// The copied entity + /// + public TEntity Copy { get; set; } + + /// + /// The original entity + /// + public TEntity Original + { + get { return EventObject; } + } + + /// + /// Gets or Sets the Id of the objects new parent. + /// + public int ParentId { get; private set; } + + public bool RelateToOriginal { get; set; } + + public bool Equals(CopyEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && EqualityComparer.Default.Equals(Copy, other.Copy) && ParentId == other.ParentId && RelateToOriginal == other.RelateToOriginal; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CopyEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Copy); + hashCode = (hashCode * 397) ^ ParentId; + hashCode = (hashCode * 397) ^ RelateToOriginal.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(CopyEventArgs left, CopyEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(CopyEventArgs left, CopyEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/DatabaseCreationEventArgs.cs b/src/Umbraco.Core/Events/DatabaseCreationEventArgs.cs index 5736a08890..08ddf2d4e6 100644 --- a/src/Umbraco.Core/Events/DatabaseCreationEventArgs.cs +++ b/src/Umbraco.Core/Events/DatabaseCreationEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Events -{ - internal class DatabaseCreationEventArgs : System.ComponentModel.CancelEventArgs{} -} +namespace Umbraco.Core.Events +{ + internal class DatabaseCreationEventArgs : System.ComponentModel.CancelEventArgs{} +} diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index 9b0d7a4e76..03f5c40ef8 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -1,202 +1,202 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Core.Events -{ - [SupersedeEvent(typeof(SaveEventArgs<>))] - [SupersedeEvent(typeof(PublishEventArgs<>))] - [SupersedeEvent(typeof(MoveEventArgs<>))] - [SupersedeEvent(typeof(CopyEventArgs<>))] - public class DeleteEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>, IDeletingMediaFilesEventArgs - { - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, eventMessages) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages) - : base(new List { eventObject }, eventMessages) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) - : base(new List { eventObject }, canCancel, eventMessages) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - public DeleteEventArgs(IEnumerable eventObject) : base(eventObject) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting a single entity instance - /// - /// - public DeleteEventArgs(TEntity eventObject) - : base(new List { eventObject }) - { - MediaFilesToDelete = new List(); - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, bool canCancel) - : base(new List { eventObject }, canCancel) - { - MediaFilesToDelete = new List(); - } - - /// - /// Returns all entities that were deleted during the operation - /// - public IEnumerable DeletedEntities - { - get { return EventObject; } - internal set { EventObject = value; } // fixme ouch! - } - - /// - /// A list of media files that can be added to during a deleted operation for which Umbraco will ensure are removed - /// - public List MediaFilesToDelete { get; private set; } - - public bool Equals(DeleteEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && MediaFilesToDelete.SequenceEqual(other.MediaFilesToDelete); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((DeleteEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ MediaFilesToDelete.GetHashCode(); - } - } - - public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) - { - return !Equals(left, right); - } - } - - public class DeleteEventArgs : CancellableEventArgs, IEquatable - { - public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages) - : base(canCancel, eventMessages) - { - Id = id; - } - - public DeleteEventArgs(int id, bool canCancel) - : base(canCancel) - { - Id = id; - } - - public DeleteEventArgs(int id) - { - Id = id; - } - - /// - /// Gets the Id of the object being deleted. - /// - public int Id { get; private set; } - - public bool Equals(DeleteEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && Id == other.Id; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((DeleteEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ Id; - } - } - - public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) - { - return !Equals(left, right); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Events +{ + [SupersedeEvent(typeof(SaveEventArgs<>))] + [SupersedeEvent(typeof(PublishEventArgs<>))] + [SupersedeEvent(typeof(MoveEventArgs<>))] + [SupersedeEvent(typeof(CopyEventArgs<>))] + public class DeleteEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>, IDeletingMediaFilesEventArgs + { + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List { eventObject }, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) + : base(new List { eventObject }, canCancel, eventMessages) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + public DeleteEventArgs(IEnumerable eventObject) : base(eventObject) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting a single entity instance + /// + /// + public DeleteEventArgs(TEntity eventObject) + : base(new List { eventObject }) + { + MediaFilesToDelete = new List(); + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, bool canCancel) + : base(new List { eventObject }, canCancel) + { + MediaFilesToDelete = new List(); + } + + /// + /// Returns all entities that were deleted during the operation + /// + public IEnumerable DeletedEntities + { + get { return EventObject; } + internal set { EventObject = value; } // fixme ouch! + } + + /// + /// A list of media files that can be added to during a deleted operation for which Umbraco will ensure are removed + /// + public List MediaFilesToDelete { get; private set; } + + public bool Equals(DeleteEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && MediaFilesToDelete.SequenceEqual(other.MediaFilesToDelete); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DeleteEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ MediaFilesToDelete.GetHashCode(); + } + } + + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) + { + return !Equals(left, right); + } + } + + public class DeleteEventArgs : CancellableEventArgs, IEquatable + { + public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages) + : base(canCancel, eventMessages) + { + Id = id; + } + + public DeleteEventArgs(int id, bool canCancel) + : base(canCancel) + { + Id = id; + } + + public DeleteEventArgs(int id) + { + Id = id; + } + + /// + /// Gets the Id of the object being deleted. + /// + public int Id { get; private set; } + + public bool Equals(DeleteEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && Id == other.Id; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DeleteEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ Id; + } + } + + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs b/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs index a93f08ebb8..46fdfc6f93 100644 --- a/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs @@ -1,69 +1,69 @@ -using System; - -namespace Umbraco.Core.Events -{ - public class DeleteRevisionsEventArgs : DeleteEventArgs, IEquatable - { - public DeleteRevisionsEventArgs(int id, bool canCancel, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) - : base(id, canCancel) - { - DeletePriorVersions = deletePriorVersions; - SpecificVersion = specificVersion; - DateToRetain = dateToRetain; - } - - public DeleteRevisionsEventArgs(int id, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) - : base(id) - { - DeletePriorVersions = deletePriorVersions; - SpecificVersion = specificVersion; - DateToRetain = dateToRetain; - } - - public bool DeletePriorVersions { get; } - public int SpecificVersion { get; } - public DateTime DateToRetain { get; } - - /// - /// Returns true if we are deleting a specific revision - /// - public bool IsDeletingSpecificRevision => SpecificVersion != default; - - public bool Equals(DeleteRevisionsEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && DateToRetain.Equals(other.DateToRetain) && DeletePriorVersions == other.DeletePriorVersions && SpecificVersion.Equals(other.SpecificVersion); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((DeleteRevisionsEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ DateToRetain.GetHashCode(); - hashCode = (hashCode * 397) ^ DeletePriorVersions.GetHashCode(); - hashCode = (hashCode * 397) ^ SpecificVersion.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) - { - return !Equals(left, right); - } - } -} +using System; + +namespace Umbraco.Core.Events +{ + public class DeleteRevisionsEventArgs : DeleteEventArgs, IEquatable + { + public DeleteRevisionsEventArgs(int id, bool canCancel, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id, canCancel) + { + DeletePriorVersions = deletePriorVersions; + SpecificVersion = specificVersion; + DateToRetain = dateToRetain; + } + + public DeleteRevisionsEventArgs(int id, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id) + { + DeletePriorVersions = deletePriorVersions; + SpecificVersion = specificVersion; + DateToRetain = dateToRetain; + } + + public bool DeletePriorVersions { get; } + public int SpecificVersion { get; } + public DateTime DateToRetain { get; } + + /// + /// Returns true if we are deleting a specific revision + /// + public bool IsDeletingSpecificRevision => SpecificVersion != default; + + public bool Equals(DeleteRevisionsEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && DateToRetain.Equals(other.DateToRetain) && DeletePriorVersions == other.DeletePriorVersions && SpecificVersion.Equals(other.SpecificVersion); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((DeleteRevisionsEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ DateToRetain.GetHashCode(); + hashCode = (hashCode * 397) ^ DeletePriorVersions.GetHashCode(); + hashCode = (hashCode * 397) ^ SpecificVersion.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/EventExtensions.cs b/src/Umbraco.Core/Events/EventExtensions.cs index 8843bcb1d6..d1c8dde5fb 100644 --- a/src/Umbraco.Core/Events/EventExtensions.cs +++ b/src/Umbraco.Core/Events/EventExtensions.cs @@ -1,46 +1,46 @@ -using System; - -namespace Umbraco.Core.Events -{ - /// - /// Extension methods for cancellable event operations - /// - public static class EventExtensions - { - // keep these two for backward compatibility reasons but understand that - // they are *not* part of any scope / event dispatcher / anything... - - /// - /// Raises a cancelable event and returns a value indicating whether the event should be cancelled. - /// - /// The type of the event source. - /// The type of the event data. - /// The event handler. - /// The event source. - /// The event data. - /// A value indicating whether the cancelable event should be cancelled - /// A cancelable event is raised by a component when it is about to perform an action that can be canceled. - public static bool IsRaisedEventCancelled(this TypedEventHandler eventHandler, TArgs args, TSender sender) - where TArgs : CancellableEventArgs - { - if (eventHandler == null) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - /// - /// Raises an event. - /// - /// The type of the event source. - /// The type of the event data. - /// The event handler. - /// The event source. - /// The event data. - public static void RaiseEvent(this TypedEventHandler eventHandler, TArgs args, TSender sender) - where TArgs : EventArgs - { - if (eventHandler == null) return; - eventHandler(sender, args); - } - } -} +using System; + +namespace Umbraco.Core.Events +{ + /// + /// Extension methods for cancellable event operations + /// + public static class EventExtensions + { + // keep these two for backward compatibility reasons but understand that + // they are *not* part of any scope / event dispatcher / anything... + + /// + /// Raises a cancelable event and returns a value indicating whether the event should be cancelled. + /// + /// The type of the event source. + /// The type of the event data. + /// The event handler. + /// The event source. + /// The event data. + /// A value indicating whether the cancelable event should be cancelled + /// A cancelable event is raised by a component when it is about to perform an action that can be canceled. + public static bool IsRaisedEventCancelled(this TypedEventHandler eventHandler, TArgs args, TSender sender) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + /// + /// Raises an event. + /// + /// The type of the event source. + /// The type of the event data. + /// The event handler. + /// The event source. + /// The event data. + public static void RaiseEvent(this TypedEventHandler eventHandler, TArgs args, TSender sender) + where TArgs : EventArgs + { + if (eventHandler == null) return; + eventHandler(sender, args); + } + } +} diff --git a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs index 7536b43e93..2e56757a25 100644 --- a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs +++ b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs @@ -1,76 +1,76 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using Umbraco.Core.Models.Packaging; - -namespace Umbraco.Core.Events -{ - public class ImportPackageEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> - { - private readonly MetaData _packageMetaData; - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use the overload specifying packageMetaData instead")] - public ImportPackageEventArgs(TEntity eventObject, bool canCancel) - : base(new[] { eventObject }, canCancel) - { - } - - public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData, bool canCancel) - : base(new[] { eventObject }, canCancel) - { - if (packageMetaData == null) throw new ArgumentNullException("packageMetaData"); - _packageMetaData = packageMetaData; - } - - public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData) - : this(eventObject, packageMetaData, true) - { - - } - - public MetaData PackageMetaData - { - get { return _packageMetaData; } - } - - public IEnumerable InstallationSummary - { - get { return EventObject; } - } - - public bool Equals(ImportPackageEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - //TODO: MetaData for package metadata has no equality operators :/ - return base.Equals(other) && _packageMetaData.Equals(other._packageMetaData); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((ImportPackageEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ _packageMetaData.GetHashCode(); - } - } - - public static bool operator ==(ImportPackageEventArgs left, ImportPackageEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(ImportPackageEventArgs left, ImportPackageEventArgs right) - { - return !Equals(left, right); - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Umbraco.Core.Models.Packaging; + +namespace Umbraco.Core.Events +{ + public class ImportPackageEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> + { + private readonly MetaData _packageMetaData; + + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the overload specifying packageMetaData instead")] + public ImportPackageEventArgs(TEntity eventObject, bool canCancel) + : base(new[] { eventObject }, canCancel) + { + } + + public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData, bool canCancel) + : base(new[] { eventObject }, canCancel) + { + if (packageMetaData == null) throw new ArgumentNullException("packageMetaData"); + _packageMetaData = packageMetaData; + } + + public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData) + : this(eventObject, packageMetaData, true) + { + + } + + public MetaData PackageMetaData + { + get { return _packageMetaData; } + } + + public IEnumerable InstallationSummary + { + get { return EventObject; } + } + + public bool Equals(ImportPackageEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + //TODO: MetaData for package metadata has no equality operators :/ + return base.Equals(other) && _packageMetaData.Equals(other._packageMetaData); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ImportPackageEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ _packageMetaData.GetHashCode(); + } + } + + public static bool operator ==(ImportPackageEventArgs left, ImportPackageEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(ImportPackageEventArgs left, ImportPackageEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs index 75312508a7..fee31356fc 100644 --- a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs +++ b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs @@ -1,42 +1,42 @@ -using System; -using Umbraco.Core.Macros; - -namespace Umbraco.Core.Events -{ - // Provides information on the macro that caused an error - public class MacroErrorEventArgs : EventArgs - { - /// - /// Name of the faulting macro. - /// - public string Name { get; set; } - - /// - /// Alias of the faulting macro. - /// - public string Alias { get; set; } - - /// - /// Filename, file path, fully qualified class name, or other key used by the macro engine to do it's processing of the faulting macro. - /// - public string MacroSource { get; set; } - - /// - /// Exception raised. - /// - public Exception Exception { get; set; } - - /// - /// Gets or sets the desired behaviour when a matching macro causes an error. See - /// for definitions. By setting this in your event - /// you can override the default behaviour defined in UmbracoSettings.config. - /// - /// Macro error behaviour enum. - public MacroErrorBehaviour Behaviour { get; set; } - - /// - /// The html code to display when Behavior is Content. - /// - public string Html { get; set; } - } -} +using System; +using Umbraco.Core.Macros; + +namespace Umbraco.Core.Events +{ + // Provides information on the macro that caused an error + public class MacroErrorEventArgs : EventArgs + { + /// + /// Name of the faulting macro. + /// + public string Name { get; set; } + + /// + /// Alias of the faulting macro. + /// + public string Alias { get; set; } + + /// + /// Filename, file path, fully qualified class name, or other key used by the macro engine to do it's processing of the faulting macro. + /// + public string MacroSource { get; set; } + + /// + /// Exception raised. + /// + public Exception Exception { get; set; } + + /// + /// Gets or sets the desired behaviour when a matching macro causes an error. See + /// for definitions. By setting this in your event + /// you can override the default behaviour defined in UmbracoSettings.config. + /// + /// Macro error behaviour enum. + public MacroErrorBehaviour Behaviour { get; set; } + + /// + /// The html code to display when Behavior is Content. + /// + public string Html { get; set; } + } +} diff --git a/src/Umbraco.Core/Events/MigrationEventArgs.cs b/src/Umbraco.Core/Events/MigrationEventArgs.cs index b4b9a53736..5349f3c374 100644 --- a/src/Umbraco.Core/Events/MigrationEventArgs.cs +++ b/src/Umbraco.Core/Events/MigrationEventArgs.cs @@ -1,91 +1,91 @@ -using System; -using System.Collections.Generic; -using Semver; -using Umbraco.Core.Migrations; - -namespace Umbraco.Core.Events -{ - public class MigrationEventArgs : CancellableObjectEventArgs>, IEquatable - { - public MigrationEventArgs(IList migrationTypes, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel) - : this(migrationTypes, null, configuredVersion, targetVersion, productName, canCancel) - { } - - internal MigrationEventArgs(IList migrationTypes, IMigrationContext migrationContext, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel) - : base(migrationTypes, canCancel) - { - MigrationContext = migrationContext; - ConfiguredSemVersion = configuredVersion; - TargetSemVersion = targetVersion; - ProductName = productName; - } - - public MigrationEventArgs(IList migrationTypes, SemVersion configuredVersion, SemVersion targetVersion, string productName) - : this(migrationTypes, null, configuredVersion, targetVersion, productName, false) - { } - - /// - /// Returns all migrations that were used in the migration runner - /// - public IList MigrationsTypes => EventObject; - - /// - /// Gets the origin version of the migration, i.e. the one that is currently installed. - /// - public SemVersion ConfiguredSemVersion { get; } - - /// - /// Gets the target version of the migration. - /// - public SemVersion TargetSemVersion { get; } - - /// - /// Gets the product name. - /// - public string ProductName { get; } - - /// - /// Gets the migration context. - /// - /// Is only available after migrations have run, for post-migrations. - internal IMigrationContext MigrationContext { get; } - - public bool Equals(MigrationEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && ConfiguredSemVersion.Equals(other.ConfiguredSemVersion) && MigrationContext.Equals(other.MigrationContext) && string.Equals(ProductName, other.ProductName) && TargetSemVersion.Equals(other.TargetSemVersion); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((MigrationEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ ConfiguredSemVersion.GetHashCode(); - hashCode = (hashCode * 397) ^ MigrationContext.GetHashCode(); - hashCode = (hashCode * 397) ^ ProductName.GetHashCode(); - hashCode = (hashCode * 397) ^ TargetSemVersion.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(MigrationEventArgs left, MigrationEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(MigrationEventArgs left, MigrationEventArgs right) - { - return !Equals(left, right); - } - } -} +using System; +using System.Collections.Generic; +using Semver; +using Umbraco.Core.Migrations; + +namespace Umbraco.Core.Events +{ + public class MigrationEventArgs : CancellableObjectEventArgs>, IEquatable + { + public MigrationEventArgs(IList migrationTypes, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel) + : this(migrationTypes, null, configuredVersion, targetVersion, productName, canCancel) + { } + + internal MigrationEventArgs(IList migrationTypes, IMigrationContext migrationContext, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel) + : base(migrationTypes, canCancel) + { + MigrationContext = migrationContext; + ConfiguredSemVersion = configuredVersion; + TargetSemVersion = targetVersion; + ProductName = productName; + } + + public MigrationEventArgs(IList migrationTypes, SemVersion configuredVersion, SemVersion targetVersion, string productName) + : this(migrationTypes, null, configuredVersion, targetVersion, productName, false) + { } + + /// + /// Returns all migrations that were used in the migration runner + /// + public IList MigrationsTypes => EventObject; + + /// + /// Gets the origin version of the migration, i.e. the one that is currently installed. + /// + public SemVersion ConfiguredSemVersion { get; } + + /// + /// Gets the target version of the migration. + /// + public SemVersion TargetSemVersion { get; } + + /// + /// Gets the product name. + /// + public string ProductName { get; } + + /// + /// Gets the migration context. + /// + /// Is only available after migrations have run, for post-migrations. + internal IMigrationContext MigrationContext { get; } + + public bool Equals(MigrationEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && ConfiguredSemVersion.Equals(other.ConfiguredSemVersion) && MigrationContext.Equals(other.MigrationContext) && string.Equals(ProductName, other.ProductName) && TargetSemVersion.Equals(other.TargetSemVersion); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((MigrationEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ ConfiguredSemVersion.GetHashCode(); + hashCode = (hashCode * 397) ^ MigrationContext.GetHashCode(); + hashCode = (hashCode * 397) ^ ProductName.GetHashCode(); + hashCode = (hashCode * 397) ^ TargetSemVersion.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(MigrationEventArgs left, MigrationEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(MigrationEventArgs left, MigrationEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/MoveEventArgs.cs b/src/Umbraco.Core/Events/MoveEventArgs.cs index 9ae5840349..29483e5e01 100644 --- a/src/Umbraco.Core/Events/MoveEventArgs.cs +++ b/src/Umbraco.Core/Events/MoveEventArgs.cs @@ -1,164 +1,164 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Core.Events -{ - public class MoveEventArgs : CancellableObjectEventArgs, IEquatable> - { - private IEnumerable> _moveInfoCollection; - - /// - /// Constructor accepting a collection of MoveEventInfo objects - /// - /// - /// - /// - /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation - /// - public MoveEventArgs(bool canCancel, EventMessages eventMessages, params MoveEventInfo[] moveInfo) - : base(default, canCancel, eventMessages) - { - if (moveInfo.FirstOrDefault() == null) - { - throw new ArgumentException("moveInfo argument must contain at least one item"); - } - - MoveInfoCollection = moveInfo; - //assign the legacy props - EventObject = moveInfo.First().Entity; - } - - /// - /// Constructor accepting a collection of MoveEventInfo objects - /// - /// - /// - /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation - /// - public MoveEventArgs(EventMessages eventMessages, params MoveEventInfo[] moveInfo) - : base(default, eventMessages) - { - if (moveInfo.FirstOrDefault() == null) - { - throw new ArgumentException("moveInfo argument must contain at least one item"); - } - - MoveInfoCollection = moveInfo; - //assign the legacy props - EventObject = moveInfo.First().Entity; - } - - /// - /// Constructor accepting a collection of MoveEventInfo objects - /// - /// - /// - /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation - /// - public MoveEventArgs(bool canCancel, params MoveEventInfo[] moveInfo) - : base(default, canCancel) - { - if (moveInfo.FirstOrDefault() == null) - { - throw new ArgumentException("moveInfo argument must contain at least one item"); - } - - MoveInfoCollection = moveInfo; - //assign the legacy props - EventObject = moveInfo.First().Entity; - } - - /// - /// Constructor accepting a collection of MoveEventInfo objects - /// - /// - /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation - /// - public MoveEventArgs(params MoveEventInfo[] moveInfo) - : base(default) - { - if (moveInfo.FirstOrDefault() == null) - { - throw new ArgumentException("moveInfo argument must contain at least one item"); - } - - MoveInfoCollection = moveInfo; - //assign the legacy props - EventObject = moveInfo.First().Entity; - } - - [Obsolete("Use the overload that specifies the MoveEventInfo object")] - public MoveEventArgs(TEntity eventObject, bool canCancel, int parentId) - : base(eventObject, canCancel) - { } - - [Obsolete("Use the overload that specifies the MoveEventInfo object")] - public MoveEventArgs(TEntity eventObject, int parentId) - : base(eventObject) - { } - - /// - /// Gets all MoveEventInfo objects used to create the object - /// - 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; - } - } - - /// - /// The entity being moved - /// - [Obsolete("Retrieve the entity object from the MoveInfoCollection property instead")] - public TEntity Entity - { - get { return EventObject; } - } - - public bool Equals(MoveEventArgs other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && MoveInfoCollection.Equals(other.MoveInfoCollection); - } - - public override bool Equals(object obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((MoveEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ MoveInfoCollection.GetHashCode(); - } - } - - public static bool operator ==(MoveEventArgs left, MoveEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(MoveEventArgs left, MoveEventArgs right) - { - return !Equals(left, right); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Events +{ + public class MoveEventArgs : CancellableObjectEventArgs, IEquatable> + { + private IEnumerable> _moveInfoCollection; + + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// + /// + /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(bool canCancel, EventMessages eventMessages, params MoveEventInfo[] moveInfo) + : base(default, canCancel, eventMessages) + { + if (moveInfo.FirstOrDefault() == null) + { + throw new ArgumentException("moveInfo argument must contain at least one item"); + } + + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + } + + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// + /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(EventMessages eventMessages, params MoveEventInfo[] moveInfo) + : base(default, eventMessages) + { + if (moveInfo.FirstOrDefault() == null) + { + throw new ArgumentException("moveInfo argument must contain at least one item"); + } + + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + } + + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// + /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(bool canCancel, params MoveEventInfo[] moveInfo) + : base(default, canCancel) + { + if (moveInfo.FirstOrDefault() == null) + { + throw new ArgumentException("moveInfo argument must contain at least one item"); + } + + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + } + + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(params MoveEventInfo[] moveInfo) + : base(default) + { + if (moveInfo.FirstOrDefault() == null) + { + throw new ArgumentException("moveInfo argument must contain at least one item"); + } + + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + } + + [Obsolete("Use the overload that specifies the MoveEventInfo object")] + public MoveEventArgs(TEntity eventObject, bool canCancel, int parentId) + : base(eventObject, canCancel) + { } + + [Obsolete("Use the overload that specifies the MoveEventInfo object")] + public MoveEventArgs(TEntity eventObject, int parentId) + : base(eventObject) + { } + + /// + /// Gets all MoveEventInfo objects used to create the object + /// + 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; + } + } + + /// + /// The entity being moved + /// + [Obsolete("Retrieve the entity object from the MoveInfoCollection property instead")] + public TEntity Entity + { + get { return EventObject; } + } + + public bool Equals(MoveEventArgs other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && MoveInfoCollection.Equals(other.MoveInfoCollection); + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((MoveEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ MoveInfoCollection.GetHashCode(); + } + } + + public static bool operator ==(MoveEventArgs left, MoveEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(MoveEventArgs left, MoveEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/NewEventArgs.cs b/src/Umbraco.Core/Events/NewEventArgs.cs index 280a628fa9..3969f17b8c 100644 --- a/src/Umbraco.Core/Events/NewEventArgs.cs +++ b/src/Umbraco.Core/Events/NewEventArgs.cs @@ -1,127 +1,127 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Events -{ - public class NewEventArgs : CancellableObjectEventArgs, IEquatable> - { - - - public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { - Alias = alias; - ParentId = parentId; - } - - public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, TEntity parent, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { - Alias = alias; - Parent = parent; - } - - public NewEventArgs(TEntity eventObject, string @alias, int parentId, EventMessages eventMessages) - : base(eventObject, eventMessages) - { - Alias = alias; - ParentId = parentId; - } - - public NewEventArgs(TEntity eventObject, string @alias, TEntity parent, EventMessages eventMessages) - : base(eventObject, eventMessages) - { - Alias = alias; - Parent = parent; - } - - - - public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId) : base(eventObject, canCancel) - { - Alias = alias; - ParentId = parentId; - } - - public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, TEntity parent) - : base(eventObject, canCancel) - { - Alias = alias; - Parent = parent; - } - - public NewEventArgs(TEntity eventObject, string @alias, int parentId) : base(eventObject) - { - Alias = alias; - ParentId = parentId; - } - - public NewEventArgs(TEntity eventObject, string @alias, TEntity parent) - : base(eventObject) - { - Alias = alias; - Parent = parent; - } - - /// - /// The entity being created - /// - public TEntity Entity - { - get { return EventObject; } - } - - /// - /// Gets or Sets the Alias. - /// - public string Alias { get; private set; } - - /// - /// Gets or Sets the Id of the parent. - /// - public int ParentId { get; private set; } - - /// - /// Gets or Sets the parent IContent object. - /// - public TEntity Parent { get; private set; } - - public bool Equals(NewEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && string.Equals(Alias, other.Alias) && EqualityComparer.Default.Equals(Parent, other.Parent) && ParentId == other.ParentId; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((NewEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ Alias.GetHashCode(); - hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Parent); - hashCode = (hashCode * 397) ^ ParentId; - return hashCode; - } - } - - public static bool operator ==(NewEventArgs left, NewEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(NewEventArgs left, NewEventArgs right) - { - return !Equals(left, right); - } - } -} +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Events +{ + public class NewEventArgs : CancellableObjectEventArgs, IEquatable> + { + + + public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + Alias = alias; + ParentId = parentId; + } + + public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, TEntity parent, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + Alias = alias; + Parent = parent; + } + + public NewEventArgs(TEntity eventObject, string @alias, int parentId, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + Alias = alias; + ParentId = parentId; + } + + public NewEventArgs(TEntity eventObject, string @alias, TEntity parent, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + Alias = alias; + Parent = parent; + } + + + + public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId) : base(eventObject, canCancel) + { + Alias = alias; + ParentId = parentId; + } + + public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, TEntity parent) + : base(eventObject, canCancel) + { + Alias = alias; + Parent = parent; + } + + public NewEventArgs(TEntity eventObject, string @alias, int parentId) : base(eventObject) + { + Alias = alias; + ParentId = parentId; + } + + public NewEventArgs(TEntity eventObject, string @alias, TEntity parent) + : base(eventObject) + { + Alias = alias; + Parent = parent; + } + + /// + /// The entity being created + /// + public TEntity Entity + { + get { return EventObject; } + } + + /// + /// Gets or Sets the Alias. + /// + public string Alias { get; private set; } + + /// + /// Gets or Sets the Id of the parent. + /// + public int ParentId { get; private set; } + + /// + /// Gets or Sets the parent IContent object. + /// + public TEntity Parent { get; private set; } + + public bool Equals(NewEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && string.Equals(Alias, other.Alias) && EqualityComparer.Default.Equals(Parent, other.Parent) && ParentId == other.ParentId; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((NewEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ Alias.GetHashCode(); + hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Parent); + hashCode = (hashCode * 397) ^ ParentId; + return hashCode; + } + } + + public static bool operator ==(NewEventArgs left, NewEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(NewEventArgs left, NewEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs index 3226162980..599bae1639 100644 --- a/src/Umbraco.Core/Events/PublishEventArgs.cs +++ b/src/Umbraco.Core/Events/PublishEventArgs.cs @@ -1,139 +1,139 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Events -{ - public class PublishEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> - { - /// - /// Constructor accepting multiple entities that are used in the publish operation - /// - /// - /// - /// - /// - public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { - IsAllRepublished = isAllPublished; - } - - /// - /// Constructor accepting multiple entities that are used in the publish operation - /// - /// - /// - public PublishEventArgs(IEnumerable eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public PublishEventArgs(TEntity eventObject, EventMessages eventMessages) - : base(new List { eventObject }, eventMessages) - { - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - /// - public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages) - : base(new List { eventObject }, canCancel, eventMessages) - { - IsAllRepublished = isAllPublished; - } - - /// - /// Constructor accepting multiple entities that are used in the publish operation - /// - /// - /// - /// - public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished) - : base(eventObject, canCancel) - { - IsAllRepublished = isAllPublished; - } - - /// - /// Constructor accepting multiple entities that are used in the publish operation - /// - /// - public PublishEventArgs(IEnumerable eventObject) - : base(eventObject) - { - } - - /// - /// Constructor accepting a single entity instance - /// - /// - public PublishEventArgs(TEntity eventObject) - : base(new List { eventObject }) - { - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished) - : base(new List { eventObject }, canCancel) - { - IsAllRepublished = isAllPublished; - } - - /// - /// Returns all entities that were published during the operation - /// - public IEnumerable PublishedEntities - { - get { return EventObject; } - } - - public bool IsAllRepublished { get; private set; } - - public bool Equals(PublishEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && IsAllRepublished == other.IsAllRepublished; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((PublishEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ IsAllRepublished.GetHashCode(); - } - } - - public static bool operator ==(PublishEventArgs left, PublishEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(PublishEventArgs left, PublishEventArgs right) - { - return !Equals(left, right); - } - } -} +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Events +{ + public class PublishEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> + { + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + /// + /// + /// + public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + IsAllRepublished = isAllPublished; + } + + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + /// + public PublishEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public PublishEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List { eventObject }, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + /// + public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages) + : base(new List { eventObject }, canCancel, eventMessages) + { + IsAllRepublished = isAllPublished; + } + + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + /// + /// + public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished) + : base(eventObject, canCancel) + { + IsAllRepublished = isAllPublished; + } + + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + public PublishEventArgs(IEnumerable eventObject) + : base(eventObject) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + public PublishEventArgs(TEntity eventObject) + : base(new List { eventObject }) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished) + : base(new List { eventObject }, canCancel) + { + IsAllRepublished = isAllPublished; + } + + /// + /// Returns all entities that were published during the operation + /// + public IEnumerable PublishedEntities + { + get { return EventObject; } + } + + public bool IsAllRepublished { get; private set; } + + public bool Equals(PublishEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && IsAllRepublished == other.IsAllRepublished; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((PublishEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ IsAllRepublished.GetHashCode(); + } + } + + public static bool operator ==(PublishEventArgs left, PublishEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(PublishEventArgs left, PublishEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs index 4a51e76108..be9a65cb0e 100644 --- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs +++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs @@ -1,81 +1,81 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Events -{ - public class RecycleBinEventArgs : CancellableEventArgs, IEquatable - { - public RecycleBinEventArgs(Guid nodeObjectType, EventMessages eventMessages) - : base(true, eventMessages) - { - NodeObjectType = nodeObjectType; - } - - public RecycleBinEventArgs(Guid nodeObjectType) - : base(true) - { - NodeObjectType = nodeObjectType; - - } +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Events +{ + public class RecycleBinEventArgs : CancellableEventArgs, IEquatable + { + public RecycleBinEventArgs(Guid nodeObjectType, EventMessages eventMessages) + : base(true, eventMessages) + { + NodeObjectType = nodeObjectType; + } + + public RecycleBinEventArgs(Guid nodeObjectType) + : base(true) + { + NodeObjectType = nodeObjectType; + + } - /// - /// Gets the Id of the node object type of the items - /// being deleted from the Recycle Bin. - /// - public Guid NodeObjectType { get; } - - /// - /// Boolean indicating whether the Recycle Bin was emptied successfully - /// - public bool RecycleBinEmptiedSuccessfully { get; set; } - - /// - /// Boolean indicating whether this event was fired for the Content's Recycle Bin. - /// - public bool IsContentRecycleBin => NodeObjectType == Constants.ObjectTypes.Document; - - /// - /// Boolean indicating whether this event was fired for the Media's Recycle Bin. - /// - public bool IsMediaRecycleBin => NodeObjectType == Constants.ObjectTypes.Media; - - public bool Equals(RecycleBinEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && NodeObjectType.Equals(other.NodeObjectType) && RecycleBinEmptiedSuccessfully == other.RecycleBinEmptiedSuccessfully; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((RecycleBinEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ NodeObjectType.GetHashCode(); - hashCode = (hashCode * 397) ^ RecycleBinEmptiedSuccessfully.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(RecycleBinEventArgs left, RecycleBinEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(RecycleBinEventArgs left, RecycleBinEventArgs right) - { - return !Equals(left, right); - } - } -} + /// + /// Gets the Id of the node object type of the items + /// being deleted from the Recycle Bin. + /// + public Guid NodeObjectType { get; } + + /// + /// Boolean indicating whether the Recycle Bin was emptied successfully + /// + public bool RecycleBinEmptiedSuccessfully { get; set; } + + /// + /// Boolean indicating whether this event was fired for the Content's Recycle Bin. + /// + public bool IsContentRecycleBin => NodeObjectType == Constants.ObjectTypes.Document; + + /// + /// Boolean indicating whether this event was fired for the Media's Recycle Bin. + /// + public bool IsMediaRecycleBin => NodeObjectType == Constants.ObjectTypes.Media; + + public bool Equals(RecycleBinEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && NodeObjectType.Equals(other.NodeObjectType) && RecycleBinEmptiedSuccessfully == other.RecycleBinEmptiedSuccessfully; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((RecycleBinEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ NodeObjectType.GetHashCode(); + hashCode = (hashCode * 397) ^ RecycleBinEmptiedSuccessfully.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(RecycleBinEventArgs left, RecycleBinEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(RecycleBinEventArgs left, RecycleBinEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/RefreshContentEventArgs.cs b/src/Umbraco.Core/Events/RefreshContentEventArgs.cs index b54c286668..c92633fcef 100644 --- a/src/Umbraco.Core/Events/RefreshContentEventArgs.cs +++ b/src/Umbraco.Core/Events/RefreshContentEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Events -{ - //public class RefreshContentEventArgs : System.ComponentModel.CancelEventArgs { } -} +namespace Umbraco.Core.Events +{ + //public class RefreshContentEventArgs : System.ComponentModel.CancelEventArgs { } +} diff --git a/src/Umbraco.Core/Events/RollbackEventArgs.cs b/src/Umbraco.Core/Events/RollbackEventArgs.cs index 279edef67c..9ebdb20e64 100644 --- a/src/Umbraco.Core/Events/RollbackEventArgs.cs +++ b/src/Umbraco.Core/Events/RollbackEventArgs.cs @@ -1,21 +1,21 @@ -namespace Umbraco.Core.Events -{ - public class RollbackEventArgs : CancellableObjectEventArgs - { - public RollbackEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) - { - } - - public RollbackEventArgs(TEntity eventObject) : base(eventObject) - { - } - - /// - /// The entity being rolledback - /// - public TEntity Entity - { - get { return EventObject; } - } - } -} +namespace Umbraco.Core.Events +{ + public class RollbackEventArgs : CancellableObjectEventArgs + { + public RollbackEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) + { + } + + public RollbackEventArgs(TEntity eventObject) : base(eventObject) + { + } + + /// + /// The entity being rolledback + /// + public TEntity Entity + { + get { return EventObject; } + } + } +} diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs index a37cb357ca..8c3fe82e2f 100644 --- a/src/Umbraco.Core/Events/SaveEventArgs.cs +++ b/src/Umbraco.Core/Events/SaveEventArgs.cs @@ -1,121 +1,121 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Core.Events -{ - public class SaveEventArgs : CancellableEnumerableObjectEventArgs - { - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(eventObject, canCancel, messages, additionalData) - { - } - - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { - } - - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - /// - public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(new List { eventObject }, canCancel, messages, additionalData) - { - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public SaveEventArgs(TEntity eventObject, EventMessages eventMessages) - : base(new List { eventObject }, eventMessages) - { - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) - : base(new List { eventObject }, canCancel, eventMessages) - { - } - - - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, bool canCancel) - : base(eventObject, canCancel) - { - } - - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - public SaveEventArgs(IEnumerable eventObject) - : base(eventObject) - { - } - - /// - /// Constructor accepting a single entity instance - /// - /// - public SaveEventArgs(TEntity eventObject) - : base(new List { eventObject }) - { - } - - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public SaveEventArgs(TEntity eventObject, bool canCancel) - : base(new List { eventObject }, canCancel) - { - } - - /// - /// Returns all entities that were saved during the operation - /// - public IEnumerable SavedEntities - { - get { return EventObject; } - } - } -} +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Events +{ + public class SaveEventArgs : CancellableEnumerableObjectEventArgs + { + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + : base(eventObject, canCancel, messages, additionalData) + { + } + + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + } + + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + /// + public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + : base(new List { eventObject }, canCancel, messages, additionalData) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public SaveEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List { eventObject }, eventMessages) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) + : base(new List { eventObject }, canCancel, eventMessages) + { + } + + + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, bool canCancel) + : base(eventObject, canCancel) + { + } + + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + public SaveEventArgs(IEnumerable eventObject) + : base(eventObject) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + public SaveEventArgs(TEntity eventObject) + : base(new List { eventObject }) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public SaveEventArgs(TEntity eventObject, bool canCancel) + : base(new List { eventObject }, canCancel) + { + } + + /// + /// Returns all entities that were saved during the operation + /// + public IEnumerable SavedEntities + { + get { return EventObject; } + } + } +} diff --git a/src/Umbraco.Core/Events/SendToPublishEventArgs.cs b/src/Umbraco.Core/Events/SendToPublishEventArgs.cs index 6895572c78..21e9276e43 100644 --- a/src/Umbraco.Core/Events/SendToPublishEventArgs.cs +++ b/src/Umbraco.Core/Events/SendToPublishEventArgs.cs @@ -1,21 +1,21 @@ -namespace Umbraco.Core.Events -{ - public class SendToPublishEventArgs : CancellableObjectEventArgs - { - public SendToPublishEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) - { - } - - public SendToPublishEventArgs(TEntity eventObject) : base(eventObject) - { - } - - /// - /// The entity being sent to publish - /// - public TEntity Entity - { - get { return EventObject; } - } - } -} +namespace Umbraco.Core.Events +{ + public class SendToPublishEventArgs : CancellableObjectEventArgs + { + public SendToPublishEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) + { + } + + public SendToPublishEventArgs(TEntity eventObject) : base(eventObject) + { + } + + /// + /// The entity being sent to publish + /// + public TEntity Entity + { + get { return EventObject; } + } + } +} diff --git a/src/Umbraco.Core/Events/TypedEventHandler.cs b/src/Umbraco.Core/Events/TypedEventHandler.cs index abe75e6da9..84bac77ca0 100644 --- a/src/Umbraco.Core/Events/TypedEventHandler.cs +++ b/src/Umbraco.Core/Events/TypedEventHandler.cs @@ -1,7 +1,7 @@ -using System; - -namespace Umbraco.Core.Events -{ - [Serializable] - public delegate void TypedEventHandler(TSender sender, TEventArgs e); -} +using System; + +namespace Umbraco.Core.Events +{ + [Serializable] + public delegate void TypedEventHandler(TSender sender, TEventArgs e); +} diff --git a/src/Umbraco.Core/ExpressionExtensions.cs b/src/Umbraco.Core/ExpressionExtensions.cs index 530ae794ee..04642c02d2 100644 --- a/src/Umbraco.Core/ExpressionExtensions.cs +++ b/src/Umbraco.Core/ExpressionExtensions.cs @@ -1,24 +1,24 @@ -using System; -using System.Linq.Expressions; - -namespace Umbraco.Core -{ - internal static class ExpressionExtensions - { - public static Expression> True() { return f => true; } - - public static Expression> False() { return f => false; } - - public static Expression> Or(this Expression> left, Expression> right) - { - var invokedExpr = Expression.Invoke(right, left.Parameters); - return Expression.Lambda>(Expression.OrElse(left.Body, invokedExpr), left.Parameters); - } - - public static Expression> And(this Expression> left, Expression> right) - { - var invokedExpr = Expression.Invoke(right, left.Parameters); - return Expression.Lambda> (Expression.AndAlso(left.Body, invokedExpr), left.Parameters); - } - } -} +using System; +using System.Linq.Expressions; + +namespace Umbraco.Core +{ + internal static class ExpressionExtensions + { + public static Expression> True() { return f => true; } + + public static Expression> False() { return f => false; } + + public static Expression> Or(this Expression> left, Expression> right) + { + var invokedExpr = Expression.Invoke(right, left.Parameters); + return Expression.Lambda>(Expression.OrElse(left.Body, invokedExpr), left.Parameters); + } + + public static Expression> And(this Expression> left, Expression> right) + { + var invokedExpr = Expression.Invoke(right, left.Parameters); + return Expression.Lambda> (Expression.AndAlso(left.Body, invokedExpr), left.Parameters); + } + } +} diff --git a/src/Umbraco.Core/ExpressionHelper.cs b/src/Umbraco.Core/ExpressionHelper.cs index e03cff4001..db0e06eedb 100644 --- a/src/Umbraco.Core/ExpressionHelper.cs +++ b/src/Umbraco.Core/ExpressionHelper.cs @@ -1,372 +1,372 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using Umbraco.Core.Persistence; - -namespace Umbraco.Core -{ - /// - /// A set of helper methods for dealing with expressions - /// - /// - internal static class ExpressionHelper - { - private static readonly ConcurrentDictionary PropertyInfoCache = new ConcurrentDictionary(); - - /// - /// Gets a object from an expression. - /// - /// The type of the source. - /// The type of the property. - /// The source. - /// The property lambda. - /// - /// - public static PropertyInfo GetPropertyInfo(this TSource source, Expression> propertyLambda) - { - return GetPropertyInfo(propertyLambda); - } - - /// - /// Gets a object from an expression. - /// - /// The type of the source. - /// The type of the property. - /// The property lambda. - /// - /// - public static PropertyInfo GetPropertyInfo(Expression> propertyLambda) - { - return PropertyInfoCache.GetOrAdd( - new LambdaExpressionCacheKey(propertyLambda), - x => - { - var type = typeof(TSource); - - var member = propertyLambda.Body as MemberExpression; - if (member == null) - { - if (propertyLambda.Body.GetType().Name == "UnaryExpression") - { - // The expression might be for some boxing, e.g. representing a value type like HiveId as an object - // in which case the expression will be Convert(x.MyProperty) - var unary = propertyLambda.Body as UnaryExpression; - if (unary != null) - { - var boxedMember = unary.Operand as MemberExpression; - if (boxedMember == null) - throw new ArgumentException("The type of property could not be infered, try specifying the type parameters explicitly. This can happen if you have tried to access PropertyInfo where the property's return type is a value type, but the expression is trying to convert it to an object"); - else member = boxedMember; - } - } - else throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.", propertyLambda)); - } - - - var propInfo = member.Member as PropertyInfo; - if (propInfo == null) - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - propertyLambda)); - - if (type != propInfo.ReflectedType && - !type.IsSubclassOf(propInfo.ReflectedType)) - throw new ArgumentException(string.Format( - "Expresion '{0}' refers to a property that is not from type {1}.", - propertyLambda, - type)); - - return propInfo; - }); - } - - public static (MemberInfo, string) FindProperty(LambdaExpression lambda) - { - void Throw() - { - throw new ArgumentException($"Expression '{lambda}' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.", nameof(lambda)); - } - - Expression expr = lambda; - var loop = true; - string alias = null; - while (loop) - { - switch (expr.NodeType) - { - case ExpressionType.Convert: - expr = ((UnaryExpression) expr).Operand; - break; - case ExpressionType.Lambda: - expr = ((LambdaExpression) expr).Body; - break; - case ExpressionType.Call: - var callExpr = (MethodCallExpression) expr; - var method = callExpr.Method; - if (method.DeclaringType != typeof(NPocoSqlExtensions.Statics) || method.Name != "Alias" || !(callExpr.Arguments[1] is ConstantExpression aliasExpr)) - Throw(); - expr = callExpr.Arguments[0]; - alias = aliasExpr.Value.ToString(); - break; - case ExpressionType.MemberAccess: - var memberExpr = (MemberExpression) expr; - if (memberExpr.Expression.NodeType != ExpressionType.Parameter && memberExpr.Expression.NodeType != ExpressionType.Convert) - Throw(); - return (memberExpr.Member, alias); - default: - loop = false; - break; - } - } - - throw new Exception("Configuration for members is only supported for top-level individual members on a type."); - } - - public static IDictionary GetMethodParams(Expression> fromExpression) - { - if (fromExpression == null) return null; - var body = fromExpression.Body as MethodCallExpression; - if (body == null) - return new Dictionary(); - - var rVal = new Dictionary(); - var parameters = body.Method.GetParameters().Select(x => x.Name).ToArray(); - var i = 0; - foreach (var argument in body.Arguments) - { - var lambda = Expression.Lambda(argument, fromExpression.Parameters); - var d = lambda.Compile(); - var value = d.DynamicInvoke(new object[1]); - rVal.Add(parameters[i], value); - i++; - } - return rVal; - } - - /// - /// Gets a from an provided it refers to a method call. - /// - /// - /// From expression. - /// The or null if is null or cannot be converted to . - /// - public static MethodInfo GetMethodInfo(Expression> fromExpression) - { - if (fromExpression == null) return null; - var body = fromExpression.Body as MethodCallExpression; - return body != null ? body.Method : null; - } - - /// - /// Gets the method info. - /// - /// The return type of the method. - /// From expression. - /// - public static MethodInfo GetMethodInfo(Expression> fromExpression) - { - if (fromExpression == null) return null; - var body = fromExpression.Body as MethodCallExpression; - return body != null ? body.Method : null; - } - - /// - /// Gets the method info. - /// - /// The type of the 1. - /// The type of the 2. - /// From expression. - /// - public static MethodInfo GetMethodInfo(Expression> fromExpression) - { - if (fromExpression == null) return null; - - MethodCallExpression me; - switch (fromExpression.Body.NodeType) - { - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - var ue = fromExpression.Body as UnaryExpression; - me = ((ue != null) ? ue.Operand : null) as MethodCallExpression; - break; - default: - me = fromExpression.Body as MethodCallExpression; - break; - } - - return me != null ? me.Method : null; - } - - /// - /// Gets a from an provided it refers to a method call. - /// - /// The expression. - /// The or null if cannot be converted to . - /// - public static MethodInfo GetMethod(Expression expression) - { - if (expression == null) return null; - return IsMethod(expression) ? (((MethodCallExpression)expression).Method) : null; - } - - /// - /// Gets a from an provided it refers to member access. - /// - /// - /// The type of the return. - /// From expression. - /// The or null if cannot be converted to . - /// - public static MemberInfo GetMemberInfo(Expression> fromExpression) - { - if (fromExpression == null) return null; - - MemberExpression me; - switch (fromExpression.Body.NodeType) - { - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - var ue = fromExpression.Body as UnaryExpression; - me = ((ue != null) ? ue.Operand : null) as MemberExpression; - break; - default: - me = fromExpression.Body as MemberExpression; - break; - } - - return me != null ? me.Member : null; - } - - /// - /// Determines whether the MethodInfo is the same based on signature, not based on the equality operator or HashCode. - /// - /// The left. - /// The right. - /// - /// true if [is method signature equal to] [the specified left]; otherwise, false. - /// - /// - /// This is useful for comparing Expression methods that may contain different generic types - /// - public static bool IsMethodSignatureEqualTo(this MethodInfo left, MethodInfo right) - { - if (left.Equals(right)) - return true; - if (left.DeclaringType != right.DeclaringType) - return false; - if (left.Name != right.Name) - return false; - var leftParams = left.GetParameters(); - var rightParams = right.GetParameters(); - if (leftParams.Length != rightParams.Length) - return false; - for (int i = 0; i < leftParams.Length; i++) - { - //if they are delegate parameters, then assume they match as they could be anything - if (typeof(Delegate).IsAssignableFrom(leftParams[i].ParameterType) && typeof(Delegate).IsAssignableFrom(rightParams[i].ParameterType)) - continue; - //if they are not delegates, then compare the types - if (leftParams[i].ParameterType != rightParams[i].ParameterType) - return false; - } - if (left.ReturnType != right.ReturnType) - return false; - return true; - } - - /// - /// Gets a from an provided it refers to member access. - /// - /// The expression. - /// - /// - public static MemberInfo GetMember(Expression expression) - { - if (expression == null) return null; - return IsMember(expression) ? (((MemberExpression)expression).Member) : null; - } - - /// - /// Gets a from a - /// - /// From method group. - /// - /// - public static MethodInfo GetStaticMethodInfo(Delegate fromMethodGroup) - { - if (fromMethodGroup == null) throw new ArgumentNullException("fromMethodGroup"); - - - return fromMethodGroup.Method; - } - - ///// - ///// Formats an unhandled item for representing the expression as a string. - ///// - ///// - ///// The unhandled item. - ///// - ///// - //public static string FormatUnhandledItem(T unhandledItem) where T : class - //{ - // if (unhandledItem == null) throw new ArgumentNullException("unhandledItem"); - - - // var itemAsExpression = unhandledItem as Expression; - // return itemAsExpression != null - // ? FormattingExpressionTreeVisitor.Format(itemAsExpression) - // : unhandledItem.ToString(); - //} - - /// - /// Determines whether the specified expression is a method. - /// - /// The expression. - /// true if the specified expression is method; otherwise, false. - /// - public static bool IsMethod(Expression expression) - { - return expression is MethodCallExpression; - } - - - /// - /// Determines whether the specified expression is a member. - /// - /// The expression. - /// true if the specified expression is member; otherwise, false. - /// - public static bool IsMember(Expression expression) - { - return expression is MemberExpression; - } - - /// - /// Determines whether the specified expression is a constant. - /// - /// The expression. - /// true if the specified expression is constant; otherwise, false. - /// - public static bool IsConstant(Expression expression) - { - return expression is ConstantExpression; - } - - /// - /// Gets the first value from the supplied arguments of an expression, for those arguments that can be cast to . - /// - /// The arguments. - /// - /// - public static object GetFirstValueFromArguments(IEnumerable arguments) - { - if (arguments == null) return false; - return - arguments.Where(x => x is ConstantExpression).Cast - ().Select(x => x.Value).DefaultIfEmpty(null).FirstOrDefault(); - } - } -} +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core +{ + /// + /// A set of helper methods for dealing with expressions + /// + /// + internal static class ExpressionHelper + { + private static readonly ConcurrentDictionary PropertyInfoCache = new ConcurrentDictionary(); + + /// + /// Gets a object from an expression. + /// + /// The type of the source. + /// The type of the property. + /// The source. + /// The property lambda. + /// + /// + public static PropertyInfo GetPropertyInfo(this TSource source, Expression> propertyLambda) + { + return GetPropertyInfo(propertyLambda); + } + + /// + /// Gets a object from an expression. + /// + /// The type of the source. + /// The type of the property. + /// The property lambda. + /// + /// + public static PropertyInfo GetPropertyInfo(Expression> propertyLambda) + { + return PropertyInfoCache.GetOrAdd( + new LambdaExpressionCacheKey(propertyLambda), + x => + { + var type = typeof(TSource); + + var member = propertyLambda.Body as MemberExpression; + if (member == null) + { + if (propertyLambda.Body.GetType().Name == "UnaryExpression") + { + // The expression might be for some boxing, e.g. representing a value type like HiveId as an object + // in which case the expression will be Convert(x.MyProperty) + var unary = propertyLambda.Body as UnaryExpression; + if (unary != null) + { + var boxedMember = unary.Operand as MemberExpression; + if (boxedMember == null) + throw new ArgumentException("The type of property could not be infered, try specifying the type parameters explicitly. This can happen if you have tried to access PropertyInfo where the property's return type is a value type, but the expression is trying to convert it to an object"); + else member = boxedMember; + } + } + else throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.", propertyLambda)); + } + + + var propInfo = member.Member as PropertyInfo; + if (propInfo == null) + throw new ArgumentException(string.Format( + "Expression '{0}' refers to a field, not a property.", + propertyLambda)); + + if (type != propInfo.ReflectedType && + !type.IsSubclassOf(propInfo.ReflectedType)) + throw new ArgumentException(string.Format( + "Expresion '{0}' refers to a property that is not from type {1}.", + propertyLambda, + type)); + + return propInfo; + }); + } + + public static (MemberInfo, string) FindProperty(LambdaExpression lambda) + { + void Throw() + { + throw new ArgumentException($"Expression '{lambda}' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.", nameof(lambda)); + } + + Expression expr = lambda; + var loop = true; + string alias = null; + while (loop) + { + switch (expr.NodeType) + { + case ExpressionType.Convert: + expr = ((UnaryExpression) expr).Operand; + break; + case ExpressionType.Lambda: + expr = ((LambdaExpression) expr).Body; + break; + case ExpressionType.Call: + var callExpr = (MethodCallExpression) expr; + var method = callExpr.Method; + if (method.DeclaringType != typeof(NPocoSqlExtensions.Statics) || method.Name != "Alias" || !(callExpr.Arguments[1] is ConstantExpression aliasExpr)) + Throw(); + expr = callExpr.Arguments[0]; + alias = aliasExpr.Value.ToString(); + break; + case ExpressionType.MemberAccess: + var memberExpr = (MemberExpression) expr; + if (memberExpr.Expression.NodeType != ExpressionType.Parameter && memberExpr.Expression.NodeType != ExpressionType.Convert) + Throw(); + return (memberExpr.Member, alias); + default: + loop = false; + break; + } + } + + throw new Exception("Configuration for members is only supported for top-level individual members on a type."); + } + + public static IDictionary GetMethodParams(Expression> fromExpression) + { + if (fromExpression == null) return null; + var body = fromExpression.Body as MethodCallExpression; + if (body == null) + return new Dictionary(); + + var rVal = new Dictionary(); + var parameters = body.Method.GetParameters().Select(x => x.Name).ToArray(); + var i = 0; + foreach (var argument in body.Arguments) + { + var lambda = Expression.Lambda(argument, fromExpression.Parameters); + var d = lambda.Compile(); + var value = d.DynamicInvoke(new object[1]); + rVal.Add(parameters[i], value); + i++; + } + return rVal; + } + + /// + /// Gets a from an provided it refers to a method call. + /// + /// + /// From expression. + /// The or null if is null or cannot be converted to . + /// + public static MethodInfo GetMethodInfo(Expression> fromExpression) + { + if (fromExpression == null) return null; + var body = fromExpression.Body as MethodCallExpression; + return body != null ? body.Method : null; + } + + /// + /// Gets the method info. + /// + /// The return type of the method. + /// From expression. + /// + public static MethodInfo GetMethodInfo(Expression> fromExpression) + { + if (fromExpression == null) return null; + var body = fromExpression.Body as MethodCallExpression; + return body != null ? body.Method : null; + } + + /// + /// Gets the method info. + /// + /// The type of the 1. + /// The type of the 2. + /// From expression. + /// + public static MethodInfo GetMethodInfo(Expression> fromExpression) + { + if (fromExpression == null) return null; + + MethodCallExpression me; + switch (fromExpression.Body.NodeType) + { + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + var ue = fromExpression.Body as UnaryExpression; + me = ((ue != null) ? ue.Operand : null) as MethodCallExpression; + break; + default: + me = fromExpression.Body as MethodCallExpression; + break; + } + + return me != null ? me.Method : null; + } + + /// + /// Gets a from an provided it refers to a method call. + /// + /// The expression. + /// The or null if cannot be converted to . + /// + public static MethodInfo GetMethod(Expression expression) + { + if (expression == null) return null; + return IsMethod(expression) ? (((MethodCallExpression)expression).Method) : null; + } + + /// + /// Gets a from an provided it refers to member access. + /// + /// + /// The type of the return. + /// From expression. + /// The or null if cannot be converted to . + /// + public static MemberInfo GetMemberInfo(Expression> fromExpression) + { + if (fromExpression == null) return null; + + MemberExpression me; + switch (fromExpression.Body.NodeType) + { + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + var ue = fromExpression.Body as UnaryExpression; + me = ((ue != null) ? ue.Operand : null) as MemberExpression; + break; + default: + me = fromExpression.Body as MemberExpression; + break; + } + + return me != null ? me.Member : null; + } + + /// + /// Determines whether the MethodInfo is the same based on signature, not based on the equality operator or HashCode. + /// + /// The left. + /// The right. + /// + /// true if [is method signature equal to] [the specified left]; otherwise, false. + /// + /// + /// This is useful for comparing Expression methods that may contain different generic types + /// + public static bool IsMethodSignatureEqualTo(this MethodInfo left, MethodInfo right) + { + if (left.Equals(right)) + return true; + if (left.DeclaringType != right.DeclaringType) + return false; + if (left.Name != right.Name) + return false; + var leftParams = left.GetParameters(); + var rightParams = right.GetParameters(); + if (leftParams.Length != rightParams.Length) + return false; + for (int i = 0; i < leftParams.Length; i++) + { + //if they are delegate parameters, then assume they match as they could be anything + if (typeof(Delegate).IsAssignableFrom(leftParams[i].ParameterType) && typeof(Delegate).IsAssignableFrom(rightParams[i].ParameterType)) + continue; + //if they are not delegates, then compare the types + if (leftParams[i].ParameterType != rightParams[i].ParameterType) + return false; + } + if (left.ReturnType != right.ReturnType) + return false; + return true; + } + + /// + /// Gets a from an provided it refers to member access. + /// + /// The expression. + /// + /// + public static MemberInfo GetMember(Expression expression) + { + if (expression == null) return null; + return IsMember(expression) ? (((MemberExpression)expression).Member) : null; + } + + /// + /// Gets a from a + /// + /// From method group. + /// + /// + public static MethodInfo GetStaticMethodInfo(Delegate fromMethodGroup) + { + if (fromMethodGroup == null) throw new ArgumentNullException("fromMethodGroup"); + + + return fromMethodGroup.Method; + } + + ///// + ///// Formats an unhandled item for representing the expression as a string. + ///// + ///// + ///// The unhandled item. + ///// + ///// + //public static string FormatUnhandledItem(T unhandledItem) where T : class + //{ + // if (unhandledItem == null) throw new ArgumentNullException("unhandledItem"); + + + // var itemAsExpression = unhandledItem as Expression; + // return itemAsExpression != null + // ? FormattingExpressionTreeVisitor.Format(itemAsExpression) + // : unhandledItem.ToString(); + //} + + /// + /// Determines whether the specified expression is a method. + /// + /// The expression. + /// true if the specified expression is method; otherwise, false. + /// + public static bool IsMethod(Expression expression) + { + return expression is MethodCallExpression; + } + + + /// + /// Determines whether the specified expression is a member. + /// + /// The expression. + /// true if the specified expression is member; otherwise, false. + /// + public static bool IsMember(Expression expression) + { + return expression is MemberExpression; + } + + /// + /// Determines whether the specified expression is a constant. + /// + /// The expression. + /// true if the specified expression is constant; otherwise, false. + /// + public static bool IsConstant(Expression expression) + { + return expression is ConstantExpression; + } + + /// + /// Gets the first value from the supplied arguments of an expression, for those arguments that can be cast to . + /// + /// The arguments. + /// + /// + public static object GetFirstValueFromArguments(IEnumerable arguments) + { + if (arguments == null) return false; + return + arguments.Where(x => x is ConstantExpression).Cast + ().Select(x => x.Value).DefaultIfEmpty(null).FirstOrDefault(); + } + } +} diff --git a/src/Umbraco.Core/HashCodeCombiner.cs b/src/Umbraco.Core/HashCodeCombiner.cs index d0bb869cc8..12ce2a6cac 100644 --- a/src/Umbraco.Core/HashCodeCombiner.cs +++ b/src/Umbraco.Core/HashCodeCombiner.cs @@ -1,100 +1,100 @@ -using System; -using System.Globalization; -using System.IO; - -namespace Umbraco.Core -{ - /// - /// 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 - { - private long _combinedHash = 5381L; - - internal void AddInt(int i) - { - _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; - } - - internal void AddObject(object o) - { - AddInt(o.GetHashCode()); - } - - internal void AddDateTime(DateTime d) - { - AddInt(d.GetHashCode()); - } - - internal void AddString(string s) - { - if (s != null) - AddInt((StringComparer.InvariantCulture).GetHashCode(s)); - } - - internal void AddCaseInsensitiveString(string s) - { - if (s != null) - AddInt((StringComparer.InvariantCultureIgnoreCase).GetHashCode(s)); - } - - internal void AddFileSystemItem(FileSystemInfo f) - { - //if it doesn't exist, don't proceed. - if (!f.Exists) - 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) - { - AddInt(fileInfo.Length.GetHashCode()); - } - - 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 hex code of the combined hash code - /// - /// - internal string GetCombinedHashCode() - { - return _combinedHash.ToString("x", CultureInfo.InvariantCulture); - } - - } -} +using System; +using System.Globalization; +using System.IO; + +namespace Umbraco.Core +{ + /// + /// 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 + { + private long _combinedHash = 5381L; + + internal void AddInt(int i) + { + _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; + } + + internal void AddObject(object o) + { + AddInt(o.GetHashCode()); + } + + internal void AddDateTime(DateTime d) + { + AddInt(d.GetHashCode()); + } + + internal void AddString(string s) + { + if (s != null) + AddInt((StringComparer.InvariantCulture).GetHashCode(s)); + } + + internal void AddCaseInsensitiveString(string s) + { + if (s != null) + AddInt((StringComparer.InvariantCultureIgnoreCase).GetHashCode(s)); + } + + internal void AddFileSystemItem(FileSystemInfo f) + { + //if it doesn't exist, don't proceed. + if (!f.Exists) + 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) + { + AddInt(fileInfo.Length.GetHashCode()); + } + + 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 hex code of the combined hash code + /// + /// + internal string GetCombinedHashCode() + { + return _combinedHash.ToString("x", CultureInfo.InvariantCulture); + } + + } +} diff --git a/src/Umbraco.Core/IDisposeOnRequestEnd.cs b/src/Umbraco.Core/IDisposeOnRequestEnd.cs index 111b97b842..cf1ec3a177 100644 --- a/src/Umbraco.Core/IDisposeOnRequestEnd.cs +++ b/src/Umbraco.Core/IDisposeOnRequestEnd.cs @@ -1,14 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Core -{ - /// - /// Any class implementing this interface that is added to the httpcontext.items keys or values will be disposed of at the end of the request. - /// - public interface IDisposeOnRequestEnd : IDisposable - { - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core +{ + /// + /// Any class implementing this interface that is added to the httpcontext.items keys or values will be disposed of at the end of the request. + /// + public interface IDisposeOnRequestEnd : IDisposable + { + } +} diff --git a/src/Umbraco.Core/IO/FileSecurityException.cs b/src/Umbraco.Core/IO/FileSecurityException.cs index df5891910c..7b4f7d2625 100644 --- a/src/Umbraco.Core/IO/FileSecurityException.cs +++ b/src/Umbraco.Core/IO/FileSecurityException.cs @@ -1,20 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Core.IO -{ - public class FileSecurityException : Exception - { - public FileSecurityException() - { - - } - - public FileSecurityException(string message) : base(message) - { - - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.IO +{ + public class FileSecurityException : Exception + { + public FileSecurityException() + { + + } + + public FileSecurityException(string message) : base(message) + { + + } + } +} diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index 7946a11528..4b73e64e80 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -1,69 +1,69 @@ -using System; -using System.IO; -using System.Threading; - -namespace Umbraco.Core.IO -{ - public static class FileSystemExtensions - { - /// - /// Attempts to open the file at filePath up to maxRetries times, - /// with a thread sleep time of sleepPerRetryInMilliseconds between retries. - /// - public static FileStream OpenReadWithRetry(this FileInfo file, int maxRetries = 5, int sleepPerRetryInMilliseconds = 50) - { - var retries = maxRetries; - - while (retries > 0) - { - try - { - return File.OpenRead(file.FullName); - } - catch(IOException) - { - retries--; - - if (retries == 0) - { - throw; - } - - Thread.Sleep(sleepPerRetryInMilliseconds); - } - } - - throw new ArgumentException("Retries must be greater than zero"); - } - - public static void CopyFile(this IFileSystem fs, string path, string newPath) - { - using (var stream = fs.OpenFile(path)) - { - fs.AddFile(newPath, stream); - } - } - - public static string GetExtension(this IFileSystem fs, string path) - { - return Path.GetExtension(fs.GetFullPath(path)); - } - - public static string GetFileName(this IFileSystem fs, string path) - { - return Path.GetFileName(fs.GetFullPath(path)); - } - - //TODO: Currently this is the only way to do this - internal static void CreateFolder(this IFileSystem fs, string folderPath) - { - var path = fs.GetRelativePath(folderPath); - var tempFile = Path.Combine(path, Guid.NewGuid().ToString("N") + ".tmp"); - using (var s = new MemoryStream()) - { - fs.AddFile(tempFile, s); - } - fs.DeleteFile(tempFile); - } - } -} +using System; +using System.IO; +using System.Threading; + +namespace Umbraco.Core.IO +{ + public static class FileSystemExtensions + { + /// + /// Attempts to open the file at filePath up to maxRetries times, + /// with a thread sleep time of sleepPerRetryInMilliseconds between retries. + /// + public static FileStream OpenReadWithRetry(this FileInfo file, int maxRetries = 5, int sleepPerRetryInMilliseconds = 50) + { + var retries = maxRetries; + + while (retries > 0) + { + try + { + return File.OpenRead(file.FullName); + } + catch(IOException) + { + retries--; + + if (retries == 0) + { + throw; + } + + Thread.Sleep(sleepPerRetryInMilliseconds); + } + } + + throw new ArgumentException("Retries must be greater than zero"); + } + + public static void CopyFile(this IFileSystem fs, string path, string newPath) + { + using (var stream = fs.OpenFile(path)) + { + fs.AddFile(newPath, stream); + } + } + + public static string GetExtension(this IFileSystem fs, string path) + { + return Path.GetExtension(fs.GetFullPath(path)); + } + + public static string GetFileName(this IFileSystem fs, string path) + { + return Path.GetFileName(fs.GetFullPath(path)); + } + + //TODO: Currently this is the only way to do this + internal static void CreateFolder(this IFileSystem fs, string folderPath) + { + var path = fs.GetRelativePath(folderPath); + var tempFile = Path.Combine(path, Guid.NewGuid().ToString("N") + ".tmp"); + using (var s = new MemoryStream()) + { + fs.AddFile(tempFile, s); + } + fs.DeleteFile(tempFile); + } + } +} diff --git a/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs b/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs index db6a414f29..b3b6cb6b79 100644 --- a/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs +++ b/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs @@ -1,19 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.CodeAnnotations; - -namespace Umbraco.Core.IO -{ - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class FileSystemProviderAttribute : Attribute - { - public string Alias { get; private set; } - - public FileSystemProviderAttribute(string alias) - { - Alias = alias; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Core.IO +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class FileSystemProviderAttribute : Attribute + { + public string Alias { get; private set; } + + public FileSystemProviderAttribute(string alias) + { + Alias = alias; + } + } +} diff --git a/src/Umbraco.Core/IO/FileSystemWrapper.cs b/src/Umbraco.Core/IO/FileSystemWrapper.cs index e9272893e4..2c4377b89b 100644 --- a/src/Umbraco.Core/IO/FileSystemWrapper.cs +++ b/src/Umbraco.Core/IO/FileSystemWrapper.cs @@ -1,118 +1,118 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Umbraco.Core.IO -{ - /// - /// All custom file systems that are based upon another IFileSystem should inherit from FileSystemWrapper - /// - /// - /// An IFileSystem is generally used as a base file system, for example like a PhysicalFileSystem or an S3FileSystem. - /// Then, other custom file systems are wrapped upon these files systems like MediaFileSystem, etc... All of the custom - /// file systems must inherit from FileSystemWrapper. - /// - /// This abstract class just wraps the 'real' IFileSystem object passed in to its constructor. - /// - public abstract class FileSystemWrapper : IFileSystem - { - protected FileSystemWrapper(IFileSystem wrapped) - { - Wrapped = wrapped; - } - - internal IFileSystem Wrapped { get; set; } - - public IEnumerable GetDirectories(string path) - { - return Wrapped.GetDirectories(path); - } - - public void DeleteDirectory(string path) - { - Wrapped.DeleteDirectory(path); - } - - public void DeleteDirectory(string path, bool recursive) - { - Wrapped.DeleteDirectory(path, recursive); - } - - public bool DirectoryExists(string path) - { - return Wrapped.DirectoryExists(path); - } - - public void AddFile(string path, Stream stream) - { - Wrapped.AddFile(path, stream); - } - - public void AddFile(string path, Stream stream, bool overrideExisting) - { - Wrapped.AddFile(path, stream, overrideExisting); - } - - public IEnumerable GetFiles(string path) - { - return Wrapped.GetFiles(path); - } - - public IEnumerable GetFiles(string path, string filter) - { - return Wrapped.GetFiles(path, filter); - } - - public Stream OpenFile(string path) - { - return Wrapped.OpenFile(path); - } - - public void DeleteFile(string path) - { - Wrapped.DeleteFile(path); - } - - public bool FileExists(string path) - { - return Wrapped.FileExists(path); - } - - public string GetRelativePath(string fullPathOrUrl) - { - return Wrapped.GetRelativePath(fullPathOrUrl); - } - - public string GetFullPath(string path) - { - return Wrapped.GetFullPath(path); - } - - public string GetUrl(string path) - { - return Wrapped.GetUrl(path); - } - - public DateTimeOffset GetLastModified(string path) - { - return Wrapped.GetLastModified(path); - } - - public DateTimeOffset GetCreated(string path) - { - return Wrapped.GetCreated(path); - } - - public long GetSize(string path) - { - return Wrapped.GetSize(path); - } - - public bool CanAddPhysical => Wrapped.CanAddPhysical; - - public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) - { - Wrapped.AddFile(path, physicalPath, overrideIfExists, copy); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; + +namespace Umbraco.Core.IO +{ + /// + /// All custom file systems that are based upon another IFileSystem should inherit from FileSystemWrapper + /// + /// + /// An IFileSystem is generally used as a base file system, for example like a PhysicalFileSystem or an S3FileSystem. + /// Then, other custom file systems are wrapped upon these files systems like MediaFileSystem, etc... All of the custom + /// file systems must inherit from FileSystemWrapper. + /// + /// This abstract class just wraps the 'real' IFileSystem object passed in to its constructor. + /// + public abstract class FileSystemWrapper : IFileSystem + { + protected FileSystemWrapper(IFileSystem wrapped) + { + Wrapped = wrapped; + } + + internal IFileSystem Wrapped { get; set; } + + public IEnumerable GetDirectories(string path) + { + return Wrapped.GetDirectories(path); + } + + public void DeleteDirectory(string path) + { + Wrapped.DeleteDirectory(path); + } + + public void DeleteDirectory(string path, bool recursive) + { + Wrapped.DeleteDirectory(path, recursive); + } + + public bool DirectoryExists(string path) + { + return Wrapped.DirectoryExists(path); + } + + public void AddFile(string path, Stream stream) + { + Wrapped.AddFile(path, stream); + } + + public void AddFile(string path, Stream stream, bool overrideExisting) + { + Wrapped.AddFile(path, stream, overrideExisting); + } + + public IEnumerable GetFiles(string path) + { + return Wrapped.GetFiles(path); + } + + public IEnumerable GetFiles(string path, string filter) + { + return Wrapped.GetFiles(path, filter); + } + + public Stream OpenFile(string path) + { + return Wrapped.OpenFile(path); + } + + public void DeleteFile(string path) + { + Wrapped.DeleteFile(path); + } + + public bool FileExists(string path) + { + return Wrapped.FileExists(path); + } + + public string GetRelativePath(string fullPathOrUrl) + { + return Wrapped.GetRelativePath(fullPathOrUrl); + } + + public string GetFullPath(string path) + { + return Wrapped.GetFullPath(path); + } + + public string GetUrl(string path) + { + return Wrapped.GetUrl(path); + } + + public DateTimeOffset GetLastModified(string path) + { + return Wrapped.GetLastModified(path); + } + + public DateTimeOffset GetCreated(string path) + { + return Wrapped.GetCreated(path); + } + + public long GetSize(string path) + { + return Wrapped.GetSize(path); + } + + public bool CanAddPhysical => Wrapped.CanAddPhysical; + + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + Wrapped.AddFile(path, physicalPath, overrideIfExists, copy); + } + } +} diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index 0baedfd479..115cb8a5c1 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -1,178 +1,178 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Umbraco.Core.IO -{ - /// - /// Provides methods allowing the manipulation of files within an Umbraco application. - /// - public interface IFileSystem - { - /// - /// Gets all directories matching the given path. - /// - /// The path to the directories. - /// - /// The representing the matched directories. - /// - IEnumerable GetDirectories(string path); - - /// - /// Deletes the specified directory. - /// - /// The name of the directory to remove. - void DeleteDirectory(string path); - - /// - /// Deletes the specified directory and, if indicated, any subdirectories and files in the directory. - /// - /// Azure blob storage has no real concept of directories so deletion is always recursive. - /// The name of the directory to remove. - /// Whether to remove directories, subdirectories, and files in path. - void DeleteDirectory(string path, bool recursive); - - /// - /// Determines whether the specified directory exists. - /// - /// The directory to check. - /// - /// True if the directory exists and the user has permission to view it; otherwise false. - /// - bool DirectoryExists(string path); - - /// - /// Adds a file to the file system. - /// - /// The path to the given file. - /// The containing the file contents. - void AddFile(string path, Stream stream); - - /// - /// Adds a file to the file system. - /// - /// The path to the given file. - /// The containing the file contents. - /// Whether to override the file if it already exists. - void AddFile(string path, Stream stream, bool overrideIfExists); - - /// - /// Gets all files matching the given path. - /// - /// The path to the files. - /// - /// The representing the matched files. - /// - IEnumerable GetFiles(string path); - - /// - /// Gets all files matching the given path and filter. - /// - /// The path to the files. - /// A filter that allows the querying of file extension. *.jpg - /// - /// The representing the matched files. - /// - IEnumerable GetFiles(string path, string filter); - - /// - /// Gets a representing the file at the gieven path. - /// - /// The path to the file. - /// - /// . - /// - Stream OpenFile(string path); - - /// - /// Deletes the specified file. - /// - /// The name of the file to remove. - void DeleteFile(string path); - - /// - /// Determines whether the specified file exists. - /// - /// The file to check. - /// - /// True if the file exists and the user has permission to view it; otherwise false. - /// - bool FileExists(string path); - - /// - /// Returns the application relative path to the file. - /// - /// The full path or url. - /// - /// The representing the relative path. - /// - string GetRelativePath(string fullPathOrUrl); - - /// - /// Gets the full qualified path to the file. - /// - /// The file to return the full path for. - /// - /// The representing the full path. - /// - string GetFullPath(string path); - - /// - /// Returns the application relative url to the file. - /// - /// The path to return the url for. - /// - /// representing the relative url. - /// - string GetUrl(string path); - - /// - /// Gets the last modified date/time of the file, expressed as a UTC value. - /// - /// The path to the file. - /// - /// . - /// - DateTimeOffset GetLastModified(string path); - - /// - /// Gets the created date/time of the file, expressed as a UTC value. - /// - /// The path to the file. - /// - /// . - /// - DateTimeOffset GetCreated(string path); - - /// - /// Gets the size of a file. - /// - /// The path to the file. - /// The size (in bytes) of the file. - long GetSize(string path); - - /// - /// Gets a value indicating whether the filesystem can add/copy - /// a file which is on a physical filesystem. - /// - /// In other words, whether the filesystem can copy/move a file - /// that is on local disk, in a fast and efficient way. - bool CanAddPhysical { get; } - - /// - /// Adds a file which is on a physical filesystem. - /// - /// The path to the file. - /// The absolute physical path to the source file. - /// A value indicating what to do if the file already exists. - /// A value indicating whether to move (default) or copy. - void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false); - - // TODO: implement these - // - //void CreateDirectory(string path); - // - //// move or rename, directory or file - //void Move(string source, string target); - } -} +using System; +using System.Collections.Generic; +using System.IO; + +namespace Umbraco.Core.IO +{ + /// + /// Provides methods allowing the manipulation of files within an Umbraco application. + /// + public interface IFileSystem + { + /// + /// Gets all directories matching the given path. + /// + /// The path to the directories. + /// + /// The representing the matched directories. + /// + IEnumerable GetDirectories(string path); + + /// + /// Deletes the specified directory. + /// + /// The name of the directory to remove. + void DeleteDirectory(string path); + + /// + /// Deletes the specified directory and, if indicated, any subdirectories and files in the directory. + /// + /// Azure blob storage has no real concept of directories so deletion is always recursive. + /// The name of the directory to remove. + /// Whether to remove directories, subdirectories, and files in path. + void DeleteDirectory(string path, bool recursive); + + /// + /// Determines whether the specified directory exists. + /// + /// The directory to check. + /// + /// True if the directory exists and the user has permission to view it; otherwise false. + /// + bool DirectoryExists(string path); + + /// + /// Adds a file to the file system. + /// + /// The path to the given file. + /// The containing the file contents. + void AddFile(string path, Stream stream); + + /// + /// Adds a file to the file system. + /// + /// The path to the given file. + /// The containing the file contents. + /// Whether to override the file if it already exists. + void AddFile(string path, Stream stream, bool overrideIfExists); + + /// + /// Gets all files matching the given path. + /// + /// The path to the files. + /// + /// The representing the matched files. + /// + IEnumerable GetFiles(string path); + + /// + /// Gets all files matching the given path and filter. + /// + /// The path to the files. + /// A filter that allows the querying of file extension. *.jpg + /// + /// The representing the matched files. + /// + IEnumerable GetFiles(string path, string filter); + + /// + /// Gets a representing the file at the gieven path. + /// + /// The path to the file. + /// + /// . + /// + Stream OpenFile(string path); + + /// + /// Deletes the specified file. + /// + /// The name of the file to remove. + void DeleteFile(string path); + + /// + /// Determines whether the specified file exists. + /// + /// The file to check. + /// + /// True if the file exists and the user has permission to view it; otherwise false. + /// + bool FileExists(string path); + + /// + /// Returns the application relative path to the file. + /// + /// The full path or url. + /// + /// The representing the relative path. + /// + string GetRelativePath(string fullPathOrUrl); + + /// + /// Gets the full qualified path to the file. + /// + /// The file to return the full path for. + /// + /// The representing the full path. + /// + string GetFullPath(string path); + + /// + /// Returns the application relative url to the file. + /// + /// The path to return the url for. + /// + /// representing the relative url. + /// + string GetUrl(string path); + + /// + /// Gets the last modified date/time of the file, expressed as a UTC value. + /// + /// The path to the file. + /// + /// . + /// + DateTimeOffset GetLastModified(string path); + + /// + /// Gets the created date/time of the file, expressed as a UTC value. + /// + /// The path to the file. + /// + /// . + /// + DateTimeOffset GetCreated(string path); + + /// + /// Gets the size of a file. + /// + /// The path to the file. + /// The size (in bytes) of the file. + long GetSize(string path); + + /// + /// Gets a value indicating whether the filesystem can add/copy + /// a file which is on a physical filesystem. + /// + /// In other words, whether the filesystem can copy/move a file + /// that is on local disk, in a fast and efficient way. + bool CanAddPhysical { get; } + + /// + /// Adds a file which is on a physical filesystem. + /// + /// The path to the file. + /// The absolute physical path to the source file. + /// A value indicating what to do if the file already exists. + /// A value indicating whether to move (default) or copy. + void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false); + + // TODO: implement these + // + //void CreateDirectory(string path); + // + //// move or rename, directory or file + //void Move(string source, string target); + } +} diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 0a61e8e542..2f1675e08a 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -1,389 +1,389 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Reflection; -using System.IO; -using System.Configuration; -using System.Linq; -using System.Web; -using System.Web.Hosting; -using System.IO.Compression; - -namespace Umbraco.Core.IO -{ - public static class IOHelper - { - /// - /// Gets or sets a value forcing Umbraco to consider it is non-hosted. - /// - /// This should always be false, unless unit testing. - public static bool ForceNotHosted { get; set; } - - private static string _rootDir = ""; - - // static compiled regex for faster performance - //private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - /// - /// Gets a value indicating whether Umbraco is hosted. - /// - public static bool IsHosted => !ForceNotHosted && (HttpContext.Current != null || HostingEnvironment.IsHosted); - - public static char DirSepChar => Path.DirectorySeparatorChar; - - internal static void UnZip(string zipFilePath, string unPackDirectory, bool deleteZipFile) - { - // Unzip - var tempDir = unPackDirectory; - Directory.CreateDirectory(tempDir); - ZipFile.ExtractToDirectory(zipFilePath, unPackDirectory); - if (deleteZipFile) - File.Delete(zipFilePath); - } - - //helper to try and match the old path to a new virtual one - public static string FindFile(string virtualPath) - { - string retval = virtualPath; - - if (virtualPath.StartsWith("~")) - retval = virtualPath.Replace("~", SystemDirectories.Root); - - if (virtualPath.StartsWith("/") && virtualPath.StartsWith(SystemDirectories.Root) == false) - retval = SystemDirectories.Root + "/" + virtualPath.TrimStart('/'); - - return retval; - } - - public static string ResolveVirtualUrl(string path) - { - if (string.IsNullOrWhiteSpace(path)) return path; - return path.StartsWith("~/") ? ResolveUrl(path) : path; - } - - //Replaces tildes with the root dir - public static string ResolveUrl(string virtualPath) - { - if (virtualPath.StartsWith("~")) - return virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/"); - else if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) - return virtualPath; - else - return VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root); - } - - public static Attempt TryResolveUrl(string virtualPath) - { - try - { - if (virtualPath.StartsWith("~")) - return Attempt.Succeed(virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/")); - if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) - return Attempt.Succeed(virtualPath); - return Attempt.Succeed(VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root)); - } - catch (Exception ex) - { - return Attempt.Fail(virtualPath, ex); - } - } - - public static string MapPath(string path, bool useHttpContext) - { - if (path == null) throw new ArgumentNullException("path"); - useHttpContext = useHttpContext && IsHosted; - - // Check if the path is already mapped - if ((path.Length >= 2 && path[1] == Path.VolumeSeparatorChar) - || path.StartsWith(@"\\")) //UNC Paths start with "\\". If the site is running off a network drive mapped paths will look like "\\Whatever\Boo\Bar" - { - return path; - } - // Check that we even have an HttpContext! otherwise things will fail anyways - // http://umbraco.codeplex.com/workitem/30946 - - if (useHttpContext && HttpContext.Current != null) - { - //string retval; - if (String.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root))) - return HostingEnvironment.MapPath(path); - else - return HostingEnvironment.MapPath("~/" + path.TrimStart('/')); - } - - var root = GetRootDirectorySafe(); - var newPath = path.TrimStart('~', '/').Replace('/', IOHelper.DirSepChar); - var retval = root + IOHelper.DirSepChar.ToString(CultureInfo.InvariantCulture) + newPath; - - return retval; - } - - public static string MapPath(string path) - { - return MapPath(path, true); - } - - public static string MapPathIfVirtual(string path) - { - return path.StartsWith("~/") ? MapPath(path) : path; - } - - //use a tilde character instead of the complete path - internal static string ReturnPath(string settingsKey, string standardPath, bool useTilde) - { - var retval = ConfigurationManager.AppSettings[settingsKey]; - - if (string.IsNullOrEmpty(retval)) - retval = standardPath; - - return retval.TrimEnd('/'); - } - - internal static string ReturnPath(string settingsKey, string standardPath) - { - return ReturnPath(settingsKey, standardPath, false); - - } - - /// - /// Verifies that the current filepath matches a directory where the user is allowed to edit a file. - /// - /// The filepath to validate. - /// The valid directory. - /// A value indicating whether the filepath is valid. - internal static bool VerifyEditPath(string filePath, string validDir) - { - return VerifyEditPath(filePath, new[] { validDir }); - } - - /// - /// Validates that the current filepath matches a directory where the user is allowed to edit a file. - /// - /// The filepath to validate. - /// The valid directory. - /// True, if the filepath is valid, else an exception is thrown. - /// The filepath is invalid. - internal static bool ValidateEditPath(string filePath, string validDir) - { - if (VerifyEditPath(filePath, validDir) == false) - throw new FileSecurityException(String.Format("The filepath '{0}' is not within an allowed directory for this type of files", filePath.Replace(MapPath(SystemDirectories.Root), ""))); - return true; - } - - /// - /// Verifies that the current filepath matches one of several directories where the user is allowed to edit a file. - /// - /// The filepath to validate. - /// The valid directories. - /// A value indicating whether the filepath is valid. - internal static bool VerifyEditPath(string filePath, IEnumerable validDirs) - { - // this is called from ScriptRepository, PartialViewRepository, etc. - // filePath is the fullPath (rooted, filesystem path, can be trusted) - // validDirs are virtual paths (eg ~/Views) - // - // except that for templates, filePath actually is a virtual path - - //TODO - // what's below is dirty, there are too many ways to get the root dir, etc. - // not going to fix everything today - - var mappedRoot = MapPath(SystemDirectories.Root); - if (filePath.StartsWith(mappedRoot) == false) - filePath = MapPath(filePath); - - // yes we can (see above) - //// don't trust what we get, it may contain relative segments - //filePath = Path.GetFullPath(filePath); - - foreach (var dir in validDirs) - { - var validDir = dir; - if (validDir.StartsWith(mappedRoot) == false) - validDir = MapPath(validDir); - - if (PathStartsWith(filePath, validDir, Path.DirectorySeparatorChar)) - return true; - } - - return false; - } - - /// - /// Verifies that the current filepath has one of several authorized extensions. - /// - /// The filepath to validate. - /// The valid extensions. - /// A value indicating whether the filepath is valid. - internal static bool VerifyFileExtension(string filePath, List validFileExtensions) - { - var ext = Path.GetExtension(filePath); - return ext != null && validFileExtensions.Contains(ext.TrimStart('.')); - } - - /// - /// Validates that the current filepath has one of several authorized extensions. - /// - /// The filepath to validate. - /// The valid extensions. - /// True, if the filepath is valid, else an exception is thrown. - /// The filepath is invalid. - internal static bool ValidateFileExtension(string filePath, List validFileExtensions) - { - if (VerifyFileExtension(filePath, validFileExtensions) == false) - throw new FileSecurityException(String.Format("The extension for the current file '{0}' is not of an allowed type for this editor. This is typically controlled from either the installed MacroEngines or based on configuration in /config/umbracoSettings.config", filePath.Replace(MapPath(SystemDirectories.Root), ""))); - return true; - } - - public static bool PathStartsWith(string path, string root, char separator) - { - // either it is identical to root, - // or it is root + separator + anything - - if (path.StartsWith(root, StringComparison.OrdinalIgnoreCase) == false) return false; - if (path.Length == root.Length) return true; - if (path.Length < root.Length) return false; - return path[root.Length] == separator; - } - - /// - /// Returns the path to the root of the application, by getting the path to where the assembly where this - /// method is included is present, then traversing until it's past the /bin directory. Ie. this makes it work - /// even if the assembly is in a /bin/debug or /bin/release folder - /// - /// - internal static string GetRootDirectorySafe() - { - if (String.IsNullOrEmpty(_rootDir) == false) - { - return _rootDir; - } - - var codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - var baseDirectory = Path.GetDirectoryName(path); - if (String.IsNullOrEmpty(baseDirectory)) - throw new Exception("No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured."); - - _rootDir = baseDirectory.Contains("bin") - ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) - : baseDirectory; - - return _rootDir; - } - - internal static string GetRootDirectoryBinFolder() - { - string binFolder = String.Empty; - if (String.IsNullOrEmpty(_rootDir)) - { - binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory.FullName; - return binFolder; - } - - binFolder = Path.Combine(GetRootDirectorySafe(), "bin"); - -#if DEBUG - var debugFolder = Path.Combine(binFolder, "debug"); - if (Directory.Exists(debugFolder)) - return debugFolder; -#endif - var releaseFolder = Path.Combine(binFolder, "release"); - if (Directory.Exists(releaseFolder)) - return releaseFolder; - - if (Directory.Exists(binFolder)) - return binFolder; - - return _rootDir; - } - - /// - /// Allows you to overwrite RootDirectory, which would otherwise be resolved - /// automatically upon application start. - /// - /// The supplied path should be the absolute path to the root of the umbraco site. - /// - internal static void SetRootDirectory(string rootPath) - { - _rootDir = rootPath; - } - - /// - /// Check to see if filename passed has any special chars in it and strips them to create a safe filename. Used to overcome an issue when Umbraco is used in IE in an intranet environment. - /// - /// The filename passed to the file handler from the upload field. - /// A safe filename without any path specific chars. - internal static string SafeFileName(string filePath) - { - // use string extensions - return filePath.ToSafeFileName(); - } - - public static void EnsurePathExists(string path) - { - var absolutePath = MapPath(path); - if (Directory.Exists(absolutePath) == false) - Directory.CreateDirectory(absolutePath); - } - - public static void EnsureFileExists(string path, string contents) - { - var absolutePath = IOHelper.MapPath(path); - if (File.Exists(absolutePath)) return; - - using (var writer = File.CreateText(absolutePath)) - { - writer.Write(contents); - } - } - - /// - /// Checks if a given path is a full path including drive letter - /// - /// - /// - // From: http://stackoverflow.com/a/35046453/5018 - internal static bool IsFullPath(this string path) - { - return string.IsNullOrWhiteSpace(path) == false - && path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1 - && Path.IsPathRooted(path) - && Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false; - } - - /// - /// Get properly formatted relative path from an existing absolute or relative path - /// - /// - /// - internal static string GetRelativePath(this string path) - { - if (path.IsFullPath()) - { - var rootDirectory = GetRootDirectorySafe(); - var relativePath = path.ToLowerInvariant().Replace(rootDirectory.ToLowerInvariant(), string.Empty); - path = relativePath; - } - - return path.EnsurePathIsApplicationRootPrefixed(); - } - - /// - /// Ensures that a path has `~/` as prefix - /// - /// - /// - internal static string EnsurePathIsApplicationRootPrefixed(this string path) - { - if (path.StartsWith("~/")) - return path; - if (path.StartsWith("/") == false && path.StartsWith("\\") == false) - path = string.Format("/{0}", path); - if (path.StartsWith("~") == false) - path = string.Format("~{0}", path); - return path; - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.IO; +using System.Configuration; +using System.Linq; +using System.Web; +using System.Web.Hosting; +using System.IO.Compression; + +namespace Umbraco.Core.IO +{ + public static class IOHelper + { + /// + /// Gets or sets a value forcing Umbraco to consider it is non-hosted. + /// + /// This should always be false, unless unit testing. + public static bool ForceNotHosted { get; set; } + + private static string _rootDir = ""; + + // static compiled regex for faster performance + //private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + /// + /// Gets a value indicating whether Umbraco is hosted. + /// + public static bool IsHosted => !ForceNotHosted && (HttpContext.Current != null || HostingEnvironment.IsHosted); + + public static char DirSepChar => Path.DirectorySeparatorChar; + + internal static void UnZip(string zipFilePath, string unPackDirectory, bool deleteZipFile) + { + // Unzip + var tempDir = unPackDirectory; + Directory.CreateDirectory(tempDir); + ZipFile.ExtractToDirectory(zipFilePath, unPackDirectory); + if (deleteZipFile) + File.Delete(zipFilePath); + } + + //helper to try and match the old path to a new virtual one + public static string FindFile(string virtualPath) + { + string retval = virtualPath; + + if (virtualPath.StartsWith("~")) + retval = virtualPath.Replace("~", SystemDirectories.Root); + + if (virtualPath.StartsWith("/") && virtualPath.StartsWith(SystemDirectories.Root) == false) + retval = SystemDirectories.Root + "/" + virtualPath.TrimStart('/'); + + return retval; + } + + public static string ResolveVirtualUrl(string path) + { + if (string.IsNullOrWhiteSpace(path)) return path; + return path.StartsWith("~/") ? ResolveUrl(path) : path; + } + + //Replaces tildes with the root dir + public static string ResolveUrl(string virtualPath) + { + if (virtualPath.StartsWith("~")) + return virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/"); + else if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) + return virtualPath; + else + return VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root); + } + + public static Attempt TryResolveUrl(string virtualPath) + { + try + { + if (virtualPath.StartsWith("~")) + return Attempt.Succeed(virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/")); + if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) + return Attempt.Succeed(virtualPath); + return Attempt.Succeed(VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root)); + } + catch (Exception ex) + { + return Attempt.Fail(virtualPath, ex); + } + } + + public static string MapPath(string path, bool useHttpContext) + { + if (path == null) throw new ArgumentNullException("path"); + useHttpContext = useHttpContext && IsHosted; + + // Check if the path is already mapped + if ((path.Length >= 2 && path[1] == Path.VolumeSeparatorChar) + || path.StartsWith(@"\\")) //UNC Paths start with "\\". If the site is running off a network drive mapped paths will look like "\\Whatever\Boo\Bar" + { + return path; + } + // Check that we even have an HttpContext! otherwise things will fail anyways + // http://umbraco.codeplex.com/workitem/30946 + + if (useHttpContext && HttpContext.Current != null) + { + //string retval; + if (String.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root))) + return HostingEnvironment.MapPath(path); + else + return HostingEnvironment.MapPath("~/" + path.TrimStart('/')); + } + + var root = GetRootDirectorySafe(); + var newPath = path.TrimStart('~', '/').Replace('/', IOHelper.DirSepChar); + var retval = root + IOHelper.DirSepChar.ToString(CultureInfo.InvariantCulture) + newPath; + + return retval; + } + + public static string MapPath(string path) + { + return MapPath(path, true); + } + + public static string MapPathIfVirtual(string path) + { + return path.StartsWith("~/") ? MapPath(path) : path; + } + + //use a tilde character instead of the complete path + internal static string ReturnPath(string settingsKey, string standardPath, bool useTilde) + { + var retval = ConfigurationManager.AppSettings[settingsKey]; + + if (string.IsNullOrEmpty(retval)) + retval = standardPath; + + return retval.TrimEnd('/'); + } + + internal static string ReturnPath(string settingsKey, string standardPath) + { + return ReturnPath(settingsKey, standardPath, false); + + } + + /// + /// Verifies that the current filepath matches a directory where the user is allowed to edit a file. + /// + /// The filepath to validate. + /// The valid directory. + /// A value indicating whether the filepath is valid. + internal static bool VerifyEditPath(string filePath, string validDir) + { + return VerifyEditPath(filePath, new[] { validDir }); + } + + /// + /// Validates that the current filepath matches a directory where the user is allowed to edit a file. + /// + /// The filepath to validate. + /// The valid directory. + /// True, if the filepath is valid, else an exception is thrown. + /// The filepath is invalid. + internal static bool ValidateEditPath(string filePath, string validDir) + { + if (VerifyEditPath(filePath, validDir) == false) + throw new FileSecurityException(String.Format("The filepath '{0}' is not within an allowed directory for this type of files", filePath.Replace(MapPath(SystemDirectories.Root), ""))); + return true; + } + + /// + /// Verifies that the current filepath matches one of several directories where the user is allowed to edit a file. + /// + /// The filepath to validate. + /// The valid directories. + /// A value indicating whether the filepath is valid. + internal static bool VerifyEditPath(string filePath, IEnumerable validDirs) + { + // this is called from ScriptRepository, PartialViewRepository, etc. + // filePath is the fullPath (rooted, filesystem path, can be trusted) + // validDirs are virtual paths (eg ~/Views) + // + // except that for templates, filePath actually is a virtual path + + //TODO + // what's below is dirty, there are too many ways to get the root dir, etc. + // not going to fix everything today + + var mappedRoot = MapPath(SystemDirectories.Root); + if (filePath.StartsWith(mappedRoot) == false) + filePath = MapPath(filePath); + + // yes we can (see above) + //// don't trust what we get, it may contain relative segments + //filePath = Path.GetFullPath(filePath); + + foreach (var dir in validDirs) + { + var validDir = dir; + if (validDir.StartsWith(mappedRoot) == false) + validDir = MapPath(validDir); + + if (PathStartsWith(filePath, validDir, Path.DirectorySeparatorChar)) + return true; + } + + return false; + } + + /// + /// Verifies that the current filepath has one of several authorized extensions. + /// + /// The filepath to validate. + /// The valid extensions. + /// A value indicating whether the filepath is valid. + internal static bool VerifyFileExtension(string filePath, List validFileExtensions) + { + var ext = Path.GetExtension(filePath); + return ext != null && validFileExtensions.Contains(ext.TrimStart('.')); + } + + /// + /// Validates that the current filepath has one of several authorized extensions. + /// + /// The filepath to validate. + /// The valid extensions. + /// True, if the filepath is valid, else an exception is thrown. + /// The filepath is invalid. + internal static bool ValidateFileExtension(string filePath, List validFileExtensions) + { + if (VerifyFileExtension(filePath, validFileExtensions) == false) + throw new FileSecurityException(String.Format("The extension for the current file '{0}' is not of an allowed type for this editor. This is typically controlled from either the installed MacroEngines or based on configuration in /config/umbracoSettings.config", filePath.Replace(MapPath(SystemDirectories.Root), ""))); + return true; + } + + public static bool PathStartsWith(string path, string root, char separator) + { + // either it is identical to root, + // or it is root + separator + anything + + if (path.StartsWith(root, StringComparison.OrdinalIgnoreCase) == false) return false; + if (path.Length == root.Length) return true; + if (path.Length < root.Length) return false; + return path[root.Length] == separator; + } + + /// + /// Returns the path to the root of the application, by getting the path to where the assembly where this + /// method is included is present, then traversing until it's past the /bin directory. Ie. this makes it work + /// even if the assembly is in a /bin/debug or /bin/release folder + /// + /// + internal static string GetRootDirectorySafe() + { + if (String.IsNullOrEmpty(_rootDir) == false) + { + return _rootDir; + } + + var codeBase = Assembly.GetExecutingAssembly().CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + var baseDirectory = Path.GetDirectoryName(path); + if (String.IsNullOrEmpty(baseDirectory)) + throw new Exception("No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured."); + + _rootDir = baseDirectory.Contains("bin") + ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) + : baseDirectory; + + return _rootDir; + } + + internal static string GetRootDirectoryBinFolder() + { + string binFolder = String.Empty; + if (String.IsNullOrEmpty(_rootDir)) + { + binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory.FullName; + return binFolder; + } + + binFolder = Path.Combine(GetRootDirectorySafe(), "bin"); + +#if DEBUG + var debugFolder = Path.Combine(binFolder, "debug"); + if (Directory.Exists(debugFolder)) + return debugFolder; +#endif + var releaseFolder = Path.Combine(binFolder, "release"); + if (Directory.Exists(releaseFolder)) + return releaseFolder; + + if (Directory.Exists(binFolder)) + return binFolder; + + return _rootDir; + } + + /// + /// Allows you to overwrite RootDirectory, which would otherwise be resolved + /// automatically upon application start. + /// + /// The supplied path should be the absolute path to the root of the umbraco site. + /// + internal static void SetRootDirectory(string rootPath) + { + _rootDir = rootPath; + } + + /// + /// Check to see if filename passed has any special chars in it and strips them to create a safe filename. Used to overcome an issue when Umbraco is used in IE in an intranet environment. + /// + /// The filename passed to the file handler from the upload field. + /// A safe filename without any path specific chars. + internal static string SafeFileName(string filePath) + { + // use string extensions + return filePath.ToSafeFileName(); + } + + public static void EnsurePathExists(string path) + { + var absolutePath = MapPath(path); + if (Directory.Exists(absolutePath) == false) + Directory.CreateDirectory(absolutePath); + } + + public static void EnsureFileExists(string path, string contents) + { + var absolutePath = IOHelper.MapPath(path); + if (File.Exists(absolutePath)) return; + + using (var writer = File.CreateText(absolutePath)) + { + writer.Write(contents); + } + } + + /// + /// Checks if a given path is a full path including drive letter + /// + /// + /// + // From: http://stackoverflow.com/a/35046453/5018 + internal static bool IsFullPath(this string path) + { + return string.IsNullOrWhiteSpace(path) == false + && path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1 + && Path.IsPathRooted(path) + && Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false; + } + + /// + /// Get properly formatted relative path from an existing absolute or relative path + /// + /// + /// + internal static string GetRelativePath(this string path) + { + if (path.IsFullPath()) + { + var rootDirectory = GetRootDirectorySafe(); + var relativePath = path.ToLowerInvariant().Replace(rootDirectory.ToLowerInvariant(), string.Empty); + path = relativePath; + } + + return path.EnsurePathIsApplicationRootPrefixed(); + } + + /// + /// Ensures that a path has `~/` as prefix + /// + /// + /// + internal static string EnsurePathIsApplicationRootPrefixed(this string path) + { + if (path.StartsWith("~/")) + return path; + if (path.StartsWith("/") == false && path.StartsWith("\\") == false) + path = string.Format("/{0}", path); + if (path.StartsWith("~") == false) + path = string.Format("~{0}", path); + return path; + } + } +} diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index f7f6e4c243..9453678a31 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -1,325 +1,325 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using LightInject; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Composing; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO.MediaPathSchemes; -using Umbraco.Core.Logging; -using Umbraco.Core.Media; -using Umbraco.Core.Media.Exif; -using Umbraco.Core.Models; - -namespace Umbraco.Core.IO -{ - /// - /// A custom file system provider for media - /// - [FileSystemProvider("media")] - public class MediaFileSystem : FileSystemWrapper - { - public MediaFileSystem(IFileSystem wrapped) - : base(wrapped) - { - // due to how FileSystems is written at the moment, the ctor cannot be used to inject - // dependencies, so we have to rely on property injection for anything we might need +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using LightInject; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; +using Umbraco.Core.IO.MediaPathSchemes; +using Umbraco.Core.Logging; +using Umbraco.Core.Media; +using Umbraco.Core.Media.Exif; +using Umbraco.Core.Models; + +namespace Umbraco.Core.IO +{ + /// + /// A custom file system provider for media + /// + [FileSystemProvider("media")] + public class MediaFileSystem : FileSystemWrapper + { + public MediaFileSystem(IFileSystem wrapped) + : base(wrapped) + { + // due to how FileSystems is written at the moment, the ctor cannot be used to inject + // dependencies, so we have to rely on property injection for anything we might need Current.Container.InjectProperties(this); - MediaPathScheme.Initialize(this); - - UploadAutoFillProperties = new UploadAutoFillProperties(this, Logger, ContentConfig); + MediaPathScheme.Initialize(this); + + UploadAutoFillProperties = new UploadAutoFillProperties(this, Logger, ContentConfig); } - [Inject] - internal IMediaPathScheme MediaPathScheme { get; set; } - - [Inject] - internal IContentSection ContentConfig { get; set; } - - [Inject] - internal ILogger Logger { get; set; } - - internal UploadAutoFillProperties UploadAutoFillProperties { get; } - - /// - /// Deletes all files passed in. - /// - /// - /// - /// - internal bool DeleteFiles(IEnumerable files, Action onError = null) - { - //ensure duplicates are removed - files = files.Distinct(); - - var allsuccess = true; - var rootRelativePath = GetRelativePath("/"); - - Parallel.ForEach(files, file => - { - try - { - if (file.IsNullOrWhiteSpace()) return; - - var relativeFilePath = GetRelativePath(file); - if (FileExists(relativeFilePath) == false) return; - - var parentDirectory = Path.GetDirectoryName(relativeFilePath); - - // don't want to delete the media folder if not using directories. - if (ContentConfig.UploadAllowDirectories && parentDirectory != rootRelativePath) - { - //issue U4-771: if there is a parent directory the recursive parameter should be true - DeleteDirectory(parentDirectory, string.IsNullOrEmpty(parentDirectory) == false); - } - else - { - DeleteFile(file); - } - } - catch (Exception e) - { - onError?.Invoke(file, e); - allsuccess = false; - } - }); - - return allsuccess; - } - - public void DeleteMediaFiles(IEnumerable files) - { - files = files.Distinct(); - - Parallel.ForEach(files, file => - { - try - { - if (file.IsNullOrWhiteSpace()) return; - if (FileExists(file) == false) return; - DeleteFile(file); - + [Inject] + internal IMediaPathScheme MediaPathScheme { get; set; } + + [Inject] + internal IContentSection ContentConfig { get; set; } + + [Inject] + internal ILogger Logger { get; set; } + + internal UploadAutoFillProperties UploadAutoFillProperties { get; } + + /// + /// Deletes all files passed in. + /// + /// + /// + /// + internal bool DeleteFiles(IEnumerable files, Action onError = null) + { + //ensure duplicates are removed + files = files.Distinct(); + + var allsuccess = true; + var rootRelativePath = GetRelativePath("/"); + + Parallel.ForEach(files, file => + { + try + { + if (file.IsNullOrWhiteSpace()) return; + + var relativeFilePath = GetRelativePath(file); + if (FileExists(relativeFilePath) == false) return; + + var parentDirectory = Path.GetDirectoryName(relativeFilePath); + + // don't want to delete the media folder if not using directories. + if (ContentConfig.UploadAllowDirectories && parentDirectory != rootRelativePath) + { + //issue U4-771: if there is a parent directory the recursive parameter should be true + DeleteDirectory(parentDirectory, string.IsNullOrEmpty(parentDirectory) == false); + } + else + { + DeleteFile(file); + } + } + catch (Exception e) + { + onError?.Invoke(file, e); + allsuccess = false; + } + }); + + return allsuccess; + } + + public void DeleteMediaFiles(IEnumerable files) + { + files = files.Distinct(); + + Parallel.ForEach(files, file => + { + try + { + if (file.IsNullOrWhiteSpace()) return; + if (FileExists(file) == false) return; + DeleteFile(file); + var directory = MediaPathScheme.GetDeleteDirectory(file); if (!directory.IsNullOrWhiteSpace()) - DeleteDirectory(directory, true); - } - catch (Exception e) - { - Logger.Error("Failed to delete attached file \"" + file + "\".", e); - } - }); - } - - #region Media Path - - /// - /// Gets the file path of a media file. - /// - /// The file name. - /// The unique identifier of the content/media owning the file. - /// The unique identifier of the property type owning the file. - /// The filesystem-relative path to the media file. - /// With the old media path scheme, this CREATES a new media path each time it is invoked. - public string GetMediaPath(string filename, Guid cuid, Guid puid) - { - filename = Path.GetFileName(filename); - if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); - filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); - - return MediaPathScheme.GetFilePath(cuid, puid, filename); - } - - /// - /// Gets the file path of a media file. - /// - /// The file name. - /// A previous file path. - /// The unique identifier of the content/media owning the file. - /// The unique identifier of the property type owning the file. - /// The filesystem-relative path to the media file. - /// In the old, legacy, number-based scheme, we try to re-use the media folder - /// specified by . Else, we CREATE a new one. Each time we are invoked. - public string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid) - { - filename = Path.GetFileName(filename); - if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); - filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); - - return MediaPathScheme.GetFilePath(cuid, puid, filename, prevpath); - } - - #endregion - - #region Associated Media Files - - /// - /// Stores a media file associated to a property of a content item. - /// - /// The content item owning the media file. - /// The property type owning the media file. - /// The media file name. - /// A stream containing the media bytes. - /// An optional filesystem-relative filepath to the previous media file. - /// The filesystem-relative filepath to the media file. - /// - /// The file is considered "owned" by the content/propertyType. - /// If an is provided then that file (and associated thumbnails if any) is deleted - /// before the new file is saved, and depending on the media path scheme, the folder may be reused for the new file. - /// - public string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (propertyType == null) throw new ArgumentNullException(nameof(propertyType)); - if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentNullOrEmptyException(nameof(filename)); - if (filestream == null) throw new ArgumentNullException(nameof(filestream)); - - // clear the old file, if any - if (string.IsNullOrWhiteSpace(oldpath) == false) - DeleteFile(oldpath); - - // get the filepath, store the data - // use oldpath as "prevpath" to try and reuse the folder, in original number-based scheme - var filepath = GetMediaPath(filename, oldpath, content.Key, propertyType.Key); - AddFile(filepath, filestream); - return filepath; - } - - /// - /// Copies a media file as a new media file, associated to a property of a content item. - /// - /// The content item owning the copy of the media file. - /// The property type owning the copy of the media file. - /// The filesystem-relative path to the source media file. - /// The filesystem-relative path to the copy of the media file. - public string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (propertyType == null) throw new ArgumentNullException(nameof(propertyType)); - if (string.IsNullOrWhiteSpace(sourcepath)) throw new ArgumentNullOrEmptyException(nameof(sourcepath)); - - // ensure we have a file to copy - if (FileExists(sourcepath) == false) return null; - - // get the filepath - var filename = Path.GetFileName(sourcepath); - var filepath = GetMediaPath(filename, content.Key, propertyType.Key); - this.CopyFile(sourcepath, filepath); - return filepath; - } - - // gets or creates a property for a content item. - private static Property GetProperty(IContentBase content, string propertyTypeAlias) - { - var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); - if (property != null) return property; - - var propertyType = content.GetContentType().CompositionPropertyTypes - .FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); - if (propertyType == null) - throw new Exception("No property type exists with alias " + propertyTypeAlias + "."); - - property = new Property(propertyType); - content.Properties.Add(property); - return property; - } - - // fixme - what's below belongs to the upload property editor, not the media filesystem! - - public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) - { - var property = GetProperty(content, propertyTypeAlias); - var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null; - var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath); - property.SetValue(GetUrl(filepath), culture, segment); - SetUploadFile(content, property, filepath, filestream, culture, segment); - } - - public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath, string culture = null, string segment = null) - { - var property = GetProperty(content, propertyTypeAlias); - // fixme delete? - var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null; - if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath) - DeleteFile(oldpath); - property.SetValue(GetUrl(filepath), culture, segment); - using (var filestream = OpenFile(filepath)) - { - SetUploadFile(content, property, filepath, filestream, culture, segment); - } - } - - // sets a file for the FileUpload property editor - // ie generates thumbnails and populates autofill properties - private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream, string culture = null, string segment = null) - { - // will use filepath for extension, and filestream for length - UploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, culture, segment); - } - - #endregion - - #region Image - - /// - /// Gets a value indicating whether the file extension corresponds to an image. - /// - /// The file extension. - /// A value indicating whether the file extension corresponds to an image. - public bool IsImageFile(string extension) - { - if (extension == null) return false; - extension = extension.TrimStart('.'); - return ContentConfig.ImageFileTypes.InvariantContains(extension); - } - - /// - /// Gets the dimensions of an image. - /// - /// A stream containing the image bytes. - /// The dimension of the image. - /// First try with EXIF as it is faster and does not load the entire image - /// in memory. Fallback to GDI which means loading the image in memory and thus - /// use potentially large amounts of memory. - public Size GetDimensions(Stream stream) - { - //Try to load with exif - try - { - var jpgInfo = ImageFile.FromStream(stream); - - if (jpgInfo.Format != ImageFileFormat.Unknown - && jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension) - && jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension)) - { - var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value); - var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value); - if (height > 0 && width > 0) - { - return new Size(width, height); - } - } - } - catch (Exception) - { - //We will just swallow, just means we can't read exif data, we don't want to log an error either - } - - //we have no choice but to try to read in via GDI - using (var image = Image.FromStream(stream)) - { - - var fileWidth = image.Width; - var fileHeight = image.Height; - return new Size(fileWidth, fileHeight); - } - } - - #endregion - } -} + DeleteDirectory(directory, true); + } + catch (Exception e) + { + Logger.Error("Failed to delete attached file \"" + file + "\".", e); + } + }); + } + + #region Media Path + + /// + /// Gets the file path of a media file. + /// + /// The file name. + /// The unique identifier of the content/media owning the file. + /// The unique identifier of the property type owning the file. + /// The filesystem-relative path to the media file. + /// With the old media path scheme, this CREATES a new media path each time it is invoked. + public string GetMediaPath(string filename, Guid cuid, Guid puid) + { + filename = Path.GetFileName(filename); + if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); + filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); + + return MediaPathScheme.GetFilePath(cuid, puid, filename); + } + + /// + /// Gets the file path of a media file. + /// + /// The file name. + /// A previous file path. + /// The unique identifier of the content/media owning the file. + /// The unique identifier of the property type owning the file. + /// The filesystem-relative path to the media file. + /// In the old, legacy, number-based scheme, we try to re-use the media folder + /// specified by . Else, we CREATE a new one. Each time we are invoked. + public string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid) + { + filename = Path.GetFileName(filename); + if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); + filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); + + return MediaPathScheme.GetFilePath(cuid, puid, filename, prevpath); + } + + #endregion + + #region Associated Media Files + + /// + /// Stores a media file associated to a property of a content item. + /// + /// The content item owning the media file. + /// The property type owning the media file. + /// The media file name. + /// A stream containing the media bytes. + /// An optional filesystem-relative filepath to the previous media file. + /// The filesystem-relative filepath to the media file. + /// + /// The file is considered "owned" by the content/propertyType. + /// If an is provided then that file (and associated thumbnails if any) is deleted + /// before the new file is saved, and depending on the media path scheme, the folder may be reused for the new file. + /// + public string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath) + { + if (content == null) throw new ArgumentNullException(nameof(content)); + if (propertyType == null) throw new ArgumentNullException(nameof(propertyType)); + if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentNullOrEmptyException(nameof(filename)); + if (filestream == null) throw new ArgumentNullException(nameof(filestream)); + + // clear the old file, if any + if (string.IsNullOrWhiteSpace(oldpath) == false) + DeleteFile(oldpath); + + // get the filepath, store the data + // use oldpath as "prevpath" to try and reuse the folder, in original number-based scheme + var filepath = GetMediaPath(filename, oldpath, content.Key, propertyType.Key); + AddFile(filepath, filestream); + return filepath; + } + + /// + /// Copies a media file as a new media file, associated to a property of a content item. + /// + /// The content item owning the copy of the media file. + /// The property type owning the copy of the media file. + /// The filesystem-relative path to the source media file. + /// The filesystem-relative path to the copy of the media file. + public string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath) + { + if (content == null) throw new ArgumentNullException(nameof(content)); + if (propertyType == null) throw new ArgumentNullException(nameof(propertyType)); + if (string.IsNullOrWhiteSpace(sourcepath)) throw new ArgumentNullOrEmptyException(nameof(sourcepath)); + + // ensure we have a file to copy + if (FileExists(sourcepath) == false) return null; + + // get the filepath + var filename = Path.GetFileName(sourcepath); + var filepath = GetMediaPath(filename, content.Key, propertyType.Key); + this.CopyFile(sourcepath, filepath); + return filepath; + } + + // gets or creates a property for a content item. + private static Property GetProperty(IContentBase content, string propertyTypeAlias) + { + var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (property != null) return property; + + var propertyType = content.GetContentType().CompositionPropertyTypes + .FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (propertyType == null) + throw new Exception("No property type exists with alias " + propertyTypeAlias + "."); + + property = new Property(propertyType); + content.Properties.Add(property); + return property; + } + + // fixme - what's below belongs to the upload property editor, not the media filesystem! + + public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) + { + var property = GetProperty(content, propertyTypeAlias); + var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null; + var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath); + property.SetValue(GetUrl(filepath), culture, segment); + SetUploadFile(content, property, filepath, filestream, culture, segment); + } + + public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath, string culture = null, string segment = null) + { + var property = GetProperty(content, propertyTypeAlias); + // fixme delete? + var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null; + if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath) + DeleteFile(oldpath); + property.SetValue(GetUrl(filepath), culture, segment); + using (var filestream = OpenFile(filepath)) + { + SetUploadFile(content, property, filepath, filestream, culture, segment); + } + } + + // sets a file for the FileUpload property editor + // ie generates thumbnails and populates autofill properties + private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream, string culture = null, string segment = null) + { + // will use filepath for extension, and filestream for length + UploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, culture, segment); + } + + #endregion + + #region Image + + /// + /// Gets a value indicating whether the file extension corresponds to an image. + /// + /// The file extension. + /// A value indicating whether the file extension corresponds to an image. + public bool IsImageFile(string extension) + { + if (extension == null) return false; + extension = extension.TrimStart('.'); + return ContentConfig.ImageFileTypes.InvariantContains(extension); + } + + /// + /// Gets the dimensions of an image. + /// + /// A stream containing the image bytes. + /// The dimension of the image. + /// First try with EXIF as it is faster and does not load the entire image + /// in memory. Fallback to GDI which means loading the image in memory and thus + /// use potentially large amounts of memory. + public Size GetDimensions(Stream stream) + { + //Try to load with exif + try + { + var jpgInfo = ImageFile.FromStream(stream); + + if (jpgInfo.Format != ImageFileFormat.Unknown + && jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension) + && jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension)) + { + var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value); + var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value); + if (height > 0 && width > 0) + { + return new Size(width, height); + } + } + } + catch (Exception) + { + //We will just swallow, just means we can't read exif data, we don't want to log an error either + } + + //we have no choice but to try to read in via GDI + using (var image = Image.FromStream(stream)) + { + + var fileWidth = image.Width; + var fileHeight = image.Height; + return new Size(fileWidth, fileHeight); + } + } + + #endregion + } +} diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index da0db83de3..bc9968153f 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -1,445 +1,445 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Umbraco.Core.Composing; -using Umbraco.Core.Exceptions; -using System.Threading; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.IO -{ - public class PhysicalFileSystem : IFileSystem - { - // the rooted, filesystem path, using directory separator chars, NOT ending with a separator - // eg "c:" or "c:\path\to\site" or "\\server\path" - private readonly string _rootPath; - - // _rootPath, but with separators replaced by forward-slashes - // eg "c:" or "c:/path/to/site" or "//server/path" - // (is used in GetRelativePath) - private readonly string _rootPathFwd; - - // the relative url, using url separator chars, NOT ending with a separator - // eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path - private readonly string _rootUrl; - - // virtualRoot should be "~/path/to/root" eg "~/Views" - // the "~/" is mandatory. - public PhysicalFileSystem(string virtualRoot) - { - if (virtualRoot == null) throw new ArgumentNullException("virtualRoot"); - if (virtualRoot.StartsWith("~/") == false) - throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'"); - - _rootPath = EnsureDirectorySeparatorChar(IOHelper.MapPath(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar); - _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); - _rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd('/'); - } - - public PhysicalFileSystem(string rootPath, string rootUrl) - { - if (string.IsNullOrEmpty(rootPath)) throw new ArgumentNullOrEmptyException(nameof(rootPath)); - if (string.IsNullOrEmpty(rootUrl)) throw new ArgumentNullOrEmptyException(nameof(rootUrl)); - if (rootPath.StartsWith("~/")) throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'"); - - // rootPath should be... rooted, as in, it's a root path! - if (Path.IsPathRooted(rootPath) == false) - { - // but the test suite App.config cannot really "root" anything so we have to do it here - var localRoot = IOHelper.GetRootDirectorySafe(); - rootPath = Path.Combine(localRoot, rootPath); - } - - _rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar); - _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); - _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd('/'); - } - - /// - /// Gets directories in a directory. - /// - /// The filesystem-relative path to the directory. - /// The filesystem-relative path to the directories in the directory. - /// Filesystem-relative paths use forward-slashes as directory separators. - public IEnumerable GetDirectories(string path) - { - var fullPath = GetFullPath(path); - - try - { - if (Directory.Exists(fullPath)) - return Directory.EnumerateDirectories(fullPath).Select(GetRelativePath); - } - catch (UnauthorizedAccessException ex) - { - Current.Logger.Error("Not authorized to get directories", ex); - } - catch (DirectoryNotFoundException ex) - { - Current.Logger.Error("Directory not found", ex); - } - - return Enumerable.Empty(); - } - - /// - /// Deletes a directory. - /// - /// The filesystem-relative path of the directory. - public void DeleteDirectory(string path) - { - DeleteDirectory(path, false); - } - - /// - /// Deletes a directory. - /// - /// The filesystem-relative path of the directory. - /// A value indicating whether to recursively delete sub-directories. - public void DeleteDirectory(string path, bool recursive) - { - var fullPath = GetFullPath(path); - if (Directory.Exists(fullPath) == false) - return; - - try - { - WithRetry(() => Directory.Delete(fullPath, recursive)); - } - catch (DirectoryNotFoundException ex) - { - Current.Logger.Error("Directory not found", ex); - } - } - - /// - /// Gets a value indicating whether a directory exists. - /// - /// The filesystem-relative path of the directory. - /// A value indicating whether a directory exists. - public bool DirectoryExists(string path) - { - var fullPath = GetFullPath(path); - return Directory.Exists(fullPath); - } - - /// - /// Saves a file. - /// - /// The filesystem-relative path of the file. - /// A stream containing the file data. - /// Overrides the existing file, if any. - public void AddFile(string path, Stream stream) - { - AddFile(path, stream, true); - } - - /// - /// Saves a file. - /// - /// The filesystem-relative path of the file. - /// A stream containing the file data. - /// A value indicating whether to override the existing file, if any. - /// If a file exists and is false, an exception is thrown. - public void AddFile(string path, Stream stream, bool overrideExisting) - { - var fullPath = GetFullPath(path); - var exists = File.Exists(fullPath); - if (exists && overrideExisting == false) - throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - - var directory = Path.GetDirectoryName(fullPath); - if (directory == null) throw new InvalidOperationException("Could not get directory."); - Directory.CreateDirectory(directory); // ensure it exists - - if (stream.CanSeek) // fixme - what else? - stream.Seek(0, 0); - - using (var destination = (Stream) File.Create(fullPath)) - stream.CopyTo(destination); - } - - /// - /// Gets files in a directory. - /// - /// The filesystem-relative path of the directory. - /// The filesystem-relative path to the files in the directory. - /// Filesystem-relative paths use forward-slashes as directory separators. - public IEnumerable GetFiles(string path) - { - return GetFiles(path, "*.*"); - } - - /// - /// Gets files in a directory. - /// - /// The filesystem-relative path of the directory. - /// A filter. - /// The filesystem-relative path to the matching files in the directory. - /// Filesystem-relative paths use forward-slashes as directory separators. - public IEnumerable GetFiles(string path, string filter) - { - var fullPath = GetFullPath(path); - - try - { - if (Directory.Exists(fullPath)) - return Directory.EnumerateFiles(fullPath, filter).Select(GetRelativePath); - } - catch (UnauthorizedAccessException ex) - { - Current.Logger.Error("Not authorized to get directories", ex); - } - catch (DirectoryNotFoundException ex) - { - Current.Logger.Error("Directory not found", ex); - } - - return Enumerable.Empty(); - } - - /// - /// Opens a file. - /// - /// The filesystem-relative path to the file. - /// - public Stream OpenFile(string path) - { - var fullPath = GetFullPath(path); - return File.OpenRead(fullPath); - } - - /// - /// Deletes a file. - /// - /// The filesystem-relative path to the file. - public void DeleteFile(string path) - { - var fullPath = GetFullPath(path); - if (File.Exists(fullPath) == false) - return; - - try - { - WithRetry(() => File.Delete(fullPath)); - } - catch (FileNotFoundException ex) - { - Current.Logger.Info(() => $"DeleteFile failed with FileNotFoundException: {ex.InnerException}"); - } - } - - /// - /// Gets a value indicating whether a file exists. - /// - /// The filesystem-relative path to the file. - /// A value indicating whether the file exists. - public bool FileExists(string path) - { - var fullpath = GetFullPath(path); - return File.Exists(fullpath); - } - - /// - /// Gets the filesystem-relative path of a full path or of an url. - /// - /// The full path or url. - /// The path, relative to this filesystem's root. - /// - /// The relative path is relative to this filesystem's root, not starting with any - /// directory separator. All separators are forward-slashes. - /// - public string GetRelativePath(string fullPathOrUrl) - { - // test url - var path = fullPathOrUrl.Replace('\\', '/'); // ensure url separator char - - // if it starts with the root url, strip it and trim the starting slash to make it relative - // eg "/Media/1234/img.jpg" => "1234/img.jpg" - if (IOHelper.PathStartsWith(path, _rootUrl, '/')) - return path.Substring(_rootUrl.Length).TrimStart('/'); - - // if it starts with the root path, strip it and trim the starting slash to make it relative - // eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg" - if (IOHelper.PathStartsWith(path, _rootPathFwd, '/')) - return path.Substring(_rootPathFwd.Length).TrimStart('/'); - - // unchanged - what else? - return path; - } - - /// - /// Gets the full path. - /// - /// The full or filesystem-relative path. - /// The full path. - /// - /// On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this - /// filesystem's root) path. All separators are Path.DirectorySeparatorChar. - /// - public string GetFullPath(string path) - { - // normalize - var opath = path; - path = EnsureDirectorySeparatorChar(path); - - // fixme - this part should go! - // not sure what we are doing here - so if input starts with a (back) slash, - // we assume it's not a FS relative path and we try to convert it... but it - // really makes little sense? - if (path.StartsWith(Path.DirectorySeparatorChar.ToString())) - path = GetRelativePath(path); - - // if not already rooted, combine with the root path - if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar) == false) - path = Path.Combine(_rootPath, path); - - // sanitize - GetFullPath will take care of any relative - // segments in path, eg '../../foo.tmp' - it may throw a SecurityException - // if the combined path reaches illegal parts of the filesystem - path = Path.GetFullPath(path); - - // at that point, path is within legal parts of the filesystem, ie we have - // permissions to reach that path, but it may nevertheless be outside of - // our root path, due to relative segments, so better check - if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) - return path; - - // nothing prevents us to reach the file, security-wise, yet it is outside - // this filesystem's root - throw - throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root."); - } - - /// - /// Gets the url. - /// - /// The filesystem-relative path. - /// The url. - /// All separators are forward-slashes. - public string GetUrl(string path) - { - path = EnsureUrlSeparatorChar(path).Trim('/'); - return _rootUrl + "/" + path; - } - - /// - /// Gets the last-modified date of a directory or file. - /// - /// The filesystem-relative path to the directory or the file. - /// The last modified date of the directory or the file. - public DateTimeOffset GetLastModified(string path) - { - var fullpath = GetFullPath(path); - return DirectoryExists(fullpath) - ? new DirectoryInfo(fullpath).LastWriteTimeUtc - : new FileInfo(fullpath).LastWriteTimeUtc; - } - - /// - /// Gets the created date of a directory or file. - /// - /// The filesystem-relative path to the directory or the file. - /// The created date of the directory or the file. - public DateTimeOffset GetCreated(string path) - { - var fullpath = GetFullPath(path); - return DirectoryExists(fullpath) - ? Directory.GetCreationTimeUtc(fullpath) - : File.GetCreationTimeUtc(fullpath); - } - - /// - /// Gets the size of a file. - /// - /// The filesystem-relative path to the file. - /// The file of the size, in bytes. - /// If the file does not exist, returns -1. - public long GetSize(string path) - { - var fullPath = GetFullPath(path); - var file = new FileInfo(fullPath); - return file.Exists ? file.Length : -1; - } - - public bool CanAddPhysical => true; - - public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) - { - var fullPath = GetFullPath(path); - - if (File.Exists(fullPath)) - { - if (overrideIfExists == false) - throw new InvalidOperationException($"A file at path '{path}' already exists"); - WithRetry(() => File.Delete(fullPath)); - } - - var directory = Path.GetDirectoryName(fullPath); - if (directory == null) throw new InvalidOperationException("Could not get directory."); - Directory.CreateDirectory(directory); // ensure it exists - - if (copy) - WithRetry(() => File.Copy(physicalPath, fullPath)); - else - WithRetry(() => File.Move(physicalPath, fullPath)); - } - - #region Helper Methods - - protected virtual void EnsureDirectory(string path) - { - path = GetFullPath(path); - Directory.CreateDirectory(path); - } - - protected string EnsureTrailingSeparator(string path) - { - return path.EnsureEndsWith(Path.DirectorySeparatorChar); - } - - protected string EnsureDirectorySeparatorChar(string path) - { - path = path.Replace('/', Path.DirectorySeparatorChar); - path = path.Replace('\\', Path.DirectorySeparatorChar); - return path; - } - - protected string EnsureUrlSeparatorChar(string path) - { - path = path.Replace('\\', '/'); - return path; - } - - protected void WithRetry(Action action) - { - // 10 times 100ms is 1s - const int count = 10; - const int pausems = 100; - - for (var i = 0;; i++) - { - try - { - action(); - break; // done - } - catch (IOException e) - { - // if it's not *exactly* IOException then it could be - // some inherited exception such as FileNotFoundException, - // and then we don't want to retry - if (e.GetType() != typeof(IOException)) throw; - - // if we have tried enough, throw, else swallow - // the exception and retry after a pause - if (i == count) throw; - } - - Thread.Sleep(pausems); - } - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; +using System.Threading; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.IO +{ + public class PhysicalFileSystem : IFileSystem + { + // the rooted, filesystem path, using directory separator chars, NOT ending with a separator + // eg "c:" or "c:\path\to\site" or "\\server\path" + private readonly string _rootPath; + + // _rootPath, but with separators replaced by forward-slashes + // eg "c:" or "c:/path/to/site" or "//server/path" + // (is used in GetRelativePath) + private readonly string _rootPathFwd; + + // the relative url, using url separator chars, NOT ending with a separator + // eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path + private readonly string _rootUrl; + + // virtualRoot should be "~/path/to/root" eg "~/Views" + // the "~/" is mandatory. + public PhysicalFileSystem(string virtualRoot) + { + if (virtualRoot == null) throw new ArgumentNullException("virtualRoot"); + if (virtualRoot.StartsWith("~/") == false) + throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'"); + + _rootPath = EnsureDirectorySeparatorChar(IOHelper.MapPath(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar); + _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); + _rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd('/'); + } + + public PhysicalFileSystem(string rootPath, string rootUrl) + { + if (string.IsNullOrEmpty(rootPath)) throw new ArgumentNullOrEmptyException(nameof(rootPath)); + if (string.IsNullOrEmpty(rootUrl)) throw new ArgumentNullOrEmptyException(nameof(rootUrl)); + if (rootPath.StartsWith("~/")) throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'"); + + // rootPath should be... rooted, as in, it's a root path! + if (Path.IsPathRooted(rootPath) == false) + { + // but the test suite App.config cannot really "root" anything so we have to do it here + var localRoot = IOHelper.GetRootDirectorySafe(); + rootPath = Path.Combine(localRoot, rootPath); + } + + _rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar); + _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); + _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd('/'); + } + + /// + /// Gets directories in a directory. + /// + /// The filesystem-relative path to the directory. + /// The filesystem-relative path to the directories in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. + public IEnumerable GetDirectories(string path) + { + var fullPath = GetFullPath(path); + + try + { + if (Directory.Exists(fullPath)) + return Directory.EnumerateDirectories(fullPath).Select(GetRelativePath); + } + catch (UnauthorizedAccessException ex) + { + Current.Logger.Error("Not authorized to get directories", ex); + } + catch (DirectoryNotFoundException ex) + { + Current.Logger.Error("Directory not found", ex); + } + + return Enumerable.Empty(); + } + + /// + /// Deletes a directory. + /// + /// The filesystem-relative path of the directory. + public void DeleteDirectory(string path) + { + DeleteDirectory(path, false); + } + + /// + /// Deletes a directory. + /// + /// The filesystem-relative path of the directory. + /// A value indicating whether to recursively delete sub-directories. + public void DeleteDirectory(string path, bool recursive) + { + var fullPath = GetFullPath(path); + if (Directory.Exists(fullPath) == false) + return; + + try + { + WithRetry(() => Directory.Delete(fullPath, recursive)); + } + catch (DirectoryNotFoundException ex) + { + Current.Logger.Error("Directory not found", ex); + } + } + + /// + /// Gets a value indicating whether a directory exists. + /// + /// The filesystem-relative path of the directory. + /// A value indicating whether a directory exists. + public bool DirectoryExists(string path) + { + var fullPath = GetFullPath(path); + return Directory.Exists(fullPath); + } + + /// + /// Saves a file. + /// + /// The filesystem-relative path of the file. + /// A stream containing the file data. + /// Overrides the existing file, if any. + public void AddFile(string path, Stream stream) + { + AddFile(path, stream, true); + } + + /// + /// Saves a file. + /// + /// The filesystem-relative path of the file. + /// A stream containing the file data. + /// A value indicating whether to override the existing file, if any. + /// If a file exists and is false, an exception is thrown. + public void AddFile(string path, Stream stream, bool overrideExisting) + { + var fullPath = GetFullPath(path); + var exists = File.Exists(fullPath); + if (exists && overrideExisting == false) + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); + + var directory = Path.GetDirectoryName(fullPath); + if (directory == null) throw new InvalidOperationException("Could not get directory."); + Directory.CreateDirectory(directory); // ensure it exists + + if (stream.CanSeek) // fixme - what else? + stream.Seek(0, 0); + + using (var destination = (Stream) File.Create(fullPath)) + stream.CopyTo(destination); + } + + /// + /// Gets files in a directory. + /// + /// The filesystem-relative path of the directory. + /// The filesystem-relative path to the files in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. + public IEnumerable GetFiles(string path) + { + return GetFiles(path, "*.*"); + } + + /// + /// Gets files in a directory. + /// + /// The filesystem-relative path of the directory. + /// A filter. + /// The filesystem-relative path to the matching files in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. + public IEnumerable GetFiles(string path, string filter) + { + var fullPath = GetFullPath(path); + + try + { + if (Directory.Exists(fullPath)) + return Directory.EnumerateFiles(fullPath, filter).Select(GetRelativePath); + } + catch (UnauthorizedAccessException ex) + { + Current.Logger.Error("Not authorized to get directories", ex); + } + catch (DirectoryNotFoundException ex) + { + Current.Logger.Error("Directory not found", ex); + } + + return Enumerable.Empty(); + } + + /// + /// Opens a file. + /// + /// The filesystem-relative path to the file. + /// + public Stream OpenFile(string path) + { + var fullPath = GetFullPath(path); + return File.OpenRead(fullPath); + } + + /// + /// Deletes a file. + /// + /// The filesystem-relative path to the file. + public void DeleteFile(string path) + { + var fullPath = GetFullPath(path); + if (File.Exists(fullPath) == false) + return; + + try + { + WithRetry(() => File.Delete(fullPath)); + } + catch (FileNotFoundException ex) + { + Current.Logger.Info(() => $"DeleteFile failed with FileNotFoundException: {ex.InnerException}"); + } + } + + /// + /// Gets a value indicating whether a file exists. + /// + /// The filesystem-relative path to the file. + /// A value indicating whether the file exists. + public bool FileExists(string path) + { + var fullpath = GetFullPath(path); + return File.Exists(fullpath); + } + + /// + /// Gets the filesystem-relative path of a full path or of an url. + /// + /// The full path or url. + /// The path, relative to this filesystem's root. + /// + /// The relative path is relative to this filesystem's root, not starting with any + /// directory separator. All separators are forward-slashes. + /// + public string GetRelativePath(string fullPathOrUrl) + { + // test url + var path = fullPathOrUrl.Replace('\\', '/'); // ensure url separator char + + // if it starts with the root url, strip it and trim the starting slash to make it relative + // eg "/Media/1234/img.jpg" => "1234/img.jpg" + if (IOHelper.PathStartsWith(path, _rootUrl, '/')) + return path.Substring(_rootUrl.Length).TrimStart('/'); + + // if it starts with the root path, strip it and trim the starting slash to make it relative + // eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg" + if (IOHelper.PathStartsWith(path, _rootPathFwd, '/')) + return path.Substring(_rootPathFwd.Length).TrimStart('/'); + + // unchanged - what else? + return path; + } + + /// + /// Gets the full path. + /// + /// The full or filesystem-relative path. + /// The full path. + /// + /// On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this + /// filesystem's root) path. All separators are Path.DirectorySeparatorChar. + /// + public string GetFullPath(string path) + { + // normalize + var opath = path; + path = EnsureDirectorySeparatorChar(path); + + // fixme - this part should go! + // not sure what we are doing here - so if input starts with a (back) slash, + // we assume it's not a FS relative path and we try to convert it... but it + // really makes little sense? + if (path.StartsWith(Path.DirectorySeparatorChar.ToString())) + path = GetRelativePath(path); + + // if not already rooted, combine with the root path + if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar) == false) + path = Path.Combine(_rootPath, path); + + // sanitize - GetFullPath will take care of any relative + // segments in path, eg '../../foo.tmp' - it may throw a SecurityException + // if the combined path reaches illegal parts of the filesystem + path = Path.GetFullPath(path); + + // at that point, path is within legal parts of the filesystem, ie we have + // permissions to reach that path, but it may nevertheless be outside of + // our root path, due to relative segments, so better check + if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) + return path; + + // nothing prevents us to reach the file, security-wise, yet it is outside + // this filesystem's root - throw + throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root."); + } + + /// + /// Gets the url. + /// + /// The filesystem-relative path. + /// The url. + /// All separators are forward-slashes. + public string GetUrl(string path) + { + path = EnsureUrlSeparatorChar(path).Trim('/'); + return _rootUrl + "/" + path; + } + + /// + /// Gets the last-modified date of a directory or file. + /// + /// The filesystem-relative path to the directory or the file. + /// The last modified date of the directory or the file. + public DateTimeOffset GetLastModified(string path) + { + var fullpath = GetFullPath(path); + return DirectoryExists(fullpath) + ? new DirectoryInfo(fullpath).LastWriteTimeUtc + : new FileInfo(fullpath).LastWriteTimeUtc; + } + + /// + /// Gets the created date of a directory or file. + /// + /// The filesystem-relative path to the directory or the file. + /// The created date of the directory or the file. + public DateTimeOffset GetCreated(string path) + { + var fullpath = GetFullPath(path); + return DirectoryExists(fullpath) + ? Directory.GetCreationTimeUtc(fullpath) + : File.GetCreationTimeUtc(fullpath); + } + + /// + /// Gets the size of a file. + /// + /// The filesystem-relative path to the file. + /// The file of the size, in bytes. + /// If the file does not exist, returns -1. + public long GetSize(string path) + { + var fullPath = GetFullPath(path); + var file = new FileInfo(fullPath); + return file.Exists ? file.Length : -1; + } + + public bool CanAddPhysical => true; + + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + var fullPath = GetFullPath(path); + + if (File.Exists(fullPath)) + { + if (overrideIfExists == false) + throw new InvalidOperationException($"A file at path '{path}' already exists"); + WithRetry(() => File.Delete(fullPath)); + } + + var directory = Path.GetDirectoryName(fullPath); + if (directory == null) throw new InvalidOperationException("Could not get directory."); + Directory.CreateDirectory(directory); // ensure it exists + + if (copy) + WithRetry(() => File.Copy(physicalPath, fullPath)); + else + WithRetry(() => File.Move(physicalPath, fullPath)); + } + + #region Helper Methods + + protected virtual void EnsureDirectory(string path) + { + path = GetFullPath(path); + Directory.CreateDirectory(path); + } + + protected string EnsureTrailingSeparator(string path) + { + return path.EnsureEndsWith(Path.DirectorySeparatorChar); + } + + protected string EnsureDirectorySeparatorChar(string path) + { + path = path.Replace('/', Path.DirectorySeparatorChar); + path = path.Replace('\\', Path.DirectorySeparatorChar); + return path; + } + + protected string EnsureUrlSeparatorChar(string path) + { + path = path.Replace('\\', '/'); + return path; + } + + protected void WithRetry(Action action) + { + // 10 times 100ms is 1s + const int count = 10; + const int pausems = 100; + + for (var i = 0;; i++) + { + try + { + action(); + break; // done + } + catch (IOException e) + { + // if it's not *exactly* IOException then it could be + // some inherited exception such as FileNotFoundException, + // and then we don't want to retry + if (e.GetType() != typeof(IOException)) throw; + + // if we have tried enough, throw, else swallow + // the exception and retry after a pause + if (i == count) throw; + } + + Thread.Sleep(pausems); + } + } + + #endregion + } +} diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index 37eb1a09b9..f18a0e68f6 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -1,79 +1,79 @@ -using System; -using System.Web; - -namespace Umbraco.Core.IO -{ - //all paths has a starting but no trailing / - public class SystemDirectories - { - //TODO: Why on earth is this even configurable? You cannot change the /Bin folder in ASP.Net - public static string Bin => IOHelper.ReturnPath("umbracoBinDirectory", "~/bin"); - - public static string Base => IOHelper.ReturnPath("umbracoBaseDirectory", "~/base"); - - public static string Config => IOHelper.ReturnPath("umbracoConfigDirectory", "~/config"); - - public static string Css => IOHelper.ReturnPath("umbracoCssDirectory", "~/css"); - - public static string Data => IOHelper.ReturnPath("umbracoStorageDirectory", "~/App_Data"); - - public static string Install => IOHelper.ReturnPath("umbracoInstallPath", "~/install"); - - public static string Masterpages => IOHelper.ReturnPath("umbracoMasterPagesPath", "~/masterpages"); - - //NOTE: this is not configurable and shouldn't need to be - public static string AppCode => "~/App_Code"; - - //NOTE: this is not configurable and shouldn't need to be - public static string AppPlugins => "~/App_Plugins"; - - //NOTE: this is not configurable and shouldn't need to be - public static string MvcViews => "~/Views"; - - public static string PartialViews => MvcViews + "/Partials/"; - - public static string MacroPartials => MvcViews + "/MacroPartials/"; - - public static string Media => IOHelper.ReturnPath("umbracoMediaPath", "~/media"); - - public static string Scripts => IOHelper.ReturnPath("umbracoScriptsPath", "~/scripts"); - - public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco"); - - public static string UmbracoClient => IOHelper.ReturnPath("umbracoClientPath", "~/umbraco_client"); - - public static string UserControls => IOHelper.ReturnPath("umbracoUsercontrolsPath", "~/usercontrols"); - - public static string WebServices => IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices"); - - //by default the packages folder should exist in the data folder - public static string Packages => IOHelper.ReturnPath("umbracoPackagesPath", Data + IOHelper.DirSepChar + "packages"); - +using System; +using System.Web; + +namespace Umbraco.Core.IO +{ + //all paths has a starting but no trailing / + public class SystemDirectories + { + //TODO: Why on earth is this even configurable? You cannot change the /Bin folder in ASP.Net + public static string Bin => IOHelper.ReturnPath("umbracoBinDirectory", "~/bin"); + + public static string Base => IOHelper.ReturnPath("umbracoBaseDirectory", "~/base"); + + public static string Config => IOHelper.ReturnPath("umbracoConfigDirectory", "~/config"); + + public static string Css => IOHelper.ReturnPath("umbracoCssDirectory", "~/css"); + + public static string Data => IOHelper.ReturnPath("umbracoStorageDirectory", "~/App_Data"); + + public static string Install => IOHelper.ReturnPath("umbracoInstallPath", "~/install"); + + public static string Masterpages => IOHelper.ReturnPath("umbracoMasterPagesPath", "~/masterpages"); + + //NOTE: this is not configurable and shouldn't need to be + public static string AppCode => "~/App_Code"; + + //NOTE: this is not configurable and shouldn't need to be + public static string AppPlugins => "~/App_Plugins"; + + //NOTE: this is not configurable and shouldn't need to be + public static string MvcViews => "~/Views"; + + public static string PartialViews => MvcViews + "/Partials/"; + + public static string MacroPartials => MvcViews + "/MacroPartials/"; + + public static string Media => IOHelper.ReturnPath("umbracoMediaPath", "~/media"); + + public static string Scripts => IOHelper.ReturnPath("umbracoScriptsPath", "~/scripts"); + + public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco"); + + public static string UmbracoClient => IOHelper.ReturnPath("umbracoClientPath", "~/umbraco_client"); + + public static string UserControls => IOHelper.ReturnPath("umbracoUsercontrolsPath", "~/usercontrols"); + + public static string WebServices => IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices"); + + //by default the packages folder should exist in the data folder + public static string Packages => IOHelper.ReturnPath("umbracoPackagesPath", Data + IOHelper.DirSepChar + "packages"); + public static string Preview => IOHelper.ReturnPath("umbracoPreviewPath", Data + IOHelper.DirSepChar + "preview"); - public static string JavaScriptLibrary => IOHelper.ReturnPath("umbracoJavaScriptLibraryPath", Umbraco + IOHelper.DirSepChar + "lib"); - + public static string JavaScriptLibrary => IOHelper.ReturnPath("umbracoJavaScriptLibraryPath", Umbraco + IOHelper.DirSepChar + "lib"); + private static string _root; - - /// - /// Gets the root path of the application - /// - public static string Root - { - get - { + + /// + /// Gets the root path of the application + /// + public static string Root + { + get + { if (_root != null) return _root; - - var appPath = HttpRuntime.AppDomainAppVirtualPath; - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (appPath == null || appPath == "/") appPath = string.Empty; - - _root = appPath; - - return _root; - } - //Only required for unit tests - internal set => _root = value; - } - } -} + + var appPath = HttpRuntime.AppDomainAppVirtualPath; + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (appPath == null || appPath == "/") appPath = string.Empty; + + _root = appPath; + + return _root; + } + //Only required for unit tests + internal set => _root = value; + } + } +} diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs index a0ca748cd6..9a8e679229 100644 --- a/src/Umbraco.Core/IO/SystemFiles.cs +++ b/src/Umbraco.Core/IO/SystemFiles.cs @@ -1,42 +1,42 @@ -using System; -using System.IO; -using System.Web; -using Umbraco.Core.Configuration; - -namespace Umbraco.Core.IO -{ - public class SystemFiles - { - public static string CreateUiXml => SystemDirectories.Umbraco + "/config/create/UI.xml"; - - public static string TinyMceConfig => SystemDirectories.Config + "/tinyMceConfig.config"; - - public static string DashboardConfig => SystemDirectories.Config + "/dashboard.config"; - - public static string NotFoundhandlersConfig => SystemDirectories.Config + "/404handlers.config"; - - public static string FeedProxyConfig => string.Concat(SystemDirectories.Config, "/feedProxy.config"); - - // fixme - kill - public static string GetContentCacheXml(IGlobalSettings globalSettings) - { - switch (globalSettings.LocalTempStorageLocation) - { - case LocalTempStorage.AspNetTemp: - return Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco.config"); - case LocalTempStorage.EnvironmentTemp: - var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); - var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", - //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back - // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not - // utilizing an old path - appDomainHash); - return Path.Combine(cachePath, "umbraco.config"); - case LocalTempStorage.Default: - return IOHelper.ReturnPath("umbracoContentXML", "~/App_Data/umbraco.config"); - default: - throw new ArgumentOutOfRangeException(); - } - } - } -} +using System; +using System.IO; +using System.Web; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.IO +{ + public class SystemFiles + { + public static string CreateUiXml => SystemDirectories.Umbraco + "/config/create/UI.xml"; + + public static string TinyMceConfig => SystemDirectories.Config + "/tinyMceConfig.config"; + + public static string DashboardConfig => SystemDirectories.Config + "/dashboard.config"; + + public static string NotFoundhandlersConfig => SystemDirectories.Config + "/404handlers.config"; + + public static string FeedProxyConfig => string.Concat(SystemDirectories.Config, "/feedProxy.config"); + + // fixme - kill + public static string GetContentCacheXml(IGlobalSettings globalSettings) + { + switch (globalSettings.LocalTempStorageLocation) + { + case LocalTempStorage.AspNetTemp: + return Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco.config"); + case LocalTempStorage.EnvironmentTemp: + var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); + var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", + //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back + // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not + // utilizing an old path + appDomainHash); + return Path.Combine(cachePath, "umbraco.config"); + case LocalTempStorage.Default: + return IOHelper.ReturnPath("umbracoContentXML", "~/App_Data/umbraco.config"); + default: + throw new ArgumentOutOfRangeException(); + } + } + } +} diff --git a/src/Umbraco.Core/IfExtensions.cs b/src/Umbraco.Core/IfExtensions.cs index 13c1a11f36..2f87e0c08c 100644 --- a/src/Umbraco.Core/IfExtensions.cs +++ b/src/Umbraco.Core/IfExtensions.cs @@ -1,67 +1,67 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Core -{ - - /// - /// Extension methods for 'If' checking like checking If something is null or not null - /// - public static class IfExtensions - { - - /// The if not null. - /// The item. - /// The action. - /// The type - public static void IfNotNull(this TItem item, Action action) where TItem : class - { - if (item != null) - { - action(item); - } - } - - /// The if true. - /// The predicate. - /// The action. - public static void IfTrue(this bool predicate, Action action) - { - if (predicate) - { - action(); - } - } - - /// - /// Checks if the item is not null, and if so returns an action on that item, or a default value - /// - /// the result type - /// The type - /// The item. - /// The action. - /// The default value. - /// - public static TResult IfNotNull(this TItem item, Func action, TResult defaultValue = default(TResult)) - where TItem : class - { - return item != null ? action(item) : defaultValue; - } - - /// - /// Checks if the value is null, if it is it returns the value specified, otherwise returns the non-null value - /// - /// - /// - /// - /// - public static TItem IfNull(this TItem item, Func action) - where TItem : class - { - return item ?? action(item); - } - - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core +{ + + /// + /// Extension methods for 'If' checking like checking If something is null or not null + /// + public static class IfExtensions + { + + /// The if not null. + /// The item. + /// The action. + /// The type + public static void IfNotNull(this TItem item, Action action) where TItem : class + { + if (item != null) + { + action(item); + } + } + + /// The if true. + /// The predicate. + /// The action. + public static void IfTrue(this bool predicate, Action action) + { + if (predicate) + { + action(); + } + } + + /// + /// Checks if the item is not null, and if so returns an action on that item, or a default value + /// + /// the result type + /// The type + /// The item. + /// The action. + /// The default value. + /// + public static TResult IfNotNull(this TItem item, Func action, TResult defaultValue = default(TResult)) + where TItem : class + { + return item != null ? action(item) : defaultValue; + } + + /// + /// Checks if the value is null, if it is it returns the value specified, otherwise returns the non-null value + /// + /// + /// + /// + /// + public static TItem IfNull(this TItem item, Func action) + where TItem : class + { + return item ?? action(item); + } + + } +} diff --git a/src/Umbraco.Core/IntExtensions.cs b/src/Umbraco.Core/IntExtensions.cs index 358c73ab8e..d50490e939 100644 --- a/src/Umbraco.Core/IntExtensions.cs +++ b/src/Umbraco.Core/IntExtensions.cs @@ -1,32 +1,32 @@ -using System; - -namespace Umbraco.Core -{ - public static class IntExtensions - { - /// - /// Does something 'x' amount of times - /// - /// - /// - public static void Times(this int n, Action action) - { - for (int i = 0; i < n; i++) - { - action(i); - } - } - - /// - /// Creates a Guid based on an integer value - /// - /// value to convert - /// - public static Guid ToGuid(this int value) - { - byte[] bytes = new byte[16]; - BitConverter.GetBytes(value).CopyTo(bytes, 0); - return new Guid(bytes); - } - } -} +using System; + +namespace Umbraco.Core +{ + public static class IntExtensions + { + /// + /// Does something 'x' amount of times + /// + /// + /// + public static void Times(this int n, Action action) + { + for (int i = 0; i < n; i++) + { + action(i); + } + } + + /// + /// Creates a Guid based on an integer value + /// + /// value to convert + /// + public static Guid ToGuid(this int value) + { + byte[] bytes = new byte[16]; + BitConverter.GetBytes(value).CopyTo(bytes, 0); + return new Guid(bytes); + } + } +} diff --git a/src/Umbraco.Core/LambdaExpressionCacheKey.cs b/src/Umbraco.Core/LambdaExpressionCacheKey.cs index fdc69cde0f..52ec786a6d 100644 --- a/src/Umbraco.Core/LambdaExpressionCacheKey.cs +++ b/src/Umbraco.Core/LambdaExpressionCacheKey.cs @@ -1,83 +1,83 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; - -namespace Umbraco.Core -{ - /// - /// Represents a simple in a form which is suitable for using as a dictionary key - /// by exposing the return type, argument types and expression string form in a single concatenated string. - /// - internal struct LambdaExpressionCacheKey - { - public LambdaExpressionCacheKey(string returnType, string expression, params string[] argTypes) - { - ReturnType = returnType; - ExpressionAsString = expression; - ArgTypes = new HashSet(argTypes); - _toString = null; - } - - public LambdaExpressionCacheKey(LambdaExpression obj) - { - ReturnType = obj.ReturnType.FullName; - ExpressionAsString = obj.ToString(); - ArgTypes = new HashSet(obj.Parameters.Select(x => x.Type.FullName)); - _toString = null; - } - - /// - /// The argument type names of the - /// - public readonly HashSet ArgTypes; - - /// - /// The return type of the - /// - public readonly string ReturnType; - - /// - /// The original string representation of the - /// - public readonly string ExpressionAsString; - - private string _toString; - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() - { - return _toString ?? (_toString = String.Concat(String.Join("|", ArgTypes), ",", ReturnType, ",", ExpressionAsString)); - } - - /// - /// Determines whether the specified is equal to this instance. - /// - /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(obj, null)) return false; - var casted = (LambdaExpressionCacheKey)obj; - return casted.ToString() == ToString(); - } - - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// - public override int GetHashCode() - { - return ToString().GetHashCode(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Umbraco.Core +{ + /// + /// Represents a simple in a form which is suitable for using as a dictionary key + /// by exposing the return type, argument types and expression string form in a single concatenated string. + /// + internal struct LambdaExpressionCacheKey + { + public LambdaExpressionCacheKey(string returnType, string expression, params string[] argTypes) + { + ReturnType = returnType; + ExpressionAsString = expression; + ArgTypes = new HashSet(argTypes); + _toString = null; + } + + public LambdaExpressionCacheKey(LambdaExpression obj) + { + ReturnType = obj.ReturnType.FullName; + ExpressionAsString = obj.ToString(); + ArgTypes = new HashSet(obj.Parameters.Select(x => x.Type.FullName)); + _toString = null; + } + + /// + /// The argument type names of the + /// + public readonly HashSet ArgTypes; + + /// + /// The return type of the + /// + public readonly string ReturnType; + + /// + /// The original string representation of the + /// + public readonly string ExpressionAsString; + + private string _toString; + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return _toString ?? (_toString = String.Concat(String.Join("|", ArgTypes), ",", ReturnType, ",", ExpressionAsString)); + } + + /// + /// Determines whether the specified is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, null)) return false; + var casted = (LambdaExpressionCacheKey)obj; + return casted.ToString() == ToString(); + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + } +} diff --git a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs index 5d314c19e0..e14ef92451 100644 --- a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs +++ b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs @@ -1,206 +1,206 @@ -using log4net.Core; -using log4net.Util; -using System; -using System.ComponentModel; -using System.Runtime.Remoting.Messaging; -using System.Security.Principal; -using System.Threading; -using System.Threading.Tasks; -using log4net.Appender; - -namespace Umbraco.Core.Logging -{ - /// - /// Based on https://github.com/cjbhaines/Log4Net.Async - /// which is based on code by Chris Haines http://cjbhaines.wordpress.com/2012/02/13/asynchronous-log4net-appenders/ - /// This is an old/deprecated logger and has been superceded by ParallelForwardingAppender which is included in Umbraco and - /// also by AsyncForwardingAppender in the Log4Net.Async library. - /// - [Obsolete("This is superceded by the ParallelForwardingAppender, this will be removed in v8, do not use this")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class AsynchronousRollingFileAppender : RollingFileAppender - { - private RingBuffer pendingAppends; - private readonly ManualResetEvent manualResetEvent; - private bool shuttingDown; - private bool hasFinished; - private bool forceStop; - private bool logBufferOverflow; - private int bufferOverflowCounter; - private DateTime lastLoggedBufferOverflow; - private int queueSizeLimit = 1000; - public int QueueSizeLimit - { - get - { - return queueSizeLimit; - } - set - { - queueSizeLimit = value; - } - } - - public AsynchronousRollingFileAppender() - { - manualResetEvent = new ManualResetEvent(false); - } - - public override void ActivateOptions() - { - base.ActivateOptions(); - pendingAppends = new RingBuffer(QueueSizeLimit); - pendingAppends.BufferOverflow += OnBufferOverflow; - StartAppendTask(); - } - - protected override void Append(LoggingEvent[] loggingEvents) - { - Array.ForEach(loggingEvents, Append); - } - - protected override void Append(LoggingEvent loggingEvent) - { - if (FilterEvent(loggingEvent)) - { - pendingAppends.Enqueue(loggingEvent); - } - } - - protected override void OnClose() - { - shuttingDown = true; - manualResetEvent.WaitOne(TimeSpan.FromSeconds(5)); - - if (!hasFinished) - { - forceStop = true; - base.Append(new LoggingEvent(new LoggingEventData - { - Level = Level.Error, - Message = "Unable to clear out the AsyncRollingFileAppender buffer in the allotted time, forcing a shutdown", - TimeStamp = DateTime.UtcNow, - Identity = "", - ExceptionString = "", - UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "", - Domain = AppDomain.CurrentDomain.FriendlyName, - ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), - LocationInfo = new LocationInfo(this.GetType().Name, "OnClose", "AsyncRollingFileAppender.cs", "75"), - LoggerName = this.GetType().FullName, - Properties = new PropertiesDictionary(), - }) - ); - } - - base.OnClose(); - } - - private void StartAppendTask() - { - if (!shuttingDown) - { - Task appendTask = new Task(AppendLoggingEvents, TaskCreationOptions.LongRunning); - appendTask.LogErrors(LogAppenderError).ContinueWith(x => StartAppendTask()).LogErrors(LogAppenderError); - appendTask.Start(); - } - } - - private void LogAppenderError(string logMessage, Exception exception) - { - base.Append(new LoggingEvent(new LoggingEventData - { - Level = Level.Error, - Message = "Appender exception: " + logMessage, - TimeStamp = DateTime.UtcNow, - Identity = "", - ExceptionString = exception.ToString(), - UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "", - Domain = AppDomain.CurrentDomain.FriendlyName, - ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), - LocationInfo = new LocationInfo(this.GetType().Name, "LogAppenderError", "AsyncRollingFileAppender.cs", "152"), - LoggerName = this.GetType().FullName, - Properties = new PropertiesDictionary(), - })); - } - - private void AppendLoggingEvents() - { - LoggingEvent loggingEventToAppend; - while (!shuttingDown) - { - if (logBufferOverflow) - { - LogBufferOverflowError(); - logBufferOverflow = false; - bufferOverflowCounter = 0; - lastLoggedBufferOverflow = DateTime.UtcNow; - } - - while (!pendingAppends.TryDequeue(out loggingEventToAppend)) - { - Thread.Sleep(10); - if (shuttingDown) - { - break; - } - } - if (loggingEventToAppend == null) - { - continue; - } - - try - { - base.Append(loggingEventToAppend); - } - catch - { - } - } - - while (pendingAppends.TryDequeue(out loggingEventToAppend) && !forceStop) - { - try - { - base.Append(loggingEventToAppend); - } - catch - { - } - } - hasFinished = true; - manualResetEvent.Set(); - } - - private void LogBufferOverflowError() - { - base.Append(new LoggingEvent(new LoggingEventData - { - Level = Level.Error, - Message = string.Format("Buffer overflow. {0} logging events have been lost in the last 30 seconds. [QueueSizeLimit: {1}]", bufferOverflowCounter, QueueSizeLimit), - TimeStamp = DateTime.UtcNow, - Identity = "", - ExceptionString = "", - UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "", - Domain = AppDomain.CurrentDomain.FriendlyName, - ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), - LocationInfo = new LocationInfo(this.GetType().Name, "LogBufferOverflowError", "AsyncRollingFileAppender.cs", "152"), - LoggerName = this.GetType().FullName, - Properties = new PropertiesDictionary(), - })); - } - - private void OnBufferOverflow(object sender, EventArgs eventArgs) - { - bufferOverflowCounter++; - if (logBufferOverflow == false) - { - if (lastLoggedBufferOverflow < DateTime.UtcNow.AddSeconds(-30)) - { - logBufferOverflow = true; - } - } - } - } - -} +using log4net.Core; +using log4net.Util; +using System; +using System.ComponentModel; +using System.Runtime.Remoting.Messaging; +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; +using log4net.Appender; + +namespace Umbraco.Core.Logging +{ + /// + /// Based on https://github.com/cjbhaines/Log4Net.Async + /// which is based on code by Chris Haines http://cjbhaines.wordpress.com/2012/02/13/asynchronous-log4net-appenders/ + /// This is an old/deprecated logger and has been superceded by ParallelForwardingAppender which is included in Umbraco and + /// also by AsyncForwardingAppender in the Log4Net.Async library. + /// + [Obsolete("This is superceded by the ParallelForwardingAppender, this will be removed in v8, do not use this")] + [EditorBrowsable(EditorBrowsableState.Never)] + public class AsynchronousRollingFileAppender : RollingFileAppender + { + private RingBuffer pendingAppends; + private readonly ManualResetEvent manualResetEvent; + private bool shuttingDown; + private bool hasFinished; + private bool forceStop; + private bool logBufferOverflow; + private int bufferOverflowCounter; + private DateTime lastLoggedBufferOverflow; + private int queueSizeLimit = 1000; + public int QueueSizeLimit + { + get + { + return queueSizeLimit; + } + set + { + queueSizeLimit = value; + } + } + + public AsynchronousRollingFileAppender() + { + manualResetEvent = new ManualResetEvent(false); + } + + public override void ActivateOptions() + { + base.ActivateOptions(); + pendingAppends = new RingBuffer(QueueSizeLimit); + pendingAppends.BufferOverflow += OnBufferOverflow; + StartAppendTask(); + } + + protected override void Append(LoggingEvent[] loggingEvents) + { + Array.ForEach(loggingEvents, Append); + } + + protected override void Append(LoggingEvent loggingEvent) + { + if (FilterEvent(loggingEvent)) + { + pendingAppends.Enqueue(loggingEvent); + } + } + + protected override void OnClose() + { + shuttingDown = true; + manualResetEvent.WaitOne(TimeSpan.FromSeconds(5)); + + if (!hasFinished) + { + forceStop = true; + base.Append(new LoggingEvent(new LoggingEventData + { + Level = Level.Error, + Message = "Unable to clear out the AsyncRollingFileAppender buffer in the allotted time, forcing a shutdown", + TimeStamp = DateTime.UtcNow, + Identity = "", + ExceptionString = "", + UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "", + Domain = AppDomain.CurrentDomain.FriendlyName, + ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), + LocationInfo = new LocationInfo(this.GetType().Name, "OnClose", "AsyncRollingFileAppender.cs", "75"), + LoggerName = this.GetType().FullName, + Properties = new PropertiesDictionary(), + }) + ); + } + + base.OnClose(); + } + + private void StartAppendTask() + { + if (!shuttingDown) + { + Task appendTask = new Task(AppendLoggingEvents, TaskCreationOptions.LongRunning); + appendTask.LogErrors(LogAppenderError).ContinueWith(x => StartAppendTask()).LogErrors(LogAppenderError); + appendTask.Start(); + } + } + + private void LogAppenderError(string logMessage, Exception exception) + { + base.Append(new LoggingEvent(new LoggingEventData + { + Level = Level.Error, + Message = "Appender exception: " + logMessage, + TimeStamp = DateTime.UtcNow, + Identity = "", + ExceptionString = exception.ToString(), + UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "", + Domain = AppDomain.CurrentDomain.FriendlyName, + ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), + LocationInfo = new LocationInfo(this.GetType().Name, "LogAppenderError", "AsyncRollingFileAppender.cs", "152"), + LoggerName = this.GetType().FullName, + Properties = new PropertiesDictionary(), + })); + } + + private void AppendLoggingEvents() + { + LoggingEvent loggingEventToAppend; + while (!shuttingDown) + { + if (logBufferOverflow) + { + LogBufferOverflowError(); + logBufferOverflow = false; + bufferOverflowCounter = 0; + lastLoggedBufferOverflow = DateTime.UtcNow; + } + + while (!pendingAppends.TryDequeue(out loggingEventToAppend)) + { + Thread.Sleep(10); + if (shuttingDown) + { + break; + } + } + if (loggingEventToAppend == null) + { + continue; + } + + try + { + base.Append(loggingEventToAppend); + } + catch + { + } + } + + while (pendingAppends.TryDequeue(out loggingEventToAppend) && !forceStop) + { + try + { + base.Append(loggingEventToAppend); + } + catch + { + } + } + hasFinished = true; + manualResetEvent.Set(); + } + + private void LogBufferOverflowError() + { + base.Append(new LoggingEvent(new LoggingEventData + { + Level = Level.Error, + Message = string.Format("Buffer overflow. {0} logging events have been lost in the last 30 seconds. [QueueSizeLimit: {1}]", bufferOverflowCounter, QueueSizeLimit), + TimeStamp = DateTime.UtcNow, + Identity = "", + ExceptionString = "", + UserName = WindowsIdentity.GetCurrent() != null ? WindowsIdentity.GetCurrent().Name : "", + Domain = AppDomain.CurrentDomain.FriendlyName, + ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(), + LocationInfo = new LocationInfo(this.GetType().Name, "LogBufferOverflowError", "AsyncRollingFileAppender.cs", "152"), + LoggerName = this.GetType().FullName, + Properties = new PropertiesDictionary(), + })); + } + + private void OnBufferOverflow(object sender, EventArgs eventArgs) + { + bufferOverflowCounter++; + if (logBufferOverflow == false) + { + if (lastLoggedBufferOverflow < DateTime.UtcNow.AddSeconds(-30)) + { + logBufferOverflow = true; + } + } + } + } + +} diff --git a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs index 614f1ce14c..1f742133c3 100644 --- a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs +++ b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs @@ -1,44 +1,44 @@ -using System; -using System.Threading.Tasks; - -namespace Umbraco.Core.Logging -{ - internal static class LoggingTaskExtension - { - /// - /// This task shouldn't be waited on (as it's not guaranteed to run), and you shouldn't wait on the parent task either (because it might throw an - /// exception that doesn't get handled). If you want to be waiting on something, use LogErrorsWaitable instead. - /// - /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably be handling - /// errors yourself. - /// - public static Task LogErrors(this Task task, Action logMethod) - { - return task.ContinueWith(t => LogErrorsInner(t, logMethod), TaskContinuationOptions.OnlyOnFaulted); - } - - /// - /// This task can be waited on (as it's guaranteed to run), and you should wait on this rather than the parent task. Because it's - /// guaranteed to run, it may be slower than using LogErrors, and you should consider using that method if you don't want to wait. - /// - /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably be handling - /// errors yourself. - /// - public static Task LogErrorsWaitable(this Task task, Action logMethod) - { - return task.ContinueWith(t => LogErrorsInner(t, logMethod)); - } - - private static void LogErrorsInner(Task task, Action logAction) - { - if (task.Exception != null) - { - logAction("Aggregate Exception with " + task.Exception.InnerExceptions.Count + " inner exceptions: ", task.Exception); - foreach (var innerException in task.Exception.InnerExceptions) - { - logAction("Inner exception from aggregate exception: ", innerException); - } - } - } - } -} +using System; +using System.Threading.Tasks; + +namespace Umbraco.Core.Logging +{ + internal static class LoggingTaskExtension + { + /// + /// This task shouldn't be waited on (as it's not guaranteed to run), and you shouldn't wait on the parent task either (because it might throw an + /// exception that doesn't get handled). If you want to be waiting on something, use LogErrorsWaitable instead. + /// + /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably be handling + /// errors yourself. + /// + public static Task LogErrors(this Task task, Action logMethod) + { + return task.ContinueWith(t => LogErrorsInner(t, logMethod), TaskContinuationOptions.OnlyOnFaulted); + } + + /// + /// This task can be waited on (as it's guaranteed to run), and you should wait on this rather than the parent task. Because it's + /// guaranteed to run, it may be slower than using LogErrors, and you should consider using that method if you don't want to wait. + /// + /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably be handling + /// errors yourself. + /// + public static Task LogErrorsWaitable(this Task task, Action logMethod) + { + return task.ContinueWith(t => LogErrorsInner(t, logMethod)); + } + + private static void LogErrorsInner(Task task, Action logAction) + { + if (task.Exception != null) + { + logAction("Aggregate Exception with " + task.Exception.InnerExceptions.Count + " inner exceptions: ", task.Exception); + foreach (var innerException in task.Exception.InnerExceptions) + { + logAction("Inner exception from aggregate exception: ", innerException); + } + } + } + } +} diff --git a/src/Umbraco.Core/Macros/MacroTagParser.cs b/src/Umbraco.Core/Macros/MacroTagParser.cs index 857d36d2da..469b2ed4d0 100644 --- a/src/Umbraco.Core/Macros/MacroTagParser.cs +++ b/src/Umbraco.Core/Macros/MacroTagParser.cs @@ -1,211 +1,211 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Text.RegularExpressions; -using HtmlAgilityPack; -using Umbraco.Core.Xml; - -namespace Umbraco.Core.Macros -{ - /// - /// Parses the macro syntax in a string and renders out it's contents - /// - internal class MacroTagParser - { - private static readonly Regex MacroRteContent = new Regex(@"()", - RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); - - private static readonly Regex MacroPersistedFormat = - new Regex(@"(<\?UMBRACO_MACRO (?:.+?)??macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?)", - RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); - - /// - /// This formats the persisted string to something useful for the rte so that the macro renders properly since we - /// persist all macro formats like {?UMBRACO_MACRO macroAlias=\"myMacro\" /} - /// - /// - /// The html attributes to be added to the div - /// - /// - /// This converts the persisted macro format to this: - /// - /// {div class='umb-macro-holder'} - /// - /// {ins}Macro alias: {strong}My Macro{/strong}{/ins} - /// {/div} - /// - /// - internal static string FormatRichTextPersistedDataForEditor(string persistedContent, IDictionary htmlAttributes) - { - return MacroPersistedFormat.Replace(persistedContent, match => - { - if (match.Groups.Count >= 3) - { - //
- var alias = match.Groups[2].Value; - var sb = new StringBuilder("
"); - sb.Append(""); - sb.Append(""); - sb.Append("Macro alias: "); - sb.Append(""); - sb.Append(alias); - sb.Append("
"); - return sb.ToString(); - } - //replace with nothing if we couldn't find the syntax for whatever reason - return ""; - }); - } - - /// - /// This formats the string content posted from a rich text editor that contains macro contents to be persisted. - /// - /// - /// - /// - /// This is required because when editors are using the rte, the html that is contained in the editor might actually be displaying - /// the entire macro content, when the data is submitted the editor will clear most of this data out but we'll still need to parse it properly - /// and ensure the correct sytnax is persisted to the db. - /// - /// When a macro is inserted into the rte editor, the html will be: - /// - /// {div class='umb-macro-holder'} - /// - /// This could be some macro content - /// {/div} - /// - /// What this method will do is remove the {div} and parse out the commented special macro syntax: {?UMBRACO_MACRO macroAlias=\"myMacro\" /} - /// since this is exactly how we need to persist it to the db. - /// - /// - internal static string FormatRichTextContentForPersistence(string rteContent) - { - if (string.IsNullOrEmpty(rteContent)) - { - return string.Empty; - } - - var html = new HtmlDocument(); - html.LoadHtml(rteContent); - - //get all the comment nodes we want - var commentNodes = html.DocumentNode.SelectNodes("//comment()[contains(., ' with the comment node itself. - foreach (var c in commentNodes) - { - var div = c.ParentNode; - var divContainer = div.ParentNode; - divContainer.ReplaceChild(c, div); - } - - var parsed = html.DocumentNode.OuterHtml; - - //now replace all the with nothing - return MacroRteContent.Replace(parsed, match => - { - if (match.Groups.Count >= 3) - { - //get the 3rd group which is the macro syntax - return match.Groups[2].Value; - } - //replace with nothing if we couldn't find the syntax for whatever reason - return string.Empty; - }); - } - - /// - /// This will accept a text block and seach/parse it for macro markup. - /// When either a text block or a a macro is found, it will call the callback method. - /// - /// - /// - /// - /// - /// - /// This method simply parses the macro contents, it does not create a string or result, - /// this is up to the developer calling this method to implement this with the callbacks. - /// - internal static void ParseMacros( - string text, - Action textFoundCallback, - Action> macroFoundCallback ) - { - if (textFoundCallback == null) throw new ArgumentNullException("textFoundCallback"); - if (macroFoundCallback == null) throw new ArgumentNullException("macroFoundCallback"); - - string elementText = text; - - var fieldResult = new StringBuilder(elementText); - - //NOTE: This is legacy code, this is definitely not the correct way to do a while loop! :) - var stop = false; - while (!stop) - { - var tagIndex = fieldResult.ToString().ToLower().IndexOf(" -1) - { - var tempElementContent = ""; - - //text block found, call the call back method - textFoundCallback(fieldResult.ToString().Substring(0, tagIndex)); - - fieldResult.Remove(0, tagIndex); - - var tag = fieldResult.ToString().Substring(0, fieldResult.ToString().IndexOf(">") + 1); - var attributes = XmlHelper.GetAttributesFromElement(tag); - - // Check whether it's a single tag () or a tag with children (...) - if (tag.Substring(tag.Length - 2, 1) != "/" && tag.IndexOf(" ") > -1) - { - string closingTag = ""; - // Tag with children are only used when a macro is inserted by the umbraco-editor, in the - // following format: "", so we - // need to delete extra information inserted which is the image-tag and the closing - // umbraco_macro tag - if (fieldResult.ToString().IndexOf(closingTag) > -1) - { - fieldResult.Remove(0, fieldResult.ToString().IndexOf(closingTag)); - } - } - - var macroAlias = attributes.ContainsKey("macroalias") ? attributes["macroalias"] : attributes["alias"]; - - //call the callback now that we have the macro parsed - macroFoundCallback(macroAlias, attributes); - - fieldResult.Remove(0, fieldResult.ToString().IndexOf(">") + 1); - fieldResult.Insert(0, tempElementContent); - } - else - { - //text block found, call the call back method - textFoundCallback(fieldResult.ToString()); - - stop = true; //break; - } - } - } - } -} +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using HtmlAgilityPack; +using Umbraco.Core.Xml; + +namespace Umbraco.Core.Macros +{ + /// + /// Parses the macro syntax in a string and renders out it's contents + /// + internal class MacroTagParser + { + private static readonly Regex MacroRteContent = new Regex(@"()", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); + + private static readonly Regex MacroPersistedFormat = + new Regex(@"(<\?UMBRACO_MACRO (?:.+?)??macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?)", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); + + /// + /// This formats the persisted string to something useful for the rte so that the macro renders properly since we + /// persist all macro formats like {?UMBRACO_MACRO macroAlias=\"myMacro\" /} + /// + /// + /// The html attributes to be added to the div + /// + /// + /// This converts the persisted macro format to this: + /// + /// {div class='umb-macro-holder'} + /// + /// {ins}Macro alias: {strong}My Macro{/strong}{/ins} + /// {/div} + /// + /// + internal static string FormatRichTextPersistedDataForEditor(string persistedContent, IDictionary htmlAttributes) + { + return MacroPersistedFormat.Replace(persistedContent, match => + { + if (match.Groups.Count >= 3) + { + //
+ var alias = match.Groups[2].Value; + var sb = new StringBuilder("
"); + sb.Append(""); + sb.Append(""); + sb.Append("Macro alias: "); + sb.Append(""); + sb.Append(alias); + sb.Append("
"); + return sb.ToString(); + } + //replace with nothing if we couldn't find the syntax for whatever reason + return ""; + }); + } + + /// + /// This formats the string content posted from a rich text editor that contains macro contents to be persisted. + /// + /// + /// + /// + /// This is required because when editors are using the rte, the html that is contained in the editor might actually be displaying + /// the entire macro content, when the data is submitted the editor will clear most of this data out but we'll still need to parse it properly + /// and ensure the correct sytnax is persisted to the db. + /// + /// When a macro is inserted into the rte editor, the html will be: + /// + /// {div class='umb-macro-holder'} + /// + /// This could be some macro content + /// {/div} + /// + /// What this method will do is remove the {div} and parse out the commented special macro syntax: {?UMBRACO_MACRO macroAlias=\"myMacro\" /} + /// since this is exactly how we need to persist it to the db. + /// + /// + internal static string FormatRichTextContentForPersistence(string rteContent) + { + if (string.IsNullOrEmpty(rteContent)) + { + return string.Empty; + } + + var html = new HtmlDocument(); + html.LoadHtml(rteContent); + + //get all the comment nodes we want + var commentNodes = html.DocumentNode.SelectNodes("//comment()[contains(., ' with the comment node itself. + foreach (var c in commentNodes) + { + var div = c.ParentNode; + var divContainer = div.ParentNode; + divContainer.ReplaceChild(c, div); + } + + var parsed = html.DocumentNode.OuterHtml; + + //now replace all the with nothing + return MacroRteContent.Replace(parsed, match => + { + if (match.Groups.Count >= 3) + { + //get the 3rd group which is the macro syntax + return match.Groups[2].Value; + } + //replace with nothing if we couldn't find the syntax for whatever reason + return string.Empty; + }); + } + + /// + /// This will accept a text block and seach/parse it for macro markup. + /// When either a text block or a a macro is found, it will call the callback method. + /// + /// + /// + /// + /// + /// + /// This method simply parses the macro contents, it does not create a string or result, + /// this is up to the developer calling this method to implement this with the callbacks. + /// + internal static void ParseMacros( + string text, + Action textFoundCallback, + Action> macroFoundCallback ) + { + if (textFoundCallback == null) throw new ArgumentNullException("textFoundCallback"); + if (macroFoundCallback == null) throw new ArgumentNullException("macroFoundCallback"); + + string elementText = text; + + var fieldResult = new StringBuilder(elementText); + + //NOTE: This is legacy code, this is definitely not the correct way to do a while loop! :) + var stop = false; + while (!stop) + { + var tagIndex = fieldResult.ToString().ToLower().IndexOf(" -1) + { + var tempElementContent = ""; + + //text block found, call the call back method + textFoundCallback(fieldResult.ToString().Substring(0, tagIndex)); + + fieldResult.Remove(0, tagIndex); + + var tag = fieldResult.ToString().Substring(0, fieldResult.ToString().IndexOf(">") + 1); + var attributes = XmlHelper.GetAttributesFromElement(tag); + + // Check whether it's a single tag () or a tag with children (...) + if (tag.Substring(tag.Length - 2, 1) != "/" && tag.IndexOf(" ") > -1) + { + string closingTag = ""; + // Tag with children are only used when a macro is inserted by the umbraco-editor, in the + // following format: "", so we + // need to delete extra information inserted which is the image-tag and the closing + // umbraco_macro tag + if (fieldResult.ToString().IndexOf(closingTag) > -1) + { + fieldResult.Remove(0, fieldResult.ToString().IndexOf(closingTag)); + } + } + + var macroAlias = attributes.ContainsKey("macroalias") ? attributes["macroalias"] : attributes["alias"]; + + //call the callback now that we have the macro parsed + macroFoundCallback(macroAlias, attributes); + + fieldResult.Remove(0, fieldResult.ToString().IndexOf(">") + 1); + fieldResult.Insert(0, tempElementContent); + } + else + { + //text block found, call the call back method + textFoundCallback(fieldResult.ToString()); + + stop = true; //break; + } + } + } + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index cad491aeb1..adf3418fb0 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -1,172 +1,172 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using Newtonsoft.Json; -using Umbraco.Core.Cache; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Manifest -{ - /// - /// Parses the Main.js file and replaces all tokens accordingly. - /// - public class ManifestParser - { - private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); - - private readonly IRuntimeCacheProvider _cache; - private readonly ILogger _logger; - private readonly ManifestValueValidatorCollection _validators; - - private string _path; - - /// - /// Initializes a new instance of the class. - /// - public ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, ILogger logger) - : this(cache, validators, "~/App_Plugins", logger) - { } - - /// - /// Initializes a new instance of the class. - /// - private ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, string path, ILogger logger) - { - _cache = cache ?? throw new ArgumentNullException(nameof(cache)); - _validators = validators ?? throw new ArgumentNullException(nameof(validators)); - if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); - Path = path; - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public string Path - { - get => _path; - set => _path = value.StartsWith("~/") ? IOHelper.MapPath(value) : value; - } - - /// - /// Gets all manifests, merged into a single manifest object. - /// - /// - public PackageManifest Manifest - => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => - { - var manifests = GetManifests(); - return MergeManifests(manifests); - }, new TimeSpan(0, 4, 0)); - - /// - /// Gets all manifests. - /// - private IEnumerable GetManifests() - { - var manifests = new List(); - - foreach (var path in GetManifestFiles()) - { - try - { - var text = File.ReadAllText(path); - text = TrimPreamble(text); - if (string.IsNullOrWhiteSpace(text)) - continue; - var manifest = ParseManifest(text); - manifests.Add(manifest); - } - catch (Exception e) - { - _logger.Error($"Failed to parse manifest at \"{path}\", ignoring.", e); - } - } - - return manifests; - } - - /// - /// Merges all manifests into one. - /// - private static PackageManifest MergeManifests(IEnumerable manifests) - { - var scripts = new HashSet(); - var stylesheets = new HashSet(); - var propertyEditors = new List(); - var parameterEditors = new List(); - var gridEditors = new List(); - - foreach (var manifest in manifests) - { - if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script); - if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet); - if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors); - if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors); - if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors); - } - - return new PackageManifest - { - Scripts = scripts.ToArray(), - Stylesheets = stylesheets.ToArray(), - PropertyEditors = propertyEditors.ToArray(), - ParameterEditors = parameterEditors.ToArray(), - GridEditors = gridEditors.ToArray() - }; - } - - // gets all manifest files (recursively) - private IEnumerable GetManifestFiles() - { - if (Directory.Exists(_path) == false) - return new string[0]; - return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories); - } - - - private static string TrimPreamble(string text) - { - // strangely StartsWith(preamble) would always return true - if (text.Substring(0, 1) == Utf8Preamble) - text = text.Remove(0, Utf8Preamble.Length); - - return text; - } - - /// - /// Parses a manifest. - /// - internal PackageManifest ParseManifest(string text) - { - if (string.IsNullOrWhiteSpace(text)) - throw new ArgumentNullOrEmptyException(nameof(text)); - - var manifest = JsonConvert.DeserializeObject(text, - new DataEditorConverter(_logger), - new ValueValidatorConverter(_validators)); - - // scripts and stylesheets are raw string, must process here - for (var i = 0; i < manifest.Scripts.Length; i++) - manifest.Scripts[i] = IOHelper.ResolveVirtualUrl(manifest.Scripts[i]); - for (var i = 0; i < manifest.Stylesheets.Length; i++) - manifest.Stylesheets[i] = IOHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); - - // add property editors that are also parameter editors, to the parameter editors list - // (the manifest format is kinda legacy) - var ppEditors = manifest.PropertyEditors.Where(x => (x.Type & EditorType.MacroParameter) > 0).ToList(); - if (ppEditors.Count > 0) - manifest.ParameterEditors = manifest.ParameterEditors.Union(ppEditors).ToArray(); - - return manifest; - } - - // purely for tests - internal IEnumerable ParseGridEditors(string text) - { - return JsonConvert.DeserializeObject>(text); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Manifest +{ + /// + /// Parses the Main.js file and replaces all tokens accordingly. + /// + public class ManifestParser + { + private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); + + private readonly IRuntimeCacheProvider _cache; + private readonly ILogger _logger; + private readonly ManifestValueValidatorCollection _validators; + + private string _path; + + /// + /// Initializes a new instance of the class. + /// + public ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, ILogger logger) + : this(cache, validators, "~/App_Plugins", logger) + { } + + /// + /// Initializes a new instance of the class. + /// + private ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, string path, ILogger logger) + { + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + _validators = validators ?? throw new ArgumentNullException(nameof(validators)); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); + Path = path; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public string Path + { + get => _path; + set => _path = value.StartsWith("~/") ? IOHelper.MapPath(value) : value; + } + + /// + /// Gets all manifests, merged into a single manifest object. + /// + /// + public PackageManifest Manifest + => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => + { + var manifests = GetManifests(); + return MergeManifests(manifests); + }, new TimeSpan(0, 4, 0)); + + /// + /// Gets all manifests. + /// + private IEnumerable GetManifests() + { + var manifests = new List(); + + foreach (var path in GetManifestFiles()) + { + try + { + var text = File.ReadAllText(path); + text = TrimPreamble(text); + if (string.IsNullOrWhiteSpace(text)) + continue; + var manifest = ParseManifest(text); + manifests.Add(manifest); + } + catch (Exception e) + { + _logger.Error($"Failed to parse manifest at \"{path}\", ignoring.", e); + } + } + + return manifests; + } + + /// + /// Merges all manifests into one. + /// + private static PackageManifest MergeManifests(IEnumerable manifests) + { + var scripts = new HashSet(); + var stylesheets = new HashSet(); + var propertyEditors = new List(); + var parameterEditors = new List(); + var gridEditors = new List(); + + foreach (var manifest in manifests) + { + if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script); + if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet); + if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors); + if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors); + if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors); + } + + return new PackageManifest + { + Scripts = scripts.ToArray(), + Stylesheets = stylesheets.ToArray(), + PropertyEditors = propertyEditors.ToArray(), + ParameterEditors = parameterEditors.ToArray(), + GridEditors = gridEditors.ToArray() + }; + } + + // gets all manifest files (recursively) + private IEnumerable GetManifestFiles() + { + if (Directory.Exists(_path) == false) + return new string[0]; + return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories); + } + + + private static string TrimPreamble(string text) + { + // strangely StartsWith(preamble) would always return true + if (text.Substring(0, 1) == Utf8Preamble) + text = text.Remove(0, Utf8Preamble.Length); + + return text; + } + + /// + /// Parses a manifest. + /// + internal PackageManifest ParseManifest(string text) + { + if (string.IsNullOrWhiteSpace(text)) + throw new ArgumentNullOrEmptyException(nameof(text)); + + var manifest = JsonConvert.DeserializeObject(text, + new DataEditorConverter(_logger), + new ValueValidatorConverter(_validators)); + + // scripts and stylesheets are raw string, must process here + for (var i = 0; i < manifest.Scripts.Length; i++) + manifest.Scripts[i] = IOHelper.ResolveVirtualUrl(manifest.Scripts[i]); + for (var i = 0; i < manifest.Stylesheets.Length; i++) + manifest.Stylesheets[i] = IOHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); + + // add property editors that are also parameter editors, to the parameter editors list + // (the manifest format is kinda legacy) + var ppEditors = manifest.PropertyEditors.Where(x => (x.Type & EditorType.MacroParameter) > 0).ToList(); + if (ppEditors.Count > 0) + manifest.ParameterEditors = manifest.ParameterEditors.Union(ppEditors).ToArray(); + + return manifest; + } + + // purely for tests + internal IEnumerable ParseGridEditors(string text) + { + return JsonConvert.DeserializeObject>(text); + } + } +} diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 1b449e4570..a1702cc58b 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -1,27 +1,27 @@ -using System; -using Newtonsoft.Json; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Manifest -{ - /// - /// Represents the content of a package manifest. - /// - public class PackageManifest - { - [JsonProperty("javascript")] - public string[] Scripts { get; set; } = Array.Empty(); - - [JsonProperty("css")] - public string[] Stylesheets { get; set; }= Array.Empty(); - - [JsonProperty("propertyEditors")] - public IDataEditor[] PropertyEditors { get; set; } = Array.Empty(); - - [JsonProperty("parameterEditors")] - public IDataEditor[] ParameterEditors { get; set; } = Array.Empty(); - - [JsonProperty("gridEditors")] - public GridEditor[] GridEditors { get; set; } = Array.Empty(); - } -} +using System; +using Newtonsoft.Json; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Manifest +{ + /// + /// Represents the content of a package manifest. + /// + public class PackageManifest + { + [JsonProperty("javascript")] + public string[] Scripts { get; set; } = Array.Empty(); + + [JsonProperty("css")] + public string[] Stylesheets { get; set; }= Array.Empty(); + + [JsonProperty("propertyEditors")] + public IDataEditor[] PropertyEditors { get; set; } = Array.Empty(); + + [JsonProperty("parameterEditors")] + public IDataEditor[] ParameterEditors { get; set; } = Array.Empty(); + + [JsonProperty("gridEditors")] + public GridEditor[] GridEditors { get; set; } = Array.Empty(); + } +} diff --git a/src/Umbraco.Core/Media/IEmbedProvider.cs b/src/Umbraco.Core/Media/IEmbedProvider.cs index 3a95f2c58b..62faa45563 100644 --- a/src/Umbraco.Core/Media/IEmbedProvider.cs +++ b/src/Umbraco.Core/Media/IEmbedProvider.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Core.Media -{ - public interface IEmbedProvider - { - bool SupportsDimensions { get; } - - string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); - } -} +namespace Umbraco.Core.Media +{ + public interface IEmbedProvider + { + bool SupportsDimensions { get; } + + string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); + } +} diff --git a/src/Umbraco.Core/Media/IEmbedSettingProvider.cs b/src/Umbraco.Core/Media/IEmbedSettingProvider.cs index 18377fd6da..b9ba611100 100644 --- a/src/Umbraco.Core/Media/IEmbedSettingProvider.cs +++ b/src/Umbraco.Core/Media/IEmbedSettingProvider.cs @@ -1,9 +1,9 @@ -using System.Xml; - -namespace Umbraco.Core.Media -{ - public interface IEmbedSettingProvider - { - object GetSetting(XmlNode settingNode); - } -} +using System.Xml; + +namespace Umbraco.Core.Media +{ + public interface IEmbedSettingProvider + { + object GetSetting(XmlNode settingNode); + } +} diff --git a/src/Umbraco.Core/Media/ProviderSetting.cs b/src/Umbraco.Core/Media/ProviderSetting.cs index d26ad6e616..9fef5efabf 100644 --- a/src/Umbraco.Core/Media/ProviderSetting.cs +++ b/src/Umbraco.Core/Media/ProviderSetting.cs @@ -1,8 +1,8 @@ -using System; - -namespace Umbraco.Core.Media -{ - public class ProviderSetting : Attribute - { - } -} +using System; + +namespace Umbraco.Core.Media +{ + public class ProviderSetting : Attribute + { + } +} diff --git a/src/Umbraco.Core/Media/Result.cs b/src/Umbraco.Core/Media/Result.cs index ee86c02ea5..82894a5ef5 100644 --- a/src/Umbraco.Core/Media/Result.cs +++ b/src/Umbraco.Core/Media/Result.cs @@ -1,11 +1,11 @@ -namespace Umbraco.Core.Media -{ - - //TODO: Could definitely have done with a better name - public class Result - { - public Status Status { get; set; } - public bool SupportsDimensions { get; set; } - public string Markup { get; set; } - } -} +namespace Umbraco.Core.Media +{ + + //TODO: Could definitely have done with a better name + public class Result + { + public Status Status { get; set; } + public bool SupportsDimensions { get; set; } + public string Markup { get; set; } + } +} diff --git a/src/Umbraco.Core/Media/Status.cs b/src/Umbraco.Core/Media/Status.cs index 5a815c3cf8..abbcca97da 100644 --- a/src/Umbraco.Core/Media/Status.cs +++ b/src/Umbraco.Core/Media/Status.cs @@ -1,11 +1,11 @@ -namespace Umbraco.Core.Media -{ - - //NOTE: Could definitely have done with a better name - public enum Status - { - NotSupported, - Error, - Success - } -} +namespace Umbraco.Core.Media +{ + + //NOTE: Could definitely have done with a better name + public enum Status + { + NotSupported, + Error, + Success + } +} diff --git a/src/Umbraco.Core/Models/ApplicationTree.cs b/src/Umbraco.Core/Models/ApplicationTree.cs index 53873b8b90..8b0bbc29c4 100644 --- a/src/Umbraco.Core/Models/ApplicationTree.cs +++ b/src/Umbraco.Core/Models/ApplicationTree.cs @@ -1,141 +1,141 @@ -using System; -using System.Collections.Concurrent; -using System.Diagnostics; - -namespace Umbraco.Core.Models -{ - [DebuggerDisplay("Tree - {Title} ({ApplicationAlias})")] - public class ApplicationTree - { - private static readonly ConcurrentDictionary ResolvedTypes = new ConcurrentDictionary(); - - /// - /// Initializes a new instance of the class. - /// - public ApplicationTree() { } - - /// - /// Initializes a new instance of the class. - /// - /// if set to true [initialize]. - /// The sort order. - /// The application alias. - /// The tree alias. - /// The tree title. - /// The icon closed. - /// The icon opened. - /// The tree type. - public ApplicationTree(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) - { - Initialize = initialize; - SortOrder = sortOrder; - ApplicationAlias = applicationAlias; - Alias = alias; - Title = title; - IconClosed = iconClosed; - IconOpened = iconOpened; - Type = type; - } - - /// - /// Gets or sets a value indicating whether this should initialize. - /// - /// true if initialize; otherwise, false. - public bool Initialize { get; set; } - - /// - /// Gets or sets the sort order. - /// - /// The sort order. - public int SortOrder { get; set; } - - /// - /// Gets the application alias. - /// - /// The application alias. - public string ApplicationAlias { get; } - - /// - /// Gets the tree alias. - /// - /// The alias. - public string Alias { get; } - - /// - /// Gets or sets the tree title. - /// - /// The title. - public string Title { get; set; } - - /// - /// Gets or sets the icon closed. - /// - /// The icon closed. - public string IconClosed { get; set; } - - /// - /// Gets or sets the icon opened. - /// - /// The icon opened. - public string IconOpened { get; set; } - - /// - /// Gets or sets the tree type assembly name. - /// - /// The type. - public string Type { get; set; } - - private Type _runtimeType; - - /// - /// Returns the CLR type based on it's assembly name stored in the config - /// - /// - public Type GetRuntimeType() - { - return _runtimeType ?? (_runtimeType = System.Type.GetType(Type)); - } - - /// - /// Used to try to get and cache the tree type - /// - /// - /// - internal static Type TryGetType(string type) - { - try - { - return ResolvedTypes.GetOrAdd(type, s => - { - var result = System.Type.GetType(type); - if (result != null) - { - return result; - } - - //we need to implement a bit of a hack here due to some trees being renamed and backwards compat - var parts = type.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length != 2) - throw new InvalidOperationException("Could not resolve type"); - if (parts[1].Trim() != "Umbraco.Web" || parts[0].StartsWith("Umbraco.Web.Trees") == false || parts[0].EndsWith("Controller")) - throw new InvalidOperationException("Could not resolve type"); - - //if it's one of our controllers but it's not suffixed with "Controller" then add it and try again - var tempType = parts[0] + "Controller, Umbraco.Web"; - - result = System.Type.GetType(tempType); - if (result != null) - return result; - - throw new InvalidOperationException("Could not resolve type"); - }); - } - catch (InvalidOperationException) - { - //swallow, this is our own exception, couldn't find the type - // fixme bad use of exceptions here! - return null; - } - } - } -} +using System; +using System.Collections.Concurrent; +using System.Diagnostics; + +namespace Umbraco.Core.Models +{ + [DebuggerDisplay("Tree - {Title} ({ApplicationAlias})")] + public class ApplicationTree + { + private static readonly ConcurrentDictionary ResolvedTypes = new ConcurrentDictionary(); + + /// + /// Initializes a new instance of the class. + /// + public ApplicationTree() { } + + /// + /// Initializes a new instance of the class. + /// + /// if set to true [initialize]. + /// The sort order. + /// The application alias. + /// The tree alias. + /// The tree title. + /// The icon closed. + /// The icon opened. + /// The tree type. + public ApplicationTree(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) + { + Initialize = initialize; + SortOrder = sortOrder; + ApplicationAlias = applicationAlias; + Alias = alias; + Title = title; + IconClosed = iconClosed; + IconOpened = iconOpened; + Type = type; + } + + /// + /// Gets or sets a value indicating whether this should initialize. + /// + /// true if initialize; otherwise, false. + public bool Initialize { get; set; } + + /// + /// Gets or sets the sort order. + /// + /// The sort order. + public int SortOrder { get; set; } + + /// + /// Gets the application alias. + /// + /// The application alias. + public string ApplicationAlias { get; } + + /// + /// Gets the tree alias. + /// + /// The alias. + public string Alias { get; } + + /// + /// Gets or sets the tree title. + /// + /// The title. + public string Title { get; set; } + + /// + /// Gets or sets the icon closed. + /// + /// The icon closed. + public string IconClosed { get; set; } + + /// + /// Gets or sets the icon opened. + /// + /// The icon opened. + public string IconOpened { get; set; } + + /// + /// Gets or sets the tree type assembly name. + /// + /// The type. + public string Type { get; set; } + + private Type _runtimeType; + + /// + /// Returns the CLR type based on it's assembly name stored in the config + /// + /// + public Type GetRuntimeType() + { + return _runtimeType ?? (_runtimeType = System.Type.GetType(Type)); + } + + /// + /// Used to try to get and cache the tree type + /// + /// + /// + internal static Type TryGetType(string type) + { + try + { + return ResolvedTypes.GetOrAdd(type, s => + { + var result = System.Type.GetType(type); + if (result != null) + { + return result; + } + + //we need to implement a bit of a hack here due to some trees being renamed and backwards compat + var parts = type.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length != 2) + throw new InvalidOperationException("Could not resolve type"); + if (parts[1].Trim() != "Umbraco.Web" || parts[0].StartsWith("Umbraco.Web.Trees") == false || parts[0].EndsWith("Controller")) + throw new InvalidOperationException("Could not resolve type"); + + //if it's one of our controllers but it's not suffixed with "Controller" then add it and try again + var tempType = parts[0] + "Controller, Umbraco.Web"; + + result = System.Type.GetType(tempType); + if (result != null) + return result; + + throw new InvalidOperationException("Could not resolve type"); + }); + } + catch (InvalidOperationException) + { + //swallow, this is our own exception, couldn't find the type + // fixme bad use of exceptions here! + return null; + } + } + } +} diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 2861ad4b26..e671b45968 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -1,636 +1,636 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Exceptions; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Content object - /// - [Serializable] - [DataContract(IsReference = true)] - public class Content : ContentBase, IContent - { - private IContentType _contentType; - private ITemplate _template; - private bool _published; - private PublishedState _publishedState; - private DateTime? _releaseDate; - private DateTime? _expireDate; - private Dictionary _publishInfos; - private HashSet _edited; - - private static readonly Lazy Ps = new Lazy(); - - /// - /// Constructor for creating a Content object - /// - /// Name of the content - /// Parent object - /// ContentType for the current Content object - public Content(string name, IContent parent, IContentType contentType, string culture = null) - : this(name, parent, contentType, new PropertyCollection(), culture) - { } - - /// - /// Constructor for creating a Content object - /// - /// Name of the content - /// Parent object - /// ContentType for the current Content object - /// Collection of properties - public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties, string culture = null) - : base(name, parent, contentType, properties, culture) - { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _publishedState = PublishedState.Unpublished; - PublishedVersionId = 0; - } - - /// - /// Constructor for creating a Content object - /// - /// Name of the content - /// Id of the Parent content - /// ContentType for the current Content object - public Content(string name, int parentId, IContentType contentType, string culture = null) - : this(name, parentId, contentType, new PropertyCollection(), culture) - { } - - /// - /// Constructor for creating a Content object - /// - /// Name of the content - /// Id of the Parent content - /// ContentType for the current Content object - /// Collection of properties - public Content(string name, int parentId, IContentType contentType, PropertyCollection properties, string culture = null) - : base(name, parentId, contentType, properties, culture) - { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _publishedState = PublishedState.Unpublished; - PublishedVersionId = 0; - } - - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); - public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); - public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ReleaseDate); - public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate); - } - - /// - /// Gets or sets the template used by the Content. - /// This is used to override the default one from the ContentType. - /// - /// - /// If no template is explicitly set on the Content object, - /// the Default template from the ContentType will be returned. - /// - [DataMember] - public virtual ITemplate Template - { - get => _template ?? _contentType.DefaultTemplate; - set => SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector); - } - - /// - /// Gets the current status of the Content - /// - [IgnoreDataMember] - public ContentStatus Status - { - get - { - if(Trashed) - return ContentStatus.Trashed; - - if(ExpireDate.HasValue && ExpireDate.Value > DateTime.MinValue && DateTime.Now > ExpireDate.Value) - return ContentStatus.Expired; - - if(ReleaseDate.HasValue && ReleaseDate.Value > DateTime.MinValue && ReleaseDate.Value > DateTime.Now) - return ContentStatus.AwaitingRelease; - - if(Published) - return ContentStatus.Published; - - return ContentStatus.Unpublished; - } - } - - /// - /// Gets or sets a value indicating whether this content item is published or not. - /// - [DataMember] - public bool Published - { - get => _published; - - // the setter is internal and should only be invoked from - // - the ContentFactory when creating a content entity from a dto - // - the ContentRepository when updating a content entity - internal set - { - SetPropertyValueAndDetectChanges(value, ref _published, Ps.Value.PublishedSelector); - _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; - } - } - - /// - /// Gets the published state of the content item. - /// - /// The state should be Published or Unpublished, depending on whether Published - /// is true or false, but can also temporarily be Publishing or Unpublishing when the - /// content item is about to be saved. - [DataMember] - public PublishedState PublishedState - { - get => _publishedState; - set - { - if (value != PublishedState.Publishing && value != PublishedState.Unpublishing) - throw new ArgumentException("Invalid state, only Publishing and Unpublishing are accepted."); - _publishedState = value; - } - } - - [IgnoreDataMember] - public bool Edited { get; internal set; } - - /// - /// The date this Content should be released and thus be published - /// - [DataMember] - public DateTime? ReleaseDate - { - get => _releaseDate; - set => SetPropertyValueAndDetectChanges(value, ref _releaseDate, Ps.Value.ReleaseDateSelector); - } - - /// - /// The date this Content should expire and thus be unpublished - /// - [DataMember] - public DateTime? ExpireDate - { - get => _expireDate; - set => SetPropertyValueAndDetectChanges(value, ref _expireDate, Ps.Value.ExpireDateSelector); - } - - /// - /// Gets the ContentType used by this content object - /// - [IgnoreDataMember] - public IContentType ContentType => _contentType; - - [IgnoreDataMember] - public DateTime? PublishDate { get; internal set; } - - [IgnoreDataMember] - public int? PublisherId { get; internal set; } - - [IgnoreDataMember] - public ITemplate PublishTemplate { get; internal set; } - - [IgnoreDataMember] - public string PublishName { get; internal set; } - - // sets publish infos - // internal for repositories - // clear by clearing name - internal void SetPublishInfos(string culture, string name, DateTime date) - { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullOrEmptyException(nameof(name)); +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Content object + /// + [Serializable] + [DataContract(IsReference = true)] + public class Content : ContentBase, IContent + { + private IContentType _contentType; + private ITemplate _template; + private bool _published; + private PublishedState _publishedState; + private DateTime? _releaseDate; + private DateTime? _expireDate; + private Dictionary _publishInfos; + private HashSet _edited; + + private static readonly Lazy Ps = new Lazy(); + + /// + /// Constructor for creating a Content object + /// + /// Name of the content + /// Parent object + /// ContentType for the current Content object + public Content(string name, IContent parent, IContentType contentType, string culture = null) + : this(name, parent, contentType, new PropertyCollection(), culture) + { } + + /// + /// Constructor for creating a Content object + /// + /// Name of the content + /// Parent object + /// ContentType for the current Content object + /// Collection of properties + public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties, string culture = null) + : base(name, parent, contentType, properties, culture) + { + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + _publishedState = PublishedState.Unpublished; + PublishedVersionId = 0; + } + + /// + /// Constructor for creating a Content object + /// + /// Name of the content + /// Id of the Parent content + /// ContentType for the current Content object + public Content(string name, int parentId, IContentType contentType, string culture = null) + : this(name, parentId, contentType, new PropertyCollection(), culture) + { } + + /// + /// Constructor for creating a Content object + /// + /// Name of the content + /// Id of the Parent content + /// ContentType for the current Content object + /// Collection of properties + public Content(string name, int parentId, IContentType contentType, PropertyCollection properties, string culture = null) + : base(name, parentId, contentType, properties, culture) + { + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + _publishedState = PublishedState.Unpublished; + PublishedVersionId = 0; + } + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); + public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); + public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ReleaseDate); + public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate); + } + + /// + /// Gets or sets the template used by the Content. + /// This is used to override the default one from the ContentType. + /// + /// + /// If no template is explicitly set on the Content object, + /// the Default template from the ContentType will be returned. + /// + [DataMember] + public virtual ITemplate Template + { + get => _template ?? _contentType.DefaultTemplate; + set => SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector); + } + + /// + /// Gets the current status of the Content + /// + [IgnoreDataMember] + public ContentStatus Status + { + get + { + if(Trashed) + return ContentStatus.Trashed; + + if(ExpireDate.HasValue && ExpireDate.Value > DateTime.MinValue && DateTime.Now > ExpireDate.Value) + return ContentStatus.Expired; + + if(ReleaseDate.HasValue && ReleaseDate.Value > DateTime.MinValue && ReleaseDate.Value > DateTime.Now) + return ContentStatus.AwaitingRelease; + + if(Published) + return ContentStatus.Published; + + return ContentStatus.Unpublished; + } + } + + /// + /// Gets or sets a value indicating whether this content item is published or not. + /// + [DataMember] + public bool Published + { + get => _published; + + // the setter is internal and should only be invoked from + // - the ContentFactory when creating a content entity from a dto + // - the ContentRepository when updating a content entity + internal set + { + SetPropertyValueAndDetectChanges(value, ref _published, Ps.Value.PublishedSelector); + _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; + } + } + + /// + /// Gets the published state of the content item. + /// + /// The state should be Published or Unpublished, depending on whether Published + /// is true or false, but can also temporarily be Publishing or Unpublishing when the + /// content item is about to be saved. + [DataMember] + public PublishedState PublishedState + { + get => _publishedState; + set + { + if (value != PublishedState.Publishing && value != PublishedState.Unpublishing) + throw new ArgumentException("Invalid state, only Publishing and Unpublishing are accepted."); + _publishedState = value; + } + } + + [IgnoreDataMember] + public bool Edited { get; internal set; } + + /// + /// The date this Content should be released and thus be published + /// + [DataMember] + public DateTime? ReleaseDate + { + get => _releaseDate; + set => SetPropertyValueAndDetectChanges(value, ref _releaseDate, Ps.Value.ReleaseDateSelector); + } + + /// + /// The date this Content should expire and thus be unpublished + /// + [DataMember] + public DateTime? ExpireDate + { + get => _expireDate; + set => SetPropertyValueAndDetectChanges(value, ref _expireDate, Ps.Value.ExpireDateSelector); + } + + /// + /// Gets the ContentType used by this content object + /// + [IgnoreDataMember] + public IContentType ContentType => _contentType; + + [IgnoreDataMember] + public DateTime? PublishDate { get; internal set; } + + [IgnoreDataMember] + public int? PublisherId { get; internal set; } + + [IgnoreDataMember] + public ITemplate PublishTemplate { get; internal set; } + + [IgnoreDataMember] + public string PublishName { get; internal set; } + + // sets publish infos + // internal for repositories + // clear by clearing name + internal void SetPublishInfos(string culture, string name, DateTime date) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullOrEmptyException(nameof(name)); // this is the only place where we set PublishName (apart from factories etc), and we must ensure // that we do have an invariant name, as soon as we have a variant name, else we would end up not // being able to publish - and not being able to change the name, as PublishName is readonly. // see also: DocumentRepository.EnsureInvariantNameValues() - which deals with Name. // see also: U4-11286 - if (culture == null || string.IsNullOrEmpty(PublishName)) - { - PublishName = name; - PublishDate = date; + if (culture == null || string.IsNullOrEmpty(PublishName)) + { + PublishName = name; + PublishDate = date; } - - if (culture != null) + + if (culture != null) { // private method, assume that culture is valid if (_publishInfos == null) _publishInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); - _publishInfos[culture] = (name, date); - } - } - - /// - [IgnoreDataMember] - public IReadOnlyDictionary PublishCultureNames => _publishInfos?.ToDictionary(x => x.Key, x => x.Value.Name, StringComparer.OrdinalIgnoreCase) ?? NoNames; - - /// - public string GetPublishName(string culture) - { - if (culture == null) return PublishName; - if (_publishInfos == null) return null; - return _publishInfos.TryGetValue(culture, out var infos) ? infos.Name : null; - } - - // clears a publish name - private void ClearPublishName(string culture) - { - if (culture == null) - { - PublishName = null; - return; - } - - if (_publishInfos == null) return; - _publishInfos.Remove(culture); - if (_publishInfos.Count == 0) - _publishInfos = null; - } - - // clears all publish names - private void ClearPublishNames() - { - PublishName = null; - _publishInfos = null; - } - - /// - public bool IsCulturePublished(string culture) - => !string.IsNullOrWhiteSpace(GetPublishName(culture)); - - /// - public DateTime GetCulturePublishDate(string culture) - { - if (_publishInfos != null && _publishInfos.TryGetValue(culture, out var infos)) - return infos.Date; - throw new InvalidOperationException($"Culture \"{culture}\" is not published."); - } - - /// - public IEnumerable PublishedCultures => _publishInfos?.Keys ?? Enumerable.Empty(); - - /// - public bool IsCultureEdited(string culture) - { - return string.IsNullOrWhiteSpace(GetPublishName(culture)) || (_edited != null && _edited.Contains(culture)); - } - - // sets a publish edited - internal void SetCultureEdited(string culture) - { - if (_edited == null) - _edited = new HashSet(StringComparer.OrdinalIgnoreCase); - _edited.Add(culture); - } - - // sets all publish edited - internal void SetCultureEdited(IEnumerable cultures) - { - _edited = new HashSet(cultures, StringComparer.OrdinalIgnoreCase); - } - - /// - public IEnumerable EditedCultures => CultureNames.Keys.Where(IsCultureEdited); - - /// - public IEnumerable AvailableCultures => CultureNames.Keys; - - [IgnoreDataMember] - public int PublishedVersionId { get; internal set; } - - [DataMember] - public bool Blueprint { get; internal set; } - - /// - internal virtual bool TryPublishAllValues() - { - // the values we want to publish should be valid - if (ValidateAllProperties().Any()) - return false; //fixme this should return an attempt with error results - - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - if (string.IsNullOrWhiteSpace(Name)) - throw new InvalidOperationException($"Cannot publish invariant culture without a name."); - PublishName = Name; - var now = DateTime.Now; - foreach (var (culture, name) in CultureNames) - { - if (string.IsNullOrWhiteSpace(name)) - return false; //fixme this should return an attempt with error results - - SetPublishInfos(culture, name, now); - } - - // property.PublishAllValues only deals with supported variations (if any) - foreach (var property in Properties) - property.PublishAllValues(); - - _publishedState = PublishedState.Publishing; - return true; - } - - /// - public virtual bool TryPublishValues(string culture = null, string segment = null) - { - // the variation should be supported by the content type - ContentType.ValidateVariation(culture, segment, throwIfInvalid: true); - - // the values we want to publish should be valid - if (ValidateProperties(culture, segment).Any()) - return false; //fixme this should return an attempt with error results - - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - if (segment == null) - { - var name = GetName(culture); - if (string.IsNullOrWhiteSpace(name)) - return false; //fixme this should return an attempt with error results - - SetPublishInfos(culture, name, DateTime.Now); - } - - // property.PublishValue throws on invalid variation, so filter them out - foreach (var property in Properties.Where(x => x.PropertyType.ValidateVariation(culture, segment, throwIfInvalid: false))) - property.PublishValue(culture, segment); - - _publishedState = PublishedState.Publishing; - return true; - } - - /// - internal virtual bool PublishCultureValues(string culture = null) + _publishInfos[culture] = (name, date); + } + } + + /// + [IgnoreDataMember] + public IReadOnlyDictionary PublishCultureNames => _publishInfos?.ToDictionary(x => x.Key, x => x.Value.Name, StringComparer.OrdinalIgnoreCase) ?? NoNames; + + /// + public string GetPublishName(string culture) + { + if (culture == null) return PublishName; + if (_publishInfos == null) return null; + return _publishInfos.TryGetValue(culture, out var infos) ? infos.Name : null; + } + + // clears a publish name + private void ClearPublishName(string culture) + { + if (culture == null) + { + PublishName = null; + return; + } + + if (_publishInfos == null) return; + _publishInfos.Remove(culture); + if (_publishInfos.Count == 0) + _publishInfos = null; + } + + // clears all publish names + private void ClearPublishNames() + { + PublishName = null; + _publishInfos = null; + } + + /// + public bool IsCulturePublished(string culture) + => !string.IsNullOrWhiteSpace(GetPublishName(culture)); + + /// + public DateTime GetCulturePublishDate(string culture) + { + if (_publishInfos != null && _publishInfos.TryGetValue(culture, out var infos)) + return infos.Date; + throw new InvalidOperationException($"Culture \"{culture}\" is not published."); + } + + /// + public IEnumerable PublishedCultures => _publishInfos?.Keys ?? Enumerable.Empty(); + + /// + public bool IsCultureEdited(string culture) + { + return string.IsNullOrWhiteSpace(GetPublishName(culture)) || (_edited != null && _edited.Contains(culture)); + } + + // sets a publish edited + internal void SetCultureEdited(string culture) + { + if (_edited == null) + _edited = new HashSet(StringComparer.OrdinalIgnoreCase); + _edited.Add(culture); + } + + // sets all publish edited + internal void SetCultureEdited(IEnumerable cultures) + { + _edited = new HashSet(cultures, StringComparer.OrdinalIgnoreCase); + } + + /// + public IEnumerable EditedCultures => CultureNames.Keys.Where(IsCultureEdited); + + /// + public IEnumerable AvailableCultures => CultureNames.Keys; + + [IgnoreDataMember] + public int PublishedVersionId { get; internal set; } + + [DataMember] + public bool Blueprint { get; internal set; } + + /// + internal virtual bool TryPublishAllValues() + { + // the values we want to publish should be valid + if (ValidateAllProperties().Any()) + return false; //fixme this should return an attempt with error results + + // Name and PublishName are managed by the repository, but Names and PublishNames + // must be managed here as they depend on the existing / supported variations. + if (string.IsNullOrWhiteSpace(Name)) + throw new InvalidOperationException($"Cannot publish invariant culture without a name."); + PublishName = Name; + var now = DateTime.Now; + foreach (var (culture, name) in CultureNames) + { + if (string.IsNullOrWhiteSpace(name)) + return false; //fixme this should return an attempt with error results + + SetPublishInfos(culture, name, now); + } + + // property.PublishAllValues only deals with supported variations (if any) + foreach (var property in Properties) + property.PublishAllValues(); + + _publishedState = PublishedState.Publishing; + return true; + } + + /// + public virtual bool TryPublishValues(string culture = null, string segment = null) + { + // the variation should be supported by the content type + ContentType.ValidateVariation(culture, segment, throwIfInvalid: true); + + // the values we want to publish should be valid + if (ValidateProperties(culture, segment).Any()) + return false; //fixme this should return an attempt with error results + + // Name and PublishName are managed by the repository, but Names and PublishNames + // must be managed here as they depend on the existing / supported variations. + if (segment == null) + { + var name = GetName(culture); + if (string.IsNullOrWhiteSpace(name)) + return false; //fixme this should return an attempt with error results + + SetPublishInfos(culture, name, DateTime.Now); + } + + // property.PublishValue throws on invalid variation, so filter them out + foreach (var property in Properties.Where(x => x.PropertyType.ValidateVariation(culture, segment, throwIfInvalid: false))) + property.PublishValue(culture, segment); + + _publishedState = PublishedState.Publishing; + return true; + } + + /// + internal virtual bool PublishCultureValues(string culture = null) { //fixme - needs API review as this is not used apart from in tests - - // the values we want to publish should be valid - if (ValidatePropertiesForCulture(culture).Any()) - return false; - - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - var name = GetName(culture); - if (string.IsNullOrWhiteSpace(name)) - throw new InvalidOperationException($"Cannot publish {culture ?? "invariant"} culture without a name."); - SetPublishInfos(culture, name, DateTime.Now); - - // property.PublishCultureValues only deals with supported variations (if any) - foreach (var property in Properties) - property.PublishCultureValues(culture); - - _publishedState = PublishedState.Publishing; - return true; - } - - /// - public virtual void ClearAllPublishedValues() - { - // property.ClearPublishedAllValues only deals with supported variations (if any) - foreach (var property in Properties) - property.ClearPublishedAllValues(); - - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - ClearPublishNames(); - - _publishedState = PublishedState.Publishing; - } - - /// - public virtual void ClearPublishedValues(string culture = null, string segment = null) - { - // the variation should be supported by the content type - ContentType.ValidateVariation(culture, segment, throwIfInvalid: true); - - // property.ClearPublishedValue throws on invalid variation, so filter them out - foreach (var property in Properties.Where(x => x.PropertyType.ValidateVariation(culture, segment, throwIfInvalid: false))) - property.ClearPublishedValue(culture, segment); - - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - ClearPublishName(culture); - - _publishedState = PublishedState.Publishing; - } - - /// - public virtual void ClearCulturePublishedValues(string culture = null) - { - // property.ClearPublishedCultureValues only deals with supported variations (if any) - foreach (var property in Properties) - property.ClearPublishedCultureValues(culture); - - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - ClearPublishName(culture); - - _publishedState = PublishedState.Publishing; - } - - private bool CopyingFromSelf(IContent other) - { - // copying from the same Id and VersionPk - return Id == other.Id && VersionId == other.VersionId; - } - - /// - public virtual void CopyAllValues(IContent other) - { - if (other.ContentTypeId != ContentTypeId) - throw new InvalidOperationException("Cannot copy values from a different content type."); - - // we could copy from another document entirely, - // or from another version of the same document, - // in which case there is a special case. - var published = CopyingFromSelf(other); - - // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails - - // clear all existing properties - foreach (var property in Properties) - foreach (var pvalue in property.Values) - if (property.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) - property.SetValue(null, pvalue.Culture, pvalue.Segment); - - // copy other properties - var otherProperties = other.Properties; - foreach (var otherProperty in otherProperties) - { - var alias = otherProperty.PropertyType.Alias; - foreach (var pvalue in otherProperty.Values) - { - if (!otherProperty.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) - continue; - var value = published ? pvalue.PublishedValue : pvalue.EditedValue; - SetValue(alias, value, pvalue.Culture, pvalue.Segment); - } - } - - // copy names - ClearNames(); - foreach (var (culture, name) in other.CultureNames) - SetName(name, culture); - Name = other.Name; - } - - /// - public virtual void CopyValues(IContent other, string culture = null, string segment = null) - { - if (other.ContentTypeId != ContentTypeId) - throw new InvalidOperationException("Cannot copy values from a different content type."); - - var published = CopyingFromSelf(other); - - // segment is invariant in comparisons - segment = segment?.ToLowerInvariant(); - - // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails - - // clear all existing properties - foreach (var property in Properties) - { - if (!property.PropertyType.ValidateVariation(culture, segment, false)) - continue; - - foreach (var pvalue in property.Values) - if (pvalue.Culture.InvariantEquals(culture) && pvalue.Segment.InvariantEquals(segment)) - property.SetValue(null, pvalue.Culture, pvalue.Segment); - } - - // copy other properties - var otherProperties = other.Properties; - foreach (var otherProperty in otherProperties) - { - if (!otherProperty.PropertyType.ValidateVariation(culture, segment, false)) - continue; - - var alias = otherProperty.PropertyType.Alias; - SetValue(alias, otherProperty.GetValue(culture, segment, published), culture, segment); - } - - // copy name - SetName(other.GetName(culture), culture); - } - - /// - public virtual void CopyCultureValues(IContent other, string culture = null) - { - if (other.ContentTypeId != ContentTypeId) - throw new InvalidOperationException("Cannot copy values from a different content type."); - - var published = CopyingFromSelf(other); - - // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails - - // clear all existing properties - foreach (var property in Properties) - foreach (var pvalue in property.Values) - if (pvalue.Culture.InvariantEquals(culture) && property.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) - property.SetValue(null, pvalue.Culture, pvalue.Segment); - - // copy other properties - var otherProperties = other.Properties; - foreach (var otherProperty in otherProperties) - { - var alias = otherProperty.PropertyType.Alias; - foreach (var pvalue in otherProperty.Values) - { - if (pvalue.Culture != culture || !otherProperty.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) - continue; - var value = published ? pvalue.PublishedValue : pvalue.EditedValue; - SetValue(alias, value, pvalue.Culture, pvalue.Segment); - } - } - - // copy name - SetName(other.GetName(culture), culture); - } - - /// - /// Changes the for the current content object - /// - /// New ContentType for this content - /// Leaves PropertyTypes intact after change - public void ChangeContentType(IContentType contentType) - { - ContentTypeId = contentType.Id; - _contentType = contentType; - ContentTypeBase = contentType; - Properties.EnsurePropertyTypes(PropertyTypes); - Properties.CollectionChanged += PropertiesChanged; - } - - /// - /// Changes the for the current content object and removes PropertyTypes, - /// which are not part of the new ContentType. - /// - /// New ContentType for this content - /// Boolean indicating whether to clear PropertyTypes upon change - public void ChangeContentType(IContentType contentType, bool clearProperties) - { - if(clearProperties) - { - ContentTypeId = contentType.Id; - _contentType = contentType; - ContentTypeBase = contentType; - Properties.EnsureCleanPropertyTypes(PropertyTypes); - Properties.CollectionChanged += PropertiesChanged; - return; - } - - ChangeContentType(contentType); - } - - public override void ResetDirtyProperties(bool rememberDirty) - { - base.ResetDirtyProperties(rememberDirty); - - // take care of the published state - _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; - } - - /// - /// Creates a deep clone of the current entity with its identity and it's property identities reset - /// - /// - public IContent DeepCloneWithResetIdentities() - { - var clone = (Content)DeepClone(); - clone.Key = Guid.Empty; - clone.VersionId = clone.PublishedVersionId = 0; - clone.ResetIdentity(); - - foreach (var property in clone.Properties) - property.ResetIdentity(); - - return clone; - } - - public override object DeepClone() - { - var clone = (Content) base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //need to manually clone this since it's not settable - clone._contentType = (IContentType)ContentType.DeepClone(); - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; - - } - } -} + + // the values we want to publish should be valid + if (ValidatePropertiesForCulture(culture).Any()) + return false; + + // Name and PublishName are managed by the repository, but Names and PublishNames + // must be managed here as they depend on the existing / supported variations. + var name = GetName(culture); + if (string.IsNullOrWhiteSpace(name)) + throw new InvalidOperationException($"Cannot publish {culture ?? "invariant"} culture without a name."); + SetPublishInfos(culture, name, DateTime.Now); + + // property.PublishCultureValues only deals with supported variations (if any) + foreach (var property in Properties) + property.PublishCultureValues(culture); + + _publishedState = PublishedState.Publishing; + return true; + } + + /// + public virtual void ClearAllPublishedValues() + { + // property.ClearPublishedAllValues only deals with supported variations (if any) + foreach (var property in Properties) + property.ClearPublishedAllValues(); + + // Name and PublishName are managed by the repository, but Names and PublishNames + // must be managed here as they depend on the existing / supported variations. + ClearPublishNames(); + + _publishedState = PublishedState.Publishing; + } + + /// + public virtual void ClearPublishedValues(string culture = null, string segment = null) + { + // the variation should be supported by the content type + ContentType.ValidateVariation(culture, segment, throwIfInvalid: true); + + // property.ClearPublishedValue throws on invalid variation, so filter them out + foreach (var property in Properties.Where(x => x.PropertyType.ValidateVariation(culture, segment, throwIfInvalid: false))) + property.ClearPublishedValue(culture, segment); + + // Name and PublishName are managed by the repository, but Names and PublishNames + // must be managed here as they depend on the existing / supported variations. + ClearPublishName(culture); + + _publishedState = PublishedState.Publishing; + } + + /// + public virtual void ClearCulturePublishedValues(string culture = null) + { + // property.ClearPublishedCultureValues only deals with supported variations (if any) + foreach (var property in Properties) + property.ClearPublishedCultureValues(culture); + + // Name and PublishName are managed by the repository, but Names and PublishNames + // must be managed here as they depend on the existing / supported variations. + ClearPublishName(culture); + + _publishedState = PublishedState.Publishing; + } + + private bool CopyingFromSelf(IContent other) + { + // copying from the same Id and VersionPk + return Id == other.Id && VersionId == other.VersionId; + } + + /// + public virtual void CopyAllValues(IContent other) + { + if (other.ContentTypeId != ContentTypeId) + throw new InvalidOperationException("Cannot copy values from a different content type."); + + // we could copy from another document entirely, + // or from another version of the same document, + // in which case there is a special case. + var published = CopyingFromSelf(other); + + // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails + + // clear all existing properties + foreach (var property in Properties) + foreach (var pvalue in property.Values) + if (property.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) + property.SetValue(null, pvalue.Culture, pvalue.Segment); + + // copy other properties + var otherProperties = other.Properties; + foreach (var otherProperty in otherProperties) + { + var alias = otherProperty.PropertyType.Alias; + foreach (var pvalue in otherProperty.Values) + { + if (!otherProperty.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) + continue; + var value = published ? pvalue.PublishedValue : pvalue.EditedValue; + SetValue(alias, value, pvalue.Culture, pvalue.Segment); + } + } + + // copy names + ClearNames(); + foreach (var (culture, name) in other.CultureNames) + SetName(name, culture); + Name = other.Name; + } + + /// + public virtual void CopyValues(IContent other, string culture = null, string segment = null) + { + if (other.ContentTypeId != ContentTypeId) + throw new InvalidOperationException("Cannot copy values from a different content type."); + + var published = CopyingFromSelf(other); + + // segment is invariant in comparisons + segment = segment?.ToLowerInvariant(); + + // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails + + // clear all existing properties + foreach (var property in Properties) + { + if (!property.PropertyType.ValidateVariation(culture, segment, false)) + continue; + + foreach (var pvalue in property.Values) + if (pvalue.Culture.InvariantEquals(culture) && pvalue.Segment.InvariantEquals(segment)) + property.SetValue(null, pvalue.Culture, pvalue.Segment); + } + + // copy other properties + var otherProperties = other.Properties; + foreach (var otherProperty in otherProperties) + { + if (!otherProperty.PropertyType.ValidateVariation(culture, segment, false)) + continue; + + var alias = otherProperty.PropertyType.Alias; + SetValue(alias, otherProperty.GetValue(culture, segment, published), culture, segment); + } + + // copy name + SetName(other.GetName(culture), culture); + } + + /// + public virtual void CopyCultureValues(IContent other, string culture = null) + { + if (other.ContentTypeId != ContentTypeId) + throw new InvalidOperationException("Cannot copy values from a different content type."); + + var published = CopyingFromSelf(other); + + // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails + + // clear all existing properties + foreach (var property in Properties) + foreach (var pvalue in property.Values) + if (pvalue.Culture.InvariantEquals(culture) && property.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) + property.SetValue(null, pvalue.Culture, pvalue.Segment); + + // copy other properties + var otherProperties = other.Properties; + foreach (var otherProperty in otherProperties) + { + var alias = otherProperty.PropertyType.Alias; + foreach (var pvalue in otherProperty.Values) + { + if (pvalue.Culture != culture || !otherProperty.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) + continue; + var value = published ? pvalue.PublishedValue : pvalue.EditedValue; + SetValue(alias, value, pvalue.Culture, pvalue.Segment); + } + } + + // copy name + SetName(other.GetName(culture), culture); + } + + /// + /// Changes the for the current content object + /// + /// New ContentType for this content + /// Leaves PropertyTypes intact after change + public void ChangeContentType(IContentType contentType) + { + ContentTypeId = contentType.Id; + _contentType = contentType; + ContentTypeBase = contentType; + Properties.EnsurePropertyTypes(PropertyTypes); + Properties.CollectionChanged += PropertiesChanged; + } + + /// + /// Changes the for the current content object and removes PropertyTypes, + /// which are not part of the new ContentType. + /// + /// New ContentType for this content + /// Boolean indicating whether to clear PropertyTypes upon change + public void ChangeContentType(IContentType contentType, bool clearProperties) + { + if(clearProperties) + { + ContentTypeId = contentType.Id; + _contentType = contentType; + ContentTypeBase = contentType; + Properties.EnsureCleanPropertyTypes(PropertyTypes); + Properties.CollectionChanged += PropertiesChanged; + return; + } + + ChangeContentType(contentType); + } + + public override void ResetDirtyProperties(bool rememberDirty) + { + base.ResetDirtyProperties(rememberDirty); + + // take care of the published state + _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; + } + + /// + /// Creates a deep clone of the current entity with its identity and it's property identities reset + /// + /// + public IContent DeepCloneWithResetIdentities() + { + var clone = (Content)DeepClone(); + clone.Key = Guid.Empty; + clone.VersionId = clone.PublishedVersionId = 0; + clone.ResetIdentity(); + + foreach (var property in clone.Properties) + property.ResetIdentity(); + + return clone; + } + + public override object DeepClone() + { + var clone = (Content) base.DeepClone(); + //turn off change tracking + clone.DisableChangeTracking(); + //need to manually clone this since it's not settable + clone._contentType = (IContentType)ContentType.DeepClone(); + //this shouldn't really be needed since we're not tracking + clone.ResetDirtyProperties(false); + //re-enable tracking + clone.EnableChangeTracking(); + + return clone; + + } + } +} diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index d10b2e770d..04fbe269f8 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -1,318 +1,318 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using System.Web; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents an abstract class for base Content properties and methods - /// - [Serializable] - [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")] - public abstract class ContentBase : TreeEntityBase, IContentBase - { - protected static readonly Dictionary NoNames = new Dictionary(); - private static readonly Lazy Ps = new Lazy(); - - private int _contentTypeId; - protected IContentTypeComposition ContentTypeBase; - private int _writerId; - private PropertyCollection _properties; - private Dictionary _cultureInfos; - - /// - /// Initializes a new instance of the class. - /// - protected ContentBase(string name, int parentId, IContentTypeComposition contentType, PropertyCollection properties, string culture = null) - : this(name, contentType, properties, culture) - { - if (parentId == 0) throw new ArgumentOutOfRangeException(nameof(parentId)); - ParentId = parentId; - } - - /// - /// Initializes a new instance of the class. - /// - protected ContentBase(string name, IContentBase parent, IContentTypeComposition contentType, PropertyCollection properties, string culture = null) - : this(name, contentType, properties, culture) - { - if (parent == null) throw new ArgumentNullException(nameof(parent)); - SetParent(parent); - } - - private ContentBase(string name, IContentTypeComposition contentType, PropertyCollection properties, string culture = null) - { - ContentTypeBase = contentType ?? throw new ArgumentNullException(nameof(contentType)); - - // initially, all new instances have - Id = 0; // no identity - VersionId = 0; // no versions - - SetName(name, culture); - - _contentTypeId = contentType.Id; - _properties = properties ?? throw new ArgumentNullException(nameof(properties)); - _properties.EnsurePropertyTypes(PropertyTypes); - } - - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); - public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); - public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId); - public readonly PropertyInfo NamesSelector = ExpressionHelper.GetPropertyInfo>(x => x.CultureNames); - } - - protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(Ps.Value.PropertyCollectionSelector); - } - - /// - /// Id of the user who wrote/updated this entity - /// - [DataMember] - public virtual int WriterId - { - get => _writerId; - set => SetPropertyValueAndDetectChanges(value, ref _writerId, Ps.Value.WriterSelector); - } - - [IgnoreDataMember] - public int VersionId { get; internal set; } - - /// - /// Integer Id of the default ContentType - /// - [DataMember] - public virtual int ContentTypeId - { - get - { - //There will be cases where this has not been updated to reflect the true content type ID. - //This will occur when inserting new content. - if (_contentTypeId == 0 && ContentTypeBase != null && ContentTypeBase.HasIdentity) - { - _contentTypeId = ContentTypeBase.Id; - } - return _contentTypeId; - } - protected set => SetPropertyValueAndDetectChanges(value, ref _contentTypeId, Ps.Value.DefaultContentTypeIdSelector); - } - - /// - /// Gets or sets the collection of properties for the entity. - /// - [DataMember] - public virtual PropertyCollection Properties - { - get => _properties; - set - { - _properties = value; - _properties.CollectionChanged += PropertiesChanged; - } - } - - /// - /// Gets the enumeration of property groups for the entity. - /// fixme is a proxy, kill this - /// - [IgnoreDataMember] - public IEnumerable PropertyGroups => ContentTypeBase.CompositionPropertyGroups; - - /// - /// Gets the numeration of property types for the entity. - /// fixme is a proxy, kill this - /// - [IgnoreDataMember] - public IEnumerable PropertyTypes => ContentTypeBase.CompositionPropertyTypes; - - #region Cultures - - /// - [DataMember] - public virtual IReadOnlyDictionary CultureNames => _cultureInfos?.ToDictionary(x => x.Key, x => x.Value.Name, StringComparer.OrdinalIgnoreCase) ?? NoNames; - - // sets culture infos - // internal for repositories - // clear by clearing name - internal void SetCultureInfos(string culture, string name, DateTime date) - { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullOrEmptyException(nameof(name)); - - if (culture == null) - { - Name = name; - return; - } - - // private method, assume that culture is valid - - if (_cultureInfos == null) - _cultureInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); - - _cultureInfos[culture] = (name, date); - } - - /// - public virtual void SetName(string name, string culture) - { - if (string.IsNullOrWhiteSpace(name)) - { - ClearName(culture); - return; - } - - if (culture == null) - { - Name = name; - return; - } - - if (ContentTypeBase.Variations.DoesNotSupportCulture()) - throw new NotSupportedException("Content type does not support varying name by culture."); - - if (_cultureInfos == null) - _cultureInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); - - _cultureInfos[culture] = (name, DateTime.Now); - OnPropertyChanged(Ps.Value.NamesSelector); - } - - /// - public virtual string GetName(string culture) - { - if (culture == null) return Name; - if (_cultureInfos == null) return null; - return _cultureInfos.TryGetValue(culture, out var infos) ? infos.Name : null; - } - - /// - public bool IsCultureAvailable(string culture) - => !string.IsNullOrWhiteSpace(GetName(culture)); - - private void ClearName(string culture) - { - if (culture == null) - { - Name = null; - return; - } - - if (ContentTypeBase.Variations.DoesNotSupportCulture()) - throw new NotSupportedException("Content type does not support varying name by culture."); - - if (_cultureInfos == null) return; - if (!_cultureInfos.ContainsKey(culture)) - throw new InvalidOperationException($"Cannot unpublish culture {culture}, the document contains only cultures {string.Join(", ", _cultureInfos.Keys)}"); - - _cultureInfos.Remove(culture); - if (_cultureInfos.Count == 0) - _cultureInfos = null; - } - - protected virtual void ClearNames() - { - if (ContentTypeBase.Variations.DoesNotSupportCulture()) - throw new NotSupportedException("Content type does not support varying name by culture."); - - _cultureInfos = null; - OnPropertyChanged(Ps.Value.NamesSelector); - } - - /// - public DateTime GetCultureDate(string culture) - { - if (_cultureInfos != null && _cultureInfos.TryGetValue(culture, out var infos)) - return infos.Date; - throw new InvalidOperationException($"Culture \"{culture}\" is not available."); - } - - #endregion - - #region Has, Get, Set, Publish Property Value - - /// - public virtual bool HasProperty(string propertyTypeAlias) - => Properties.Contains(propertyTypeAlias); - - /// - public virtual object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) - { - return Properties.TryGetValue(propertyTypeAlias, out var property) - ? property.GetValue(culture, segment, published) - : null; - } - - /// - public virtual TValue GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) - { - if (!Properties.TryGetValue(propertyTypeAlias, out var property)) - return default; - - var convertAttempt = property.GetValue(culture, segment, published).TryConvertTo(); - return convertAttempt.Success ? convertAttempt.Result : default; - } - - /// - public virtual void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null) - { - if (Properties.Contains(propertyTypeAlias)) - { - Properties[propertyTypeAlias].SetValue(value, culture, segment); - return; - } - - var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); - if (propertyType == null) - throw new InvalidOperationException($"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\"."); - - var property = propertyType.CreateProperty(); - property.SetValue(value, culture, segment); - Properties.Add(property); - } - - // HttpPostedFileBase is the base class that can be mocked - // HttpPostedFile is what we get in ASP.NET - // HttpPostedFileWrapper wraps sealed HttpPostedFile as HttpPostedFileBase - - /// - /// Sets the posted file value of a property. - /// - public virtual void SetValue(string propertyTypeAlias, HttpPostedFile value, string culture = null, string segment = null) - { - ContentExtensions.SetValue(this, propertyTypeAlias, new HttpPostedFileWrapper(value), culture, segment); - } - - /// - /// Sets the posted file value of a property. - /// - public virtual void SetValue(string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null) - { - ContentExtensions.SetValue(this, propertyTypeAlias, value, culture, segment); - } - - #endregion - - #region Validation - - internal virtual Property[] ValidateAllProperties() +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Web; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents an abstract class for base Content properties and methods + /// + [Serializable] + [DataContract(IsReference = true)] + [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")] + public abstract class ContentBase : TreeEntityBase, IContentBase + { + protected static readonly Dictionary NoNames = new Dictionary(); + private static readonly Lazy Ps = new Lazy(); + + private int _contentTypeId; + protected IContentTypeComposition ContentTypeBase; + private int _writerId; + private PropertyCollection _properties; + private Dictionary _cultureInfos; + + /// + /// Initializes a new instance of the class. + /// + protected ContentBase(string name, int parentId, IContentTypeComposition contentType, PropertyCollection properties, string culture = null) + : this(name, contentType, properties, culture) + { + if (parentId == 0) throw new ArgumentOutOfRangeException(nameof(parentId)); + ParentId = parentId; + } + + /// + /// Initializes a new instance of the class. + /// + protected ContentBase(string name, IContentBase parent, IContentTypeComposition contentType, PropertyCollection properties, string culture = null) + : this(name, contentType, properties, culture) + { + if (parent == null) throw new ArgumentNullException(nameof(parent)); + SetParent(parent); + } + + private ContentBase(string name, IContentTypeComposition contentType, PropertyCollection properties, string culture = null) + { + ContentTypeBase = contentType ?? throw new ArgumentNullException(nameof(contentType)); + + // initially, all new instances have + Id = 0; // no identity + VersionId = 0; // no versions + + SetName(name, culture); + + _contentTypeId = contentType.Id; + _properties = properties ?? throw new ArgumentNullException(nameof(properties)); + _properties.EnsurePropertyTypes(PropertyTypes); + } + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); + public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId); + public readonly PropertyInfo NamesSelector = ExpressionHelper.GetPropertyInfo>(x => x.CultureNames); + } + + protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(Ps.Value.PropertyCollectionSelector); + } + + /// + /// Id of the user who wrote/updated this entity + /// + [DataMember] + public virtual int WriterId + { + get => _writerId; + set => SetPropertyValueAndDetectChanges(value, ref _writerId, Ps.Value.WriterSelector); + } + + [IgnoreDataMember] + public int VersionId { get; internal set; } + + /// + /// Integer Id of the default ContentType + /// + [DataMember] + public virtual int ContentTypeId + { + get + { + //There will be cases where this has not been updated to reflect the true content type ID. + //This will occur when inserting new content. + if (_contentTypeId == 0 && ContentTypeBase != null && ContentTypeBase.HasIdentity) + { + _contentTypeId = ContentTypeBase.Id; + } + return _contentTypeId; + } + protected set => SetPropertyValueAndDetectChanges(value, ref _contentTypeId, Ps.Value.DefaultContentTypeIdSelector); + } + + /// + /// Gets or sets the collection of properties for the entity. + /// + [DataMember] + public virtual PropertyCollection Properties + { + get => _properties; + set + { + _properties = value; + _properties.CollectionChanged += PropertiesChanged; + } + } + + /// + /// Gets the enumeration of property groups for the entity. + /// fixme is a proxy, kill this + /// + [IgnoreDataMember] + public IEnumerable PropertyGroups => ContentTypeBase.CompositionPropertyGroups; + + /// + /// Gets the numeration of property types for the entity. + /// fixme is a proxy, kill this + /// + [IgnoreDataMember] + public IEnumerable PropertyTypes => ContentTypeBase.CompositionPropertyTypes; + + #region Cultures + + /// + [DataMember] + public virtual IReadOnlyDictionary CultureNames => _cultureInfos?.ToDictionary(x => x.Key, x => x.Value.Name, StringComparer.OrdinalIgnoreCase) ?? NoNames; + + // sets culture infos + // internal for repositories + // clear by clearing name + internal void SetCultureInfos(string culture, string name, DateTime date) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullOrEmptyException(nameof(name)); + + if (culture == null) + { + Name = name; + return; + } + + // private method, assume that culture is valid + + if (_cultureInfos == null) + _cultureInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); + + _cultureInfos[culture] = (name, date); + } + + /// + public virtual void SetName(string name, string culture) + { + if (string.IsNullOrWhiteSpace(name)) + { + ClearName(culture); + return; + } + + if (culture == null) + { + Name = name; + return; + } + + if (ContentTypeBase.Variations.DoesNotSupportCulture()) + throw new NotSupportedException("Content type does not support varying name by culture."); + + if (_cultureInfos == null) + _cultureInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); + + _cultureInfos[culture] = (name, DateTime.Now); + OnPropertyChanged(Ps.Value.NamesSelector); + } + + /// + public virtual string GetName(string culture) + { + if (culture == null) return Name; + if (_cultureInfos == null) return null; + return _cultureInfos.TryGetValue(culture, out var infos) ? infos.Name : null; + } + + /// + public bool IsCultureAvailable(string culture) + => !string.IsNullOrWhiteSpace(GetName(culture)); + + private void ClearName(string culture) + { + if (culture == null) + { + Name = null; + return; + } + + if (ContentTypeBase.Variations.DoesNotSupportCulture()) + throw new NotSupportedException("Content type does not support varying name by culture."); + + if (_cultureInfos == null) return; + if (!_cultureInfos.ContainsKey(culture)) + throw new InvalidOperationException($"Cannot unpublish culture {culture}, the document contains only cultures {string.Join(", ", _cultureInfos.Keys)}"); + + _cultureInfos.Remove(culture); + if (_cultureInfos.Count == 0) + _cultureInfos = null; + } + + protected virtual void ClearNames() + { + if (ContentTypeBase.Variations.DoesNotSupportCulture()) + throw new NotSupportedException("Content type does not support varying name by culture."); + + _cultureInfos = null; + OnPropertyChanged(Ps.Value.NamesSelector); + } + + /// + public DateTime GetCultureDate(string culture) + { + if (_cultureInfos != null && _cultureInfos.TryGetValue(culture, out var infos)) + return infos.Date; + throw new InvalidOperationException($"Culture \"{culture}\" is not available."); + } + + #endregion + + #region Has, Get, Set, Publish Property Value + + /// + public virtual bool HasProperty(string propertyTypeAlias) + => Properties.Contains(propertyTypeAlias); + + /// + public virtual object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) + { + return Properties.TryGetValue(propertyTypeAlias, out var property) + ? property.GetValue(culture, segment, published) + : null; + } + + /// + public virtual TValue GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) + { + if (!Properties.TryGetValue(propertyTypeAlias, out var property)) + return default; + + var convertAttempt = property.GetValue(culture, segment, published).TryConvertTo(); + return convertAttempt.Success ? convertAttempt.Result : default; + } + + /// + public virtual void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null) + { + if (Properties.Contains(propertyTypeAlias)) + { + Properties[propertyTypeAlias].SetValue(value, culture, segment); + return; + } + + var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (propertyType == null) + throw new InvalidOperationException($"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\"."); + + var property = propertyType.CreateProperty(); + property.SetValue(value, culture, segment); + Properties.Add(property); + } + + // HttpPostedFileBase is the base class that can be mocked + // HttpPostedFile is what we get in ASP.NET + // HttpPostedFileWrapper wraps sealed HttpPostedFile as HttpPostedFileBase + + /// + /// Sets the posted file value of a property. + /// + public virtual void SetValue(string propertyTypeAlias, HttpPostedFile value, string culture = null, string segment = null) + { + ContentExtensions.SetValue(this, propertyTypeAlias, new HttpPostedFileWrapper(value), culture, segment); + } + + /// + /// Sets the posted file value of a property. + /// + public virtual void SetValue(string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null) + { + ContentExtensions.SetValue(this, propertyTypeAlias, value, culture, segment); + } + + #endregion + + #region Validation + + internal virtual Property[] ValidateAllProperties() { //fixme - needs API review as this is not used apart from in tests - - return Properties.Where(x => !x.IsAllValid()).ToArray(); - } + + return Properties.Where(x => !x.IsAllValid()).ToArray(); + } /// public bool IsValid(string culture = null, string segment = null) @@ -322,9 +322,9 @@ namespace Umbraco.Core.Models return ValidateProperties(culture, segment).Length == 0; } - /// - public virtual Property[] ValidateProperties(string culture = null, string segment = null) - { + /// + public virtual Property[] ValidateProperties(string culture = null, string segment = null) + { return Properties.Where(x => { if (!culture.IsNullOrWhiteSpace() && x.PropertyType.Variations.DoesNotSupportCulture()) @@ -336,99 +336,99 @@ namespace Umbraco.Core.Models if (segment.IsNullOrWhiteSpace() && x.PropertyType.Variations.DoesNotSupportNeutral()) return false; //no segment, this prop is only non segment neutral, ignore return !x.IsValid(culture, segment); - }).ToArray(); - } - - internal virtual Property[] ValidatePropertiesForCulture(string culture = null) + }).ToArray(); + } + + internal virtual Property[] ValidatePropertiesForCulture(string culture = null) { //fixme - needs API review as this is not used apart from in tests - - return Properties.Where(x => !x.IsCultureValid(culture)).ToArray(); - } - - #endregion - - #region Dirty - - /// - /// Overriden to include user properties. - public override void ResetDirtyProperties(bool rememberDirty) - { - base.ResetDirtyProperties(rememberDirty); - - // also reset dirty changes made to user's properties - foreach (var prop in Properties) - prop.ResetDirtyProperties(rememberDirty); - } - - /// - /// Overriden to include user properties. - public override bool IsDirty() - { - return IsEntityDirty() || this.IsAnyUserPropertyDirty(); - } - - /// - /// Overriden to include user properties. - public override bool WasDirty() - { - return WasEntityDirty() || this.WasAnyUserPropertyDirty(); - } - - /// - /// Gets a value indicating whether the current entity's own properties (not user) are dirty. - /// - public bool IsEntityDirty() - { - return base.IsDirty(); - } - - /// - /// Gets a value indicating whether the current entity's own properties (not user) were dirty. - /// - public bool WasEntityDirty() - { - return base.WasDirty(); - } - - /// - /// Overriden to include user properties. - public override bool IsPropertyDirty(string propertyName) - { - if (base.IsPropertyDirty(propertyName)) - return true; - - return Properties.Contains(propertyName) && Properties[propertyName].IsDirty(); - } - - /// - /// Overriden to include user properties. - public override bool WasPropertyDirty(string propertyName) - { - if (base.WasPropertyDirty(propertyName)) - return true; - - return Properties.Contains(propertyName) && Properties[propertyName].WasDirty(); - } - - /// - /// Overriden to include user properties. - public override IEnumerable GetDirtyProperties() - { - var instanceProperties = base.GetDirtyProperties(); - var propertyTypes = Properties.Where(x => x.IsDirty()).Select(x => x.Alias); - return instanceProperties.Concat(propertyTypes); - } - - /// - /// Overriden to include user properties. - public override IEnumerable GetWereDirtyProperties() - { - var instanceProperties = base.GetWereDirtyProperties(); - var propertyTypes = Properties.Where(x => x.WasDirty()).Select(x => x.Alias); - return instanceProperties.Concat(propertyTypes); - } - - #endregion - } -} + + return Properties.Where(x => !x.IsCultureValid(culture)).ToArray(); + } + + #endregion + + #region Dirty + + /// + /// Overriden to include user properties. + public override void ResetDirtyProperties(bool rememberDirty) + { + base.ResetDirtyProperties(rememberDirty); + + // also reset dirty changes made to user's properties + foreach (var prop in Properties) + prop.ResetDirtyProperties(rememberDirty); + } + + /// + /// Overriden to include user properties. + public override bool IsDirty() + { + return IsEntityDirty() || this.IsAnyUserPropertyDirty(); + } + + /// + /// Overriden to include user properties. + public override bool WasDirty() + { + return WasEntityDirty() || this.WasAnyUserPropertyDirty(); + } + + /// + /// Gets a value indicating whether the current entity's own properties (not user) are dirty. + /// + public bool IsEntityDirty() + { + return base.IsDirty(); + } + + /// + /// Gets a value indicating whether the current entity's own properties (not user) were dirty. + /// + public bool WasEntityDirty() + { + return base.WasDirty(); + } + + /// + /// Overriden to include user properties. + public override bool IsPropertyDirty(string propertyName) + { + if (base.IsPropertyDirty(propertyName)) + return true; + + return Properties.Contains(propertyName) && Properties[propertyName].IsDirty(); + } + + /// + /// Overriden to include user properties. + public override bool WasPropertyDirty(string propertyName) + { + if (base.WasPropertyDirty(propertyName)) + return true; + + return Properties.Contains(propertyName) && Properties[propertyName].WasDirty(); + } + + /// + /// Overriden to include user properties. + public override IEnumerable GetDirtyProperties() + { + var instanceProperties = base.GetDirtyProperties(); + var propertyTypes = Properties.Where(x => x.IsDirty()).Select(x => x.Alias); + return instanceProperties.Concat(propertyTypes); + } + + /// + /// Overriden to include user properties. + public override IEnumerable GetWereDirtyProperties() + { + var instanceProperties = base.GetWereDirtyProperties(); + var propertyTypes = Properties.Where(x => x.WasDirty()).Select(x => x.Alias); + return instanceProperties.Concat(propertyTypes); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index 0c232ff1e5..2c013e9b90 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -1,516 +1,516 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Web; -using System.Xml.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core.Composing; -using Umbraco.Core.IO; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; - -namespace Umbraco.Core.Models -{ - public static class ContentExtensions - { - // this ain't pretty - private static MediaFileSystem _mediaFileSystem; - private static MediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem); - - #region IContent - - /// - /// Returns true if this entity was just published as part of a recent save operation (i.e. it wasn't previously published) - /// - /// - /// - /// - /// This is helpful for determining if the published event will execute during the saved event for a content item. - /// - internal static bool JustPublished(this IContent entity) - { - var dirty = (IRememberBeingDirty)entity; - return dirty.WasPropertyDirty("Published") && entity.Published; - } - - /// - /// Returns a list of the current contents ancestors, not including the content itself. - /// - /// Current content - /// - /// An enumerable list of objects - public static IEnumerable Ancestors(this IContent content, IContentService contentService) - { - return contentService.GetAncestors(content); - } - - /// - /// Returns a list of the current contents children. - /// - /// Current content - /// - /// An enumerable list of objects - public static IEnumerable Children(this IContent content, IContentService contentService) - { - return contentService.GetChildren(content.Id); - } - - /// - /// Returns a list of the current contents descendants, not including the content itself. - /// - /// Current content - /// - /// An enumerable list of objects - public static IEnumerable Descendants(this IContent content, IContentService contentService) - { - return contentService.GetDescendants(content); - } - - /// - /// Returns the parent of the current content. - /// - /// Current content - /// - /// An object - public static IContent Parent(this IContent content, IContentService contentService) - { - return contentService.GetById(content.ParentId); - } - - #endregion - - #region IMedia - - /// - /// Returns a list of the current medias ancestors, not including the media itself. - /// - /// Current media - /// - /// An enumerable list of objects - public static IEnumerable Ancestors(this IMedia media, IMediaService mediaService) - { - return mediaService.GetAncestors(media); - } - - [Obsolete("Use the overload with the service reference instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IEnumerable Ancestors(this IMedia media) - { - return Current.Services.MediaService.GetAncestors(media); - } - - /// - /// Returns a list of the current medias children. - /// - /// Current media - /// - /// An enumerable list of objects - public static IEnumerable Children(this IMedia media, IMediaService mediaService) - { - return mediaService.GetChildren(media.Id); - } - - [Obsolete("Use the overload with the service reference instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IEnumerable Children(this IMedia media) - { - return Current.Services.MediaService.GetChildren(media.Id); - } - - /// - /// Returns a list of the current medias descendants, not including the media itself. - /// - /// Current media - /// - /// An enumerable list of objects - public static IEnumerable Descendants(this IMedia media, IMediaService mediaService) - { - return mediaService.GetDescendants(media); - } - - [Obsolete("Use the overload with the service reference instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IEnumerable Descendants(this IMedia media) - { - return Current.Services.MediaService.GetDescendants(media); - } - - /// - /// Returns the parent of the current media. - /// - /// Current media - /// - /// An object - public static IMedia Parent(this IMedia media, IMediaService mediaService) - { - return mediaService.GetById(media.ParentId); - } - - [Obsolete("Use the overload with the service reference instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IMedia Parent(this IMedia media) - { - return Current.Services.MediaService.GetById(media.ParentId); - } - #endregion - - /// - /// Removes characters that are not valide XML characters from all entity properties - /// of type string. See: http://stackoverflow.com/a/961504/5018 - /// - /// - /// - /// If this is not done then the xml cache can get corrupt and it will throw YSODs upon reading it. - /// - /// - public static void SanitizeEntityPropertiesForXmlStorage(this IContentBase entity) - { - entity.Name = entity.Name.ToValidXmlString(); - foreach (var property in entity.Properties) - { - foreach (var propertyValue in property.Values) - { - if (propertyValue.EditedValue is string editString) - propertyValue.EditedValue = editString.ToValidXmlString(); - if (propertyValue.PublishedValue is string publishedString) - propertyValue.PublishedValue = publishedString.ToValidXmlString(); - } - } - } - - /// - /// Checks if the IContentBase has children - /// - /// - /// - /// - /// - /// This is a bit of a hack because we need to type check! - /// - internal static bool HasChildren(IContentBase content, ServiceContext services) - { - if (content is IContent) - { - return services.ContentService.HasChildren(content.Id); - } - if (content is IMedia) - { - return services.MediaService.HasChildren(content.Id); - } - return false; - } - - /// - /// Returns the children for the content base item - /// - /// - /// - /// - /// - /// This is a bit of a hack because we need to type check! - /// - internal static IEnumerable Children(IContentBase content, ServiceContext services) - { - if (content is IContent) - { - return services.ContentService.GetChildren(content.Id); - } - if (content is IMedia) - { - return services.MediaService.GetChildren(content.Id); - } - return null; - } - - /// - /// Returns properties that do not belong to a group - /// - /// - /// - public static IEnumerable GetNonGroupedProperties(this IContentBase content) - { - var propertyIdsInTabs = content.PropertyGroups.SelectMany(pg => pg.PropertyTypes); - return content.Properties - .Where(property => propertyIdsInTabs.Contains(property.PropertyType) == false) - .OrderBy(x => x.PropertyType.SortOrder); - } - - /// - /// Returns the Property object for the given property group - /// - /// - /// - /// - public static IEnumerable GetPropertiesForGroup(this IContentBase content, PropertyGroup propertyGroup) - { - //get the properties for the current tab - return content.Properties - .Where(property => propertyGroup.PropertyTypes - .Select(propertyType => propertyType.Id) - .Contains(property.PropertyTypeId)); - } - - public static IContentTypeComposition GetContentType(this IContentBase contentBase) - { - if (contentBase == null) throw new ArgumentNullException(nameof(contentBase)); - - if (contentBase is IContent content) return content.ContentType; - if (contentBase is IMedia media) return media.ContentType; - if (contentBase is IMember member) return member.ContentType; - throw new NotSupportedException("Unsupported IContentBase implementation: " + contentBase.GetType().FullName + "."); - } - - #region SetValue for setting file contents - - /// - /// Sets the posted file value of a property. - /// - public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null) - { - // ensure we get the filename without the path in IE in intranet mode - // http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie - var filename = value.FileName; - var pos = filename.LastIndexOf(@"\", StringComparison.InvariantCulture); - if (pos > 0) - filename = filename.Substring(pos + 1); - - // strip any directory info - pos = filename.LastIndexOf(IOHelper.DirSepChar); - if (pos > 0) - filename = filename.Substring(pos + 1); - - // get a safe & clean filename - filename = IOHelper.SafeFileName(filename); - if (string.IsNullOrWhiteSpace(filename)) return; - filename = filename.ToLower(); // fixme - er... why? - - MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream, culture, segment); - } - - /// - /// Sets the posted file value of a property. - /// - /// This really is for FileUpload fields only, and should be obsoleted. For anything else, - /// you need to store the file by yourself using Store and then figure out - /// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself. - public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) - { - if (filename == null || filestream == null) return; - - // get a safe & clean filename - filename = IOHelper.SafeFileName(filename); - if (string.IsNullOrWhiteSpace(filename)) return; - filename = filename.ToLower(); // fixme - er... why? - - MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream, culture, segment); - } - - /// - /// Stores a file. - /// - /// A content item. - /// The property alias. - /// The name of the file. - /// A stream containing the file data. - /// The original file path, if any. - /// The path to the file, relative to the media filesystem. - /// - /// Does NOT set the property value, so one should probably store the file and then do - /// something alike: property.Value = MediaHelper.FileSystem.GetUrl(filepath). - /// The original file path is used, in the old media file path scheme, to try and reuse - /// the "folder number" that was assigned to the previous file referenced by the property, - /// if any. - /// - public static string StoreFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string filepath) - { - var propertyType = content.GetContentType() - .CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); - if (propertyType == null) throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + "."); - return MediaFileSystem.StoreFile(content, propertyType, filename, filestream, filepath); - } - - #endregion - - #region User/Profile methods - - - [Obsolete("Use the overload that declares the IUserService to use")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IProfile GetCreatorProfile(this IMedia media) - { - return Current.Services.UserService.GetProfileById(media.CreatorId); - } - - /// - /// Gets the for the Creator of this media item. - /// - public static IProfile GetCreatorProfile(this IMedia media, IUserService userService) - { - return userService.GetProfileById(media.CreatorId); - } - - [Obsolete("Use the overload that declares the IUserService to use")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IProfile GetCreatorProfile(this IContentBase content) - { - return Current.Services.UserService.GetProfileById(content.CreatorId); - } - - /// - /// Gets the for the Creator of this content item. - /// - public static IProfile GetCreatorProfile(this IContentBase content, IUserService userService) - { - return userService.GetProfileById(content.CreatorId); - } - - [Obsolete("Use the overload that declares the IUserService to use")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IProfile GetWriterProfile(this IContent content) - { - return Current.Services.UserService.GetProfileById(content.WriterId); - } - - /// - /// Gets the for the Writer of this content. - /// - public static IProfile GetWriterProfile(this IContent content, IUserService userService) - { - return userService.GetProfileById(content.WriterId); - } - - /// - /// Gets the for the Writer of this content. - /// - public static IProfile GetWriterProfile(this IMedia content, IUserService userService) - { - return userService.GetProfileById(content.WriterId); - } - - #endregion - - #region XML methods - - /// - /// Creates the full xml representation for the object and all of it's descendants - /// - /// to generate xml for - /// - /// Xml representation of the passed in - internal static XElement ToDeepXml(this IContent content, IPackagingService packagingService) - { - return packagingService.Export(content, true, raiseEvents: false); - } - - - [Obsolete("Use the overload that declares the IPackagingService to use")] - public static XElement ToXml(this IContent content) - { - return Current.Services.PackagingService.Export(content, raiseEvents: false); - } - - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// - /// Xml representation of the passed in - public static XElement ToXml(this IContent content, IPackagingService packagingService) - { - return packagingService.Export(content, raiseEvents: false); - } - - [Obsolete("Use the overload that declares the IPackagingService to use")] - public static XElement ToXml(this IMedia media) - { - return Current.Services.PackagingService.Export(media, raiseEvents: false); - } - - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// - /// Xml representation of the passed in - public static XElement ToXml(this IMedia media, IPackagingService packagingService) - { - return packagingService.Export(media, raiseEvents: false); - } - - /// - /// Creates the full xml representation for the object and all of it's descendants - /// - /// to generate xml for - /// - /// Xml representation of the passed in - internal static XElement ToDeepXml(this IMedia media, IPackagingService packagingService) - { - return packagingService.Export(media, true, raiseEvents: false); - } - - [Obsolete("Use the overload that declares the IPackagingService to use")] - public static XElement ToXml(this IContent content, bool isPreview) - { - //TODO Do a proper implementation of this - //If current IContent is published we should get latest unpublished version - return content.ToXml(); - } - - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// - /// Boolean indicating whether the xml should be generated for preview - /// Xml representation of the passed in - public static XElement ToXml(this IContent content, IPackagingService packagingService, bool isPreview) - { - //TODO Do a proper implementation of this - //If current IContent is published we should get latest unpublished version - return content.ToXml(packagingService); - } - - [Obsolete("Use the overload that declares the IPackagingService to use")] - public static XElement ToXml(this IMember member) - { - return ((PackagingService)(Current.Services.PackagingService)).Export(member); - } - - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// - /// Xml representation of the passed in - public static XElement ToXml(this IMember member, IPackagingService packagingService) - { - return ((PackagingService)(packagingService)).Export(member); - } - #endregion - - #region Dirty - - public static IEnumerable GetDirtyUserProperties(this IContentBase entity) - { - return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias); - } - - public static bool IsAnyUserPropertyDirty(this IContentBase entity) - { - return entity.Properties.Any(x => x.IsDirty()); - } - - public static bool WasAnyUserPropertyDirty(this IContentBase entity) - { - return entity.Properties.Any(x => x.WasDirty()); - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Web; +using System.Xml.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.IO; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Core.Models +{ + public static class ContentExtensions + { + // this ain't pretty + private static MediaFileSystem _mediaFileSystem; + private static MediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem); + + #region IContent + + /// + /// Returns true if this entity was just published as part of a recent save operation (i.e. it wasn't previously published) + /// + /// + /// + /// + /// This is helpful for determining if the published event will execute during the saved event for a content item. + /// + internal static bool JustPublished(this IContent entity) + { + var dirty = (IRememberBeingDirty)entity; + return dirty.WasPropertyDirty("Published") && entity.Published; + } + + /// + /// Returns a list of the current contents ancestors, not including the content itself. + /// + /// Current content + /// + /// An enumerable list of objects + public static IEnumerable Ancestors(this IContent content, IContentService contentService) + { + return contentService.GetAncestors(content); + } + + /// + /// Returns a list of the current contents children. + /// + /// Current content + /// + /// An enumerable list of objects + public static IEnumerable Children(this IContent content, IContentService contentService) + { + return contentService.GetChildren(content.Id); + } + + /// + /// Returns a list of the current contents descendants, not including the content itself. + /// + /// Current content + /// + /// An enumerable list of objects + public static IEnumerable Descendants(this IContent content, IContentService contentService) + { + return contentService.GetDescendants(content); + } + + /// + /// Returns the parent of the current content. + /// + /// Current content + /// + /// An object + public static IContent Parent(this IContent content, IContentService contentService) + { + return contentService.GetById(content.ParentId); + } + + #endregion + + #region IMedia + + /// + /// Returns a list of the current medias ancestors, not including the media itself. + /// + /// Current media + /// + /// An enumerable list of objects + public static IEnumerable Ancestors(this IMedia media, IMediaService mediaService) + { + return mediaService.GetAncestors(media); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IEnumerable Ancestors(this IMedia media) + { + return Current.Services.MediaService.GetAncestors(media); + } + + /// + /// Returns a list of the current medias children. + /// + /// Current media + /// + /// An enumerable list of objects + public static IEnumerable Children(this IMedia media, IMediaService mediaService) + { + return mediaService.GetChildren(media.Id); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IEnumerable Children(this IMedia media) + { + return Current.Services.MediaService.GetChildren(media.Id); + } + + /// + /// Returns a list of the current medias descendants, not including the media itself. + /// + /// Current media + /// + /// An enumerable list of objects + public static IEnumerable Descendants(this IMedia media, IMediaService mediaService) + { + return mediaService.GetDescendants(media); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IEnumerable Descendants(this IMedia media) + { + return Current.Services.MediaService.GetDescendants(media); + } + + /// + /// Returns the parent of the current media. + /// + /// Current media + /// + /// An object + public static IMedia Parent(this IMedia media, IMediaService mediaService) + { + return mediaService.GetById(media.ParentId); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IMedia Parent(this IMedia media) + { + return Current.Services.MediaService.GetById(media.ParentId); + } + #endregion + + /// + /// Removes characters that are not valide XML characters from all entity properties + /// of type string. See: http://stackoverflow.com/a/961504/5018 + /// + /// + /// + /// If this is not done then the xml cache can get corrupt and it will throw YSODs upon reading it. + /// + /// + public static void SanitizeEntityPropertiesForXmlStorage(this IContentBase entity) + { + entity.Name = entity.Name.ToValidXmlString(); + foreach (var property in entity.Properties) + { + foreach (var propertyValue in property.Values) + { + if (propertyValue.EditedValue is string editString) + propertyValue.EditedValue = editString.ToValidXmlString(); + if (propertyValue.PublishedValue is string publishedString) + propertyValue.PublishedValue = publishedString.ToValidXmlString(); + } + } + } + + /// + /// Checks if the IContentBase has children + /// + /// + /// + /// + /// + /// This is a bit of a hack because we need to type check! + /// + internal static bool HasChildren(IContentBase content, ServiceContext services) + { + if (content is IContent) + { + return services.ContentService.HasChildren(content.Id); + } + if (content is IMedia) + { + return services.MediaService.HasChildren(content.Id); + } + return false; + } + + /// + /// Returns the children for the content base item + /// + /// + /// + /// + /// + /// This is a bit of a hack because we need to type check! + /// + internal static IEnumerable Children(IContentBase content, ServiceContext services) + { + if (content is IContent) + { + return services.ContentService.GetChildren(content.Id); + } + if (content is IMedia) + { + return services.MediaService.GetChildren(content.Id); + } + return null; + } + + /// + /// Returns properties that do not belong to a group + /// + /// + /// + public static IEnumerable GetNonGroupedProperties(this IContentBase content) + { + var propertyIdsInTabs = content.PropertyGroups.SelectMany(pg => pg.PropertyTypes); + return content.Properties + .Where(property => propertyIdsInTabs.Contains(property.PropertyType) == false) + .OrderBy(x => x.PropertyType.SortOrder); + } + + /// + /// Returns the Property object for the given property group + /// + /// + /// + /// + public static IEnumerable GetPropertiesForGroup(this IContentBase content, PropertyGroup propertyGroup) + { + //get the properties for the current tab + return content.Properties + .Where(property => propertyGroup.PropertyTypes + .Select(propertyType => propertyType.Id) + .Contains(property.PropertyTypeId)); + } + + public static IContentTypeComposition GetContentType(this IContentBase contentBase) + { + if (contentBase == null) throw new ArgumentNullException(nameof(contentBase)); + + if (contentBase is IContent content) return content.ContentType; + if (contentBase is IMedia media) return media.ContentType; + if (contentBase is IMember member) return member.ContentType; + throw new NotSupportedException("Unsupported IContentBase implementation: " + contentBase.GetType().FullName + "."); + } + + #region SetValue for setting file contents + + /// + /// Sets the posted file value of a property. + /// + public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null) + { + // ensure we get the filename without the path in IE in intranet mode + // http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie + var filename = value.FileName; + var pos = filename.LastIndexOf(@"\", StringComparison.InvariantCulture); + if (pos > 0) + filename = filename.Substring(pos + 1); + + // strip any directory info + pos = filename.LastIndexOf(IOHelper.DirSepChar); + if (pos > 0) + filename = filename.Substring(pos + 1); + + // get a safe & clean filename + filename = IOHelper.SafeFileName(filename); + if (string.IsNullOrWhiteSpace(filename)) return; + filename = filename.ToLower(); // fixme - er... why? + + MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream, culture, segment); + } + + /// + /// Sets the posted file value of a property. + /// + /// This really is for FileUpload fields only, and should be obsoleted. For anything else, + /// you need to store the file by yourself using Store and then figure out + /// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself. + public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) + { + if (filename == null || filestream == null) return; + + // get a safe & clean filename + filename = IOHelper.SafeFileName(filename); + if (string.IsNullOrWhiteSpace(filename)) return; + filename = filename.ToLower(); // fixme - er... why? + + MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream, culture, segment); + } + + /// + /// Stores a file. + /// + /// A content item. + /// The property alias. + /// The name of the file. + /// A stream containing the file data. + /// The original file path, if any. + /// The path to the file, relative to the media filesystem. + /// + /// Does NOT set the property value, so one should probably store the file and then do + /// something alike: property.Value = MediaHelper.FileSystem.GetUrl(filepath). + /// The original file path is used, in the old media file path scheme, to try and reuse + /// the "folder number" that was assigned to the previous file referenced by the property, + /// if any. + /// + public static string StoreFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string filepath) + { + var propertyType = content.GetContentType() + .CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (propertyType == null) throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + "."); + return MediaFileSystem.StoreFile(content, propertyType, filename, filestream, filepath); + } + + #endregion + + #region User/Profile methods + + + [Obsolete("Use the overload that declares the IUserService to use")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IProfile GetCreatorProfile(this IMedia media) + { + return Current.Services.UserService.GetProfileById(media.CreatorId); + } + + /// + /// Gets the for the Creator of this media item. + /// + public static IProfile GetCreatorProfile(this IMedia media, IUserService userService) + { + return userService.GetProfileById(media.CreatorId); + } + + [Obsolete("Use the overload that declares the IUserService to use")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IProfile GetCreatorProfile(this IContentBase content) + { + return Current.Services.UserService.GetProfileById(content.CreatorId); + } + + /// + /// Gets the for the Creator of this content item. + /// + public static IProfile GetCreatorProfile(this IContentBase content, IUserService userService) + { + return userService.GetProfileById(content.CreatorId); + } + + [Obsolete("Use the overload that declares the IUserService to use")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IProfile GetWriterProfile(this IContent content) + { + return Current.Services.UserService.GetProfileById(content.WriterId); + } + + /// + /// Gets the for the Writer of this content. + /// + public static IProfile GetWriterProfile(this IContent content, IUserService userService) + { + return userService.GetProfileById(content.WriterId); + } + + /// + /// Gets the for the Writer of this content. + /// + public static IProfile GetWriterProfile(this IMedia content, IUserService userService) + { + return userService.GetProfileById(content.WriterId); + } + + #endregion + + #region XML methods + + /// + /// Creates the full xml representation for the object and all of it's descendants + /// + /// to generate xml for + /// + /// Xml representation of the passed in + internal static XElement ToDeepXml(this IContent content, IPackagingService packagingService) + { + return packagingService.Export(content, true, raiseEvents: false); + } + + + [Obsolete("Use the overload that declares the IPackagingService to use")] + public static XElement ToXml(this IContent content) + { + return Current.Services.PackagingService.Export(content, raiseEvents: false); + } + + /// + /// Creates the xml representation for the object + /// + /// to generate xml for + /// + /// Xml representation of the passed in + public static XElement ToXml(this IContent content, IPackagingService packagingService) + { + return packagingService.Export(content, raiseEvents: false); + } + + [Obsolete("Use the overload that declares the IPackagingService to use")] + public static XElement ToXml(this IMedia media) + { + return Current.Services.PackagingService.Export(media, raiseEvents: false); + } + + /// + /// Creates the xml representation for the object + /// + /// to generate xml for + /// + /// Xml representation of the passed in + public static XElement ToXml(this IMedia media, IPackagingService packagingService) + { + return packagingService.Export(media, raiseEvents: false); + } + + /// + /// Creates the full xml representation for the object and all of it's descendants + /// + /// to generate xml for + /// + /// Xml representation of the passed in + internal static XElement ToDeepXml(this IMedia media, IPackagingService packagingService) + { + return packagingService.Export(media, true, raiseEvents: false); + } + + [Obsolete("Use the overload that declares the IPackagingService to use")] + public static XElement ToXml(this IContent content, bool isPreview) + { + //TODO Do a proper implementation of this + //If current IContent is published we should get latest unpublished version + return content.ToXml(); + } + + /// + /// Creates the xml representation for the object + /// + /// to generate xml for + /// + /// Boolean indicating whether the xml should be generated for preview + /// Xml representation of the passed in + public static XElement ToXml(this IContent content, IPackagingService packagingService, bool isPreview) + { + //TODO Do a proper implementation of this + //If current IContent is published we should get latest unpublished version + return content.ToXml(packagingService); + } + + [Obsolete("Use the overload that declares the IPackagingService to use")] + public static XElement ToXml(this IMember member) + { + return ((PackagingService)(Current.Services.PackagingService)).Export(member); + } + + /// + /// Creates the xml representation for the object + /// + /// to generate xml for + /// + /// Xml representation of the passed in + public static XElement ToXml(this IMember member, IPackagingService packagingService) + { + return ((PackagingService)(packagingService)).Export(member); + } + #endregion + + #region Dirty + + public static IEnumerable GetDirtyUserProperties(this IContentBase entity) + { + return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias); + } + + public static bool IsAnyUserPropertyDirty(this IContentBase entity) + { + return entity.Properties.Any(x => x.IsDirty()); + } + + public static bool WasAnyUserPropertyDirty(this IContentBase entity) + { + return entity.Properties.Any(x => x.WasDirty()); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/ContentStatus.cs b/src/Umbraco.Core/Models/ContentStatus.cs index b31bf24f82..4caf214399 100644 --- a/src/Umbraco.Core/Models/ContentStatus.cs +++ b/src/Umbraco.Core/Models/ContentStatus.cs @@ -1,24 +1,24 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models -{ - /// - /// Enum for the various statuses a Content object can have - /// - [Serializable] - [DataContract] - public enum ContentStatus - { - [EnumMember] - Unpublished, - [EnumMember] - Published, - [EnumMember] - Expired, - [EnumMember] - Trashed, - [EnumMember] - AwaitingRelease - } -} +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Enum for the various statuses a Content object can have + /// + [Serializable] + [DataContract] + public enum ContentStatus + { + [EnumMember] + Unpublished, + [EnumMember] + Published, + [EnumMember] + Expired, + [EnumMember] + Trashed, + [EnumMember] + AwaitingRelease + } +} diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index a7418a1441..4a39166dc8 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -1,159 +1,159 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models -{ - /// - /// Represents the content type that a object is based on - /// - [Serializable] - [DataContract(IsReference = true)] - public class ContentType : ContentTypeCompositionBase, IContentType - { - private static readonly Lazy Ps = new Lazy(); - public const bool IsPublishingConst = true; - - private int _defaultTemplate; - private IEnumerable _allowedTemplates; - - /// - /// Constuctor for creating a ContentType with the parent's id. - /// - /// Only use this for creating ContentTypes at the root (with ParentId -1). - /// - public ContentType(int parentId) : base(parentId) - { - _allowedTemplates = new List(); - } - - /// - /// Constuctor for creating a ContentType with the parent as an inherited type. - /// - /// Use this to ensure inheritance from parent. - /// - [Obsolete("This method is obsolete, use ContentType(IContentType parent, string alias) instead.", false)] - public ContentType(IContentType parent) : this(parent, null) - { - } - - /// - /// Constuctor for creating a ContentType with the parent as an inherited type. - /// - /// Use this to ensure inheritance from parent. - /// - /// - public ContentType(IContentType parent, string alias) - : base(parent, alias) - { - _allowedTemplates = new List(); - } - - /// - public override bool IsPublishing => IsPublishingConst; - - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId); - public readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedTemplates); - - //Custom comparer for enumerable - public readonly DelegateEqualityComparer> TemplateComparer = new DelegateEqualityComparer>( - (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), - templates => templates.GetHashCode()); - } - - /// - /// Gets or sets the alias of the default Template. - /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! - /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, - /// we should not store direct entity - /// - [IgnoreDataMember] - public ITemplate DefaultTemplate - { - get { return AllowedTemplates.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId); } - } - - /// - /// Internal property to store the Id of the default template - /// - [DataMember] - internal int DefaultTemplateId - { - get { return _defaultTemplate; } - set { SetPropertyValueAndDetectChanges(value, ref _defaultTemplate, Ps.Value.DefaultTemplateSelector); } - } - - /// - /// Gets or Sets a list of Templates which are allowed for the ContentType - /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! - /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, - /// we should not store direct entity - /// - [DataMember] - public IEnumerable AllowedTemplates - { - get => _allowedTemplates; - set - { - SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, Ps.Value.TemplateComparer); - - if (_allowedTemplates.Any(x => x.Id == _defaultTemplate) == false) - DefaultTemplateId = 0; - } - } - - /// - /// Sets the default template for the ContentType - /// - /// Default - public void SetDefaultTemplate(ITemplate template) - { - if (template == null) - { - DefaultTemplateId = 0; - return; - } - - DefaultTemplateId = template.Id; - if (_allowedTemplates.Any(x => x != null && x.Id == template.Id) == false) - { - var templates = AllowedTemplates.ToList(); - templates.Add(template); - AllowedTemplates = templates; - } - } - - /// - /// Removes a template from the list of allowed templates - /// - /// to remove - /// True if template was removed, otherwise False - public bool RemoveTemplate(ITemplate template) - { - if (DefaultTemplateId == template.Id) - DefaultTemplateId = default(int); - - var templates = AllowedTemplates.ToList(); - var remove = templates.FirstOrDefault(x => x.Id == template.Id); - var result = templates.Remove(remove); - AllowedTemplates = templates; - - return result; - } - - /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset - /// - /// - [Obsolete("Use DeepCloneWithResetIdentities instead")] - public IContentType Clone(string alias) - { - return DeepCloneWithResetIdentities(alias); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Represents the content type that a object is based on + /// + [Serializable] + [DataContract(IsReference = true)] + public class ContentType : ContentTypeCompositionBase, IContentType + { + private static readonly Lazy Ps = new Lazy(); + public const bool IsPublishingConst = true; + + private int _defaultTemplate; + private IEnumerable _allowedTemplates; + + /// + /// Constuctor for creating a ContentType with the parent's id. + /// + /// Only use this for creating ContentTypes at the root (with ParentId -1). + /// + public ContentType(int parentId) : base(parentId) + { + _allowedTemplates = new List(); + } + + /// + /// Constuctor for creating a ContentType with the parent as an inherited type. + /// + /// Use this to ensure inheritance from parent. + /// + [Obsolete("This method is obsolete, use ContentType(IContentType parent, string alias) instead.", false)] + public ContentType(IContentType parent) : this(parent, null) + { + } + + /// + /// Constuctor for creating a ContentType with the parent as an inherited type. + /// + /// Use this to ensure inheritance from parent. + /// + /// + public ContentType(IContentType parent, string alias) + : base(parent, alias) + { + _allowedTemplates = new List(); + } + + /// + public override bool IsPublishing => IsPublishingConst; + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId); + public readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedTemplates); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> TemplateComparer = new DelegateEqualityComparer>( + (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), + templates => templates.GetHashCode()); + } + + /// + /// Gets or sets the alias of the default Template. + /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// we should not store direct entity + /// + [IgnoreDataMember] + public ITemplate DefaultTemplate + { + get { return AllowedTemplates.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId); } + } + + /// + /// Internal property to store the Id of the default template + /// + [DataMember] + internal int DefaultTemplateId + { + get { return _defaultTemplate; } + set { SetPropertyValueAndDetectChanges(value, ref _defaultTemplate, Ps.Value.DefaultTemplateSelector); } + } + + /// + /// Gets or Sets a list of Templates which are allowed for the ContentType + /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// we should not store direct entity + /// + [DataMember] + public IEnumerable AllowedTemplates + { + get => _allowedTemplates; + set + { + SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, Ps.Value.TemplateComparer); + + if (_allowedTemplates.Any(x => x.Id == _defaultTemplate) == false) + DefaultTemplateId = 0; + } + } + + /// + /// Sets the default template for the ContentType + /// + /// Default + public void SetDefaultTemplate(ITemplate template) + { + if (template == null) + { + DefaultTemplateId = 0; + return; + } + + DefaultTemplateId = template.Id; + if (_allowedTemplates.Any(x => x != null && x.Id == template.Id) == false) + { + var templates = AllowedTemplates.ToList(); + templates.Add(template); + AllowedTemplates = templates; + } + } + + /// + /// Removes a template from the list of allowed templates + /// + /// to remove + /// True if template was removed, otherwise False + public bool RemoveTemplate(ITemplate template) + { + if (DefaultTemplateId == template.Id) + DefaultTemplateId = default(int); + + var templates = AllowedTemplates.ToList(); + var remove = templates.FirstOrDefault(x => x.Id == template.Id); + var result = templates.Remove(remove); + AllowedTemplates = templates; + + return result; + } + + /// + /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset + /// + /// + [Obsolete("Use DeepCloneWithResetIdentities instead")] + public IContentType Clone(string alias) + { + return DeepCloneWithResetIdentities(alias); + } + } +} diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 952fa0cd88..f1b61f424a 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -1,524 +1,524 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Strings; - -namespace Umbraco.Core.Models -{ - /// - /// Represents an abstract class for base ContentType properties and methods - /// - [Serializable] - [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] - public abstract class ContentTypeBase : TreeEntityBase, IContentTypeBase - { - private static readonly Lazy Ps = new Lazy(); - - private string _alias; - private string _description; - private string _icon = "icon-folder"; - private string _thumbnail = "folder.png"; - private bool _allowedAsRoot; // note: only one that's not 'pure element type' - private bool _isContainer; - private PropertyGroupCollection _propertyGroups; - private PropertyTypeCollection _propertyTypes; - private IEnumerable _allowedContentTypes; - private bool _hasPropertyTypeBeenRemoved; - private ContentVariation _variations; - - protected ContentTypeBase(int parentId) - { - if (parentId == 0) throw new ArgumentOutOfRangeException(nameof(parentId)); - ParentId = parentId; - - _allowedContentTypes = new List(); - _propertyGroups = new PropertyGroupCollection(); - - // actually OK as IsPublishing is constant - // ReSharper disable once VirtualMemberCallInConstructor - _propertyTypes = new PropertyTypeCollection(IsPublishing); - _propertyTypes.CollectionChanged += PropertyTypesChanged; - - _variations = ContentVariation.InvariantNeutral; - } - - protected ContentTypeBase(IContentTypeBase parent) - : this(parent, null) - { } - - protected ContentTypeBase(IContentTypeBase parent, string alias) - { - if (parent == null) throw new ArgumentNullException(nameof(parent)); - SetParent(parent); - - _alias = alias; - _allowedContentTypes = new List(); - _propertyGroups = new PropertyGroupCollection(); - - // actually OK as IsPublishing is constant - // ReSharper disable once VirtualMemberCallInConstructor - _propertyTypes = new PropertyTypeCollection(IsPublishing); - _propertyTypes.CollectionChanged += PropertyTypesChanged; - - _variations = ContentVariation.InvariantNeutral; - } - - /// - /// Gets a value indicating whether the content type is publishing. - /// - /// - /// A publishing content type supports draft and published values for properties. - /// It is possible to retrieve either the draft (default) or published value of a property. - /// Setting the value always sets the draft value, which then needs to be published. - /// A non-publishing content type only supports one value for properties. Getting - /// the draft or published value of a property returns the same thing, and publishing - /// a value property has no effect. - /// - public abstract bool IsPublishing { get; } - - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); - public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); - public readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.Thumbnail); - public readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo(x => x.AllowedAsRoot); - public readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo(x => x.IsContainer); - public readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedContentTypes); - public readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); - public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyTypes); - public readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPropertyTypeBeenRemoved); - public readonly PropertyInfo VaryBy = ExpressionHelper.GetPropertyInfo(x => x.Variations); - - //Custom comparer for enumerable - public readonly DelegateEqualityComparer> ContentTypeSortComparer = - new DelegateEqualityComparer>( - (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), - sorts => sorts.GetHashCode()); - } - - protected void PropertyGroupsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector); - } - - protected void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); - } - - /// - /// The Alias of the ContentType - /// - [DataMember] - public virtual string Alias - { - get => _alias; - set => SetPropertyValueAndDetectChanges( - value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), - ref _alias, - Ps.Value.AliasSelector); - } - - /// - /// Description for the ContentType - /// - [DataMember] - public virtual string Description - { - get => _description; - set => SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); - } - - /// - /// Name of the icon (sprite class) used to identify the ContentType - /// - [DataMember] - public virtual string Icon - { - get => _icon; - set => SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); - } - - /// - /// Name of the thumbnail used to identify the ContentType - /// - [DataMember] - public virtual string Thumbnail - { - get => _thumbnail; - set => SetPropertyValueAndDetectChanges(value, ref _thumbnail, Ps.Value.ThumbnailSelector); - } - - /// - /// Gets or Sets a boolean indicating whether this ContentType is allowed at the root - /// - [DataMember] - public virtual bool AllowedAsRoot - { - get => _allowedAsRoot; - set => SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, Ps.Value.AllowedAsRootSelector); - } - - /// - /// Gets or Sets a boolean indicating whether this ContentType is a Container - /// - /// - /// ContentType Containers doesn't show children in the tree, but rather in grid-type view. - /// - [DataMember] - public virtual bool IsContainer - { - get => _isContainer; - set => SetPropertyValueAndDetectChanges(value, ref _isContainer, Ps.Value.IsContainerSelector); - } - - /// - /// Gets or sets a list of integer Ids for allowed ContentTypes - /// - [DataMember] - public virtual IEnumerable AllowedContentTypes - { - get => _allowedContentTypes; - set => SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector, - Ps.Value.ContentTypeSortComparer); - } - - /// - /// Gets or sets the content variation of the content type. - /// - public virtual ContentVariation Variations - { - get => _variations; - set => SetPropertyValueAndDetectChanges(value, ref _variations, Ps.Value.VaryBy); - } - - /// - /// Validates that a variation is valid for the content type. - /// - public bool ValidateVariation(string culture, string segment, bool throwIfInvalid) - { - ContentVariation variation; - if (culture != null) - { - variation = segment != null - ? ContentVariation.CultureSegment - : ContentVariation.CultureNeutral; - } - else if (segment != null) - { - variation = ContentVariation.InvariantSegment; - } - else - { - variation = ContentVariation.InvariantNeutral; - } - if (!Variations.Has(variation)) - { - if (throwIfInvalid) - throw new NotSupportedException($"Variation {variation} is invalid for content type \"{Alias}\"."); - return false; - } - return true; - } - - /// - /// List of PropertyGroups available on this ContentType - /// - /// - /// A PropertyGroup corresponds to a Tab in the UI - /// - [DataMember] - public virtual PropertyGroupCollection PropertyGroups - { - get => _propertyGroups; - set - { - _propertyGroups = value; - _propertyGroups.CollectionChanged += PropertyGroupsChanged; - PropertyGroupsChanged(_propertyGroups, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - } - - /// - /// Gets all property types, across all property groups. - /// - [IgnoreDataMember] - [DoNotClone] - public virtual IEnumerable PropertyTypes - { - get - { - return _propertyTypes.Union(PropertyGroups.SelectMany(x => x.PropertyTypes)); - } - } - - /// - /// Gets or sets the property types that are not in a group. - /// - public IEnumerable NoGroupPropertyTypes - { - get => _propertyTypes; - set - { - _propertyTypes = new PropertyTypeCollection(IsPublishing, value); - _propertyTypes.CollectionChanged += PropertyTypesChanged; - PropertyTypesChanged(_propertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - } - - /// - /// A boolean flag indicating if a property type has been removed from this instance. - /// - /// - /// This is currently (specifically) used in order to know that we need to refresh the content cache which - /// needs to occur when a property has been removed from a content type - /// - [IgnoreDataMember] - internal bool HasPropertyTypeBeenRemoved - { - get => _hasPropertyTypeBeenRemoved; - private set - { - _hasPropertyTypeBeenRemoved = value; - OnPropertyChanged(Ps.Value.HasPropertyTypeBeenRemovedSelector); - } - } - - /// - /// Checks whether a PropertyType with a given alias already exists - /// - /// Alias of the PropertyType - /// Returns True if a PropertyType with the passed in alias exists, otherwise False - public abstract bool PropertyTypeExists(string propertyTypeAlias); - - /// - /// Adds a PropertyGroup. - /// This method will also check if a group already exists with the same name and link it to the parent. - /// - /// Name of the PropertyGroup to add - /// Returns True if a PropertyGroup with the passed in name was added, otherwise False - public abstract bool AddPropertyGroup(string groupName); - - /// - /// Adds a PropertyType to a specific PropertyGroup - /// - /// to add - /// Name of the PropertyGroup to add the PropertyType to - /// Returns True if PropertyType was added, otherwise False - public abstract bool AddPropertyType(PropertyType propertyType, string propertyGroupName); - - /// - /// Adds a PropertyType, which does not belong to a PropertyGroup. - /// - /// to add - /// Returns True if PropertyType was added, otherwise False - public bool AddPropertyType(PropertyType propertyType) - { - if (PropertyTypeExists(propertyType.Alias) == false) - { - _propertyTypes.Add(propertyType); - return true; - } - - return false; - } - - /// - /// Moves a PropertyType to a specified PropertyGroup - /// - /// Alias of the PropertyType to move - /// Name of the PropertyGroup to move the PropertyType to - /// - /// If is null then the property is moved back to - /// "generic properties" ie does not have a tab anymore. - public bool MovePropertyType(string propertyTypeAlias, string propertyGroupName) - { - // note: not dealing with alias casing at all here? - - // get property, ensure it exists - var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); - if (propertyType == null) return false; - - // get new group, if required, and ensure it exists - var newPropertyGroup = propertyGroupName == null - ? null - : PropertyGroups.FirstOrDefault(x => x.Name == propertyGroupName); - if (propertyGroupName != null && newPropertyGroup == null) return false; - - // get old group - var oldPropertyGroup = PropertyGroups.FirstOrDefault(x => - x.PropertyTypes.Any(y => y.Alias == propertyTypeAlias)); - - // set new group - propertyType.PropertyGroupId = newPropertyGroup == null ? null : new Lazy(() => newPropertyGroup.Id, false); - - // remove from old group, if any - add to new group, if any - oldPropertyGroup?.PropertyTypes.RemoveItem(propertyTypeAlias); - newPropertyGroup?.PropertyTypes.Add(propertyType); - - return true; - } - - /// - /// Removes a PropertyType from the current ContentType - /// - /// Alias of the to remove - public void RemovePropertyType(string propertyTypeAlias) - { - //check if the property exist in one of our collections - if (PropertyGroups.Any(group => group.PropertyTypes.Any(pt => pt.Alias == propertyTypeAlias)) - || _propertyTypes.Any(x => x.Alias == propertyTypeAlias)) - { - //set the flag that a property has been removed - HasPropertyTypeBeenRemoved = true; - } - - foreach (var propertyGroup in PropertyGroups) - { - propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias); - } - - if (_propertyTypes.Any(x => x.Alias == propertyTypeAlias)) - { - _propertyTypes.RemoveItem(propertyTypeAlias); - } - } - - /// - /// Removes a PropertyGroup from the current ContentType - /// - /// Name of the to remove - public void RemovePropertyGroup(string propertyGroupName) - { - // if no group exists with that name, do nothing - var group = PropertyGroups[propertyGroupName]; - if (group == null) return; - - // re-assign the group's properties to no group - foreach (var property in group.PropertyTypes) - { - property.PropertyGroupId = null; - _propertyTypes.Add(property); - } - - // actually remove the group - PropertyGroups.RemoveItem(propertyGroupName); - OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector); - } - - /// - /// PropertyTypes that are not part of a PropertyGroup - /// - [IgnoreDataMember] - internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes; - - /// - /// Indicates whether a specific property on the current entity is dirty. - /// - /// Name of the property to check - /// True if Property is dirty, otherwise False - public override bool IsPropertyDirty(string propertyName) - { - bool existsInEntity = base.IsPropertyDirty(propertyName); - - bool anyDirtyGroups = PropertyGroups.Any(x => x.IsPropertyDirty(propertyName)); - bool anyDirtyTypes = PropertyTypes.Any(x => x.IsPropertyDirty(propertyName)); - - return existsInEntity || anyDirtyGroups || anyDirtyTypes; - } - - /// - /// Indicates whether the current entity is dirty. - /// - /// True if entity is dirty, otherwise False - public override bool IsDirty() - { - bool dirtyEntity = base.IsDirty(); - - bool dirtyGroups = PropertyGroups.Any(x => x.IsDirty()); - bool dirtyTypes = PropertyTypes.Any(x => x.IsDirty()); - - return dirtyEntity || dirtyGroups || dirtyTypes; - } - - /// - /// Resets dirty properties by clearing the dictionary used to track changes. - /// - /// - /// Please note that resetting the dirty properties could potentially - /// obstruct the saving of a new or updated entity. - /// - public override void ResetDirtyProperties() - { - base.ResetDirtyProperties(); - - //loop through each property group to reset the property types - var propertiesReset = new List(); - - foreach (var propertyGroup in PropertyGroups) - { - propertyGroup.ResetDirtyProperties(); - foreach (var propertyType in propertyGroup.PropertyTypes) - { - propertyType.ResetDirtyProperties(); - propertiesReset.Add(propertyType.Id); - } - } - //then loop through our property type collection since some might not exist on a property group - //but don't re-reset ones we've already done. - foreach (var propertyType in PropertyTypes.Where(x => propertiesReset.Contains(x.Id) == false)) - { - propertyType.ResetDirtyProperties(); - } - } - - public override object DeepClone() - { - var clone = (ContentTypeBase)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //need to manually wire up the event handlers for the property type collections - we've ensured - // its ignored from the auto-clone process because its return values are unions, not raw and - // we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842 - - clone._propertyTypes = (PropertyTypeCollection)_propertyTypes.DeepClone(); - clone._propertyTypes.CollectionChanged += clone.PropertyTypesChanged; - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; - } - - public IContentType DeepCloneWithResetIdentities(string alias) - { - var clone = (ContentType)DeepClone(); - clone.Alias = alias; - clone.Key = Guid.Empty; - foreach (var propertyGroup in clone.PropertyGroups) - { - propertyGroup.ResetIdentity(); - propertyGroup.ResetDirtyProperties(false); - } - foreach (var propertyType in clone.PropertyTypes) - { - propertyType.ResetIdentity(); - propertyType.ResetDirtyProperties(false); - } - - clone.ResetIdentity(); - clone.ResetDirtyProperties(false); - return clone; - } - } -} +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Strings; + +namespace Umbraco.Core.Models +{ + /// + /// Represents an abstract class for base ContentType properties and methods + /// + [Serializable] + [DataContract(IsReference = true)] + [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] + public abstract class ContentTypeBase : TreeEntityBase, IContentTypeBase + { + private static readonly Lazy Ps = new Lazy(); + + private string _alias; + private string _description; + private string _icon = "icon-folder"; + private string _thumbnail = "folder.png"; + private bool _allowedAsRoot; // note: only one that's not 'pure element type' + private bool _isContainer; + private PropertyGroupCollection _propertyGroups; + private PropertyTypeCollection _propertyTypes; + private IEnumerable _allowedContentTypes; + private bool _hasPropertyTypeBeenRemoved; + private ContentVariation _variations; + + protected ContentTypeBase(int parentId) + { + if (parentId == 0) throw new ArgumentOutOfRangeException(nameof(parentId)); + ParentId = parentId; + + _allowedContentTypes = new List(); + _propertyGroups = new PropertyGroupCollection(); + + // actually OK as IsPublishing is constant + // ReSharper disable once VirtualMemberCallInConstructor + _propertyTypes = new PropertyTypeCollection(IsPublishing); + _propertyTypes.CollectionChanged += PropertyTypesChanged; + + _variations = ContentVariation.InvariantNeutral; + } + + protected ContentTypeBase(IContentTypeBase parent) + : this(parent, null) + { } + + protected ContentTypeBase(IContentTypeBase parent, string alias) + { + if (parent == null) throw new ArgumentNullException(nameof(parent)); + SetParent(parent); + + _alias = alias; + _allowedContentTypes = new List(); + _propertyGroups = new PropertyGroupCollection(); + + // actually OK as IsPublishing is constant + // ReSharper disable once VirtualMemberCallInConstructor + _propertyTypes = new PropertyTypeCollection(IsPublishing); + _propertyTypes.CollectionChanged += PropertyTypesChanged; + + _variations = ContentVariation.InvariantNeutral; + } + + /// + /// Gets a value indicating whether the content type is publishing. + /// + /// + /// A publishing content type supports draft and published values for properties. + /// It is possible to retrieve either the draft (default) or published value of a property. + /// Setting the value always sets the draft value, which then needs to be published. + /// A non-publishing content type only supports one value for properties. Getting + /// the draft or published value of a property returns the same thing, and publishing + /// a value property has no effect. + /// + public abstract bool IsPublishing { get; } + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); + public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); + public readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.Thumbnail); + public readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo(x => x.AllowedAsRoot); + public readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo(x => x.IsContainer); + public readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedContentTypes); + public readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); + public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyTypes); + public readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPropertyTypeBeenRemoved); + public readonly PropertyInfo VaryBy = ExpressionHelper.GetPropertyInfo(x => x.Variations); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> ContentTypeSortComparer = + new DelegateEqualityComparer>( + (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), + sorts => sorts.GetHashCode()); + } + + protected void PropertyGroupsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector); + } + + protected void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); + } + + /// + /// The Alias of the ContentType + /// + [DataMember] + public virtual string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges( + value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), + ref _alias, + Ps.Value.AliasSelector); + } + + /// + /// Description for the ContentType + /// + [DataMember] + public virtual string Description + { + get => _description; + set => SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); + } + + /// + /// Name of the icon (sprite class) used to identify the ContentType + /// + [DataMember] + public virtual string Icon + { + get => _icon; + set => SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); + } + + /// + /// Name of the thumbnail used to identify the ContentType + /// + [DataMember] + public virtual string Thumbnail + { + get => _thumbnail; + set => SetPropertyValueAndDetectChanges(value, ref _thumbnail, Ps.Value.ThumbnailSelector); + } + + /// + /// Gets or Sets a boolean indicating whether this ContentType is allowed at the root + /// + [DataMember] + public virtual bool AllowedAsRoot + { + get => _allowedAsRoot; + set => SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, Ps.Value.AllowedAsRootSelector); + } + + /// + /// Gets or Sets a boolean indicating whether this ContentType is a Container + /// + /// + /// ContentType Containers doesn't show children in the tree, but rather in grid-type view. + /// + [DataMember] + public virtual bool IsContainer + { + get => _isContainer; + set => SetPropertyValueAndDetectChanges(value, ref _isContainer, Ps.Value.IsContainerSelector); + } + + /// + /// Gets or sets a list of integer Ids for allowed ContentTypes + /// + [DataMember] + public virtual IEnumerable AllowedContentTypes + { + get => _allowedContentTypes; + set => SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector, + Ps.Value.ContentTypeSortComparer); + } + + /// + /// Gets or sets the content variation of the content type. + /// + public virtual ContentVariation Variations + { + get => _variations; + set => SetPropertyValueAndDetectChanges(value, ref _variations, Ps.Value.VaryBy); + } + + /// + /// Validates that a variation is valid for the content type. + /// + public bool ValidateVariation(string culture, string segment, bool throwIfInvalid) + { + ContentVariation variation; + if (culture != null) + { + variation = segment != null + ? ContentVariation.CultureSegment + : ContentVariation.CultureNeutral; + } + else if (segment != null) + { + variation = ContentVariation.InvariantSegment; + } + else + { + variation = ContentVariation.InvariantNeutral; + } + if (!Variations.Has(variation)) + { + if (throwIfInvalid) + throw new NotSupportedException($"Variation {variation} is invalid for content type \"{Alias}\"."); + return false; + } + return true; + } + + /// + /// List of PropertyGroups available on this ContentType + /// + /// + /// A PropertyGroup corresponds to a Tab in the UI + /// + [DataMember] + public virtual PropertyGroupCollection PropertyGroups + { + get => _propertyGroups; + set + { + _propertyGroups = value; + _propertyGroups.CollectionChanged += PropertyGroupsChanged; + PropertyGroupsChanged(_propertyGroups, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } + + /// + /// Gets all property types, across all property groups. + /// + [IgnoreDataMember] + [DoNotClone] + public virtual IEnumerable PropertyTypes + { + get + { + return _propertyTypes.Union(PropertyGroups.SelectMany(x => x.PropertyTypes)); + } + } + + /// + /// Gets or sets the property types that are not in a group. + /// + public IEnumerable NoGroupPropertyTypes + { + get => _propertyTypes; + set + { + _propertyTypes = new PropertyTypeCollection(IsPublishing, value); + _propertyTypes.CollectionChanged += PropertyTypesChanged; + PropertyTypesChanged(_propertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } + + /// + /// A boolean flag indicating if a property type has been removed from this instance. + /// + /// + /// This is currently (specifically) used in order to know that we need to refresh the content cache which + /// needs to occur when a property has been removed from a content type + /// + [IgnoreDataMember] + internal bool HasPropertyTypeBeenRemoved + { + get => _hasPropertyTypeBeenRemoved; + private set + { + _hasPropertyTypeBeenRemoved = value; + OnPropertyChanged(Ps.Value.HasPropertyTypeBeenRemovedSelector); + } + } + + /// + /// Checks whether a PropertyType with a given alias already exists + /// + /// Alias of the PropertyType + /// Returns True if a PropertyType with the passed in alias exists, otherwise False + public abstract bool PropertyTypeExists(string propertyTypeAlias); + + /// + /// Adds a PropertyGroup. + /// This method will also check if a group already exists with the same name and link it to the parent. + /// + /// Name of the PropertyGroup to add + /// Returns True if a PropertyGroup with the passed in name was added, otherwise False + public abstract bool AddPropertyGroup(string groupName); + + /// + /// Adds a PropertyType to a specific PropertyGroup + /// + /// to add + /// Name of the PropertyGroup to add the PropertyType to + /// Returns True if PropertyType was added, otherwise False + public abstract bool AddPropertyType(PropertyType propertyType, string propertyGroupName); + + /// + /// Adds a PropertyType, which does not belong to a PropertyGroup. + /// + /// to add + /// Returns True if PropertyType was added, otherwise False + public bool AddPropertyType(PropertyType propertyType) + { + if (PropertyTypeExists(propertyType.Alias) == false) + { + _propertyTypes.Add(propertyType); + return true; + } + + return false; + } + + /// + /// Moves a PropertyType to a specified PropertyGroup + /// + /// Alias of the PropertyType to move + /// Name of the PropertyGroup to move the PropertyType to + /// + /// If is null then the property is moved back to + /// "generic properties" ie does not have a tab anymore. + public bool MovePropertyType(string propertyTypeAlias, string propertyGroupName) + { + // note: not dealing with alias casing at all here? + + // get property, ensure it exists + var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); + if (propertyType == null) return false; + + // get new group, if required, and ensure it exists + var newPropertyGroup = propertyGroupName == null + ? null + : PropertyGroups.FirstOrDefault(x => x.Name == propertyGroupName); + if (propertyGroupName != null && newPropertyGroup == null) return false; + + // get old group + var oldPropertyGroup = PropertyGroups.FirstOrDefault(x => + x.PropertyTypes.Any(y => y.Alias == propertyTypeAlias)); + + // set new group + propertyType.PropertyGroupId = newPropertyGroup == null ? null : new Lazy(() => newPropertyGroup.Id, false); + + // remove from old group, if any - add to new group, if any + oldPropertyGroup?.PropertyTypes.RemoveItem(propertyTypeAlias); + newPropertyGroup?.PropertyTypes.Add(propertyType); + + return true; + } + + /// + /// Removes a PropertyType from the current ContentType + /// + /// Alias of the to remove + public void RemovePropertyType(string propertyTypeAlias) + { + //check if the property exist in one of our collections + if (PropertyGroups.Any(group => group.PropertyTypes.Any(pt => pt.Alias == propertyTypeAlias)) + || _propertyTypes.Any(x => x.Alias == propertyTypeAlias)) + { + //set the flag that a property has been removed + HasPropertyTypeBeenRemoved = true; + } + + foreach (var propertyGroup in PropertyGroups) + { + propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias); + } + + if (_propertyTypes.Any(x => x.Alias == propertyTypeAlias)) + { + _propertyTypes.RemoveItem(propertyTypeAlias); + } + } + + /// + /// Removes a PropertyGroup from the current ContentType + /// + /// Name of the to remove + public void RemovePropertyGroup(string propertyGroupName) + { + // if no group exists with that name, do nothing + var group = PropertyGroups[propertyGroupName]; + if (group == null) return; + + // re-assign the group's properties to no group + foreach (var property in group.PropertyTypes) + { + property.PropertyGroupId = null; + _propertyTypes.Add(property); + } + + // actually remove the group + PropertyGroups.RemoveItem(propertyGroupName); + OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector); + } + + /// + /// PropertyTypes that are not part of a PropertyGroup + /// + [IgnoreDataMember] + internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes; + + /// + /// Indicates whether a specific property on the current entity is dirty. + /// + /// Name of the property to check + /// True if Property is dirty, otherwise False + public override bool IsPropertyDirty(string propertyName) + { + bool existsInEntity = base.IsPropertyDirty(propertyName); + + bool anyDirtyGroups = PropertyGroups.Any(x => x.IsPropertyDirty(propertyName)); + bool anyDirtyTypes = PropertyTypes.Any(x => x.IsPropertyDirty(propertyName)); + + return existsInEntity || anyDirtyGroups || anyDirtyTypes; + } + + /// + /// Indicates whether the current entity is dirty. + /// + /// True if entity is dirty, otherwise False + public override bool IsDirty() + { + bool dirtyEntity = base.IsDirty(); + + bool dirtyGroups = PropertyGroups.Any(x => x.IsDirty()); + bool dirtyTypes = PropertyTypes.Any(x => x.IsDirty()); + + return dirtyEntity || dirtyGroups || dirtyTypes; + } + + /// + /// Resets dirty properties by clearing the dictionary used to track changes. + /// + /// + /// Please note that resetting the dirty properties could potentially + /// obstruct the saving of a new or updated entity. + /// + public override void ResetDirtyProperties() + { + base.ResetDirtyProperties(); + + //loop through each property group to reset the property types + var propertiesReset = new List(); + + foreach (var propertyGroup in PropertyGroups) + { + propertyGroup.ResetDirtyProperties(); + foreach (var propertyType in propertyGroup.PropertyTypes) + { + propertyType.ResetDirtyProperties(); + propertiesReset.Add(propertyType.Id); + } + } + //then loop through our property type collection since some might not exist on a property group + //but don't re-reset ones we've already done. + foreach (var propertyType in PropertyTypes.Where(x => propertiesReset.Contains(x.Id) == false)) + { + propertyType.ResetDirtyProperties(); + } + } + + public override object DeepClone() + { + var clone = (ContentTypeBase)base.DeepClone(); + //turn off change tracking + clone.DisableChangeTracking(); + //need to manually wire up the event handlers for the property type collections - we've ensured + // its ignored from the auto-clone process because its return values are unions, not raw and + // we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842 + + clone._propertyTypes = (PropertyTypeCollection)_propertyTypes.DeepClone(); + clone._propertyTypes.CollectionChanged += clone.PropertyTypesChanged; + //this shouldn't really be needed since we're not tracking + clone.ResetDirtyProperties(false); + //re-enable tracking + clone.EnableChangeTracking(); + + return clone; + } + + public IContentType DeepCloneWithResetIdentities(string alias) + { + var clone = (ContentType)DeepClone(); + clone.Alias = alias; + clone.Key = Guid.Empty; + foreach (var propertyGroup in clone.PropertyGroups) + { + propertyGroup.ResetIdentity(); + propertyGroup.ResetDirtyProperties(false); + } + foreach (var propertyType in clone.PropertyTypes) + { + propertyType.ResetIdentity(); + propertyType.ResetDirtyProperties(false); + } + + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 8fdd7904e4..c4a6ed1c8e 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -1,274 +1,274 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Exceptions; - -namespace Umbraco.Core.Models -{ - /// - /// Represents an abstract class for composition specific ContentType properties and methods - /// - [Serializable] - [DataContract(IsReference = true)] - public abstract class ContentTypeCompositionBase : ContentTypeBase, IContentTypeComposition - { - private static readonly Lazy Ps = new Lazy(); - - private List _contentTypeComposition = new List(); - internal List RemovedContentTypeKeyTracker = new List(); - - protected ContentTypeCompositionBase(int parentId) : base(parentId) - { } - - protected ContentTypeCompositionBase(IContentTypeComposition parent) - : this(parent, null) - { } - - protected ContentTypeCompositionBase(IContentTypeComposition parent, string alias) - : base(parent, alias) - { - AddContentType(parent); - } - - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo ContentTypeCompositionSelector = - ExpressionHelper.GetPropertyInfo>(x => x.ContentTypeComposition); - } - - /// - /// Gets or sets the content types that compose this content type. - /// - [DataMember] - public IEnumerable ContentTypeComposition - { - get => _contentTypeComposition; - set - { - _contentTypeComposition = value.ToList(); - OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); - } - } - - /// - /// Gets the property groups for the entire composition. - /// - [IgnoreDataMember] - public IEnumerable CompositionPropertyGroups - { - get - { - var groups = ContentTypeComposition.SelectMany(x => x.CompositionPropertyGroups).Union(PropertyGroups); - return groups; - } - } - - /// - /// Gets the property types for the entire composition. - /// - [IgnoreDataMember] - public IEnumerable CompositionPropertyTypes - { - get - { - var propertyTypes = ContentTypeComposition.SelectMany(x => x.CompositionPropertyTypes).Union(PropertyTypes); - return propertyTypes; - } - } - - /// - /// Adds a content type to the composition. - /// - /// The content type to add. - /// True if the content type was added, otherwise false. - public bool AddContentType(IContentTypeComposition contentType) - { - if (contentType.ContentTypeComposition.Any(x => x.CompositionAliases().Any(ContentTypeCompositionExists))) - return false; - - if (string.IsNullOrEmpty(Alias) == false && Alias.Equals(contentType.Alias)) - return false; - - if (ContentTypeCompositionExists(contentType.Alias) == false) - { - //Before we actually go ahead and add the ContentType as a Composition we ensure that we don't - //end up with duplicate PropertyType aliases - in which case we throw an exception. - var conflictingPropertyTypeAliases = CompositionPropertyTypes.SelectMany( - x => contentType.CompositionPropertyTypes - .Where(y => y.Alias.Equals(x.Alias, StringComparison.InvariantCultureIgnoreCase)) - .Select(p => p.Alias)).ToList(); - - if (conflictingPropertyTypeAliases.Any()) - throw new InvalidCompositionException(Alias, contentType.Alias, conflictingPropertyTypeAliases.ToArray()); - - _contentTypeComposition.Add(contentType); - OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); - return true; - } - return false; - } - - /// - /// Removes a content type with a specified alias from the composition. - /// - /// The alias of the content type to remove. - /// True if the content type was removed, otherwise false. - public bool RemoveContentType(string alias) - { - if (ContentTypeCompositionExists(alias)) - { - var contentTypeComposition = ContentTypeComposition.FirstOrDefault(x => x.Alias == alias); - if (contentTypeComposition == null)//You can't remove a composition from another composition - return false; - - RemovedContentTypeKeyTracker.Add(contentTypeComposition.Id); - - //If the ContentType we are removing has Compositions of its own these needs to be removed as well - var compositionIdsToRemove = contentTypeComposition.CompositionIds().ToList(); - if (compositionIdsToRemove.Any()) - RemovedContentTypeKeyTracker.AddRange(compositionIdsToRemove); - - OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); - return _contentTypeComposition.Remove(contentTypeComposition); - } - return false; - } - - /// - /// Checks if a ContentType with the supplied alias exists in the list of composite ContentTypes - /// - /// Alias of a - /// True if ContentType with alias exists, otherwise returns False - public bool ContentTypeCompositionExists(string alias) - { - if (ContentTypeComposition.Any(x => x.Alias.Equals(alias))) - return true; - - if (ContentTypeComposition.Any(x => x.ContentTypeCompositionExists(alias))) - return true; - - return false; - } - - /// - /// Checks whether a PropertyType with a given alias already exists - /// - /// Alias of the PropertyType - /// Returns True if a PropertyType with the passed in alias exists, otherwise False - public override bool PropertyTypeExists(string propertyTypeAlias) - { - return CompositionPropertyTypes.Any(x => x.Alias == propertyTypeAlias); - } - - /// - /// Adds a PropertyGroup. - /// - /// Name of the PropertyGroup to add - /// Returns True if a PropertyGroup with the passed in name was added, otherwise False - public override bool AddPropertyGroup(string groupName) - { - return AddAndReturnPropertyGroup(groupName) != null; - } - - private PropertyGroup AddAndReturnPropertyGroup(string name) - { - // ensure we don't have it already - if (PropertyGroups.Any(x => x.Name == name)) - return null; - - // create the new group - var group = new PropertyGroup(IsPublishing) { Name = name, SortOrder = 0 }; - - // check if it is inherited - there might be more than 1 but we want the 1st, to - // reuse its sort order - if there are more than 1 and they have different sort - // orders... there isn't much we can do anyways - var inheritGroup = CompositionPropertyGroups.FirstOrDefault(x => x.Name == name); - if (inheritGroup == null) - { - // no, just local, set sort order - var lastGroup = PropertyGroups.LastOrDefault(); - if (lastGroup != null) - group.SortOrder = lastGroup.SortOrder + 1; - } - else - { - // yes, inherited, re-use sort order - group.SortOrder = inheritGroup.SortOrder; - } - - // add - PropertyGroups.Add(group); - - return group; - } - - /// - /// Adds a PropertyType to a specific PropertyGroup - /// - /// to add - /// Name of the PropertyGroup to add the PropertyType to - /// Returns True if PropertyType was added, otherwise False - public override bool AddPropertyType(PropertyType propertyType, string propertyGroupName) - { - // ensure no duplicate alias - over all composition properties - if (PropertyTypeExists(propertyType.Alias)) - return false; - - // get and ensure a group local to this content type - var group = PropertyGroups.Contains(propertyGroupName) - ? PropertyGroups[propertyGroupName] - : AddAndReturnPropertyGroup(propertyGroupName); - if (group == null) - return false; - - // add property to group - propertyType.PropertyGroupId = new Lazy(() => group.Id); - group.PropertyTypes.Add(propertyType); - - return true; - } - - /// - /// Gets a list of ContentType aliases from the current composition - /// - /// An enumerable list of string aliases - /// Does not contain the alias of the Current ContentType - public IEnumerable CompositionAliases() - { - return ContentTypeComposition - .Select(x => x.Alias) - .Union(ContentTypeComposition.SelectMany(x => x.CompositionAliases())); - } - - /// - /// Gets a list of ContentType Ids from the current composition - /// - /// An enumerable list of integer ids - /// Does not contain the Id of the Current ContentType - public IEnumerable CompositionIds() - { - return ContentTypeComposition - .Select(x => x.Id) - .Union(ContentTypeComposition.SelectMany(x => x.CompositionIds())); - } - - public override object DeepClone() - { - var clone = (ContentTypeCompositionBase)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //need to manually assign since this is an internal field and will not be automatically mapped - clone.RemovedContentTypeKeyTracker = new List(); - clone._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Models +{ + /// + /// Represents an abstract class for composition specific ContentType properties and methods + /// + [Serializable] + [DataContract(IsReference = true)] + public abstract class ContentTypeCompositionBase : ContentTypeBase, IContentTypeComposition + { + private static readonly Lazy Ps = new Lazy(); + + private List _contentTypeComposition = new List(); + internal List RemovedContentTypeKeyTracker = new List(); + + protected ContentTypeCompositionBase(int parentId) : base(parentId) + { } + + protected ContentTypeCompositionBase(IContentTypeComposition parent) + : this(parent, null) + { } + + protected ContentTypeCompositionBase(IContentTypeComposition parent, string alias) + : base(parent, alias) + { + AddContentType(parent); + } + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo ContentTypeCompositionSelector = + ExpressionHelper.GetPropertyInfo>(x => x.ContentTypeComposition); + } + + /// + /// Gets or sets the content types that compose this content type. + /// + [DataMember] + public IEnumerable ContentTypeComposition + { + get => _contentTypeComposition; + set + { + _contentTypeComposition = value.ToList(); + OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); + } + } + + /// + /// Gets the property groups for the entire composition. + /// + [IgnoreDataMember] + public IEnumerable CompositionPropertyGroups + { + get + { + var groups = ContentTypeComposition.SelectMany(x => x.CompositionPropertyGroups).Union(PropertyGroups); + return groups; + } + } + + /// + /// Gets the property types for the entire composition. + /// + [IgnoreDataMember] + public IEnumerable CompositionPropertyTypes + { + get + { + var propertyTypes = ContentTypeComposition.SelectMany(x => x.CompositionPropertyTypes).Union(PropertyTypes); + return propertyTypes; + } + } + + /// + /// Adds a content type to the composition. + /// + /// The content type to add. + /// True if the content type was added, otherwise false. + public bool AddContentType(IContentTypeComposition contentType) + { + if (contentType.ContentTypeComposition.Any(x => x.CompositionAliases().Any(ContentTypeCompositionExists))) + return false; + + if (string.IsNullOrEmpty(Alias) == false && Alias.Equals(contentType.Alias)) + return false; + + if (ContentTypeCompositionExists(contentType.Alias) == false) + { + //Before we actually go ahead and add the ContentType as a Composition we ensure that we don't + //end up with duplicate PropertyType aliases - in which case we throw an exception. + var conflictingPropertyTypeAliases = CompositionPropertyTypes.SelectMany( + x => contentType.CompositionPropertyTypes + .Where(y => y.Alias.Equals(x.Alias, StringComparison.InvariantCultureIgnoreCase)) + .Select(p => p.Alias)).ToList(); + + if (conflictingPropertyTypeAliases.Any()) + throw new InvalidCompositionException(Alias, contentType.Alias, conflictingPropertyTypeAliases.ToArray()); + + _contentTypeComposition.Add(contentType); + OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); + return true; + } + return false; + } + + /// + /// Removes a content type with a specified alias from the composition. + /// + /// The alias of the content type to remove. + /// True if the content type was removed, otherwise false. + public bool RemoveContentType(string alias) + { + if (ContentTypeCompositionExists(alias)) + { + var contentTypeComposition = ContentTypeComposition.FirstOrDefault(x => x.Alias == alias); + if (contentTypeComposition == null)//You can't remove a composition from another composition + return false; + + RemovedContentTypeKeyTracker.Add(contentTypeComposition.Id); + + //If the ContentType we are removing has Compositions of its own these needs to be removed as well + var compositionIdsToRemove = contentTypeComposition.CompositionIds().ToList(); + if (compositionIdsToRemove.Any()) + RemovedContentTypeKeyTracker.AddRange(compositionIdsToRemove); + + OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); + return _contentTypeComposition.Remove(contentTypeComposition); + } + return false; + } + + /// + /// Checks if a ContentType with the supplied alias exists in the list of composite ContentTypes + /// + /// Alias of a + /// True if ContentType with alias exists, otherwise returns False + public bool ContentTypeCompositionExists(string alias) + { + if (ContentTypeComposition.Any(x => x.Alias.Equals(alias))) + return true; + + if (ContentTypeComposition.Any(x => x.ContentTypeCompositionExists(alias))) + return true; + + return false; + } + + /// + /// Checks whether a PropertyType with a given alias already exists + /// + /// Alias of the PropertyType + /// Returns True if a PropertyType with the passed in alias exists, otherwise False + public override bool PropertyTypeExists(string propertyTypeAlias) + { + return CompositionPropertyTypes.Any(x => x.Alias == propertyTypeAlias); + } + + /// + /// Adds a PropertyGroup. + /// + /// Name of the PropertyGroup to add + /// Returns True if a PropertyGroup with the passed in name was added, otherwise False + public override bool AddPropertyGroup(string groupName) + { + return AddAndReturnPropertyGroup(groupName) != null; + } + + private PropertyGroup AddAndReturnPropertyGroup(string name) + { + // ensure we don't have it already + if (PropertyGroups.Any(x => x.Name == name)) + return null; + + // create the new group + var group = new PropertyGroup(IsPublishing) { Name = name, SortOrder = 0 }; + + // check if it is inherited - there might be more than 1 but we want the 1st, to + // reuse its sort order - if there are more than 1 and they have different sort + // orders... there isn't much we can do anyways + var inheritGroup = CompositionPropertyGroups.FirstOrDefault(x => x.Name == name); + if (inheritGroup == null) + { + // no, just local, set sort order + var lastGroup = PropertyGroups.LastOrDefault(); + if (lastGroup != null) + group.SortOrder = lastGroup.SortOrder + 1; + } + else + { + // yes, inherited, re-use sort order + group.SortOrder = inheritGroup.SortOrder; + } + + // add + PropertyGroups.Add(group); + + return group; + } + + /// + /// Adds a PropertyType to a specific PropertyGroup + /// + /// to add + /// Name of the PropertyGroup to add the PropertyType to + /// Returns True if PropertyType was added, otherwise False + public override bool AddPropertyType(PropertyType propertyType, string propertyGroupName) + { + // ensure no duplicate alias - over all composition properties + if (PropertyTypeExists(propertyType.Alias)) + return false; + + // get and ensure a group local to this content type + var group = PropertyGroups.Contains(propertyGroupName) + ? PropertyGroups[propertyGroupName] + : AddAndReturnPropertyGroup(propertyGroupName); + if (group == null) + return false; + + // add property to group + propertyType.PropertyGroupId = new Lazy(() => group.Id); + group.PropertyTypes.Add(propertyType); + + return true; + } + + /// + /// Gets a list of ContentType aliases from the current composition + /// + /// An enumerable list of string aliases + /// Does not contain the alias of the Current ContentType + public IEnumerable CompositionAliases() + { + return ContentTypeComposition + .Select(x => x.Alias) + .Union(ContentTypeComposition.SelectMany(x => x.CompositionAliases())); + } + + /// + /// Gets a list of ContentType Ids from the current composition + /// + /// An enumerable list of integer ids + /// Does not contain the Id of the Current ContentType + public IEnumerable CompositionIds() + { + return ContentTypeComposition + .Select(x => x.Id) + .Union(ContentTypeComposition.SelectMany(x => x.CompositionIds())); + } + + public override object DeepClone() + { + var clone = (ContentTypeCompositionBase)base.DeepClone(); + //turn off change tracking + clone.DisableChangeTracking(); + //need to manually assign since this is an internal field and will not be automatically mapped + clone.RemovedContentTypeKeyTracker = new List(); + clone._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); + //this shouldn't really be needed since we're not tracking + clone.ResetDirtyProperties(false); + //re-enable tracking + clone.EnableChangeTracking(); + + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/ContentTypeSort.cs b/src/Umbraco.Core/Models/ContentTypeSort.cs index d689a58e82..000d80777c 100644 --- a/src/Umbraco.Core/Models/ContentTypeSort.cs +++ b/src/Umbraco.Core/Models/ContentTypeSort.cs @@ -1,80 +1,80 @@ -using System; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a POCO for setting sort order on a ContentType reference - /// - public class ContentTypeSort : IValueObject, IDeepCloneable - { - [Obsolete("This parameterless constructor should never be used")] - public ContentTypeSort() - { - } - - /// - /// Initializes a new instance of the class. - /// - public ContentTypeSort(int id, int sortOrder) - { - Id = new Lazy(() => id); - SortOrder = sortOrder; - } - - public ContentTypeSort(Lazy id, int sortOrder, string @alias) - { - Id = id; - SortOrder = sortOrder; - Alias = alias; - } - - /// - /// Gets or sets the Id of the ContentType - /// - public Lazy Id { get; set; } - - /// - /// Gets or sets the Sort Order of the ContentType - /// - public int SortOrder { get; set; } - - /// - /// Gets or sets the Alias of the ContentType - /// - public string Alias { get; set; } - - - public object DeepClone() - { - var clone = (ContentTypeSort)MemberwiseClone(); - var id = Id.Value; - clone.Id = new Lazy(() => id); - return clone; - } - - protected bool Equals(ContentTypeSort other) - { - return Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((ContentTypeSort) obj); - } - - public override int GetHashCode() - { - unchecked - { - //The hash code will just be the alias if one is assigned, otherwise it will be the hash code of the Id. - //In some cases the alias can be null of the non lazy ctor is used, in that case, the lazy Id will already have a value created. - return Alias != null ? Alias.GetHashCode() : (Id.Value.GetHashCode() * 397); - } - } - - } -} +using System; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a POCO for setting sort order on a ContentType reference + /// + public class ContentTypeSort : IValueObject, IDeepCloneable + { + [Obsolete("This parameterless constructor should never be used")] + public ContentTypeSort() + { + } + + /// + /// Initializes a new instance of the class. + /// + public ContentTypeSort(int id, int sortOrder) + { + Id = new Lazy(() => id); + SortOrder = sortOrder; + } + + public ContentTypeSort(Lazy id, int sortOrder, string @alias) + { + Id = id; + SortOrder = sortOrder; + Alias = alias; + } + + /// + /// Gets or sets the Id of the ContentType + /// + public Lazy Id { get; set; } + + /// + /// Gets or sets the Sort Order of the ContentType + /// + public int SortOrder { get; set; } + + /// + /// Gets or sets the Alias of the ContentType + /// + public string Alias { get; set; } + + + public object DeepClone() + { + var clone = (ContentTypeSort)MemberwiseClone(); + var id = Id.Value; + clone.Id = new Lazy(() => id); + return clone; + } + + protected bool Equals(ContentTypeSort other) + { + return Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ContentTypeSort) obj); + } + + public override int GetHashCode() + { + unchecked + { + //The hash code will just be the alias if one is assigned, otherwise it will be the hash code of the Id. + //In some cases the alias can be null of the non lazy ctor is used, in that case, the lazy Id will already have a value created. + return Alias != null ? Alias.GetHashCode() : (Id.Value.GetHashCode() * 397); + } + } + + } +} diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 42f7e04fff..919f1b0a1f 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -1,92 +1,92 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Dictionary Item - /// - [Serializable] - [DataContract(IsReference = true)] - public class DictionaryItem : EntityBase, IDictionaryItem - { - public Func GetLanguage { get; set; } - private Guid? _parentId; - private string _itemKey; - private IEnumerable _translations; - - public DictionaryItem(string itemKey) - : this(null, itemKey) - {} - - public DictionaryItem(Guid? parentId, string itemKey) - { - _parentId = parentId; - _itemKey = itemKey; - _translations = new List(); - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey); - public readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations); - - //Custom comparer for enumerable - public readonly DelegateEqualityComparer> DictionaryTranslationComparer = - new DelegateEqualityComparer>( - (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), - enumerable => enumerable.GetHashCode()); - } - - /// - /// Gets or Sets the Parent Id of the Dictionary Item - /// - [DataMember] - public Guid? ParentId - { - get { return _parentId; } - set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } - } - - /// - /// Gets or sets the Key for the Dictionary Item - /// - [DataMember] - public string ItemKey - { - get { return _itemKey; } - set { SetPropertyValueAndDetectChanges(value, ref _itemKey, Ps.Value.ItemKeySelector); } - } - - /// - /// Gets or sets a list of translations for the Dictionary Item - /// - [DataMember] - public IEnumerable Translations - { - get { return _translations; } - set - { - var asArray = value.ToArray(); - //ensure the language callback is set on each translation - if (GetLanguage != null) - { - foreach (var translation in asArray.OfType()) - { - translation.GetLanguage = GetLanguage; - } - } - - SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector, - Ps.Value.DictionaryTranslationComparer); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Dictionary Item + /// + [Serializable] + [DataContract(IsReference = true)] + public class DictionaryItem : EntityBase, IDictionaryItem + { + public Func GetLanguage { get; set; } + private Guid? _parentId; + private string _itemKey; + private IEnumerable _translations; + + public DictionaryItem(string itemKey) + : this(null, itemKey) + {} + + public DictionaryItem(Guid? parentId, string itemKey) + { + _parentId = parentId; + _itemKey = itemKey; + _translations = new List(); + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey); + public readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> DictionaryTranslationComparer = + new DelegateEqualityComparer>( + (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), + enumerable => enumerable.GetHashCode()); + } + + /// + /// Gets or Sets the Parent Id of the Dictionary Item + /// + [DataMember] + public Guid? ParentId + { + get { return _parentId; } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } + } + + /// + /// Gets or sets the Key for the Dictionary Item + /// + [DataMember] + public string ItemKey + { + get { return _itemKey; } + set { SetPropertyValueAndDetectChanges(value, ref _itemKey, Ps.Value.ItemKeySelector); } + } + + /// + /// Gets or sets a list of translations for the Dictionary Item + /// + [DataMember] + public IEnumerable Translations + { + get { return _translations; } + set + { + var asArray = value.ToArray(); + //ensure the language callback is set on each translation + if (GetLanguage != null) + { + foreach (var translation in asArray.OfType()) + { + translation.GetLanguage = GetLanguage; + } + } + + SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector, + Ps.Value.DictionaryTranslationComparer); + } + } + } +} diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index ef41cedc91..2105e8057c 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -1,126 +1,126 @@ -using System; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Mappers; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a translation for a - /// - [Serializable] - [DataContract(IsReference = true)] - public class DictionaryTranslation : EntityBase, IDictionaryTranslation - { - internal Func GetLanguage { get; set; } - - private ILanguage _language; - private string _value; - //note: this will be memberwise cloned - private int _languageId; - - public DictionaryTranslation(ILanguage language, string value) - { - if (language == null) throw new ArgumentNullException("language"); - _language = language; - _languageId = _language.Id; - _value = value; - } - - public DictionaryTranslation(ILanguage language, string value, Guid uniqueId) - { - if (language == null) throw new ArgumentNullException("language"); - _language = language; - _languageId = _language.Id; - _value = value; - Key = uniqueId; - } - - internal DictionaryTranslation(int languageId, string value) - { - _languageId = languageId; - _value = value; - } - - internal DictionaryTranslation(int languageId, string value, Guid uniqueId) - { - _languageId = languageId; - _value = value; - Key = uniqueId; - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); - public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); - } - - /// - /// Gets or sets the for the translation - /// - /// - /// Marked as DoNotClone - TODO: this member shouldn't really exist here in the first place, the DictionaryItem - /// class will have a deep hierarchy of objects which all get deep cloned which we don't want. This should have simply - /// just referenced a language ID not the actual language object. In v8 we need to fix this. - /// We're going to have to do the same hacky stuff we had to do with the Template/File contents so that this is returned - /// on a callback. - /// - [DataMember] - [DoNotClone] - public ILanguage Language - { - get - { - if (_language != null) - return _language; - - // else, must lazy-load - if (GetLanguage != null && _languageId > 0) - _language = GetLanguage(_languageId); - return _language; - } - set - { - SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); - _languageId = _language == null ? -1 : _language.Id; - } - } - - public int LanguageId - { - get { return _languageId; } - } - - /// - /// Gets or sets the translated text - /// - [DataMember] - public string Value - { - get { return _value; } - set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } - } - - public override object DeepClone() - { - var clone = (DictionaryTranslation)base.DeepClone(); - - // clear fields that were memberwise-cloned and that we don't want to clone - clone._language = null; - - // turn off change tracking - clone.DisableChangeTracking(); - - // this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - - // re-enable tracking - clone.EnableChangeTracking(); - - return clone; - } - } -} +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Mappers; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a translation for a + /// + [Serializable] + [DataContract(IsReference = true)] + public class DictionaryTranslation : EntityBase, IDictionaryTranslation + { + internal Func GetLanguage { get; set; } + + private ILanguage _language; + private string _value; + //note: this will be memberwise cloned + private int _languageId; + + public DictionaryTranslation(ILanguage language, string value) + { + if (language == null) throw new ArgumentNullException("language"); + _language = language; + _languageId = _language.Id; + _value = value; + } + + public DictionaryTranslation(ILanguage language, string value, Guid uniqueId) + { + if (language == null) throw new ArgumentNullException("language"); + _language = language; + _languageId = _language.Id; + _value = value; + Key = uniqueId; + } + + internal DictionaryTranslation(int languageId, string value) + { + _languageId = languageId; + _value = value; + } + + internal DictionaryTranslation(int languageId, string value, Guid uniqueId) + { + _languageId = languageId; + _value = value; + Key = uniqueId; + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + } + + /// + /// Gets or sets the for the translation + /// + /// + /// Marked as DoNotClone - TODO: this member shouldn't really exist here in the first place, the DictionaryItem + /// class will have a deep hierarchy of objects which all get deep cloned which we don't want. This should have simply + /// just referenced a language ID not the actual language object. In v8 we need to fix this. + /// We're going to have to do the same hacky stuff we had to do with the Template/File contents so that this is returned + /// on a callback. + /// + [DataMember] + [DoNotClone] + public ILanguage Language + { + get + { + if (_language != null) + return _language; + + // else, must lazy-load + if (GetLanguage != null && _languageId > 0) + _language = GetLanguage(_languageId); + return _language; + } + set + { + SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); + _languageId = _language == null ? -1 : _language.Id; + } + } + + public int LanguageId + { + get { return _languageId; } + } + + /// + /// Gets or sets the translated text + /// + [DataMember] + public string Value + { + get { return _value; } + set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } + } + + public override object DeepClone() + { + var clone = (DictionaryTranslation)base.DeepClone(); + + // clear fields that were memberwise-cloned and that we don't want to clone + clone._language = null; + + // turn off change tracking + clone.DisableChangeTracking(); + + // this shouldn't really be needed since we're not tracking + clone.ResetDirtyProperties(false); + + // re-enable tracking + clone.EnableChangeTracking(); + + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs b/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs index ee4955cda3..c51b1c4f51 100644 --- a/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs +++ b/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs @@ -1,45 +1,45 @@ -using System; - -namespace Umbraco.Core.Models.Editors -{ - /// - /// Represents data that has been submitted to be saved for a content property - /// - /// - /// This object exists because we may need to save additional data for each property, more than just - /// the string representation of the value being submitted. An example of this is uploaded files. - /// - public class ContentPropertyData - { - public ContentPropertyData(object value, object dataTypeConfiguration) - { - Value = value; - DataTypeConfiguration = dataTypeConfiguration; - } - - /// - /// The value submitted for the property - /// - public object Value { get; } - - /// - /// The data type configuration for the property. - /// - public object DataTypeConfiguration { get; } - - /// - /// Gets or sets the unique identifier of the content owning the property. - /// - public Guid ContentKey { get; set; } - - /// - /// Gets or sets the unique identifier of the property type. - /// - public Guid PropertyTypeKey { get; set; } - - /// - /// Gets or sets the uploaded files. - /// - public ContentPropertyFile[] Files { get; set; } - } -} +using System; + +namespace Umbraco.Core.Models.Editors +{ + /// + /// Represents data that has been submitted to be saved for a content property + /// + /// + /// This object exists because we may need to save additional data for each property, more than just + /// the string representation of the value being submitted. An example of this is uploaded files. + /// + public class ContentPropertyData + { + public ContentPropertyData(object value, object dataTypeConfiguration) + { + Value = value; + DataTypeConfiguration = dataTypeConfiguration; + } + + /// + /// The value submitted for the property + /// + public object Value { get; } + + /// + /// The data type configuration for the property. + /// + public object DataTypeConfiguration { get; } + + /// + /// Gets or sets the unique identifier of the content owning the property. + /// + public Guid ContentKey { get; set; } + + /// + /// Gets or sets the unique identifier of the property type. + /// + public Guid PropertyTypeKey { get; set; } + + /// + /// Gets or sets the uploaded files. + /// + public ContentPropertyFile[] Files { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 41aff25a81..2e85b13261 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -1,181 +1,181 @@ -using System; -using System.IO; -using System.Reflection; -using System.Runtime.Serialization; -using System.Text; -using Umbraco.Core.IO; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents an abstract file which provides basic functionality for a File with an Alias and Name - /// - [Serializable] - [DataContract(IsReference = true)] - public abstract class File : EntityBase, IFile - { - private string _path; - private string _originalPath; - - // initialize to string.Empty so that it is possible to save a new file, - // should use the lazyContent ctor to set it to null when loading existing. - // cannot simply use HasIdentity as some classes (eg Script) override it - // in a weird way. - private string _content; - internal Func GetFileContent { get; set; } - - protected File(string path, Func getFileContent = null) - { - _path = SanitizePath(path); - _originalPath = _path; - GetFileContent = getFileContent; - _content = getFileContent != null ? null : string.Empty; - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.Content); - public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - } - - private string _alias; - private string _name; - - private static string SanitizePath(string path) - { - return path - .Replace('\\', System.IO.Path.DirectorySeparatorChar) - .Replace('/', System.IO.Path.DirectorySeparatorChar); - - //Don't strip the start - this was a bug fixed in 7.3, see ScriptRepositoryTests.PathTests - //.TrimStart(System.IO.Path.DirectorySeparatorChar) - //.TrimStart('/'); - } - - /// - /// Gets or sets the Name of the File including extension - /// - [DataMember] - public virtual string Name - { - get { return _name ?? (_name = System.IO.Path.GetFileName(Path)); } - } - - /// - /// Gets or sets the Alias of the File, which is the name without the extension - /// - [DataMember] - public virtual string Alias - { - get - { - if (_alias == null) - { - var name = System.IO.Path.GetFileName(Path); - if (name == null) return string.Empty; - var lastIndexOf = name.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase); - _alias = name.Substring(0, lastIndexOf); - } - return _alias; - } - } - - /// - /// Gets or sets the Path to the File from the root of the file's associated IFileSystem - /// - [DataMember] - public virtual string Path - { - get { return _path; } - set - { - //reset - _alias = null; - _name = null; - - SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, Ps.Value.PathSelector); - } - } - - /// - /// Gets the original path of the file - /// - public string OriginalPath - { - get { return _originalPath; } - } - - /// - /// Called to re-set the OriginalPath to the Path - /// - public void ResetOriginalPath() - { - _originalPath = _path; - } - - /// - /// Gets or sets the Content of a File - /// - /// Marked as DoNotClone, because it should be lazy-reloaded from disk. - [DataMember] - [DoNotClone] - public virtual string Content - { - get - { - if (_content != null) - return _content; - - // else, must lazy-load, and ensure it's not null - if (GetFileContent != null) - _content = GetFileContent(this); - return _content ?? (_content = string.Empty); - } - set - { - SetPropertyValueAndDetectChanges( - value ?? string.Empty, // cannot set to null - ref _content, Ps.Value.ContentSelector); - } - } - - /// - /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) - /// - public string VirtualPath { get; set; } - - // this exists so that class that manage name and alias differently, eg Template, - // can implement their own cloning - (though really, not sure it's even needed) - protected virtual void DeepCloneNameAndAlias(File clone) - { - // set fields that have a lazy value, by forcing evaluation of the lazy - clone._name = Name; - clone._alias = Alias; - } - - public override object DeepClone() - { - var clone = (File) base.DeepClone(); - - // clear fields that were memberwise-cloned and that we don't want to clone - clone._content = null; - - // turn off change tracking - clone.DisableChangeTracking(); - - // ... - DeepCloneNameAndAlias(clone); - - // this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - - // re-enable tracking - clone.EnableChangeTracking(); - - return clone; - } - } -} +using System; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using Umbraco.Core.IO; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents an abstract file which provides basic functionality for a File with an Alias and Name + /// + [Serializable] + [DataContract(IsReference = true)] + public abstract class File : EntityBase, IFile + { + private string _path; + private string _originalPath; + + // initialize to string.Empty so that it is possible to save a new file, + // should use the lazyContent ctor to set it to null when loading existing. + // cannot simply use HasIdentity as some classes (eg Script) override it + // in a weird way. + private string _content; + internal Func GetFileContent { get; set; } + + protected File(string path, Func getFileContent = null) + { + _path = SanitizePath(path); + _originalPath = _path; + GetFileContent = getFileContent; + _content = getFileContent != null ? null : string.Empty; + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.Content); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + } + + private string _alias; + private string _name; + + private static string SanitizePath(string path) + { + return path + .Replace('\\', System.IO.Path.DirectorySeparatorChar) + .Replace('/', System.IO.Path.DirectorySeparatorChar); + + //Don't strip the start - this was a bug fixed in 7.3, see ScriptRepositoryTests.PathTests + //.TrimStart(System.IO.Path.DirectorySeparatorChar) + //.TrimStart('/'); + } + + /// + /// Gets or sets the Name of the File including extension + /// + [DataMember] + public virtual string Name + { + get { return _name ?? (_name = System.IO.Path.GetFileName(Path)); } + } + + /// + /// Gets or sets the Alias of the File, which is the name without the extension + /// + [DataMember] + public virtual string Alias + { + get + { + if (_alias == null) + { + var name = System.IO.Path.GetFileName(Path); + if (name == null) return string.Empty; + var lastIndexOf = name.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase); + _alias = name.Substring(0, lastIndexOf); + } + return _alias; + } + } + + /// + /// Gets or sets the Path to the File from the root of the file's associated IFileSystem + /// + [DataMember] + public virtual string Path + { + get { return _path; } + set + { + //reset + _alias = null; + _name = null; + + SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, Ps.Value.PathSelector); + } + } + + /// + /// Gets the original path of the file + /// + public string OriginalPath + { + get { return _originalPath; } + } + + /// + /// Called to re-set the OriginalPath to the Path + /// + public void ResetOriginalPath() + { + _originalPath = _path; + } + + /// + /// Gets or sets the Content of a File + /// + /// Marked as DoNotClone, because it should be lazy-reloaded from disk. + [DataMember] + [DoNotClone] + public virtual string Content + { + get + { + if (_content != null) + return _content; + + // else, must lazy-load, and ensure it's not null + if (GetFileContent != null) + _content = GetFileContent(this); + return _content ?? (_content = string.Empty); + } + set + { + SetPropertyValueAndDetectChanges( + value ?? string.Empty, // cannot set to null + ref _content, Ps.Value.ContentSelector); + } + } + + /// + /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) + /// + public string VirtualPath { get; set; } + + // this exists so that class that manage name and alias differently, eg Template, + // can implement their own cloning - (though really, not sure it's even needed) + protected virtual void DeepCloneNameAndAlias(File clone) + { + // set fields that have a lazy value, by forcing evaluation of the lazy + clone._name = Name; + clone._alias = Alias; + } + + public override object DeepClone() + { + var clone = (File) base.DeepClone(); + + // clear fields that were memberwise-cloned and that we don't want to clone + clone._content = null; + + // turn off change tracking + clone.DisableChangeTracking(); + + // ... + DeepCloneNameAndAlias(clone); + + // this shouldn't really be needed since we're not tracking + clone.ResetDirtyProperties(false); + + // re-enable tracking + clone.EnableChangeTracking(); + + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 7bf52ea56b..13797425ed 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -1,228 +1,228 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a document. - /// - /// - /// A document can be published, rendered by a template. - /// - public interface IContent : IContentBase - { - /// - /// Gets or sets the template used to render the content. - /// - ITemplate Template { get; set; } - - /// - /// Gets a value indicating whether the content is published. - /// - bool Published { get; } - - PublishedState PublishedState { get; } - - /// - /// Gets a value indicating whether the content has been edited. - /// - bool Edited { get; } - - /// - /// Gets the published version identifier. - /// - int PublishedVersionId { get; } - - /// - /// Gets a value indicating whether the content item is a blueprint. - /// - bool Blueprint { get; } - - /// - /// Gets the template used to render the published version of the content. - /// - /// When editing the content, the template can change, but this will not until the content is published. - ITemplate PublishTemplate { get; } - - /// - /// Gets the name of the published version of the content. - /// - /// When editing the content, the name can change, but this will not until the content is published. - string PublishName { get; } - - /// - /// Gets the identifier of the user who published the content. - /// - int? PublisherId { get; } - - /// - /// Gets the date and time the content was published. - /// - DateTime? PublishDate { get; } - - /// - /// Gets or sets the date and time the content item should be published. - /// - DateTime? ReleaseDate { get; set; } - - /// - /// Gets or sets the date and time the content should be unpublished. - /// - DateTime? ExpireDate { get; set; } - - /// - /// Gets the content type of this content. - /// - IContentType ContentType { get; } - - /// - /// Gets the current status of the content. - /// - ContentStatus Status { get; } - - /// - /// Gets a value indicating whether a given culture is published. - /// - /// - /// A culture becomes published whenever values for this culture are published, - /// and the content published name for this culture is non-null. It becomes non-published - /// whenever values for this culture are unpublished. - /// - bool IsCulturePublished(string culture); +using System; +using System.Collections.Generic; - /// - /// Gets the date a culture was published. - /// - DateTime GetCulturePublishDate(string culture); +namespace Umbraco.Core.Models +{ + /// + /// Represents a document. + /// + /// + /// A document can be published, rendered by a template. + /// + public interface IContent : IContentBase + { + /// + /// Gets or sets the template used to render the content. + /// + ITemplate Template { get; set; } - /// - /// Gets a value indicated whether a given culture is edited. - /// - /// - /// A culture is edited when it is not published, or when it is published but - /// it has changes. - /// - bool IsCultureEdited(string culture); - - /// - /// Gets the name of the published version of the content for a given culture. - /// - /// - /// When editing the content, the name can change, but this will not until the content is published. - /// When is null, gets the invariant - /// language, which is the value of the property. - /// - string GetPublishName(string culture); + /// + /// Gets a value indicating whether the content is published. + /// + bool Published { get; } - /// - /// Gets the published names of the content. - /// - /// - /// Because a dictionary key cannot be null this cannot get the invariant - /// name, which must be get via the property. - /// - IReadOnlyDictionary PublishCultureNames { get; } + PublishedState PublishedState { get; } - /// - /// Gets the available cultures. - /// + /// + /// Gets a value indicating whether the content has been edited. + /// + bool Edited { get; } + + /// + /// Gets the published version identifier. + /// + int PublishedVersionId { get; } + + /// + /// Gets a value indicating whether the content item is a blueprint. + /// + bool Blueprint { get; } + + /// + /// Gets the template used to render the published version of the content. + /// + /// When editing the content, the template can change, but this will not until the content is published. + ITemplate PublishTemplate { get; } + + /// + /// Gets the name of the published version of the content. + /// + /// When editing the content, the name can change, but this will not until the content is published. + string PublishName { get; } + + /// + /// Gets the identifier of the user who published the content. + /// + int? PublisherId { get; } + + /// + /// Gets the date and time the content was published. + /// + DateTime? PublishDate { get; } + + /// + /// Gets or sets the date and time the content item should be published. + /// + DateTime? ReleaseDate { get; set; } + + /// + /// Gets or sets the date and time the content should be unpublished. + /// + DateTime? ExpireDate { get; set; } + + /// + /// Gets the content type of this content. + /// + IContentType ContentType { get; } + + /// + /// Gets the current status of the content. + /// + ContentStatus Status { get; } + + /// + /// Gets a value indicating whether a given culture is published. + /// + /// + /// A culture becomes published whenever values for this culture are published, + /// and the content published name for this culture is non-null. It becomes non-published + /// whenever values for this culture are unpublished. + /// + bool IsCulturePublished(string culture); + + /// + /// Gets the date a culture was published. + /// + DateTime GetCulturePublishDate(string culture); + + /// + /// Gets a value indicated whether a given culture is edited. + /// + /// + /// A culture is edited when it is not published, or when it is published but + /// it has changes. + /// + bool IsCultureEdited(string culture); + + /// + /// Gets the name of the published version of the content for a given culture. + /// + /// + /// When editing the content, the name can change, but this will not until the content is published. + /// When is null, gets the invariant + /// language, which is the value of the property. + /// + string GetPublishName(string culture); + + /// + /// Gets the published names of the content. + /// + /// + /// Because a dictionary key cannot be null this cannot get the invariant + /// name, which must be get via the property. + /// + IReadOnlyDictionary PublishCultureNames { get; } + + /// + /// Gets the available cultures. + /// IEnumerable AvailableCultures { get; } - /// - /// Gets the published cultures. - /// - IEnumerable PublishedCultures { get; } + /// + /// Gets the published cultures. + /// + IEnumerable PublishedCultures { get; } - /// - /// Gets the edited cultures. - /// - IEnumerable EditedCultures { get; } - - // fixme - these two should move to some kind of service - - /// - /// Changes the for the current content object - /// - /// New ContentType for this content - /// Leaves PropertyTypes intact after change - void ChangeContentType(IContentType contentType); - - /// - /// Changes the for the current content object and removes PropertyTypes, - /// which are not part of the new ContentType. - /// - /// New ContentType for this content - /// Boolean indicating whether to clear PropertyTypes upon change - void ChangeContentType(IContentType contentType, bool clearProperties); - - /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset - /// - /// - IContent DeepCloneWithResetIdentities(); - - /// - /// Publishes all values. - /// - /// A value indicating whether the values could be published. - /// - /// The document must then be published via the content service. - /// Values are not published if they are not valid. - /// - //fixme return an Attempt with some error results if it doesn't work - //fixme - needs API review as this is not used apart from in tests - //bool TryPublishAllValues(); - - /// - /// Publishes values. - /// - /// A value indicating whether the values could be published. - /// - /// The document must then be published via the content service. - /// Values are not published if they are not valid. - /// - //fixme return an Attempt with some error results if it doesn't work - bool TryPublishValues(string culture = null, string segment = null); - - /// - /// Publishes the culture/any values. - /// - /// A value indicating whether the values could be published. - /// - /// The document must then be published via the content service. - /// Values are not published if they are not valie. + /// + /// Gets the edited cultures. + /// + IEnumerable EditedCultures { get; } + + // fixme - these two should move to some kind of service + + /// + /// Changes the for the current content object + /// + /// New ContentType for this content + /// Leaves PropertyTypes intact after change + void ChangeContentType(IContentType contentType); + + /// + /// Changes the for the current content object and removes PropertyTypes, + /// which are not part of the new ContentType. + /// + /// New ContentType for this content + /// Boolean indicating whether to clear PropertyTypes upon change + void ChangeContentType(IContentType contentType, bool clearProperties); + + /// + /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset + /// + /// + IContent DeepCloneWithResetIdentities(); + + /// + /// Publishes all values. + /// + /// A value indicating whether the values could be published. + /// + /// The document must then be published via the content service. + /// Values are not published if they are not valid. /// - //fixme - needs API review as this is not used apart from in tests - //bool PublishCultureValues(string culture = null); - - /// - /// Clears all published values. - /// - void ClearAllPublishedValues(); - - /// - /// Clears published values. - /// - void ClearPublishedValues(string culture = null, string segment = null); - - /// - /// Clears the culture/any published values. - /// - void ClearCulturePublishedValues(string culture = null); - - /// - /// Copies values from another document. - /// - void CopyAllValues(IContent other); - - /// - /// Copies values from another document. - /// - void CopyValues(IContent other, string culture = null, string segment = null); - - /// - /// Copies culture/any values from another document. - /// - void CopyCultureValues(IContent other, string culture = null); - } -} + //fixme return an Attempt with some error results if it doesn't work + //fixme - needs API review as this is not used apart from in tests + //bool TryPublishAllValues(); + + /// + /// Publishes values. + /// + /// A value indicating whether the values could be published. + /// + /// The document must then be published via the content service. + /// Values are not published if they are not valid. + /// + //fixme return an Attempt with some error results if it doesn't work + bool TryPublishValues(string culture = null, string segment = null); + + /// + /// Publishes the culture/any values. + /// + /// A value indicating whether the values could be published. + /// + /// The document must then be published via the content service. + /// Values are not published if they are not valie. + /// + //fixme - needs API review as this is not used apart from in tests + //bool PublishCultureValues(string culture = null); + + /// + /// Clears all published values. + /// + void ClearAllPublishedValues(); + + /// + /// Clears published values. + /// + void ClearPublishedValues(string culture = null, string segment = null); + + /// + /// Clears the culture/any published values. + /// + void ClearCulturePublishedValues(string culture = null); + + /// + /// Copies values from another document. + /// + void CopyAllValues(IContent other); + + /// + /// Copies values from another document. + /// + void CopyValues(IContent other, string culture = null, string segment = null); + + /// + /// Copies culture/any values from another document. + /// + void CopyCultureValues(IContent other, string culture = null); + } +} diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 527b4455ce..1605c1da01 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -1,143 +1,143 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Provides a base class for content items. - /// - /// - /// Content items are documents, medias and members. - /// Content items have a content type, and properties. - /// - public interface IContentBase : IUmbracoEntity - { - /// - /// Integer Id of the default ContentType - /// - int ContentTypeId { get; } - - /// - /// Gets the identifier of the writer. - /// - int WriterId { get; set; } - - /// - /// Gets the version identifier. - /// - int VersionId { get; } +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; - /// - /// Sets the name of the content item for a specified language. - /// - /// - /// When is null, sets the invariant - /// language, which sets the property. - /// - void SetName(string value, string culture); +namespace Umbraco.Core.Models +{ + /// + /// Provides a base class for content items. + /// + /// + /// Content items are documents, medias and members. + /// Content items have a content type, and properties. + /// + public interface IContentBase : IUmbracoEntity + { + /// + /// Integer Id of the default ContentType + /// + int ContentTypeId { get; } - /// - /// Gets the name of the content item for a specified language. - /// - /// - /// When is null, gets the invariant - /// language, which is the value of the property. - /// + /// + /// Gets the identifier of the writer. + /// + int WriterId { get; set; } + + /// + /// Gets the version identifier. + /// + int VersionId { get; } + + /// + /// Sets the name of the content item for a specified language. + /// + /// + /// When is null, sets the invariant + /// language, which sets the property. + /// + void SetName(string value, string culture); + + /// + /// Gets the name of the content item for a specified language. + /// + /// + /// When is null, gets the invariant + /// language, which is the value of the property. + /// string GetName(string culture); - /// - /// Gets the names of the content item. - /// - /// - /// Because a dictionary key cannot be null this cannot get the invariant - /// name, which must be get or set via the property. - /// - IReadOnlyDictionary CultureNames { get; } - - /// - /// Gets a value indicating whether a given culture is available. - /// - /// - /// A culture becomes available whenever the content name for this culture is - /// non-null, and it becomes unavailable whenever the content name is null. - /// - bool IsCultureAvailable(string culture); + /// + /// Gets the names of the content item. + /// + /// + /// Because a dictionary key cannot be null this cannot get the invariant + /// name, which must be get or set via the property. + /// + IReadOnlyDictionary CultureNames { get; } - /// - /// Gets the date a culture was created. - /// - DateTime GetCultureDate(string culture); + /// + /// Gets a value indicating whether a given culture is available. + /// + /// + /// A culture becomes available whenever the content name for this culture is + /// non-null, and it becomes unavailable whenever the content name is null. + /// + bool IsCultureAvailable(string culture); - /// - /// List of properties, which make up all the data available for this Content object - /// - /// Properties are loaded as part of the Content object graph - PropertyCollection Properties { get; set; } - - /// - /// List of PropertyGroups available on this Content object - /// - /// PropertyGroups are kind of lazy loaded as part of the object graph - IEnumerable PropertyGroups { get; } - - /// - /// List of PropertyTypes available on this Content object - /// - /// PropertyTypes are kind of lazy loaded as part of the object graph - IEnumerable PropertyTypes { get; } - - /// - /// Gets a value indicating whether the content entity has a property with the supplied alias. - /// - /// Indicates that the content entity has a property with the supplied alias, but - /// not necessarily that the content has a value for that property. Could be missing. - bool HasProperty(string propertyTypeAlias); - - /// - /// Gets the value of a Property - /// - object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false); - - /// - /// Gets the typed value of a Property - /// - TValue GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false); - - /// - /// Sets the (edited) value of a Property - /// - void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null); + /// + /// Gets the date a culture was created. + /// + DateTime GetCultureDate(string culture); - /// - /// Checks if the content and property values are valid in order to be persisted. - /// - /// - /// + /// + /// List of properties, which make up all the data available for this Content object + /// + /// Properties are loaded as part of the Content object graph + PropertyCollection Properties { get; set; } + + /// + /// List of PropertyGroups available on this Content object + /// + /// PropertyGroups are kind of lazy loaded as part of the object graph + IEnumerable PropertyGroups { get; } + + /// + /// List of PropertyTypes available on this Content object + /// + /// PropertyTypes are kind of lazy loaded as part of the object graph + IEnumerable PropertyTypes { get; } + + /// + /// Gets a value indicating whether the content entity has a property with the supplied alias. + /// + /// Indicates that the content entity has a property with the supplied alias, but + /// not necessarily that the content has a value for that property. Could be missing. + bool HasProperty(string propertyTypeAlias); + + /// + /// Gets the value of a Property + /// + object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false); + + /// + /// Gets the typed value of a Property + /// + TValue GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false); + + /// + /// Sets the (edited) value of a Property + /// + void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null); + + /// + /// Checks if the content and property values are valid in order to be persisted. + /// + /// + /// /// bool IsValid(string culture = null, string segment = null); - - /// - /// Gets a value indicating if all properties values are valid. + + /// + /// Gets a value indicating if all properties values are valid. /// - //fixme - needs API review as this is not used apart from in tests - //Property[] ValidateAllProperties(); - - /// - /// Validates the content item's properties for the provided culture/segment - /// - /// - /// + //fixme - needs API review as this is not used apart from in tests + //Property[] ValidateAllProperties(); + + /// + /// Validates the content item's properties for the provided culture/segment + /// + /// + /// /// /// /// This will not perform validation for properties that do not match the required ContentVariation based on the culture/segment values provided - /// - Property[] ValidateProperties(string culture = null, string segment = null); - - /// - /// Gets a value indicating if the culture properties values are valid. + /// + Property[] ValidateProperties(string culture = null, string segment = null); + + /// + /// Gets a value indicating if the culture properties values are valid. /// - //fixme - needs API review as this is not used apart from in tests - //Property[] ValidatePropertiesForCulture(string culture = null); - } -} + //fixme - needs API review as this is not used apart from in tests + //Property[] ValidatePropertiesForCulture(string culture = null); + } +} diff --git a/src/Umbraco.Core/Models/IContentType.cs b/src/Umbraco.Core/Models/IContentType.cs index 66c4aed37a..951a685d22 100644 --- a/src/Umbraco.Core/Models/IContentType.cs +++ b/src/Umbraco.Core/Models/IContentType.cs @@ -1,40 +1,40 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Models -{ - /// - /// Defines a ContentType, which Content is based on - /// - public interface IContentType : IContentTypeComposition - { - /// - /// Gets the default Template of the ContentType - /// - ITemplate DefaultTemplate { get; } - - /// - /// Gets or Sets a list of Templates which are allowed for the ContentType - /// - IEnumerable AllowedTemplates { get; set; } - - /// - /// Sets the default template for the ContentType - /// - /// Default - void SetDefaultTemplate(ITemplate template); - - /// - /// Removes a template from the list of allowed templates - /// - /// to remove - /// True if template was removed, otherwise False - bool RemoveTemplate(ITemplate template); - - /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset - /// - /// - /// - IContentType DeepCloneWithResetIdentities(string newAlias); - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + /// + /// Defines a ContentType, which Content is based on + /// + public interface IContentType : IContentTypeComposition + { + /// + /// Gets the default Template of the ContentType + /// + ITemplate DefaultTemplate { get; } + + /// + /// Gets or Sets a list of Templates which are allowed for the ContentType + /// + IEnumerable AllowedTemplates { get; set; } + + /// + /// Sets the default template for the ContentType + /// + /// Default + void SetDefaultTemplate(ITemplate template); + + /// + /// Removes a template from the list of allowed templates + /// + /// to remove + /// True if template was removed, otherwise False + bool RemoveTemplate(ITemplate template); + + /// + /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset + /// + /// + /// + IContentType DeepCloneWithResetIdentities(string newAlias); + } +} diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index 7c8cc8eafe..df171d5efc 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -1,126 +1,126 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Defines the base for a ContentType with properties that - /// are shared between ContentTypes and MediaTypes. - /// - public interface IContentTypeBase : IUmbracoEntity - { - /// - /// Gets or Sets the Alias of the ContentType - /// - string Alias { get; set; } - - /// - /// Gets or Sets the Description for the ContentType - /// - string Description { get; set; } - - /// - /// Gets or Sets the Icon for the ContentType - /// - string Icon { get; set; } - - /// - /// Gets or Sets the Thumbnail for the ContentType - /// - string Thumbnail { get; set; } - - /// - /// Gets or Sets a boolean indicating whether this ContentType is allowed at the root - /// - bool AllowedAsRoot { get; set; } - - /// - /// Gets or Sets a boolean indicating whether this ContentType is a Container - /// - /// - /// ContentType Containers doesn't show children in the tree, but rather in grid-type view. - /// - bool IsContainer { get; set; } - - /// - /// Gets or sets the content variation of the content type. - /// - ContentVariation Variations { get; set; } - - /// - /// Validates that a variation is valid for the content type. - /// - bool ValidateVariation(string culture, string segment, bool throwIfInvalid); - - /// - /// Gets or Sets a list of integer Ids of the ContentTypes allowed under the ContentType - /// - IEnumerable AllowedContentTypes { get; set; } - - /// - /// Gets or Sets a collection of Property Groups - /// - PropertyGroupCollection PropertyGroups { get; set; } - - /// - /// Gets all property types, across all property groups. - /// - IEnumerable PropertyTypes { get; } - - /// - /// Gets or sets the property types that are not in a group. - /// - IEnumerable NoGroupPropertyTypes { get; set; } - - /// - /// Removes a PropertyType from the current ContentType - /// - /// Alias of the to remove - void RemovePropertyType(string propertyTypeAlias); - - /// - /// Removes a PropertyGroup from the current ContentType - /// - /// Name of the to remove - void RemovePropertyGroup(string propertyGroupName); - - /// - /// Checks whether a PropertyType with a given alias already exists - /// - /// Alias of the PropertyType - /// Returns True if a PropertyType with the passed in alias exists, otherwise False - bool PropertyTypeExists(string propertyTypeAlias); - - /// - /// Adds a PropertyType to a specific PropertyGroup - /// - /// to add - /// Name of the PropertyGroup to add the PropertyType to - /// Returns True if PropertyType was added, otherwise False - bool AddPropertyType(PropertyType propertyType, string propertyGroupName); - - /// - /// Adds a PropertyType, which does not belong to a PropertyGroup. - /// - /// to add - /// Returns True if PropertyType was added, otherwise False - bool AddPropertyType(PropertyType propertyType); - - /// - /// Adds a PropertyGroup. - /// This method will also check if a group already exists with the same name and link it to the parent. - /// - /// Name of the PropertyGroup to add - /// Returns True if a PropertyGroup with the passed in name was added, otherwise False - bool AddPropertyGroup(string groupName); - - /// - /// Moves a PropertyType to a specified PropertyGroup - /// - /// Alias of the PropertyType to move - /// Name of the PropertyGroup to move the PropertyType to - /// - bool MovePropertyType(string propertyTypeAlias, string propertyGroupName); - } -} +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Defines the base for a ContentType with properties that + /// are shared between ContentTypes and MediaTypes. + /// + public interface IContentTypeBase : IUmbracoEntity + { + /// + /// Gets or Sets the Alias of the ContentType + /// + string Alias { get; set; } + + /// + /// Gets or Sets the Description for the ContentType + /// + string Description { get; set; } + + /// + /// Gets or Sets the Icon for the ContentType + /// + string Icon { get; set; } + + /// + /// Gets or Sets the Thumbnail for the ContentType + /// + string Thumbnail { get; set; } + + /// + /// Gets or Sets a boolean indicating whether this ContentType is allowed at the root + /// + bool AllowedAsRoot { get; set; } + + /// + /// Gets or Sets a boolean indicating whether this ContentType is a Container + /// + /// + /// ContentType Containers doesn't show children in the tree, but rather in grid-type view. + /// + bool IsContainer { get; set; } + + /// + /// Gets or sets the content variation of the content type. + /// + ContentVariation Variations { get; set; } + + /// + /// Validates that a variation is valid for the content type. + /// + bool ValidateVariation(string culture, string segment, bool throwIfInvalid); + + /// + /// Gets or Sets a list of integer Ids of the ContentTypes allowed under the ContentType + /// + IEnumerable AllowedContentTypes { get; set; } + + /// + /// Gets or Sets a collection of Property Groups + /// + PropertyGroupCollection PropertyGroups { get; set; } + + /// + /// Gets all property types, across all property groups. + /// + IEnumerable PropertyTypes { get; } + + /// + /// Gets or sets the property types that are not in a group. + /// + IEnumerable NoGroupPropertyTypes { get; set; } + + /// + /// Removes a PropertyType from the current ContentType + /// + /// Alias of the to remove + void RemovePropertyType(string propertyTypeAlias); + + /// + /// Removes a PropertyGroup from the current ContentType + /// + /// Name of the to remove + void RemovePropertyGroup(string propertyGroupName); + + /// + /// Checks whether a PropertyType with a given alias already exists + /// + /// Alias of the PropertyType + /// Returns True if a PropertyType with the passed in alias exists, otherwise False + bool PropertyTypeExists(string propertyTypeAlias); + + /// + /// Adds a PropertyType to a specific PropertyGroup + /// + /// to add + /// Name of the PropertyGroup to add the PropertyType to + /// Returns True if PropertyType was added, otherwise False + bool AddPropertyType(PropertyType propertyType, string propertyGroupName); + + /// + /// Adds a PropertyType, which does not belong to a PropertyGroup. + /// + /// to add + /// Returns True if PropertyType was added, otherwise False + bool AddPropertyType(PropertyType propertyType); + + /// + /// Adds a PropertyGroup. + /// This method will also check if a group already exists with the same name and link it to the parent. + /// + /// Name of the PropertyGroup to add + /// Returns True if a PropertyGroup with the passed in name was added, otherwise False + bool AddPropertyGroup(string groupName); + + /// + /// Moves a PropertyType to a specified PropertyGroup + /// + /// Alias of the PropertyType to move + /// Name of the PropertyGroup to move the PropertyType to + /// + bool MovePropertyType(string propertyTypeAlias, string propertyGroupName); + } +} diff --git a/src/Umbraco.Core/Models/IContentTypeComposition.cs b/src/Umbraco.Core/Models/IContentTypeComposition.cs index 3d206e4660..b5277a23be 100644 --- a/src/Umbraco.Core/Models/IContentTypeComposition.cs +++ b/src/Umbraco.Core/Models/IContentTypeComposition.cs @@ -1,58 +1,58 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Models -{ - /// - /// Defines the Composition of a ContentType - /// - public interface IContentTypeComposition : IContentTypeBase - { - /// - /// Gets or sets the content types that compose this content type. - /// - IEnumerable ContentTypeComposition { get; set; } - - /// - /// Gets the property groups for the entire composition. - /// - IEnumerable CompositionPropertyGroups { get; } - - /// - /// Gets the property types for the entire composition. - /// - IEnumerable CompositionPropertyTypes { get; } - - /// - /// Adds a new ContentType to the list of composite ContentTypes - /// - /// to add - /// True if ContentType was added, otherwise returns False - bool AddContentType(IContentTypeComposition contentType); - - /// - /// Removes a ContentType with the supplied alias from the the list of composite ContentTypes - /// - /// Alias of a - /// True if ContentType was removed, otherwise returns False - bool RemoveContentType(string alias); - - /// - /// Checks if a ContentType with the supplied alias exists in the list of composite ContentTypes - /// - /// Alias of a - /// True if ContentType with alias exists, otherwise returns False - bool ContentTypeCompositionExists(string alias); - - /// - /// Gets a list of ContentType aliases from the current composition - /// - /// An enumerable list of string aliases - IEnumerable CompositionAliases(); - - /// - /// Gets a list of ContentType Ids from the current composition - /// - /// An enumerable list of integer ids - IEnumerable CompositionIds(); - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + /// + /// Defines the Composition of a ContentType + /// + public interface IContentTypeComposition : IContentTypeBase + { + /// + /// Gets or sets the content types that compose this content type. + /// + IEnumerable ContentTypeComposition { get; set; } + + /// + /// Gets the property groups for the entire composition. + /// + IEnumerable CompositionPropertyGroups { get; } + + /// + /// Gets the property types for the entire composition. + /// + IEnumerable CompositionPropertyTypes { get; } + + /// + /// Adds a new ContentType to the list of composite ContentTypes + /// + /// to add + /// True if ContentType was added, otherwise returns False + bool AddContentType(IContentTypeComposition contentType); + + /// + /// Removes a ContentType with the supplied alias from the the list of composite ContentTypes + /// + /// Alias of a + /// True if ContentType was removed, otherwise returns False + bool RemoveContentType(string alias); + + /// + /// Checks if a ContentType with the supplied alias exists in the list of composite ContentTypes + /// + /// Alias of a + /// True if ContentType with alias exists, otherwise returns False + bool ContentTypeCompositionExists(string alias); + + /// + /// Gets a list of ContentType aliases from the current composition + /// + /// An enumerable list of string aliases + IEnumerable CompositionAliases(); + + /// + /// Gets a list of ContentType Ids from the current composition + /// + /// An enumerable list of integer ids + IEnumerable CompositionIds(); + } +} diff --git a/src/Umbraco.Core/Models/IDictionaryItem.cs b/src/Umbraco.Core/Models/IDictionaryItem.cs index 6983a536d3..1176eb3833 100644 --- a/src/Umbraco.Core/Models/IDictionaryItem.cs +++ b/src/Umbraco.Core/Models/IDictionaryItem.cs @@ -1,28 +1,28 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - public interface IDictionaryItem : IEntity, IRememberBeingDirty - { - /// - /// Gets or Sets the Parent Id of the Dictionary Item - /// - [DataMember] - Guid? ParentId { get; set; } - - /// - /// Gets or sets the Key for the Dictionary Item - /// - [DataMember] - string ItemKey { get; set; } - - /// - /// Gets or sets a list of translations for the Dictionary Item - /// - [DataMember] - IEnumerable Translations { get; set; } - } -} +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + public interface IDictionaryItem : IEntity, IRememberBeingDirty + { + /// + /// Gets or Sets the Parent Id of the Dictionary Item + /// + [DataMember] + Guid? ParentId { get; set; } + + /// + /// Gets or sets the Key for the Dictionary Item + /// + [DataMember] + string ItemKey { get; set; } + + /// + /// Gets or sets a list of translations for the Dictionary Item + /// + [DataMember] + IEnumerable Translations { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/IDictionaryTranslation.cs b/src/Umbraco.Core/Models/IDictionaryTranslation.cs index c66322b75d..1c6db5e099 100644 --- a/src/Umbraco.Core/Models/IDictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/IDictionaryTranslation.cs @@ -1,23 +1,23 @@ -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Mappers; - -namespace Umbraco.Core.Models -{ - public interface IDictionaryTranslation : IEntity - { - /// - /// Gets or sets the for the translation - /// - [DataMember] - ILanguage Language { get; set; } - - int LanguageId { get; } - - /// - /// Gets or sets the translated text - /// - [DataMember] - string Value { get; set; } - } -} +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Mappers; + +namespace Umbraco.Core.Models +{ + public interface IDictionaryTranslation : IEntity + { + /// + /// Gets or sets the for the translation + /// + [DataMember] + ILanguage Language { get; set; } + + int LanguageId { get; } + + /// + /// Gets or sets the translated text + /// + [DataMember] + string Value { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/IFile.cs b/src/Umbraco.Core/Models/IFile.cs index 109d65f554..0d98c90ffb 100644 --- a/src/Umbraco.Core/Models/IFile.cs +++ b/src/Umbraco.Core/Models/IFile.cs @@ -1,48 +1,48 @@ -using System; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Defines a File - /// - /// Used for Scripts, Stylesheets and Templates - public interface IFile : IEntity - { - /// - /// Gets the Name of the File including extension - /// - string Name { get; } - - /// - /// Gets the Alias of the File, which is the name without the extension - /// - string Alias { get; } - - /// - /// Gets or sets the Path to the File from the root of the file's associated IFileSystem - /// - string Path { get; set; } - - /// - /// Gets the original path of the file - /// - string OriginalPath { get; } - - /// - /// Called to re-set the OriginalPath to the Path - /// - void ResetOriginalPath(); - - /// - /// Gets or sets the Content of a File - /// - string Content { get; set; } - - /// - /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) - /// - string VirtualPath { get; set; } - - } -} +using System; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Defines a File + /// + /// Used for Scripts, Stylesheets and Templates + public interface IFile : IEntity + { + /// + /// Gets the Name of the File including extension + /// + string Name { get; } + + /// + /// Gets the Alias of the File, which is the name without the extension + /// + string Alias { get; } + + /// + /// Gets or sets the Path to the File from the root of the file's associated IFileSystem + /// + string Path { get; set; } + + /// + /// Gets the original path of the file + /// + string OriginalPath { get; } + + /// + /// Called to re-set the OriginalPath to the Path + /// + void ResetOriginalPath(); + + /// + /// Gets or sets the Content of a File + /// + string Content { get; set; } + + /// + /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) + /// + string VirtualPath { get; set; } + + } +} diff --git a/src/Umbraco.Core/Models/ILanguage.cs b/src/Umbraco.Core/Models/ILanguage.cs index 23cef54180..7bf9e9b32c 100644 --- a/src/Umbraco.Core/Models/ILanguage.cs +++ b/src/Umbraco.Core/Models/ILanguage.cs @@ -1,37 +1,37 @@ -using System.Globalization; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - public interface ILanguage : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the Iso Code for the Language - /// - [DataMember] - string IsoCode { get; set; } - - /// - /// Gets or sets the Culture Name for the Language - /// - [DataMember] - string CultureName { get; set; } - - /// - /// Returns a object for the current Language - /// - [IgnoreDataMember] - CultureInfo CultureInfo { get; } - - /// - /// Defines if this language is the default variant language when language variants are in use - /// - bool IsDefaultVariantLanguage { get; set; } - - /// - /// If true, a variant node cannot be published unless this language variant is created - /// - bool Mandatory { get; set; } - } -} +using System.Globalization; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + public interface ILanguage : IEntity, IRememberBeingDirty + { + /// + /// Gets or sets the Iso Code for the Language + /// + [DataMember] + string IsoCode { get; set; } + + /// + /// Gets or sets the Culture Name for the Language + /// + [DataMember] + string CultureName { get; set; } + + /// + /// Returns a object for the current Language + /// + [IgnoreDataMember] + CultureInfo CultureInfo { get; } + + /// + /// Defines if this language is the default variant language when language variants are in use + /// + bool IsDefaultVariantLanguage { get; set; } + + /// + /// If true, a variant node cannot be published unless this language variant is created + /// + bool Mandatory { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/IMacro.cs b/src/Umbraco.Core/Models/IMacro.cs index 472c135e32..c3d37b0700 100644 --- a/src/Umbraco.Core/Models/IMacro.cs +++ b/src/Umbraco.Core/Models/IMacro.cs @@ -1,79 +1,79 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Defines a Macro - /// - public interface IMacro : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the alias of the Macro - /// - [DataMember] - string Alias { get; set; } - - /// - /// Gets or sets the name of the Macro - /// - [DataMember] - string Name { get; set; } - - /// - /// Gets or sets a boolean indicating whether the Macro can be used in an Editor - /// - [DataMember] - bool UseInEditor { get; set; } - - /// - /// Gets or sets the Cache Duration for the Macro - /// - [DataMember] - int CacheDuration { get; set; } - - /// - /// Gets or sets a boolean indicating whether the Macro should be Cached by Page - /// - [DataMember] - bool CacheByPage { get; set; } - - /// - /// Gets or sets a boolean indicating whether the Macro should be Cached Personally - /// - [DataMember] - bool CacheByMember { get; set; } - - /// - /// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor - /// - [DataMember] - bool DontRender { get; set; } - - /// - /// Gets or set the path to the macro source to render - /// - [DataMember] +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Defines a Macro + /// + public interface IMacro : IEntity, IRememberBeingDirty + { + /// + /// Gets or sets the alias of the Macro + /// + [DataMember] + string Alias { get; set; } + + /// + /// Gets or sets the name of the Macro + /// + [DataMember] + string Name { get; set; } + + /// + /// Gets or sets a boolean indicating whether the Macro can be used in an Editor + /// + [DataMember] + bool UseInEditor { get; set; } + + /// + /// Gets or sets the Cache Duration for the Macro + /// + [DataMember] + int CacheDuration { get; set; } + + /// + /// Gets or sets a boolean indicating whether the Macro should be Cached by Page + /// + [DataMember] + bool CacheByPage { get; set; } + + /// + /// Gets or sets a boolean indicating whether the Macro should be Cached Personally + /// + [DataMember] + bool CacheByMember { get; set; } + + /// + /// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor + /// + [DataMember] + bool DontRender { get; set; } + + /// + /// Gets or set the path to the macro source to render + /// + [DataMember] string MacroSource { get; set; } - /// - /// Gets or set the macro type - /// - [DataMember] + /// + /// Gets or set the macro type + /// + [DataMember] MacroTypes MacroType { get; set; } - /// - /// Gets or sets a list of Macro Properties - /// - [DataMember] - MacroPropertyCollection Properties { get; } - - ///// - ///// Returns an enum based on the properties on the Macro - ///// - ///// - //MacroTypes MacroType(); - } -} + /// + /// Gets or sets a list of Macro Properties + /// + [DataMember] + MacroPropertyCollection Properties { get; } + + ///// + ///// Returns an enum based on the properties on the Macro + ///// + ///// + //MacroTypes MacroType(); + } +} diff --git a/src/Umbraco.Core/Models/IMacroProperty.cs b/src/Umbraco.Core/Models/IMacroProperty.cs index b3c592fd5d..a414c285c7 100644 --- a/src/Umbraco.Core/Models/IMacroProperty.cs +++ b/src/Umbraco.Core/Models/IMacroProperty.cs @@ -1,42 +1,42 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Defines a Property for a Macro - /// - public interface IMacroProperty : IValueObject, IDeepCloneable, IRememberBeingDirty - { - [DataMember] - int Id { get; set; } - - [DataMember] - Guid Key { get; set; } - - /// - /// Gets or sets the Alias of the Property - /// - [DataMember] - string Alias { get; set; } - - /// - /// Gets or sets the Name of the Property - /// - [DataMember] - string Name { get; set; } - - /// - /// Gets or sets the Sort Order of the Property - /// - [DataMember] - int SortOrder { get; set; } - - /// - /// Gets or sets the parameter editor alias - /// - [DataMember] - string EditorAlias { get; set; } - } -} +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Defines a Property for a Macro + /// + public interface IMacroProperty : IValueObject, IDeepCloneable, IRememberBeingDirty + { + [DataMember] + int Id { get; set; } + + [DataMember] + Guid Key { get; set; } + + /// + /// Gets or sets the Alias of the Property + /// + [DataMember] + string Alias { get; set; } + + /// + /// Gets or sets the Name of the Property + /// + [DataMember] + string Name { get; set; } + + /// + /// Gets or sets the Sort Order of the Property + /// + [DataMember] + int SortOrder { get; set; } + + /// + /// Gets or sets the parameter editor alias + /// + [DataMember] + string EditorAlias { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/IMedia.cs b/src/Umbraco.Core/Models/IMedia.cs index 02aa5db61c..cfb79bbf2c 100644 --- a/src/Umbraco.Core/Models/IMedia.cs +++ b/src/Umbraco.Core/Models/IMedia.cs @@ -1,27 +1,27 @@ -using Umbraco.Core.Persistence.Mappers; - -namespace Umbraco.Core.Models -{ - public interface IMedia : IContentBase - { - /// - /// Gets the ContentType used by this Media object - /// - IMediaType ContentType { get; } - - /// - /// Changes the for the current content object - /// - /// New ContentType for this content - /// Leaves PropertyTypes intact after change - void ChangeContentType(IMediaType contentType); - - /// - /// Changes the for the current content object and removes PropertyTypes, - /// which are not part of the new ContentType. - /// - /// New ContentType for this content - /// Boolean indicating whether to clear PropertyTypes upon change - void ChangeContentType(IMediaType contentType, bool clearProperties); - } -} +using Umbraco.Core.Persistence.Mappers; + +namespace Umbraco.Core.Models +{ + public interface IMedia : IContentBase + { + /// + /// Gets the ContentType used by this Media object + /// + IMediaType ContentType { get; } + + /// + /// Changes the for the current content object + /// + /// New ContentType for this content + /// Leaves PropertyTypes intact after change + void ChangeContentType(IMediaType contentType); + + /// + /// Changes the for the current content object and removes PropertyTypes, + /// which are not part of the new ContentType. + /// + /// New ContentType for this content + /// Boolean indicating whether to clear PropertyTypes upon change + void ChangeContentType(IMediaType contentType, bool clearProperties); + } +} diff --git a/src/Umbraco.Core/Models/IMediaType.cs b/src/Umbraco.Core/Models/IMediaType.cs index 28c5e89f19..25a5d0fa35 100644 --- a/src/Umbraco.Core/Models/IMediaType.cs +++ b/src/Umbraco.Core/Models/IMediaType.cs @@ -1,18 +1,18 @@ -using Umbraco.Core.Persistence.Mappers; - -namespace Umbraco.Core.Models -{ - /// - /// Defines a ContentType, which Media is based on - /// - public interface IMediaType : IContentTypeComposition - { - - /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset - /// - /// - /// - IMediaType DeepCloneWithResetIdentities(string newAlias); - } -} +using Umbraco.Core.Persistence.Mappers; + +namespace Umbraco.Core.Models +{ + /// + /// Defines a ContentType, which Media is based on + /// + public interface IMediaType : IContentTypeComposition + { + + /// + /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset + /// + /// + /// + IMediaType DeepCloneWithResetIdentities(string newAlias); + } +} diff --git a/src/Umbraco.Core/Models/IMember.cs b/src/Umbraco.Core/Models/IMember.cs index 83540b200b..15826f03f3 100644 --- a/src/Umbraco.Core/Models/IMember.cs +++ b/src/Umbraco.Core/Models/IMember.cs @@ -1,20 +1,20 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Models.Membership; - -namespace Umbraco.Core.Models -{ - public interface IMember : IContentBase, IMembershipUser, IHaveAdditionalData - { - /// - /// String alias of the default ContentType - /// - string ContentTypeAlias { get; } - - /// - /// Gets the ContentType used by this content object - /// - IMemberType ContentType { get; } - } -} +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Models +{ + public interface IMember : IContentBase, IMembershipUser, IHaveAdditionalData + { + /// + /// String alias of the default ContentType + /// + string ContentTypeAlias { get; } + + /// + /// Gets the ContentType used by this content object + /// + IMemberType ContentType { get; } + } +} diff --git a/src/Umbraco.Core/Models/IMemberType.cs b/src/Umbraco.Core/Models/IMemberType.cs index 9596d88cca..29d78eddcb 100644 --- a/src/Umbraco.Core/Models/IMemberType.cs +++ b/src/Umbraco.Core/Models/IMemberType.cs @@ -1,50 +1,50 @@ -namespace Umbraco.Core.Models -{ - /// - /// Defines a MemberType, which Member is based on - /// - public interface IMemberType : IContentTypeComposition - { - /// - /// Gets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to check - /// - bool MemberCanEditProperty(string propertyTypeAlias); - - /// - /// Gets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to check - /// - bool MemberCanViewProperty(string propertyTypeAlias); - - /// - /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. - /// - /// PropertyType Alias of the Property to check - /// - bool IsSensitiveProperty(string propertyTypeAlias); - - /// - /// Sets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - void SetMemberCanEditProperty(string propertyTypeAlias, bool value); - - /// - /// Sets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - void SetMemberCanViewProperty(string propertyTypeAlias, bool value); - - /// - /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - void SetIsSensitiveProperty(string propertyTypeAlias, bool value); - } -} +namespace Umbraco.Core.Models +{ + /// + /// Defines a MemberType, which Member is based on + /// + public interface IMemberType : IContentTypeComposition + { + /// + /// Gets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to check + /// + bool MemberCanEditProperty(string propertyTypeAlias); + + /// + /// Gets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + bool MemberCanViewProperty(string propertyTypeAlias); + + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + bool IsSensitiveProperty(string propertyTypeAlias); + + /// + /// Sets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetMemberCanEditProperty(string propertyTypeAlias, bool value); + + /// + /// Sets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetMemberCanViewProperty(string propertyTypeAlias, bool value); + + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetIsSensitiveProperty(string propertyTypeAlias, bool value); + } +} diff --git a/src/Umbraco.Core/Models/IRelation.cs b/src/Umbraco.Core/Models/IRelation.cs index bb8d218d9c..745216fba1 100644 --- a/src/Umbraco.Core/Models/IRelation.cs +++ b/src/Umbraco.Core/Models/IRelation.cs @@ -1,38 +1,38 @@ -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - public interface IRelation : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the Parent Id of the Relation (Source) - /// - [DataMember] - int ParentId { get; set; } - - /// - /// Gets or sets the Child Id of the Relation (Destination) - /// - [DataMember] - int ChildId { get; set; } - - /// - /// Gets or sets the for the Relation - /// - [DataMember] - IRelationType RelationType { get; set; } - - /// - /// Gets or sets a comment for the Relation - /// - [DataMember] - string Comment { get; set; } - - /// - /// Gets the Id of the that this Relation is based on. - /// - [IgnoreDataMember] - int RelationTypeId { get; } - } -} +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + public interface IRelation : IEntity, IRememberBeingDirty + { + /// + /// Gets or sets the Parent Id of the Relation (Source) + /// + [DataMember] + int ParentId { get; set; } + + /// + /// Gets or sets the Child Id of the Relation (Destination) + /// + [DataMember] + int ChildId { get; set; } + + /// + /// Gets or sets the for the Relation + /// + [DataMember] + IRelationType RelationType { get; set; } + + /// + /// Gets or sets a comment for the Relation + /// + [DataMember] + string Comment { get; set; } + + /// + /// Gets the Id of the that this Relation is based on. + /// + [IgnoreDataMember] + int RelationTypeId { get; } + } +} diff --git a/src/Umbraco.Core/Models/IRelationType.cs b/src/Umbraco.Core/Models/IRelationType.cs index c52075efac..8bbe657427 100644 --- a/src/Umbraco.Core/Models/IRelationType.cs +++ b/src/Umbraco.Core/Models/IRelationType.cs @@ -1,41 +1,41 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - public interface IRelationType : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the Name of the RelationType - /// - [DataMember] - string Name { get; set; } - - /// - /// Gets or sets the Alias of the RelationType - /// - [DataMember] - string Alias { get; set; } - - /// - /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) - /// - [DataMember] - bool IsBidirectional { get; set; } - - /// - /// Gets or sets the Parents object type id - /// - /// Corresponds to the NodeObjectType in the umbracoNode table - [DataMember] - Guid ParentObjectType { get; set; } - - /// - /// Gets or sets the Childs object type id - /// - /// Corresponds to the NodeObjectType in the umbracoNode table - [DataMember] - Guid ChildObjectType { get; set; } - } -} +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + public interface IRelationType : IEntity, IRememberBeingDirty + { + /// + /// Gets or sets the Name of the RelationType + /// + [DataMember] + string Name { get; set; } + + /// + /// Gets or sets the Alias of the RelationType + /// + [DataMember] + string Alias { get; set; } + + /// + /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) + /// + [DataMember] + bool IsBidirectional { get; set; } + + /// + /// Gets or sets the Parents object type id + /// + /// Corresponds to the NodeObjectType in the umbracoNode table + [DataMember] + Guid ParentObjectType { get; set; } + + /// + /// Gets or sets the Childs object type id + /// + /// Corresponds to the NodeObjectType in the umbracoNode table + [DataMember] + Guid ChildObjectType { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/ITag.cs b/src/Umbraco.Core/Models/ITag.cs index cdd71c6553..6f492a2d78 100644 --- a/src/Umbraco.Core/Models/ITag.cs +++ b/src/Umbraco.Core/Models/ITag.cs @@ -1,29 +1,29 @@ -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a tag entity. - /// - public interface ITag : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the tag group. - /// - [DataMember] - string Group { get; set; } - - /// - /// Gets or sets the tag text. - /// - [DataMember] - string Text { get; set; } - - /// - /// Gets the number of nodes tagged with this tag. - /// - /// Only when returning from queries. - int NodeCount { get; } - } -} +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a tag entity. + /// + public interface ITag : IEntity, IRememberBeingDirty + { + /// + /// Gets or sets the tag group. + /// + [DataMember] + string Group { get; set; } + + /// + /// Gets or sets the tag text. + /// + [DataMember] + string Text { get; set; } + + /// + /// Gets the number of nodes tagged with this tag. + /// + /// Only when returning from queries. + int NodeCount { get; } + } +} diff --git a/src/Umbraco.Core/Models/ITemplate.cs b/src/Umbraco.Core/Models/ITemplate.cs index 8e3571a83c..97b9324415 100644 --- a/src/Umbraco.Core/Models/ITemplate.cs +++ b/src/Umbraco.Core/Models/ITemplate.cs @@ -1,38 +1,38 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Defines a Template File (Masterpage or Mvc View) - /// - public interface ITemplate : IFile, IRememberBeingDirty, ICanBeDirty - { - /// - /// Gets the Name of the File including extension - /// - new string Name { get; set; } - - /// - /// Gets the Alias of the File, which is the name without the extension - /// - new string Alias { get; set; } - - /// - /// Returns true if the template is used as a layout for other templates (i.e. it has 'children') - /// - bool IsMasterTemplate { get; } - - /// - /// returns the master template alias - /// - string MasterTemplateAlias { get; } - - /// - /// Set the mastertemplate - /// - /// - void SetMasterTemplate(ITemplate masterTemplate); - } -} +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Defines a Template File (Masterpage or Mvc View) + /// + public interface ITemplate : IFile, IRememberBeingDirty, ICanBeDirty + { + /// + /// Gets the Name of the File including extension + /// + new string Name { get; set; } + + /// + /// Gets the Alias of the File, which is the name without the extension + /// + new string Alias { get; set; } + + /// + /// Returns true if the template is used as a layout for other templates (i.e. it has 'children') + /// + bool IsMasterTemplate { get; } + + /// + /// returns the master template alias + /// + string MasterTemplateAlias { get; } + + /// + /// Set the mastertemplate + /// + /// + void SetMasterTemplate(ITemplate masterTemplate); + } +} diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index ee50661639..fa1c9dc826 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -1,75 +1,75 @@ -using System; -using System.Globalization; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Language. - /// - [Serializable] - [DataContract(IsReference = true)] - public class Language : EntityBase, ILanguage - { - private static readonly Lazy Ps = new Lazy(); - - private string _isoCode; - private string _cultureName; - private bool _isDefaultVariantLanguage; - private bool _mandatory; - - public Language(string isoCode) - { - IsoCode = isoCode; - } - - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo(x => x.IsoCode); - public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo(x => x.CultureName); - public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDefaultVariantLanguage); - public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); - } - - /// - /// Gets or sets the Iso Code for the Language - /// - [DataMember] - public string IsoCode - { - get => _isoCode; - set => SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector); - } - - /// - /// Gets or sets the Culture Name for the Language - /// - [DataMember] - public string CultureName - { - get => _cultureName ?? CultureInfo.GetCultureInfo(IsoCode).DisplayName; - set => SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector); - } - - /// - /// Returns a object for the current Language - /// - [IgnoreDataMember] - public CultureInfo CultureInfo => CultureInfo.GetCultureInfo(IsoCode); - - public bool IsDefaultVariantLanguage - { - get => _isDefaultVariantLanguage; - set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, Ps.Value.IsDefaultVariantLanguageSelector); - } - - public bool Mandatory - { - get => _mandatory; - set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector); - } - } -} +using System; +using System.Globalization; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Language. + /// + [Serializable] + [DataContract(IsReference = true)] + public class Language : EntityBase, ILanguage + { + private static readonly Lazy Ps = new Lazy(); + + private string _isoCode; + private string _cultureName; + private bool _isDefaultVariantLanguage; + private bool _mandatory; + + public Language(string isoCode) + { + IsoCode = isoCode; + } + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo(x => x.IsoCode); + public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo(x => x.CultureName); + public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDefaultVariantLanguage); + public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); + } + + /// + /// Gets or sets the Iso Code for the Language + /// + [DataMember] + public string IsoCode + { + get => _isoCode; + set => SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector); + } + + /// + /// Gets or sets the Culture Name for the Language + /// + [DataMember] + public string CultureName + { + get => _cultureName ?? CultureInfo.GetCultureInfo(IsoCode).DisplayName; + set => SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector); + } + + /// + /// Returns a object for the current Language + /// + [IgnoreDataMember] + public CultureInfo CultureInfo => CultureInfo.GetCultureInfo(IsoCode); + + public bool IsDefaultVariantLanguage + { + get => _isDefaultVariantLanguage; + set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, Ps.Value.IsDefaultVariantLanguageSelector); + } + + public bool Mandatory + { + get => _mandatory; + set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector); + } + } +} diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index dc411a0291..6e68bda439 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -1,305 +1,305 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Strings; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Macro - /// - [Serializable] - [DataContract(IsReference = true)] - public class Macro : EntityBase, IMacro - { - public Macro() - { - _properties = new MacroPropertyCollection(); - _properties.CollectionChanged += PropertiesChanged; - _addedProperties = new List(); - _removedProperties = new List(); - } - - /// - /// Creates an item with pre-filled properties - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public Macro(int id, Guid key, bool useInEditor, int cacheDuration, string @alias, string name, bool cacheByPage, bool cacheByMember, bool dontRender, string macroSource, MacroTypes macroType) - : this() - { - Id = id; - Key = key; - UseInEditor = useInEditor; - CacheDuration = cacheDuration; - Alias = alias.ToCleanString(CleanStringType.Alias); - Name = name; - CacheByPage = cacheByPage; - CacheByMember = cacheByMember; - DontRender = dontRender; - MacroSource = macroSource; - MacroType = macroType; - } - - /// - /// Creates an instance for persisting a new item - /// - /// - /// - /// - /// - /// - /// - /// - /// - public Macro(string @alias, string name, - string macroSource, - MacroTypes macroType, - bool cacheByPage = false, - bool cacheByMember = false, - bool dontRender = true, - bool useInEditor = false, - int cacheDuration = 0) - : this() - { - UseInEditor = useInEditor; - CacheDuration = cacheDuration; - Alias = alias.ToCleanString(CleanStringType.Alias); - Name = name; - CacheByPage = cacheByPage; - CacheByMember = cacheByMember; - DontRender = dontRender; - MacroSource = macroSource; - MacroType = macroType; - } - - private string _alias; - private string _name; - private bool _useInEditor; - private int _cacheDuration; - private bool _cacheByPage; - private bool _cacheByMember; - private bool _dontRender; - private string _macroSource; - private MacroTypes _macroType = MacroTypes.Unknown; - private MacroPropertyCollection _properties; - private List _addedProperties; - private List _removedProperties; - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo UseInEditorSelector = ExpressionHelper.GetPropertyInfo(x => x.UseInEditor); - public readonly PropertyInfo CacheDurationSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheDuration); - public readonly PropertyInfo CacheByPageSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByPage); - public readonly PropertyInfo CacheByMemberSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByMember); - public readonly PropertyInfo DontRenderSelector = ExpressionHelper.GetPropertyInfo(x => x.DontRender); - public readonly PropertyInfo ScriptPathSelector = ExpressionHelper.GetPropertyInfo(x => x.MacroSource); - public readonly PropertyInfo MacroTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.MacroType); - public readonly PropertyInfo PropertiesSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); - } - - void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(Ps.Value.PropertiesSelector); - - if (e.Action == NotifyCollectionChangedAction.Add) - { - //listen for changes - var prop = e.NewItems.Cast().First(); - prop.PropertyChanged += PropertyDataChanged; - - var alias = prop.Alias; - - if (_addedProperties.Contains(alias) == false) - { - //add to the added props - _addedProperties.Add(alias); - } - } - else if (e.Action == NotifyCollectionChangedAction.Remove) - { - //remove listening for changes - var prop = e.OldItems.Cast().First(); - prop.PropertyChanged -= PropertyDataChanged; - - var alias = prop.Alias; - - if (_removedProperties.Contains(alias) == false) - { - _removedProperties.Add(alias); - } - } - } - - /// - /// When some data of a property has changed ensure our Properties flag is dirty - /// - /// - /// - void PropertyDataChanged(object sender, PropertyChangedEventArgs e) - { - OnPropertyChanged(Ps.Value.PropertiesSelector); - } - - public override void ResetDirtyProperties(bool rememberDirty) - { - _addedProperties.Clear(); - _removedProperties.Clear(); - base.ResetDirtyProperties(rememberDirty); - foreach (var prop in Properties) - { - ((BeingDirtyBase)prop).ResetDirtyProperties(rememberDirty); - } - } - - /// - /// Used internally to check if we need to add a section in the repository to the db - /// - internal IEnumerable AddedProperties - { - get { return _addedProperties; } - } - - /// - /// Used internally to check if we need to remove a section in the repository to the db - /// - internal IEnumerable RemovedProperties - { - get { return _removedProperties; } - } - - /// - /// Gets or sets the alias of the Macro - /// - [DataMember] - public string Alias - { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.Alias), ref _alias, Ps.Value.AliasSelector); } - } - - /// - /// Gets or sets the name of the Macro - /// - [DataMember] - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// Gets or sets a boolean indicating whether the Macro can be used in an Editor - /// - [DataMember] - public bool UseInEditor - { - get { return _useInEditor; } - set { SetPropertyValueAndDetectChanges(value, ref _useInEditor, Ps.Value.UseInEditorSelector); } - } - - /// - /// Gets or sets the Cache Duration for the Macro - /// - [DataMember] - public int CacheDuration - { - get { return _cacheDuration; } - set { SetPropertyValueAndDetectChanges(value, ref _cacheDuration, Ps.Value.CacheDurationSelector); } - } - - /// - /// Gets or sets a boolean indicating whether the Macro should be Cached by Page - /// - [DataMember] - public bool CacheByPage - { - get { return _cacheByPage; } - set { SetPropertyValueAndDetectChanges(value, ref _cacheByPage, Ps.Value.CacheByPageSelector); } - } - - /// - /// Gets or sets a boolean indicating whether the Macro should be Cached Personally - /// - [DataMember] - public bool CacheByMember - { - get { return _cacheByMember; } - set { SetPropertyValueAndDetectChanges(value, ref _cacheByMember, Ps.Value.CacheByMemberSelector); } - } - - /// - /// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor - /// - [DataMember] - public bool DontRender - { - get { return _dontRender; } - set { SetPropertyValueAndDetectChanges(value, ref _dontRender, Ps.Value.DontRenderSelector); } - } - - /// - /// Gets or set the path to the Partial View to render - /// - [DataMember] - public string MacroSource - { - get { return _macroSource; } - set { SetPropertyValueAndDetectChanges(value, ref _macroSource, Ps.Value.ScriptPathSelector); } +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Strings; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Macro + /// + [Serializable] + [DataContract(IsReference = true)] + public class Macro : EntityBase, IMacro + { + public Macro() + { + _properties = new MacroPropertyCollection(); + _properties.CollectionChanged += PropertiesChanged; + _addedProperties = new List(); + _removedProperties = new List(); } - /// - /// Gets or set the path to the Partial View to render - /// - [DataMember] - public MacroTypes MacroType - { - get { return _macroType; } - set { SetPropertyValueAndDetectChanges(value, ref _macroType, Ps.Value.MacroTypeSelector); } - } - - /// - /// Gets or sets a list of Macro Properties - /// - [DataMember] - public MacroPropertyCollection Properties - { - get { return _properties; } - } - - public override object DeepClone() - { - var clone = (Macro)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - clone._addedProperties = new List(); - clone._removedProperties = new List(); - clone._properties = (MacroPropertyCollection)Properties.DeepClone(); - //re-assign the event handler - clone._properties.CollectionChanged += clone.PropertiesChanged; - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; - } - } -} + /// + /// Creates an item with pre-filled properties + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public Macro(int id, Guid key, bool useInEditor, int cacheDuration, string @alias, string name, bool cacheByPage, bool cacheByMember, bool dontRender, string macroSource, MacroTypes macroType) + : this() + { + Id = id; + Key = key; + UseInEditor = useInEditor; + CacheDuration = cacheDuration; + Alias = alias.ToCleanString(CleanStringType.Alias); + Name = name; + CacheByPage = cacheByPage; + CacheByMember = cacheByMember; + DontRender = dontRender; + MacroSource = macroSource; + MacroType = macroType; + } + + /// + /// Creates an instance for persisting a new item + /// + /// + /// + /// + /// + /// + /// + /// + /// + public Macro(string @alias, string name, + string macroSource, + MacroTypes macroType, + bool cacheByPage = false, + bool cacheByMember = false, + bool dontRender = true, + bool useInEditor = false, + int cacheDuration = 0) + : this() + { + UseInEditor = useInEditor; + CacheDuration = cacheDuration; + Alias = alias.ToCleanString(CleanStringType.Alias); + Name = name; + CacheByPage = cacheByPage; + CacheByMember = cacheByMember; + DontRender = dontRender; + MacroSource = macroSource; + MacroType = macroType; + } + + private string _alias; + private string _name; + private bool _useInEditor; + private int _cacheDuration; + private bool _cacheByPage; + private bool _cacheByMember; + private bool _dontRender; + private string _macroSource; + private MacroTypes _macroType = MacroTypes.Unknown; + private MacroPropertyCollection _properties; + private List _addedProperties; + private List _removedProperties; + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo UseInEditorSelector = ExpressionHelper.GetPropertyInfo(x => x.UseInEditor); + public readonly PropertyInfo CacheDurationSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheDuration); + public readonly PropertyInfo CacheByPageSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByPage); + public readonly PropertyInfo CacheByMemberSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByMember); + public readonly PropertyInfo DontRenderSelector = ExpressionHelper.GetPropertyInfo(x => x.DontRender); + public readonly PropertyInfo ScriptPathSelector = ExpressionHelper.GetPropertyInfo(x => x.MacroSource); + public readonly PropertyInfo MacroTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.MacroType); + public readonly PropertyInfo PropertiesSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + } + + void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(Ps.Value.PropertiesSelector); + + if (e.Action == NotifyCollectionChangedAction.Add) + { + //listen for changes + var prop = e.NewItems.Cast().First(); + prop.PropertyChanged += PropertyDataChanged; + + var alias = prop.Alias; + + if (_addedProperties.Contains(alias) == false) + { + //add to the added props + _addedProperties.Add(alias); + } + } + else if (e.Action == NotifyCollectionChangedAction.Remove) + { + //remove listening for changes + var prop = e.OldItems.Cast().First(); + prop.PropertyChanged -= PropertyDataChanged; + + var alias = prop.Alias; + + if (_removedProperties.Contains(alias) == false) + { + _removedProperties.Add(alias); + } + } + } + + /// + /// When some data of a property has changed ensure our Properties flag is dirty + /// + /// + /// + void PropertyDataChanged(object sender, PropertyChangedEventArgs e) + { + OnPropertyChanged(Ps.Value.PropertiesSelector); + } + + public override void ResetDirtyProperties(bool rememberDirty) + { + _addedProperties.Clear(); + _removedProperties.Clear(); + base.ResetDirtyProperties(rememberDirty); + foreach (var prop in Properties) + { + ((BeingDirtyBase)prop).ResetDirtyProperties(rememberDirty); + } + } + + /// + /// Used internally to check if we need to add a section in the repository to the db + /// + internal IEnumerable AddedProperties + { + get { return _addedProperties; } + } + + /// + /// Used internally to check if we need to remove a section in the repository to the db + /// + internal IEnumerable RemovedProperties + { + get { return _removedProperties; } + } + + /// + /// Gets or sets the alias of the Macro + /// + [DataMember] + public string Alias + { + get { return _alias; } + set { SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.Alias), ref _alias, Ps.Value.AliasSelector); } + } + + /// + /// Gets or sets the name of the Macro + /// + [DataMember] + public string Name + { + get { return _name; } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + } + + /// + /// Gets or sets a boolean indicating whether the Macro can be used in an Editor + /// + [DataMember] + public bool UseInEditor + { + get { return _useInEditor; } + set { SetPropertyValueAndDetectChanges(value, ref _useInEditor, Ps.Value.UseInEditorSelector); } + } + + /// + /// Gets or sets the Cache Duration for the Macro + /// + [DataMember] + public int CacheDuration + { + get { return _cacheDuration; } + set { SetPropertyValueAndDetectChanges(value, ref _cacheDuration, Ps.Value.CacheDurationSelector); } + } + + /// + /// Gets or sets a boolean indicating whether the Macro should be Cached by Page + /// + [DataMember] + public bool CacheByPage + { + get { return _cacheByPage; } + set { SetPropertyValueAndDetectChanges(value, ref _cacheByPage, Ps.Value.CacheByPageSelector); } + } + + /// + /// Gets or sets a boolean indicating whether the Macro should be Cached Personally + /// + [DataMember] + public bool CacheByMember + { + get { return _cacheByMember; } + set { SetPropertyValueAndDetectChanges(value, ref _cacheByMember, Ps.Value.CacheByMemberSelector); } + } + + /// + /// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor + /// + [DataMember] + public bool DontRender + { + get { return _dontRender; } + set { SetPropertyValueAndDetectChanges(value, ref _dontRender, Ps.Value.DontRenderSelector); } + } + + /// + /// Gets or set the path to the Partial View to render + /// + [DataMember] + public string MacroSource + { + get { return _macroSource; } + set { SetPropertyValueAndDetectChanges(value, ref _macroSource, Ps.Value.ScriptPathSelector); } + } + + /// + /// Gets or set the path to the Partial View to render + /// + [DataMember] + public MacroTypes MacroType + { + get { return _macroType; } + set { SetPropertyValueAndDetectChanges(value, ref _macroType, Ps.Value.MacroTypeSelector); } + } + + /// + /// Gets or sets a list of Macro Properties + /// + [DataMember] + public MacroPropertyCollection Properties + { + get { return _properties; } + } + + public override object DeepClone() + { + var clone = (Macro)base.DeepClone(); + //turn off change tracking + clone.DisableChangeTracking(); + clone._addedProperties = new List(); + clone._removedProperties = new List(); + clone._properties = (MacroPropertyCollection)Properties.DeepClone(); + //re-assign the event handler + clone._properties.CollectionChanged += clone.PropertiesChanged; + //this shouldn't really be needed since we're not tracking + clone.ResetDirtyProperties(false); + //re-enable tracking + clone.EnableChangeTracking(); + + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/MacroProperty.cs b/src/Umbraco.Core/Models/MacroProperty.cs index 2235efea95..380705b3d5 100644 --- a/src/Umbraco.Core/Models/MacroProperty.cs +++ b/src/Umbraco.Core/Models/MacroProperty.cs @@ -1,174 +1,174 @@ -using System; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Macro Property - /// - [Serializable] - [DataContract(IsReference = true)] - public class MacroProperty : BeingDirtyBase, IMacroProperty, IRememberBeingDirty, IDeepCloneable - { - public MacroProperty() - { - _key = Guid.NewGuid(); - } - - /// - /// Ctor for creating a new property - /// - /// - /// - /// - /// - public MacroProperty(string @alias, string name, int sortOrder, string editorAlias) - { - _alias = alias; - _name = name; - _sortOrder = sortOrder; - _key = Guid.NewGuid(); - _editorAlias = editorAlias; - } - - /// - /// Ctor for creating an existing property - /// - /// - /// - /// - /// - /// - /// - internal MacroProperty(int id, Guid key, string @alias, string name, int sortOrder, string editorAlias) - { - _id = id; - _alias = alias; - _name = name; - _sortOrder = sortOrder; - _key = key; - _editorAlias = editorAlias; - } - - private Guid _key; - private string _alias; - private string _name; - private int _sortOrder; - private int _id; - private string _editorAlias; - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); - public readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.EditorAlias); - } - - /// - /// Gets or sets the Key of the Property - /// - [DataMember] - public Guid Key - { - get { return _key; } - set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); } - } - - /// - /// Gets or sets the Alias of the Property - /// - [DataMember] - public int Id - { - get { return _id; } - set { SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); } - } - - /// - /// Gets or sets the Alias of the Property - /// - [DataMember] - public string Alias - { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } - } - - /// - /// Gets or sets the Name of the Property - /// - [DataMember] - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// Gets or sets the Sort Order of the Property - /// - [DataMember] - public int SortOrder - { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } - } - - /// - /// Gets or sets the Type for this Property - /// - /// - /// The MacroPropertyTypes acts as a plugin for Macros. - /// All types was previously contained in the database, but has been ported to code. - /// - [DataMember] - public string EditorAlias - { - get { return _editorAlias; } - set - { - SetPropertyValueAndDetectChanges(value, ref _editorAlias, Ps.Value.PropertyTypeSelector); - } - } - - public object DeepClone() - { - //Memberwise clone on MacroProperty will work since it doesn't have any deep elements - // for any sub class this will work for standard properties as well that aren't complex object's themselves. - var clone = (MacroProperty)MemberwiseClone(); - //Automatically deep clone ref properties that are IDeepCloneable - DeepCloneHelper.DeepCloneRefProperties(this, clone); - clone.ResetDirtyProperties(false); - return clone; - } - - protected bool Equals(MacroProperty other) - { - return string.Equals(_alias, other._alias) && _id == other._id; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((MacroProperty) obj); - } - - public override int GetHashCode() - { - unchecked - { - return ((_alias != null ? _alias.GetHashCode() : 0)*397) ^ _id; - } - } - } -} +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Macro Property + /// + [Serializable] + [DataContract(IsReference = true)] + public class MacroProperty : BeingDirtyBase, IMacroProperty, IRememberBeingDirty, IDeepCloneable + { + public MacroProperty() + { + _key = Guid.NewGuid(); + } + + /// + /// Ctor for creating a new property + /// + /// + /// + /// + /// + public MacroProperty(string @alias, string name, int sortOrder, string editorAlias) + { + _alias = alias; + _name = name; + _sortOrder = sortOrder; + _key = Guid.NewGuid(); + _editorAlias = editorAlias; + } + + /// + /// Ctor for creating an existing property + /// + /// + /// + /// + /// + /// + /// + internal MacroProperty(int id, Guid key, string @alias, string name, int sortOrder, string editorAlias) + { + _id = id; + _alias = alias; + _name = name; + _sortOrder = sortOrder; + _key = key; + _editorAlias = editorAlias; + } + + private Guid _key; + private string _alias; + private string _name; + private int _sortOrder; + private int _id; + private string _editorAlias; + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); + public readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.EditorAlias); + } + + /// + /// Gets or sets the Key of the Property + /// + [DataMember] + public Guid Key + { + get { return _key; } + set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); } + } + + /// + /// Gets or sets the Alias of the Property + /// + [DataMember] + public int Id + { + get { return _id; } + set { SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); } + } + + /// + /// Gets or sets the Alias of the Property + /// + [DataMember] + public string Alias + { + get { return _alias; } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } + } + + /// + /// Gets or sets the Name of the Property + /// + [DataMember] + public string Name + { + get { return _name; } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + } + + /// + /// Gets or sets the Sort Order of the Property + /// + [DataMember] + public int SortOrder + { + get { return _sortOrder; } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } + } + + /// + /// Gets or sets the Type for this Property + /// + /// + /// The MacroPropertyTypes acts as a plugin for Macros. + /// All types was previously contained in the database, but has been ported to code. + /// + [DataMember] + public string EditorAlias + { + get { return _editorAlias; } + set + { + SetPropertyValueAndDetectChanges(value, ref _editorAlias, Ps.Value.PropertyTypeSelector); + } + } + + public object DeepClone() + { + //Memberwise clone on MacroProperty will work since it doesn't have any deep elements + // for any sub class this will work for standard properties as well that aren't complex object's themselves. + var clone = (MacroProperty)MemberwiseClone(); + //Automatically deep clone ref properties that are IDeepCloneable + DeepCloneHelper.DeepCloneRefProperties(this, clone); + clone.ResetDirtyProperties(false); + return clone; + } + + protected bool Equals(MacroProperty other) + { + return string.Equals(_alias, other._alias) && _id == other._id; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((MacroProperty) obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((_alias != null ? _alias.GetHashCode() : 0)*397) ^ _id; + } + } + } +} diff --git a/src/Umbraco.Core/Models/MacroPropertyCollection.cs b/src/Umbraco.Core/Models/MacroPropertyCollection.cs index bf5b29be9a..dd5056e19c 100644 --- a/src/Umbraco.Core/Models/MacroPropertyCollection.cs +++ b/src/Umbraco.Core/Models/MacroPropertyCollection.cs @@ -1,68 +1,68 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Collections; - -namespace Umbraco.Core.Models -{ - /// - /// A macro's property collection - /// - public class MacroPropertyCollection : ObservableDictionary, IDeepCloneable - { - public MacroPropertyCollection() - : base(property => property.Alias) - { - } - - public object DeepClone() - { - var clone = new MacroPropertyCollection(); - foreach (var item in this) - { - clone.Add((IMacroProperty)item.DeepClone()); - } - return clone; - } - - /// - /// Used to update an existing macro property - /// - /// - /// - /// - /// - /// The existing property alias - /// - /// - public void UpdateProperty(string currentAlias, string name = null, int? sortOrder = null, string editorAlias = null, string newAlias = null) - { - var prop = this[currentAlias]; - if (prop == null) - { - throw new InvalidOperationException("No property exists with alias " + currentAlias); - } - - if (name.IsNullOrWhiteSpace() == false) - { - prop.Name = name; - } - if (sortOrder.HasValue) - { - prop.SortOrder = sortOrder.Value; - } - if (name.IsNullOrWhiteSpace() == false) - { - prop.EditorAlias = editorAlias; - } - - if (newAlias.IsNullOrWhiteSpace() == false && currentAlias != newAlias) - { - prop.Alias = newAlias; - ChangeKey(currentAlias, newAlias); - } - } - } - -} +using System; +using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Collections; + +namespace Umbraco.Core.Models +{ + /// + /// A macro's property collection + /// + public class MacroPropertyCollection : ObservableDictionary, IDeepCloneable + { + public MacroPropertyCollection() + : base(property => property.Alias) + { + } + + public object DeepClone() + { + var clone = new MacroPropertyCollection(); + foreach (var item in this) + { + clone.Add((IMacroProperty)item.DeepClone()); + } + return clone; + } + + /// + /// Used to update an existing macro property + /// + /// + /// + /// + /// + /// The existing property alias + /// + /// + public void UpdateProperty(string currentAlias, string name = null, int? sortOrder = null, string editorAlias = null, string newAlias = null) + { + var prop = this[currentAlias]; + if (prop == null) + { + throw new InvalidOperationException("No property exists with alias " + currentAlias); + } + + if (name.IsNullOrWhiteSpace() == false) + { + prop.Name = name; + } + if (sortOrder.HasValue) + { + prop.SortOrder = sortOrder.Value; + } + if (name.IsNullOrWhiteSpace() == false) + { + prop.EditorAlias = editorAlias; + } + + if (newAlias.IsNullOrWhiteSpace() == false && currentAlias != newAlias) + { + prop.Alias = newAlias; + ChangeKey(currentAlias, newAlias); + } + } + } + +} diff --git a/src/Umbraco.Core/Models/MacroTypes.cs b/src/Umbraco.Core/Models/MacroTypes.cs index 310d6ccd7c..1f8ae696c5 100644 --- a/src/Umbraco.Core/Models/MacroTypes.cs +++ b/src/Umbraco.Core/Models/MacroTypes.cs @@ -1,20 +1,20 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models -{ - /// - /// Enum for the various types of Macros - /// - [Serializable] - [DataContract(IsReference = true)] - public enum MacroTypes - { - [EnumMember] - UserControl = 3, - [EnumMember] - Unknown = 4, - [EnumMember] - PartialView = 7 - } -} +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Enum for the various types of Macros + /// + [Serializable] + [DataContract(IsReference = true)] + public enum MacroTypes + { + [EnumMember] + UserControl = 3, + [EnumMember] + Unknown = 4, + [EnumMember] + PartialView = 7 + } +} diff --git a/src/Umbraco.Core/Models/Media.cs b/src/Umbraco.Core/Models/Media.cs index 162e40a96e..c3f7cb6dd5 100644 --- a/src/Umbraco.Core/Models/Media.cs +++ b/src/Umbraco.Core/Models/Media.cs @@ -1,117 +1,117 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Core.Persistence.Mappers; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Media object - /// - [Serializable] - [DataContract(IsReference = true)] - public class Media : ContentBase, IMedia - { - private IMediaType _contentType; - - /// - /// Constructor for creating a Media object - /// - /// ame of the Media object - /// Parent object - /// MediaType for the current Media object - public Media(string name, IMedia parent, IMediaType contentType) - : this(name, parent, contentType, new PropertyCollection()) - { - } - - /// - /// Constructor for creating a Media object - /// - /// ame of the Media object - /// Parent object - /// MediaType for the current Media object - /// Collection of properties - public Media(string name, IMedia parent, IMediaType contentType, PropertyCollection properties) - : base(name, parent, contentType, properties) - { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - } - - /// - /// Constructor for creating a Media object - /// - /// ame of the Media object - /// Id of the Parent IMedia - /// MediaType for the current Media object - public Media(string name, int parentId, IMediaType contentType) - : this(name, parentId, contentType, new PropertyCollection()) - { - } - - /// - /// Constructor for creating a Media object - /// - /// Name of the Media object - /// Id of the Parent IMedia - /// MediaType for the current Media object - /// Collection of properties - public Media(string name, int parentId, IMediaType contentType, PropertyCollection properties) - : base(name, parentId, contentType, properties) - { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - } - - /// - /// Gets the ContentType used by this Media object - /// - [IgnoreDataMember] - public IMediaType ContentType => _contentType; - - /// - /// Changes the for the current Media object - /// - /// New MediaType for this Media - /// Leaves PropertyTypes intact after change - public void ChangeContentType(IMediaType contentType) - { - ContentTypeId = contentType.Id; - _contentType = contentType; - ContentTypeBase = contentType; - Properties.EnsurePropertyTypes(PropertyTypes); - Properties.CollectionChanged += PropertiesChanged; - } - - /// - /// Changes the for the current Media object and removes PropertyTypes, - /// which are not part of the new MediaType. - /// - /// New MediaType for this Media - /// Boolean indicating whether to clear PropertyTypes upon change - public void ChangeContentType(IMediaType contentType, bool clearProperties) - { - if (clearProperties) - { - ContentTypeId = contentType.Id; - _contentType = contentType; - ContentTypeBase = contentType; - Properties.EnsureCleanPropertyTypes(PropertyTypes); - Properties.CollectionChanged += PropertiesChanged; - return; - } - - ChangeContentType(contentType); - } - - /// - /// Changes the Trashed state of the content object - /// - /// Boolean indicating whether content is trashed (true) or not trashed (false) - /// - public void ChangeTrashedState(bool isTrashed, int parentId = -20) - { - Trashed = isTrashed; - //The Media Recycle Bin Id is -21 so we correct that here - ParentId = parentId == -20 ? -21 : parentId; - } - } -} +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Persistence.Mappers; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Media object + /// + [Serializable] + [DataContract(IsReference = true)] + public class Media : ContentBase, IMedia + { + private IMediaType _contentType; + + /// + /// Constructor for creating a Media object + /// + /// ame of the Media object + /// Parent object + /// MediaType for the current Media object + public Media(string name, IMedia parent, IMediaType contentType) + : this(name, parent, contentType, new PropertyCollection()) + { + } + + /// + /// Constructor for creating a Media object + /// + /// ame of the Media object + /// Parent object + /// MediaType for the current Media object + /// Collection of properties + public Media(string name, IMedia parent, IMediaType contentType, PropertyCollection properties) + : base(name, parent, contentType, properties) + { + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + } + + /// + /// Constructor for creating a Media object + /// + /// ame of the Media object + /// Id of the Parent IMedia + /// MediaType for the current Media object + public Media(string name, int parentId, IMediaType contentType) + : this(name, parentId, contentType, new PropertyCollection()) + { + } + + /// + /// Constructor for creating a Media object + /// + /// Name of the Media object + /// Id of the Parent IMedia + /// MediaType for the current Media object + /// Collection of properties + public Media(string name, int parentId, IMediaType contentType, PropertyCollection properties) + : base(name, parentId, contentType, properties) + { + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + } + + /// + /// Gets the ContentType used by this Media object + /// + [IgnoreDataMember] + public IMediaType ContentType => _contentType; + + /// + /// Changes the for the current Media object + /// + /// New MediaType for this Media + /// Leaves PropertyTypes intact after change + public void ChangeContentType(IMediaType contentType) + { + ContentTypeId = contentType.Id; + _contentType = contentType; + ContentTypeBase = contentType; + Properties.EnsurePropertyTypes(PropertyTypes); + Properties.CollectionChanged += PropertiesChanged; + } + + /// + /// Changes the for the current Media object and removes PropertyTypes, + /// which are not part of the new MediaType. + /// + /// New MediaType for this Media + /// Boolean indicating whether to clear PropertyTypes upon change + public void ChangeContentType(IMediaType contentType, bool clearProperties) + { + if (clearProperties) + { + ContentTypeId = contentType.Id; + _contentType = contentType; + ContentTypeBase = contentType; + Properties.EnsureCleanPropertyTypes(PropertyTypes); + Properties.CollectionChanged += PropertiesChanged; + return; + } + + ChangeContentType(contentType); + } + + /// + /// Changes the Trashed state of the content object + /// + /// Boolean indicating whether content is trashed (true) or not trashed (false) + /// + public void ChangeTrashedState(bool isTrashed, int parentId = -20) + { + Trashed = isTrashed; + //The Media Recycle Bin Id is -21 so we correct that here + ParentId = parentId == -20 ? -21 : parentId; + } + } +} diff --git a/src/Umbraco.Core/Models/MediaType.cs b/src/Umbraco.Core/Models/MediaType.cs index 9612fce728..4ae2fd190c 100644 --- a/src/Umbraco.Core/Models/MediaType.cs +++ b/src/Umbraco.Core/Models/MediaType.cs @@ -1,72 +1,72 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models -{ - /// - /// Represents the content type that a object is based on - /// - [Serializable] - [DataContract(IsReference = true)] - public class MediaType : ContentTypeCompositionBase, IMediaType - { - public const bool IsPublishingConst = false; - - /// - /// Constuctor for creating a MediaType with the parent's id. - /// - /// Only use this for creating MediaTypes at the root (with ParentId -1). - /// - public MediaType(int parentId) : base(parentId) - { - } - - /// - /// Constuctor for creating a MediaType with the parent as an inherited type. - /// - /// Use this to ensure inheritance from parent. - /// - public MediaType(IMediaType parent) : this(parent, null) - { - } - - /// - /// Constuctor for creating a MediaType with the parent as an inherited type. - /// - /// Use this to ensure inheritance from parent. - /// - /// - public MediaType(IMediaType parent, string alias) - : base(parent, alias) - { - } - - /// - public override bool IsPublishing => IsPublishingConst; - - /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset - /// - /// - public new IMediaType DeepCloneWithResetIdentities(string alias) - { - var clone = (MediaType)DeepClone(); - clone.Alias = alias; - clone.Key = Guid.Empty; - foreach (var propertyGroup in clone.PropertyGroups) - { - propertyGroup.ResetIdentity(); - propertyGroup.ResetDirtyProperties(false); - } - foreach (var propertyType in clone.PropertyTypes) - { - propertyType.ResetIdentity(); - propertyType.ResetDirtyProperties(false); - } - - clone.ResetIdentity(); - clone.ResetDirtyProperties(false); - return clone; - } - } -} +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Represents the content type that a object is based on + /// + [Serializable] + [DataContract(IsReference = true)] + public class MediaType : ContentTypeCompositionBase, IMediaType + { + public const bool IsPublishingConst = false; + + /// + /// Constuctor for creating a MediaType with the parent's id. + /// + /// Only use this for creating MediaTypes at the root (with ParentId -1). + /// + public MediaType(int parentId) : base(parentId) + { + } + + /// + /// Constuctor for creating a MediaType with the parent as an inherited type. + /// + /// Use this to ensure inheritance from parent. + /// + public MediaType(IMediaType parent) : this(parent, null) + { + } + + /// + /// Constuctor for creating a MediaType with the parent as an inherited type. + /// + /// Use this to ensure inheritance from parent. + /// + /// + public MediaType(IMediaType parent, string alias) + : base(parent, alias) + { + } + + /// + public override bool IsPublishing => IsPublishingConst; + + /// + /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset + /// + /// + public new IMediaType DeepCloneWithResetIdentities(string alias) + { + var clone = (MediaType)DeepClone(); + clone.Alias = alias; + clone.Key = Guid.Empty; + foreach (var propertyGroup in clone.PropertyGroups) + { + propertyGroup.ResetIdentity(); + propertyGroup.ResetDirtyProperties(false); + } + foreach (var propertyType in clone.PropertyTypes) + { + propertyType.ResetIdentity(); + propertyType.ResetDirtyProperties(false); + } + + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index a45b1e356b..e0d52f7077 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -1,616 +1,616 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Composing; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Member object - /// - [Serializable] - [DataContract(IsReference = true)] - public class Member : ContentBase, IMember - { - private IDictionary _additionalData; - private IMemberType _contentType; - private readonly string _contentTypeAlias; - private string _username; - private string _email; - private string _rawPasswordValue; - private object _providerUserKey; - - /// - /// Constructor for creating an empty Member object - /// - /// ContentType for the current Content object - public Member(IMemberType contentType) - : base("", -1, contentType, new PropertyCollection()) - { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _contentTypeAlias = contentType.Alias; - IsApproved = true; - - //this cannot be null but can be empty - _rawPasswordValue = ""; - _email = ""; - _username = ""; - } - - /// - /// Constructor for creating a Member object - /// - /// Name of the content - /// ContentType for the current Content object - public Member(string name, IMemberType contentType) - : base(name, -1, contentType, new PropertyCollection()) - { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _contentTypeAlias = contentType.Alias; - IsApproved = true; - - //this cannot be null but can be empty - _rawPasswordValue = ""; - _email = ""; - _username = ""; - } - - /// - /// Constructor for creating a Member object - /// - /// - /// - /// - /// - public Member(string name, string email, string username, IMemberType contentType, bool isApproved = true) - : base(name, -1, contentType, new PropertyCollection()) - { - if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullOrEmptyException(nameof(email)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullOrEmptyException(nameof(username)); - - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _contentTypeAlias = contentType.Alias; - _email = email; - _username = username; - IsApproved = isApproved; - - //this cannot be null but can be empty - _rawPasswordValue = ""; - } - - /// - /// Constructor for creating a Member object - /// - /// - /// - /// - /// - /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password - /// - /// - public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType) - : base(name, -1, contentType, new PropertyCollection()) - { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _contentTypeAlias = contentType.Alias; - - _email = email; - _username = username; - _rawPasswordValue = rawPasswordValue; - IsApproved = true; - } - - /// - /// Constructor for creating a Member object - /// - /// - /// - /// - /// - /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password - /// - /// - /// - public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, bool isApproved) - : base(name, -1, contentType, new PropertyCollection()) - { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _contentTypeAlias = contentType.Alias; - _email = email; - _username = username; - _rawPasswordValue = rawPasswordValue; - IsApproved = isApproved; - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); - public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); - public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); - public readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKey); - } - - /// - /// Gets or sets the Username - /// - [DataMember] - public string Username - { - get { return _username; } - set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); } - } - - /// - /// Gets or sets the Email - /// - [DataMember] - public string Email - { - get { return _email; } - set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } - } - - /// - /// Gets or sets the raw password value - /// - [IgnoreDataMember] - public string RawPasswordValue - { - get { return _rawPasswordValue; } - set - { - if (value == null) - { - //special case, this is used to ensure that the password is not updated when persisting, in this case - //we don't want to track changes either - _rawPasswordValue = null; - } - else - { - SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); - } - } - } - - /// - /// Gets or sets the Groups that Member is part of - /// - [DataMember] - public IEnumerable Groups { get; set; } - - //TODO: When get/setting all of these properties we MUST: - // * Check if we are using the umbraco membership provider, if so then we need to use the configured fields - not the explicit fields below - // * If any of the fields don't exist, what should we do? Currently it will throw an exception! - - /// - /// Gets or sets the Password Question - /// - /// - /// Alias: umbracoMemberPasswordRetrievalQuestion - /// Part of the standard properties collection. - /// - [DataMember] - public string PasswordQuestion - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.PasswordQuestion, "PasswordQuestion", default(string)); - if (a.Success == false) return a.Result; - - return Properties[Constants.Conventions.Member.PasswordQuestion].GetValue() == null - ? string.Empty - : Properties[Constants.Conventions.Member.PasswordQuestion].GetValue().ToString(); - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.PasswordQuestion, - "PasswordQuestion") == false) return; - - Properties[Constants.Conventions.Member.PasswordQuestion].SetValue(value); - } - } - - /// - /// Gets or sets the raw password answer value - /// - /// - /// For security reasons this value should be encrypted, the encryption process is handled by the memberhip provider - /// Alias: umbracoMemberPasswordRetrievalAnswer - /// - /// Part of the standard properties collection. - /// - [IgnoreDataMember] - public string RawPasswordAnswerValue - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.PasswordAnswer, "PasswordAnswer", default(string)); - if (a.Success == false) return a.Result; - - return Properties[Constants.Conventions.Member.PasswordAnswer].GetValue() == null - ? string.Empty - : Properties[Constants.Conventions.Member.PasswordAnswer].GetValue().ToString(); - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.PasswordAnswer, - "PasswordAnswer") == false) return; - - Properties[Constants.Conventions.Member.PasswordAnswer].SetValue(value); - } - } - - /// - /// Gets or set the comments for the member - /// - /// - /// Alias: umbracoMemberComments - /// Part of the standard properties collection. - /// - [DataMember] - public string Comments - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, "Comments", default(string)); - if (a.Success == false) return a.Result; - - return Properties[Constants.Conventions.Member.Comments].GetValue() == null - ? string.Empty - : Properties[Constants.Conventions.Member.Comments].GetValue().ToString(); - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.Comments, - "Comments") == false) return; - - Properties[Constants.Conventions.Member.Comments].SetValue(value); - } - } - - /// - /// Gets or sets a boolean indicating whether the Member is approved - /// - /// - /// Alias: umbracoMemberApproved - /// Part of the standard properties collection. - /// - [DataMember] - public bool IsApproved - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsApproved, "IsApproved", - //This is the default value if the prop is not found - true); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.IsApproved].GetValue() == null) return true; - var tryConvert = Properties[Constants.Conventions.Member.IsApproved].GetValue().TryConvertTo(); - if (tryConvert.Success) - { - return tryConvert.Result; - } - //if the property exists but it cannot be converted, we will assume true - return true; - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.IsApproved, - "IsApproved") == false) return; - - Properties[Constants.Conventions.Member.IsApproved].SetValue(value); - } - } - - /// - /// Gets or sets a boolean indicating whether the Member is locked out - /// - /// - /// Alias: umbracoMemberLockedOut - /// Part of the standard properties collection. - /// - [DataMember] - public bool IsLockedOut - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsLockedOut, "IsLockedOut", false); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.IsLockedOut].GetValue() == null) return false; - var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].GetValue().TryConvertTo(); - if (tryConvert.Success) - { - return tryConvert.Result; - } - return false; - //TODO: Use TryConvertTo instead - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.IsLockedOut, - "IsLockedOut") == false) return; - - Properties[Constants.Conventions.Member.IsLockedOut].SetValue(value); - } - } - - /// - /// Gets or sets the date for last login - /// - /// - /// Alias: umbracoMemberLastLogin - /// Part of the standard properties collection. - /// - [DataMember] - public DateTime LastLoginDate - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLoginDate, "LastLoginDate", default(DateTime)); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastLoginDate].GetValue() == null) return default(DateTime); - var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].GetValue().TryConvertTo(); - if (tryConvert.Success) - { - return tryConvert.Result; - } - return default(DateTime); - //TODO: Use TryConvertTo instead - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.LastLoginDate, - "LastLoginDate") == false) return; - - Properties[Constants.Conventions.Member.LastLoginDate].SetValue(value); - } - } - - /// - /// Gest or sets the date for last password change - /// - /// - /// Alias: umbracoMemberLastPasswordChangeDate - /// Part of the standard properties collection. - /// - [DataMember] - public DateTime LastPasswordChangeDate - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastPasswordChangeDate, "LastPasswordChangeDate", default(DateTime)); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue() == null) return default(DateTime); - var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue().TryConvertTo(); - if (tryConvert.Success) - { - return tryConvert.Result; - } - return default(DateTime); - //TODO: Use TryConvertTo instead - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.LastPasswordChangeDate, - "LastPasswordChangeDate") == false) return; - - Properties[Constants.Conventions.Member.LastPasswordChangeDate].SetValue(value); - } - } - - /// - /// Gets or sets the date for when Member was locked out - /// - /// - /// Alias: umbracoMemberLastLockoutDate - /// Part of the standard properties collection. - /// - [DataMember] - public DateTime LastLockoutDate - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLockoutDate, "LastLockoutDate", default(DateTime)); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastLockoutDate].GetValue() == null) return default(DateTime); - var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].GetValue().TryConvertTo(); - if (tryConvert.Success) - { - return tryConvert.Result; - } - return default(DateTime); - //TODO: Use TryConvertTo instead - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.LastLockoutDate, - "LastLockoutDate") == false) return; - - Properties[Constants.Conventions.Member.LastLockoutDate].SetValue(value); - } - } - - /// - /// Gets or sets the number of failed password attempts. - /// This is the number of times the password was entered incorrectly upon login. - /// - /// - /// Alias: umbracoMemberFailedPasswordAttempts - /// Part of the standard properties collection. - /// - [DataMember] - public int FailedPasswordAttempts - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.FailedPasswordAttempts, "FailedPasswordAttempts", 0); - if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue() == null) return default(int); - var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue().TryConvertTo(); - if (tryConvert.Success) - { - return tryConvert.Result; - } - return default(int); - //TODO: Use TryConvertTo instead - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.FailedPasswordAttempts, - "FailedPasswordAttempts") == false) return; - - Properties[Constants.Conventions.Member.FailedPasswordAttempts].SetValue(value); - } - } - - /// - /// String alias of the default ContentType - /// - [DataMember] - public virtual string ContentTypeAlias - { - get { return _contentTypeAlias; } - } - - /// - /// User key from the Provider. - /// - /// - /// When using standard umbraco provider this key will - /// correspond to the guid UniqueId/Key. - /// Otherwise it will the one available from the asp.net - /// membership provider. - /// - [DataMember] - public virtual object ProviderUserKey - { - get - { - return _providerUserKey; - } - set { SetPropertyValueAndDetectChanges(value, ref _providerUserKey, Ps.Value.ProviderUserKeySelector); } - } - - - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() - { - base.AddingEntity(); - - if (ProviderUserKey == null) - ProviderUserKey = Key; - } - - /// - /// Gets the ContentType used by this content object - /// - [IgnoreDataMember] - public IMemberType ContentType - { - get { return _contentType; } - } - - /* Internal experiment - only used for mapping queries. - * Adding these to have first level properties instead of the Properties collection. - */ - [IgnoreDataMember] - internal string LongStringPropertyValue { get; set; } - [IgnoreDataMember] - internal string ShortStringPropertyValue { get; set; } - [IgnoreDataMember] - internal int IntegerPropertyValue { get; set; } - [IgnoreDataMember] - internal bool BoolPropertyValue { get; set; } - [IgnoreDataMember] - internal DateTime DateTimePropertyValue { get; set; } - [IgnoreDataMember] - internal string PropertyTypeAlias { get; set; } - - private Attempt WarnIfPropertyTypeNotFoundOnGet(string propertyAlias, string propertyName, T defaultVal) - { - void DoLog(string logPropertyAlias, string logPropertyName) - => Current.Logger.Warn($"Trying to access the '{logPropertyName}' property on " + typeof(Member) - + $" but the {logPropertyAlias} property does not exist on the member type so a default value is returned. Ensure that you have a property type with alias: " - + logPropertyAlias + $" configured on your member type in order to use the '{logPropertyName}' property on the model correctly."); - - // if the property doesn't exist, - if (Properties.Contains(propertyAlias) == false) - { - // put a warn in the log if this entity has been persisted - // then return a failure - if (HasIdentity) - DoLog(propertyAlias, propertyName); - return Attempt.Fail(defaultVal); - } - - return Attempt.Succeed(); - } - - private bool WarnIfPropertyTypeNotFoundOnSet(string propertyAlias, string propertyName) - { - void DoLog(string logPropertyAlias, string logPropertyName) - => Current.Logger.Warn($"An attempt was made to set a value on the property '{logPropertyName}' on type " + typeof(Member) - + $" but the property type {logPropertyAlias} does not exist on the member type, ensure that this property type exists so that setting this property works correctly."); - - // if the property doesn't exist, - if (Properties.Contains(propertyAlias) == false) - { - // put a warn in the log if this entity has been persisted - // then return a failure - if (HasIdentity) - DoLog(propertyAlias, propertyName); - return false; - } - - return true; - } - - public override object DeepClone() - { - var clone = (Member)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //need to manually clone this since it's not settable - clone._contentType = (IMemberType)ContentType.DeepClone(); - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; - - } - - /// - [DataMember] - [DoNotClone] - public IDictionary AdditionalData => _additionalData ?? (_additionalData = new Dictionary()); - - /// - [IgnoreDataMember] - public bool HasAdditionalData => _additionalData != null; - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Member object + /// + [Serializable] + [DataContract(IsReference = true)] + public class Member : ContentBase, IMember + { + private IDictionary _additionalData; + private IMemberType _contentType; + private readonly string _contentTypeAlias; + private string _username; + private string _email; + private string _rawPasswordValue; + private object _providerUserKey; + + /// + /// Constructor for creating an empty Member object + /// + /// ContentType for the current Content object + public Member(IMemberType contentType) + : base("", -1, contentType, new PropertyCollection()) + { + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + _contentTypeAlias = contentType.Alias; + IsApproved = true; + + //this cannot be null but can be empty + _rawPasswordValue = ""; + _email = ""; + _username = ""; + } + + /// + /// Constructor for creating a Member object + /// + /// Name of the content + /// ContentType for the current Content object + public Member(string name, IMemberType contentType) + : base(name, -1, contentType, new PropertyCollection()) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + _contentTypeAlias = contentType.Alias; + IsApproved = true; + + //this cannot be null but can be empty + _rawPasswordValue = ""; + _email = ""; + _username = ""; + } + + /// + /// Constructor for creating a Member object + /// + /// + /// + /// + /// + public Member(string name, string email, string username, IMemberType contentType, bool isApproved = true) + : base(name, -1, contentType, new PropertyCollection()) + { + if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullOrEmptyException(nameof(email)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullOrEmptyException(nameof(username)); + + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + _contentTypeAlias = contentType.Alias; + _email = email; + _username = username; + IsApproved = isApproved; + + //this cannot be null but can be empty + _rawPasswordValue = ""; + } + + /// + /// Constructor for creating a Member object + /// + /// + /// + /// + /// + /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password + /// + /// + public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType) + : base(name, -1, contentType, new PropertyCollection()) + { + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + _contentTypeAlias = contentType.Alias; + + _email = email; + _username = username; + _rawPasswordValue = rawPasswordValue; + IsApproved = true; + } + + /// + /// Constructor for creating a Member object + /// + /// + /// + /// + /// + /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password + /// + /// + /// + public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, bool isApproved) + : base(name, -1, contentType, new PropertyCollection()) + { + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + _contentTypeAlias = contentType.Alias; + _email = email; + _username = username; + _rawPasswordValue = rawPasswordValue; + IsApproved = isApproved; + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); + public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); + public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); + public readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKey); + } + + /// + /// Gets or sets the Username + /// + [DataMember] + public string Username + { + get { return _username; } + set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); } + } + + /// + /// Gets or sets the Email + /// + [DataMember] + public string Email + { + get { return _email; } + set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } + } + + /// + /// Gets or sets the raw password value + /// + [IgnoreDataMember] + public string RawPasswordValue + { + get { return _rawPasswordValue; } + set + { + if (value == null) + { + //special case, this is used to ensure that the password is not updated when persisting, in this case + //we don't want to track changes either + _rawPasswordValue = null; + } + else + { + SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); + } + } + } + + /// + /// Gets or sets the Groups that Member is part of + /// + [DataMember] + public IEnumerable Groups { get; set; } + + //TODO: When get/setting all of these properties we MUST: + // * Check if we are using the umbraco membership provider, if so then we need to use the configured fields - not the explicit fields below + // * If any of the fields don't exist, what should we do? Currently it will throw an exception! + + /// + /// Gets or sets the Password Question + /// + /// + /// Alias: umbracoMemberPasswordRetrievalQuestion + /// Part of the standard properties collection. + /// + [DataMember] + public string PasswordQuestion + { + get + { + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.PasswordQuestion, "PasswordQuestion", default(string)); + if (a.Success == false) return a.Result; + + return Properties[Constants.Conventions.Member.PasswordQuestion].GetValue() == null + ? string.Empty + : Properties[Constants.Conventions.Member.PasswordQuestion].GetValue().ToString(); + } + set + { + if (WarnIfPropertyTypeNotFoundOnSet( + Constants.Conventions.Member.PasswordQuestion, + "PasswordQuestion") == false) return; + + Properties[Constants.Conventions.Member.PasswordQuestion].SetValue(value); + } + } + + /// + /// Gets or sets the raw password answer value + /// + /// + /// For security reasons this value should be encrypted, the encryption process is handled by the memberhip provider + /// Alias: umbracoMemberPasswordRetrievalAnswer + /// + /// Part of the standard properties collection. + /// + [IgnoreDataMember] + public string RawPasswordAnswerValue + { + get + { + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.PasswordAnswer, "PasswordAnswer", default(string)); + if (a.Success == false) return a.Result; + + return Properties[Constants.Conventions.Member.PasswordAnswer].GetValue() == null + ? string.Empty + : Properties[Constants.Conventions.Member.PasswordAnswer].GetValue().ToString(); + } + set + { + if (WarnIfPropertyTypeNotFoundOnSet( + Constants.Conventions.Member.PasswordAnswer, + "PasswordAnswer") == false) return; + + Properties[Constants.Conventions.Member.PasswordAnswer].SetValue(value); + } + } + + /// + /// Gets or set the comments for the member + /// + /// + /// Alias: umbracoMemberComments + /// Part of the standard properties collection. + /// + [DataMember] + public string Comments + { + get + { + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, "Comments", default(string)); + if (a.Success == false) return a.Result; + + return Properties[Constants.Conventions.Member.Comments].GetValue() == null + ? string.Empty + : Properties[Constants.Conventions.Member.Comments].GetValue().ToString(); + } + set + { + if (WarnIfPropertyTypeNotFoundOnSet( + Constants.Conventions.Member.Comments, + "Comments") == false) return; + + Properties[Constants.Conventions.Member.Comments].SetValue(value); + } + } + + /// + /// Gets or sets a boolean indicating whether the Member is approved + /// + /// + /// Alias: umbracoMemberApproved + /// Part of the standard properties collection. + /// + [DataMember] + public bool IsApproved + { + get + { + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsApproved, "IsApproved", + //This is the default value if the prop is not found + true); + if (a.Success == false) return a.Result; + if (Properties[Constants.Conventions.Member.IsApproved].GetValue() == null) return true; + var tryConvert = Properties[Constants.Conventions.Member.IsApproved].GetValue().TryConvertTo(); + if (tryConvert.Success) + { + return tryConvert.Result; + } + //if the property exists but it cannot be converted, we will assume true + return true; + } + set + { + if (WarnIfPropertyTypeNotFoundOnSet( + Constants.Conventions.Member.IsApproved, + "IsApproved") == false) return; + + Properties[Constants.Conventions.Member.IsApproved].SetValue(value); + } + } + + /// + /// Gets or sets a boolean indicating whether the Member is locked out + /// + /// + /// Alias: umbracoMemberLockedOut + /// Part of the standard properties collection. + /// + [DataMember] + public bool IsLockedOut + { + get + { + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsLockedOut, "IsLockedOut", false); + if (a.Success == false) return a.Result; + if (Properties[Constants.Conventions.Member.IsLockedOut].GetValue() == null) return false; + var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].GetValue().TryConvertTo(); + if (tryConvert.Success) + { + return tryConvert.Result; + } + return false; + //TODO: Use TryConvertTo instead + } + set + { + if (WarnIfPropertyTypeNotFoundOnSet( + Constants.Conventions.Member.IsLockedOut, + "IsLockedOut") == false) return; + + Properties[Constants.Conventions.Member.IsLockedOut].SetValue(value); + } + } + + /// + /// Gets or sets the date for last login + /// + /// + /// Alias: umbracoMemberLastLogin + /// Part of the standard properties collection. + /// + [DataMember] + public DateTime LastLoginDate + { + get + { + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLoginDate, "LastLoginDate", default(DateTime)); + if (a.Success == false) return a.Result; + if (Properties[Constants.Conventions.Member.LastLoginDate].GetValue() == null) return default(DateTime); + var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].GetValue().TryConvertTo(); + if (tryConvert.Success) + { + return tryConvert.Result; + } + return default(DateTime); + //TODO: Use TryConvertTo instead + } + set + { + if (WarnIfPropertyTypeNotFoundOnSet( + Constants.Conventions.Member.LastLoginDate, + "LastLoginDate") == false) return; + + Properties[Constants.Conventions.Member.LastLoginDate].SetValue(value); + } + } + + /// + /// Gest or sets the date for last password change + /// + /// + /// Alias: umbracoMemberLastPasswordChangeDate + /// Part of the standard properties collection. + /// + [DataMember] + public DateTime LastPasswordChangeDate + { + get + { + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastPasswordChangeDate, "LastPasswordChangeDate", default(DateTime)); + if (a.Success == false) return a.Result; + if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue() == null) return default(DateTime); + var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue().TryConvertTo(); + if (tryConvert.Success) + { + return tryConvert.Result; + } + return default(DateTime); + //TODO: Use TryConvertTo instead + } + set + { + if (WarnIfPropertyTypeNotFoundOnSet( + Constants.Conventions.Member.LastPasswordChangeDate, + "LastPasswordChangeDate") == false) return; + + Properties[Constants.Conventions.Member.LastPasswordChangeDate].SetValue(value); + } + } + + /// + /// Gets or sets the date for when Member was locked out + /// + /// + /// Alias: umbracoMemberLastLockoutDate + /// Part of the standard properties collection. + /// + [DataMember] + public DateTime LastLockoutDate + { + get + { + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLockoutDate, "LastLockoutDate", default(DateTime)); + if (a.Success == false) return a.Result; + if (Properties[Constants.Conventions.Member.LastLockoutDate].GetValue() == null) return default(DateTime); + var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].GetValue().TryConvertTo(); + if (tryConvert.Success) + { + return tryConvert.Result; + } + return default(DateTime); + //TODO: Use TryConvertTo instead + } + set + { + if (WarnIfPropertyTypeNotFoundOnSet( + Constants.Conventions.Member.LastLockoutDate, + "LastLockoutDate") == false) return; + + Properties[Constants.Conventions.Member.LastLockoutDate].SetValue(value); + } + } + + /// + /// Gets or sets the number of failed password attempts. + /// This is the number of times the password was entered incorrectly upon login. + /// + /// + /// Alias: umbracoMemberFailedPasswordAttempts + /// Part of the standard properties collection. + /// + [DataMember] + public int FailedPasswordAttempts + { + get + { + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.FailedPasswordAttempts, "FailedPasswordAttempts", 0); + if (a.Success == false) return a.Result; + if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue() == null) return default(int); + var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue().TryConvertTo(); + if (tryConvert.Success) + { + return tryConvert.Result; + } + return default(int); + //TODO: Use TryConvertTo instead + } + set + { + if (WarnIfPropertyTypeNotFoundOnSet( + Constants.Conventions.Member.FailedPasswordAttempts, + "FailedPasswordAttempts") == false) return; + + Properties[Constants.Conventions.Member.FailedPasswordAttempts].SetValue(value); + } + } + + /// + /// String alias of the default ContentType + /// + [DataMember] + public virtual string ContentTypeAlias + { + get { return _contentTypeAlias; } + } + + /// + /// User key from the Provider. + /// + /// + /// When using standard umbraco provider this key will + /// correspond to the guid UniqueId/Key. + /// Otherwise it will the one available from the asp.net + /// membership provider. + /// + [DataMember] + public virtual object ProviderUserKey + { + get + { + return _providerUserKey; + } + set { SetPropertyValueAndDetectChanges(value, ref _providerUserKey, Ps.Value.ProviderUserKeySelector); } + } + + + /// + /// Method to call when Entity is being saved + /// + /// Created date is set and a Unique key is assigned + internal override void AddingEntity() + { + base.AddingEntity(); + + if (ProviderUserKey == null) + ProviderUserKey = Key; + } + + /// + /// Gets the ContentType used by this content object + /// + [IgnoreDataMember] + public IMemberType ContentType + { + get { return _contentType; } + } + + /* Internal experiment - only used for mapping queries. + * Adding these to have first level properties instead of the Properties collection. + */ + [IgnoreDataMember] + internal string LongStringPropertyValue { get; set; } + [IgnoreDataMember] + internal string ShortStringPropertyValue { get; set; } + [IgnoreDataMember] + internal int IntegerPropertyValue { get; set; } + [IgnoreDataMember] + internal bool BoolPropertyValue { get; set; } + [IgnoreDataMember] + internal DateTime DateTimePropertyValue { get; set; } + [IgnoreDataMember] + internal string PropertyTypeAlias { get; set; } + + private Attempt WarnIfPropertyTypeNotFoundOnGet(string propertyAlias, string propertyName, T defaultVal) + { + void DoLog(string logPropertyAlias, string logPropertyName) + => Current.Logger.Warn($"Trying to access the '{logPropertyName}' property on " + typeof(Member) + + $" but the {logPropertyAlias} property does not exist on the member type so a default value is returned. Ensure that you have a property type with alias: " + + logPropertyAlias + $" configured on your member type in order to use the '{logPropertyName}' property on the model correctly."); + + // if the property doesn't exist, + if (Properties.Contains(propertyAlias) == false) + { + // put a warn in the log if this entity has been persisted + // then return a failure + if (HasIdentity) + DoLog(propertyAlias, propertyName); + return Attempt.Fail(defaultVal); + } + + return Attempt.Succeed(); + } + + private bool WarnIfPropertyTypeNotFoundOnSet(string propertyAlias, string propertyName) + { + void DoLog(string logPropertyAlias, string logPropertyName) + => Current.Logger.Warn($"An attempt was made to set a value on the property '{logPropertyName}' on type " + typeof(Member) + + $" but the property type {logPropertyAlias} does not exist on the member type, ensure that this property type exists so that setting this property works correctly."); + + // if the property doesn't exist, + if (Properties.Contains(propertyAlias) == false) + { + // put a warn in the log if this entity has been persisted + // then return a failure + if (HasIdentity) + DoLog(propertyAlias, propertyName); + return false; + } + + return true; + } + + public override object DeepClone() + { + var clone = (Member)base.DeepClone(); + //turn off change tracking + clone.DisableChangeTracking(); + //need to manually clone this since it's not settable + clone._contentType = (IMemberType)ContentType.DeepClone(); + //this shouldn't really be needed since we're not tracking + clone.ResetDirtyProperties(false); + //re-enable tracking + clone.EnableChangeTracking(); + + return clone; + + } + + /// + [DataMember] + [DoNotClone] + public IDictionary AdditionalData => _additionalData ?? (_additionalData = new Dictionary()); + + /// + [IgnoreDataMember] + public bool HasAdditionalData => _additionalData != null; + } +} diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 76450e9115..bcc7e14fd7 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -1,170 +1,170 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models -{ - /// - /// Represents the content type that a object is based on - /// - [Serializable] - [DataContract(IsReference = true)] - public class MemberType : ContentTypeCompositionBase, IMemberType - { - private static readonly Lazy Ps = new Lazy(); - public const bool IsPublishingConst = false; - - //Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId - private string _alias; - - public MemberType(int parentId) : base(parentId) - { - MemberTypePropertyTypes = new Dictionary(); - } - - public MemberType(IContentTypeComposition parent) : this(parent, null) - { - } - - public MemberType(IContentTypeComposition parent, string alias) - : base(parent, alias) - { - MemberTypePropertyTypes = new Dictionary(); - } - - /// - public override bool IsPublishing => IsPublishingConst; - - public override ContentVariation Variations - { - // note: although technically possible, variations on members don't make much sense - // and therefore are disabled - they are fully supported at service level, though, - // but not at published snapshot level. - - get => base.Variations; - set => throw new NotSupportedException("Variations are not supported on members."); - } - - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - } - - /// - /// The Alias of the ContentType - /// - [DataMember] - public override string Alias - { - get => _alias; - set - { - //NOTE: WE are overriding this because we don't want to do a ToSafeAlias when the alias is the special case of - // "_umbracoSystemDefaultProtectType" which is used internally, currently there is an issue with the safe alias as it strips - // leading underscores which we don't want in this case. - // see : http://issues.umbraco.org/issue/U4-3968 - - //TODO: BUT, I'm pretty sure we could do this with regards to underscores now: - // .ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase) - // Need to ask Stephen - - var newVal = value == "_umbracoSystemDefaultProtectType" - ? value - : (value == null ? string.Empty : value.ToSafeAlias()); - - SetPropertyValueAndDetectChanges(newVal, ref _alias, Ps.Value.AliasSelector); - } - } - - /// - /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, IsSensitive) by the PropertyTypes' alias. - /// - [DataMember] - internal IDictionary MemberTypePropertyTypes { get; private set; } - - /// - /// Gets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to check - /// - public bool MemberCanEditProperty(string propertyTypeAlias) - { - return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsEditable; - } - - /// - /// Gets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to check - /// - public bool MemberCanViewProperty(string propertyTypeAlias) - { - return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsVisible; - } - /// - /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. - /// - /// PropertyType Alias of the Property to check - /// - public bool IsSensitiveProperty(string propertyTypeAlias) - { - return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsSensitive; - } - - /// - /// Sets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) - { - if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) - { - propertyProfile.IsEditable = value; - } - else - { - var tuple = new MemberTypePropertyProfileAccess(false, value, false); - MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); - } - } - - /// - /// Sets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) - { - if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) - { - propertyProfile.IsVisible = value; - } - else - { - var tuple = new MemberTypePropertyProfileAccess(value, false, false); - MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); - } - } - - /// - /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - public void SetIsSensitiveProperty(string propertyTypeAlias, bool value) - { - if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) - { - propertyProfile.IsSensitive = value; - } - else - { - var tuple = new MemberTypePropertyProfileAccess(false, false, true); - MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Represents the content type that a object is based on + /// + [Serializable] + [DataContract(IsReference = true)] + public class MemberType : ContentTypeCompositionBase, IMemberType + { + private static readonly Lazy Ps = new Lazy(); + public const bool IsPublishingConst = false; + + //Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId + private string _alias; + + public MemberType(int parentId) : base(parentId) + { + MemberTypePropertyTypes = new Dictionary(); + } + + public MemberType(IContentTypeComposition parent) : this(parent, null) + { + } + + public MemberType(IContentTypeComposition parent, string alias) + : base(parent, alias) + { + MemberTypePropertyTypes = new Dictionary(); + } + + /// + public override bool IsPublishing => IsPublishingConst; + + public override ContentVariation Variations + { + // note: although technically possible, variations on members don't make much sense + // and therefore are disabled - they are fully supported at service level, though, + // but not at published snapshot level. + + get => base.Variations; + set => throw new NotSupportedException("Variations are not supported on members."); + } + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + } + + /// + /// The Alias of the ContentType + /// + [DataMember] + public override string Alias + { + get => _alias; + set + { + //NOTE: WE are overriding this because we don't want to do a ToSafeAlias when the alias is the special case of + // "_umbracoSystemDefaultProtectType" which is used internally, currently there is an issue with the safe alias as it strips + // leading underscores which we don't want in this case. + // see : http://issues.umbraco.org/issue/U4-3968 + + //TODO: BUT, I'm pretty sure we could do this with regards to underscores now: + // .ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase) + // Need to ask Stephen + + var newVal = value == "_umbracoSystemDefaultProtectType" + ? value + : (value == null ? string.Empty : value.ToSafeAlias()); + + SetPropertyValueAndDetectChanges(newVal, ref _alias, Ps.Value.AliasSelector); + } + } + + /// + /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, IsSensitive) by the PropertyTypes' alias. + /// + [DataMember] + internal IDictionary MemberTypePropertyTypes { get; private set; } + + /// + /// Gets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to check + /// + public bool MemberCanEditProperty(string propertyTypeAlias) + { + return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsEditable; + } + + /// + /// Gets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + public bool MemberCanViewProperty(string propertyTypeAlias) + { + return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsVisible; + } + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + public bool IsSensitiveProperty(string propertyTypeAlias) + { + return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsSensitive; + } + + /// + /// Sets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) + { + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) + { + propertyProfile.IsEditable = value; + } + else + { + var tuple = new MemberTypePropertyProfileAccess(false, value, false); + MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); + } + } + + /// + /// Sets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) + { + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) + { + propertyProfile.IsVisible = value; + } + else + { + var tuple = new MemberTypePropertyProfileAccess(value, false, false); + MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); + } + } + + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetIsSensitiveProperty(string propertyTypeAlias, bool value) + { + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) + { + propertyProfile.IsSensitive = value; + } + else + { + var tuple = new MemberTypePropertyProfileAccess(false, false, true); + MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); + } + } + } +} diff --git a/src/Umbraco.Core/Models/Membership/EntityPermission.cs b/src/Umbraco.Core/Models/Membership/EntityPermission.cs index 11f86a1cf2..156ab2c4bd 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermission.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermission.cs @@ -1,66 +1,66 @@ -using System; - -namespace Umbraco.Core.Models.Membership -{ - /// - /// Represents an entity permission (defined on the user group and derived to retrieve permissions for a given user) - /// - public class EntityPermission : IEquatable - { - public EntityPermission(int groupId, int entityId, string[] assignedPermissions) - { - UserGroupId = groupId; - EntityId = entityId; - AssignedPermissions = assignedPermissions; - IsDefaultPermissions = false; - } - - public EntityPermission(int groupId, int entityId, string[] assignedPermissions, bool isDefaultPermissions) - { - UserGroupId = groupId; - EntityId = entityId; - AssignedPermissions = assignedPermissions; - IsDefaultPermissions = isDefaultPermissions; - } - - public int EntityId { get; private set; } - public int UserGroupId { get; private set; } - - /// - /// The assigned permissions for the user/entity combo - /// - public string[] AssignedPermissions { get; private set; } - - /// - /// True if the permissions assigned to this object are the group's default permissions and not explicitly defined permissions - /// - /// - /// This will be the case when looking up entity permissions and falling back to the default permissions - /// - public bool IsDefaultPermissions { get; private set; } - - public bool Equals(EntityPermission other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return EntityId == other.EntityId && UserGroupId == other.UserGroupId; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((EntityPermission) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (EntityId * 397) ^ UserGroupId; - } - } - } - -} +using System; + +namespace Umbraco.Core.Models.Membership +{ + /// + /// Represents an entity permission (defined on the user group and derived to retrieve permissions for a given user) + /// + public class EntityPermission : IEquatable + { + public EntityPermission(int groupId, int entityId, string[] assignedPermissions) + { + UserGroupId = groupId; + EntityId = entityId; + AssignedPermissions = assignedPermissions; + IsDefaultPermissions = false; + } + + public EntityPermission(int groupId, int entityId, string[] assignedPermissions, bool isDefaultPermissions) + { + UserGroupId = groupId; + EntityId = entityId; + AssignedPermissions = assignedPermissions; + IsDefaultPermissions = isDefaultPermissions; + } + + public int EntityId { get; private set; } + public int UserGroupId { get; private set; } + + /// + /// The assigned permissions for the user/entity combo + /// + public string[] AssignedPermissions { get; private set; } + + /// + /// True if the permissions assigned to this object are the group's default permissions and not explicitly defined permissions + /// + /// + /// This will be the case when looking up entity permissions and falling back to the default permissions + /// + public bool IsDefaultPermissions { get; private set; } + + public bool Equals(EntityPermission other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return EntityId == other.EntityId && UserGroupId == other.UserGroupId; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((EntityPermission) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (EntityId * 397) ^ UserGroupId; + } + } + } + +} diff --git a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs index 6cffa5bcd8..5ea857be51 100644 --- a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs +++ b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs @@ -1,47 +1,47 @@ -using System; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models.Membership -{ - /// - /// Defines the base contract for and - /// - public interface IMembershipUser : IEntity - { - object ProviderUserKey { get; set; } - string Username { get; set; } - string Email { get; set; } - - /// - /// Gets or sets the raw password value - /// - string RawPasswordValue { get; set; } - - string PasswordQuestion { get; set; } - - /// - /// Gets or sets the raw password answer value - /// - string RawPasswordAnswerValue { get; set; } - - string Comments { get; set; } - bool IsApproved { get; set; } - bool IsLockedOut { get; set; } - DateTime LastLoginDate { get; set; } - DateTime LastPasswordChangeDate { get; set; } - DateTime LastLockoutDate { get; set; } - - /// - /// Gets or sets the number of failed password attempts. - /// This is the number of times the password was entered incorrectly upon login. - /// - /// - /// Alias: umbracoMemberFailedPasswordAttempts - /// Part of the standard properties collection. - /// - int FailedPasswordAttempts { get; set; } - - //object ProfileId { get; set; } - //IEnumerable Groups { get; set; } - } -} +using System; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models.Membership +{ + /// + /// Defines the base contract for and + /// + public interface IMembershipUser : IEntity + { + object ProviderUserKey { get; set; } + string Username { get; set; } + string Email { get; set; } + + /// + /// Gets or sets the raw password value + /// + string RawPasswordValue { get; set; } + + string PasswordQuestion { get; set; } + + /// + /// Gets or sets the raw password answer value + /// + string RawPasswordAnswerValue { get; set; } + + string Comments { get; set; } + bool IsApproved { get; set; } + bool IsLockedOut { get; set; } + DateTime LastLoginDate { get; set; } + DateTime LastPasswordChangeDate { get; set; } + DateTime LastLockoutDate { get; set; } + + /// + /// Gets or sets the number of failed password attempts. + /// This is the number of times the password was entered incorrectly upon login. + /// + /// + /// Alias: umbracoMemberFailedPasswordAttempts + /// Part of the standard properties collection. + /// + int FailedPasswordAttempts { get; set; } + + //object ProfileId { get; set; } + //IEnumerable Groups { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Membership/IProfile.cs b/src/Umbraco.Core/Models/Membership/IProfile.cs index e711c43cbe..335faa6c11 100644 --- a/src/Umbraco.Core/Models/Membership/IProfile.cs +++ b/src/Umbraco.Core/Models/Membership/IProfile.cs @@ -1,11 +1,11 @@ -namespace Umbraco.Core.Models.Membership -{ - /// - /// Defines the the User Profile interface - /// - public interface IProfile - { - int Id { get; } - string Name { get; } - } -} +namespace Umbraco.Core.Models.Membership +{ + /// + /// Defines the the User Profile interface + /// + public interface IProfile + { + int Id { get; } + string Name { get; } + } +} diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index f3092a0106..d010d0e669 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -1,56 +1,56 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models.Membership -{ - /// - /// Defines the interface for a - /// - /// Will be left internal until a proper Membership implementation is part of the roadmap - public interface IUser : IMembershipUser, IRememberBeingDirty, ICanBeDirty - { - UserState UserState { get; } - - string Name { get; set; } - int SessionTimeout { get; set; } - int[] StartContentIds { get; set; } - int[] StartMediaIds { get; set; } - string Language { get; set; } - - DateTime? EmailConfirmedDate { get; set; } - DateTime? InvitedDate { get; set; } - - /// - /// Gets the groups that user is part of - /// - IEnumerable Groups { get; } - - void RemoveGroup(string group); - void ClearGroups(); - void AddGroup(IReadOnlyUserGroup group); - - IEnumerable AllowedSections { get; } - - /// - /// Exposes the basic profile data - /// - IProfile ProfileData { get; } - - /// - /// The security stamp used by ASP.Net identity - /// - string SecurityStamp { get; set; } - - /// - /// Will hold the media file system relative path of the users custom avatar if they uploaded one - /// +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models.Membership +{ + /// + /// Defines the interface for a + /// + /// Will be left internal until a proper Membership implementation is part of the roadmap + public interface IUser : IMembershipUser, IRememberBeingDirty, ICanBeDirty + { + UserState UserState { get; } + + string Name { get; set; } + int SessionTimeout { get; set; } + int[] StartContentIds { get; set; } + int[] StartMediaIds { get; set; } + string Language { get; set; } + + DateTime? EmailConfirmedDate { get; set; } + DateTime? InvitedDate { get; set; } + + /// + /// Gets the groups that user is part of + /// + IEnumerable Groups { get; } + + void RemoveGroup(string group); + void ClearGroups(); + void AddGroup(IReadOnlyUserGroup group); + + IEnumerable AllowedSections { get; } + + /// + /// Exposes the basic profile data + /// + IProfile ProfileData { get; } + + /// + /// The security stamp used by ASP.Net identity + /// + string SecurityStamp { get; set; } + + /// + /// Will hold the media file system relative path of the users custom avatar if they uploaded one + /// string Avatar { get; set; } - /// - /// A Json blob stored for recording tour data for a user - /// - string TourData { get; set; } - } + /// + /// A Json blob stored for recording tour data for a user + /// + string TourData { get; set; } + } } diff --git a/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs b/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs index af3b2940d8..831300051b 100644 --- a/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs @@ -1,50 +1,50 @@ -using System; -using System.Web.Security; -using Umbraco.Core.Composing; -using Umbraco.Core.Security; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Models.Membership -{ - internal static class MembershipUserExtensions - { - internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMembershipUser member, string providerName, bool providerKeyAsGuid = false) - { - var membershipMember = new UmbracoMembershipMember(member, providerName, providerKeyAsGuid); - return membershipMember; - } - - internal static IMembershipUser AsIMember(this UmbracoMembershipMember membershipMember) - { - var member = membershipMember; - if (member != null) - { - return member.Member; - } - - throw new NotImplementedException(); - } - - private static MembershipScenario? _scenario = null; - /// - /// Returns the currently configured membership scenario for members in umbraco - /// - /// - internal static MembershipScenario GetMembershipScenario(this IMemberService memberService) - { - if (_scenario.HasValue == false) - { - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider()) - { - return MembershipScenario.NativeUmbraco; - } - var memberType = Current.Services.MemberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); - return memberType != null - ? MembershipScenario.CustomProviderWithUmbracoLink - : MembershipScenario.StandaloneCustomProvider; - } - return _scenario.Value; - } - } -} +using System; +using System.Web.Security; +using Umbraco.Core.Composing; +using Umbraco.Core.Security; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Models.Membership +{ + internal static class MembershipUserExtensions + { + internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMembershipUser member, string providerName, bool providerKeyAsGuid = false) + { + var membershipMember = new UmbracoMembershipMember(member, providerName, providerKeyAsGuid); + return membershipMember; + } + + internal static IMembershipUser AsIMember(this UmbracoMembershipMember membershipMember) + { + var member = membershipMember; + if (member != null) + { + return member.Member; + } + + throw new NotImplementedException(); + } + + private static MembershipScenario? _scenario = null; + /// + /// Returns the currently configured membership scenario for members in umbraco + /// + /// + internal static MembershipScenario GetMembershipScenario(this IMemberService memberService) + { + if (_scenario.HasValue == false) + { + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + if (provider.IsUmbracoMembershipProvider()) + { + return MembershipScenario.NativeUmbraco; + } + var memberType = Current.Services.MemberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); + return memberType != null + ? MembershipScenario.CustomProviderWithUmbracoLink + : MembershipScenario.StandaloneCustomProvider; + } + return _scenario.Value; + } + } +} diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs index 0b189d70aa..ed3422d4f9 100644 --- a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs +++ b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs @@ -1,128 +1,128 @@ -using System; -using System.Web.Security; -using Umbraco.Core.Configuration; - -namespace Umbraco.Core.Models.Membership -{ - - internal class UmbracoMembershipMember : MembershipUser - { - private readonly IMembershipUser _member; - private readonly string _userName; - private readonly object _providerUserKey; - private readonly string _passwordQuestion; - private readonly bool _isLockedOut; - private readonly DateTime _lastLockoutDate; - private readonly DateTime _creationDate; - private DateTime _lastLoginDate; - private readonly DateTime _lastPasswordChangedDate; - private readonly string _providerName; - private string _email; - private string _comment; - private bool _isApproved; - private DateTime _lastActivityDate; - - //NOTE: We are only overriding the properties that matter, we don't override things like IsOnline since that is handled with the sub-class and the membership providers. - - //NOTE: We are not calling the base constructor which will validate that a provider with the specified name exists which causes issues with unit tests. The ctor - // validation for that doesn't need to be there anyways (have checked the source). - public UmbracoMembershipMember(IMembershipUser member, string providerName, bool providerKeyAsGuid = false) - { - _member = member; - //NOTE: We are copying the values here so that everything is consistent with how the underlying built-in ASP.Net membership user - // handles data! We don't want to do anything differently there but since we cannot use their ctor we'll need to handle this logic ourselves. - if (member.Username != null) - _userName = member.Username.Trim(); - if (member.Email != null) - _email = member.Email.Trim(); - if (member.PasswordQuestion != null) - _passwordQuestion = member.PasswordQuestion.Trim(); - _providerName = providerName; - _providerUserKey = providerKeyAsGuid ? member.ProviderUserKey : member.Id; - _comment = member.Comments; - _isApproved = member.IsApproved; - _isLockedOut = member.IsLockedOut; - _creationDate = member.CreateDate.ToUniversalTime(); - _lastLoginDate = member.LastLoginDate.ToUniversalTime(); - //TODO: We currently don't really have any place to store this data!! - _lastActivityDate = member.LastLoginDate.ToUniversalTime(); - _lastPasswordChangedDate = member.LastPasswordChangeDate.ToUniversalTime(); - _lastLockoutDate = member.LastLockoutDate.ToUniversalTime(); - } - - internal IMembershipUser Member - { - get { return _member; } - } - - public override string UserName - { - get { return _userName; } - } - - public override object ProviderUserKey - { - get { return _providerUserKey; } - } - - public override string Email - { - get { return _email; } - set { _email = value; } - } - - public override string PasswordQuestion - { - get { return _passwordQuestion; } - } - - public override string Comment - { - get { return _comment; } - set { _comment = value; } - } - - public override bool IsApproved - { - get { return _isApproved; } - set { _isApproved = value; } - } - - public override bool IsLockedOut - { - get { return _isLockedOut; } - } - - public override DateTime LastLockoutDate - { - get { return _lastLockoutDate; } - } - - public override DateTime CreationDate - { - get { return _creationDate; } - } - - public override DateTime LastLoginDate - { - get { return _lastLoginDate; } - set { _lastLoginDate = value; } - } - - public override DateTime LastActivityDate - { - get { return _lastActivityDate; } - set { _lastActivityDate = value; } - } - - public override DateTime LastPasswordChangedDate - { - get { return _lastPasswordChangedDate; } - } - - public override string ProviderName - { - get { return _providerName; } - } - } -} +using System; +using System.Web.Security; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Models.Membership +{ + + internal class UmbracoMembershipMember : MembershipUser + { + private readonly IMembershipUser _member; + private readonly string _userName; + private readonly object _providerUserKey; + private readonly string _passwordQuestion; + private readonly bool _isLockedOut; + private readonly DateTime _lastLockoutDate; + private readonly DateTime _creationDate; + private DateTime _lastLoginDate; + private readonly DateTime _lastPasswordChangedDate; + private readonly string _providerName; + private string _email; + private string _comment; + private bool _isApproved; + private DateTime _lastActivityDate; + + //NOTE: We are only overriding the properties that matter, we don't override things like IsOnline since that is handled with the sub-class and the membership providers. + + //NOTE: We are not calling the base constructor which will validate that a provider with the specified name exists which causes issues with unit tests. The ctor + // validation for that doesn't need to be there anyways (have checked the source). + public UmbracoMembershipMember(IMembershipUser member, string providerName, bool providerKeyAsGuid = false) + { + _member = member; + //NOTE: We are copying the values here so that everything is consistent with how the underlying built-in ASP.Net membership user + // handles data! We don't want to do anything differently there but since we cannot use their ctor we'll need to handle this logic ourselves. + if (member.Username != null) + _userName = member.Username.Trim(); + if (member.Email != null) + _email = member.Email.Trim(); + if (member.PasswordQuestion != null) + _passwordQuestion = member.PasswordQuestion.Trim(); + _providerName = providerName; + _providerUserKey = providerKeyAsGuid ? member.ProviderUserKey : member.Id; + _comment = member.Comments; + _isApproved = member.IsApproved; + _isLockedOut = member.IsLockedOut; + _creationDate = member.CreateDate.ToUniversalTime(); + _lastLoginDate = member.LastLoginDate.ToUniversalTime(); + //TODO: We currently don't really have any place to store this data!! + _lastActivityDate = member.LastLoginDate.ToUniversalTime(); + _lastPasswordChangedDate = member.LastPasswordChangeDate.ToUniversalTime(); + _lastLockoutDate = member.LastLockoutDate.ToUniversalTime(); + } + + internal IMembershipUser Member + { + get { return _member; } + } + + public override string UserName + { + get { return _userName; } + } + + public override object ProviderUserKey + { + get { return _providerUserKey; } + } + + public override string Email + { + get { return _email; } + set { _email = value; } + } + + public override string PasswordQuestion + { + get { return _passwordQuestion; } + } + + public override string Comment + { + get { return _comment; } + set { _comment = value; } + } + + public override bool IsApproved + { + get { return _isApproved; } + set { _isApproved = value; } + } + + public override bool IsLockedOut + { + get { return _isLockedOut; } + } + + public override DateTime LastLockoutDate + { + get { return _lastLockoutDate; } + } + + public override DateTime CreationDate + { + get { return _creationDate; } + } + + public override DateTime LastLoginDate + { + get { return _lastLoginDate; } + set { _lastLoginDate = value; } + } + + public override DateTime LastActivityDate + { + get { return _lastActivityDate; } + set { _lastActivityDate = value; } + } + + public override DateTime LastPasswordChangedDate + { + get { return _lastPasswordChangedDate; } + } + + public override string ProviderName + { + get { return _providerName; } + } + } +} diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index cc91f5ed47..561eb0f3cd 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -1,532 +1,532 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models.Membership -{ - /// - /// Represents a backoffice user - /// - [Serializable] - [DataContract(IsReference = true)] - public class User : EntityBase, IUser, IProfile - { - /// - /// Constructor for creating a new/empty user - /// - public User() - { - SessionTimeout = 60; - _userGroups = new HashSet(); - _language = UmbracoConfig.For.GlobalSettings().DefaultUILanguage; //fixme inject somehow? - _isApproved = true; - _isLockedOut = false; - _startContentIds = new int[] { }; - _startMediaIds = new int[] { }; - //cannot be null - _rawPasswordValue = ""; - } - - /// - /// Constructor for creating a new/empty user - /// - /// - /// - /// - /// - public User(string name, string email, string username, string rawPasswordValue) - : this() - { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); - if (string.IsNullOrWhiteSpace(email)) throw new ArgumentException("Value cannot be null or whitespace.", "email"); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); - if (string.IsNullOrEmpty(rawPasswordValue)) throw new ArgumentException("Value cannot be null or empty.", "rawPasswordValue"); - - _name = name; - _email = email; - _username = username; - _rawPasswordValue = rawPasswordValue; - _userGroups = new HashSet(); - _isApproved = true; - _isLockedOut = false; - _startContentIds = new int[] { }; - _startMediaIds = new int[] { }; - - } - - /// - /// Constructor for creating a new User instance for an existing user - /// - /// - /// - /// - /// - /// - /// - /// - /// - public User(int id, string name, string email, string username, string rawPasswordValue, IEnumerable userGroups, int[] startContentIds, int[] startMediaIds) - : this() - { - //we allow whitespace for this value so just check null - if (rawPasswordValue == null) throw new ArgumentNullException("rawPasswordValue"); - if (userGroups == null) throw new ArgumentNullException("userGroups"); - if (startContentIds == null) throw new ArgumentNullException("startContentIds"); - if (startMediaIds == null) throw new ArgumentNullException("startMediaIds"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); - - Id = id; - _name = name; - _email = email; - _username = username; - _rawPasswordValue = rawPasswordValue; - _userGroups = new HashSet(userGroups); - _isApproved = true; - _isLockedOut = false; - _startContentIds = startContentIds; - _startMediaIds = startMediaIds; - } - - private string _name; - private string _securityStamp; - private string _avatar; - private string _tourData; - private int _sessionTimeout; - private int[] _startContentIds; - private int[] _startMediaIds; - private int _failedLoginAttempts; - - private string _username; - private DateTime? _emailConfirmedDate; - private DateTime? _invitedDate; - private string _email; - private string _rawPasswordValue; - private IEnumerable _allowedSections; - private HashSet _userGroups; - private bool _isApproved; - private bool _isLockedOut; - private string _language; - private DateTime _lastPasswordChangedDate; - private DateTime _lastLoginDate; - private DateTime _lastLockoutDate; - private bool _defaultToLiveEditing; - private IDictionary _additionalData; - private object _additionalDataLock = new object(); - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo FailedPasswordAttemptsSelector = ExpressionHelper.GetPropertyInfo(x => x.FailedPasswordAttempts); - public readonly PropertyInfo LastLockoutDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLockoutDate); - public readonly PropertyInfo LastLoginDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLoginDate); - public readonly PropertyInfo LastPasswordChangeDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastPasswordChangeDate); - - public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo(x => x.SecurityStamp); - public readonly PropertyInfo AvatarSelector = ExpressionHelper.GetPropertyInfo(x => x.Avatar); - public readonly PropertyInfo TourDataSelector = ExpressionHelper.GetPropertyInfo(x => x.TourData); - public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo(x => x.SessionTimeout); - public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentIds); - public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaIds); - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - - public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); - public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); - public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); - public readonly PropertyInfo IsLockedOutSelector = ExpressionHelper.GetPropertyInfo(x => x.IsLockedOut); - public readonly PropertyInfo IsApprovedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsApproved); - public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); - public readonly PropertyInfo EmailConfirmedDateSelector = ExpressionHelper.GetPropertyInfo(x => x.EmailConfirmedDate); - public readonly PropertyInfo InvitedDateSelector = ExpressionHelper.GetPropertyInfo(x => x.InvitedDate); - - public readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); - - public readonly PropertyInfo UserGroupsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Groups); - - //Custom comparer for enumerable - public readonly DelegateEqualityComparer> IntegerEnumerableComparer = - new DelegateEqualityComparer>( - (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), - enum1 => enum1.GetHashCode()); - } - - #region Implementation of IMembershipUser - - [IgnoreDataMember] - public object ProviderUserKey - { - get { return Id; } - set { throw new NotSupportedException("Cannot set the provider user key for a user"); } - } - - [DataMember] - public DateTime? EmailConfirmedDate - { - get { return _emailConfirmedDate; } - set { SetPropertyValueAndDetectChanges(value, ref _emailConfirmedDate, Ps.Value.EmailConfirmedDateSelector); } - } - [DataMember] - public DateTime? InvitedDate - { - get { return _invitedDate; } - set { SetPropertyValueAndDetectChanges(value, ref _invitedDate, Ps.Value.InvitedDateSelector); } - } - [DataMember] - public string Username - { - get { return _username; } - set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); } - } - [DataMember] - public string Email - { - get { return _email; } - set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } - } - [DataMember] - public string RawPasswordValue - { - get { return _rawPasswordValue; } - set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); } - } - - [DataMember] - public bool IsApproved - { - get { return _isApproved; } - set { SetPropertyValueAndDetectChanges(value, ref _isApproved, Ps.Value.IsApprovedSelector); } - } - - [IgnoreDataMember] - public bool IsLockedOut - { - get { return _isLockedOut; } - set { SetPropertyValueAndDetectChanges(value, ref _isLockedOut, Ps.Value.IsLockedOutSelector); } - } - - [IgnoreDataMember] - public DateTime LastLoginDate - { - get { return _lastLoginDate; } - set { SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, Ps.Value.LastLoginDateSelector); } - } - - [IgnoreDataMember] - public DateTime LastPasswordChangeDate - { - get { return _lastPasswordChangedDate; } - set { SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangedDate, Ps.Value.LastPasswordChangeDateSelector); } - } - - [IgnoreDataMember] - public DateTime LastLockoutDate - { - get { return _lastLockoutDate; } - set { SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, Ps.Value.LastLockoutDateSelector); } - } - - [IgnoreDataMember] - public int FailedPasswordAttempts - { - get { return _failedLoginAttempts; } - set { SetPropertyValueAndDetectChanges(value, ref _failedLoginAttempts, Ps.Value.FailedPasswordAttemptsSelector); } - } - - //TODO: Figure out how to support all of this! - we cannot have NotImplementedExceptions because these get used by the IMembershipMemberService service so - // we'll just have them as generic get/set which don't interact with the db. - - [IgnoreDataMember] - public string PasswordQuestion { get; set; } - [IgnoreDataMember] - public string RawPasswordAnswerValue { get; set; } - [IgnoreDataMember] - public string Comments { get; set; } - - #endregion - - #region Implementation of IUser - - public UserState UserState - { - get - { - if (LastLoginDate == default(DateTime) && IsApproved == false && InvitedDate != null) - return UserState.Invited; - - if (IsLockedOut) - return UserState.LockedOut; - if (IsApproved == false) - return UserState.Disabled; - - return UserState.Active; - } - } - - [DataMember] - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - public IEnumerable AllowedSections - { - get { return _allowedSections ?? (_allowedSections = new List(_userGroups.SelectMany(x => x.AllowedSections).Distinct())); } - } - - /// - /// This used purely for hacking backwards compatibility into this class for < 7.7 compat - /// - [DoNotClone] - [IgnoreDataMember] - internal List GroupsToSave = new List(); - - public IProfile ProfileData - { - get { return new WrappedUserProfile(this); } - } - - /// - /// The security stamp used by ASP.Net identity - /// - [IgnoreDataMember] - public string SecurityStamp - { - get { return _securityStamp; } - set { SetPropertyValueAndDetectChanges(value, ref _securityStamp, Ps.Value.SecurityStampSelector); } - } - - [DataMember] - public string Avatar - { - get { return _avatar; } - set { SetPropertyValueAndDetectChanges(value, ref _avatar, Ps.Value.AvatarSelector); } - } - - /// - /// A Json blob stored for recording tour data for a user - /// - [DataMember] - public string TourData - { - get { return _tourData; } - set { SetPropertyValueAndDetectChanges(value, ref _tourData, Ps.Value.TourDataSelector); } +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models.Membership +{ + /// + /// Represents a backoffice user + /// + [Serializable] + [DataContract(IsReference = true)] + public class User : EntityBase, IUser, IProfile + { + /// + /// Constructor for creating a new/empty user + /// + public User() + { + SessionTimeout = 60; + _userGroups = new HashSet(); + _language = UmbracoConfig.For.GlobalSettings().DefaultUILanguage; //fixme inject somehow? + _isApproved = true; + _isLockedOut = false; + _startContentIds = new int[] { }; + _startMediaIds = new int[] { }; + //cannot be null + _rawPasswordValue = ""; } - /// - /// Gets or sets the session timeout. - /// - /// - /// The session timeout. - /// - [DataMember] - public int SessionTimeout - { - get { return _sessionTimeout; } - set { SetPropertyValueAndDetectChanges(value, ref _sessionTimeout, Ps.Value.SessionTimeoutSelector); } - } - - /// - /// Gets or sets the start content id. - /// - /// - /// The start content id. - /// - [DataMember] - [DoNotClone] - public int[] StartContentIds - { - get { return _startContentIds; } - set { SetPropertyValueAndDetectChanges(value, ref _startContentIds, Ps.Value.StartContentIdSelector, Ps.Value.IntegerEnumerableComparer); } - } - - /// - /// Gets or sets the start media id. - /// - /// - /// The start media id. - /// - [DataMember] - [DoNotClone] - public int[] StartMediaIds - { - get { return _startMediaIds; } - set { SetPropertyValueAndDetectChanges(value, ref _startMediaIds, Ps.Value.StartMediaIdSelector, Ps.Value.IntegerEnumerableComparer); } - } - - [DataMember] - public string Language - { - get { return _language; } - set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); } - } - - [IgnoreDataMember] - internal bool DefaultToLiveEditing - { - get { return _defaultToLiveEditing; } - set { SetPropertyValueAndDetectChanges(value, ref _defaultToLiveEditing, Ps.Value.DefaultToLiveEditingSelector); } - } - - /// - /// Gets the groups that user is part of - /// - [DataMember] - public IEnumerable Groups - { - get { return _userGroups; } - } - - public void RemoveGroup(string group) - { - foreach (var userGroup in _userGroups.ToArray()) - { - if (userGroup.Alias == group) - { - _userGroups.Remove(userGroup); - //reset this flag so it's rebuilt with the assigned groups - _allowedSections = null; - OnPropertyChanged(Ps.Value.UserGroupsSelector); - } - } - } - - public void ClearGroups() - { - if (_userGroups.Count > 0) - { - _userGroups.Clear(); - //reset this flag so it's rebuilt with the assigned groups - _allowedSections = null; - OnPropertyChanged(Ps.Value.UserGroupsSelector); - } - } - - public void AddGroup(IReadOnlyUserGroup group) - { - if (_userGroups.Add(group)) - { - //reset this flag so it's rebuilt with the assigned groups - _allowedSections = null; - OnPropertyChanged(Ps.Value.UserGroupsSelector); - } - } - - #endregion - - /// - /// This is used as an internal cache for this entity - specifically for calculating start nodes so we don't re-calculated all of the time - /// - [IgnoreDataMember] - [DoNotClone] - internal IDictionary AdditionalData - { - get - { - lock (_additionalDataLock) - { - return _additionalData ?? (_additionalData = new Dictionary()); - } - } - } - - [IgnoreDataMember] - [DoNotClone] - internal object AdditionalDataLock { get { return _additionalDataLock; } } - - public override object DeepClone() - { - var clone = (User)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //manually clone the start node props - clone._startContentIds = _startContentIds.ToArray(); - clone._startMediaIds = _startMediaIds.ToArray(); - - // this value has been cloned and points to the same object - // which obviously is bad - needs to point to a new object - clone._additionalDataLock = new object(); - - if (_additionalData != null) - { - // clone._additionalData points to the same dictionary, which is bad, because - // changing one clone impacts all of them - so we need to reset it with a fresh - // dictionary that will contain the same values - and, if some values are deep - // cloneable, they should be deep-cloned too - var cloneAdditionalData = clone._additionalData = new Dictionary(); - - lock (_additionalDataLock) - { - foreach (var kvp in _additionalData) - { - var deepCloneable = kvp.Value as IDeepCloneable; - cloneAdditionalData[kvp.Key] = deepCloneable == null ? kvp.Value : deepCloneable.DeepClone(); - } - } - } - - //need to create new collections otherwise they'll get copied by ref - clone._userGroups = new HashSet(_userGroups); - clone._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; - //re-create the event handler - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; - } - - /// - /// Internal class used to wrap the user in a profile - /// - private class WrappedUserProfile : IProfile - { - private readonly IUser _user; - - public WrappedUserProfile(IUser user) - { - _user = user; - } - - public int Id - { - get { return _user.Id; } - } - - public string Name - { - get { return _user.Name; } - } - - private bool Equals(WrappedUserProfile other) - { - return _user.Equals(other._user); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((WrappedUserProfile) obj); - } - - public override int GetHashCode() - { - return _user.GetHashCode(); - } - } - - } -} + /// + /// Constructor for creating a new/empty user + /// + /// + /// + /// + /// + public User(string name, string email, string username, string rawPasswordValue) + : this() + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + if (string.IsNullOrWhiteSpace(email)) throw new ArgumentException("Value cannot be null or whitespace.", "email"); + if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); + if (string.IsNullOrEmpty(rawPasswordValue)) throw new ArgumentException("Value cannot be null or empty.", "rawPasswordValue"); + + _name = name; + _email = email; + _username = username; + _rawPasswordValue = rawPasswordValue; + _userGroups = new HashSet(); + _isApproved = true; + _isLockedOut = false; + _startContentIds = new int[] { }; + _startMediaIds = new int[] { }; + + } + + /// + /// Constructor for creating a new User instance for an existing user + /// + /// + /// + /// + /// + /// + /// + /// + /// + public User(int id, string name, string email, string username, string rawPasswordValue, IEnumerable userGroups, int[] startContentIds, int[] startMediaIds) + : this() + { + //we allow whitespace for this value so just check null + if (rawPasswordValue == null) throw new ArgumentNullException("rawPasswordValue"); + if (userGroups == null) throw new ArgumentNullException("userGroups"); + if (startContentIds == null) throw new ArgumentNullException("startContentIds"); + if (startMediaIds == null) throw new ArgumentNullException("startMediaIds"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); + + Id = id; + _name = name; + _email = email; + _username = username; + _rawPasswordValue = rawPasswordValue; + _userGroups = new HashSet(userGroups); + _isApproved = true; + _isLockedOut = false; + _startContentIds = startContentIds; + _startMediaIds = startMediaIds; + } + + private string _name; + private string _securityStamp; + private string _avatar; + private string _tourData; + private int _sessionTimeout; + private int[] _startContentIds; + private int[] _startMediaIds; + private int _failedLoginAttempts; + + private string _username; + private DateTime? _emailConfirmedDate; + private DateTime? _invitedDate; + private string _email; + private string _rawPasswordValue; + private IEnumerable _allowedSections; + private HashSet _userGroups; + private bool _isApproved; + private bool _isLockedOut; + private string _language; + private DateTime _lastPasswordChangedDate; + private DateTime _lastLoginDate; + private DateTime _lastLockoutDate; + private bool _defaultToLiveEditing; + private IDictionary _additionalData; + private object _additionalDataLock = new object(); + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo FailedPasswordAttemptsSelector = ExpressionHelper.GetPropertyInfo(x => x.FailedPasswordAttempts); + public readonly PropertyInfo LastLockoutDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLockoutDate); + public readonly PropertyInfo LastLoginDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLoginDate); + public readonly PropertyInfo LastPasswordChangeDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastPasswordChangeDate); + + public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo(x => x.SecurityStamp); + public readonly PropertyInfo AvatarSelector = ExpressionHelper.GetPropertyInfo(x => x.Avatar); + public readonly PropertyInfo TourDataSelector = ExpressionHelper.GetPropertyInfo(x => x.TourData); + public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo(x => x.SessionTimeout); + public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentIds); + public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaIds); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + + public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); + public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); + public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); + public readonly PropertyInfo IsLockedOutSelector = ExpressionHelper.GetPropertyInfo(x => x.IsLockedOut); + public readonly PropertyInfo IsApprovedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsApproved); + public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + public readonly PropertyInfo EmailConfirmedDateSelector = ExpressionHelper.GetPropertyInfo(x => x.EmailConfirmedDate); + public readonly PropertyInfo InvitedDateSelector = ExpressionHelper.GetPropertyInfo(x => x.InvitedDate); + + public readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); + + public readonly PropertyInfo UserGroupsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Groups); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> IntegerEnumerableComparer = + new DelegateEqualityComparer>( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode()); + } + + #region Implementation of IMembershipUser + + [IgnoreDataMember] + public object ProviderUserKey + { + get { return Id; } + set { throw new NotSupportedException("Cannot set the provider user key for a user"); } + } + + [DataMember] + public DateTime? EmailConfirmedDate + { + get { return _emailConfirmedDate; } + set { SetPropertyValueAndDetectChanges(value, ref _emailConfirmedDate, Ps.Value.EmailConfirmedDateSelector); } + } + [DataMember] + public DateTime? InvitedDate + { + get { return _invitedDate; } + set { SetPropertyValueAndDetectChanges(value, ref _invitedDate, Ps.Value.InvitedDateSelector); } + } + [DataMember] + public string Username + { + get { return _username; } + set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); } + } + [DataMember] + public string Email + { + get { return _email; } + set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } + } + [DataMember] + public string RawPasswordValue + { + get { return _rawPasswordValue; } + set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); } + } + + [DataMember] + public bool IsApproved + { + get { return _isApproved; } + set { SetPropertyValueAndDetectChanges(value, ref _isApproved, Ps.Value.IsApprovedSelector); } + } + + [IgnoreDataMember] + public bool IsLockedOut + { + get { return _isLockedOut; } + set { SetPropertyValueAndDetectChanges(value, ref _isLockedOut, Ps.Value.IsLockedOutSelector); } + } + + [IgnoreDataMember] + public DateTime LastLoginDate + { + get { return _lastLoginDate; } + set { SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, Ps.Value.LastLoginDateSelector); } + } + + [IgnoreDataMember] + public DateTime LastPasswordChangeDate + { + get { return _lastPasswordChangedDate; } + set { SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangedDate, Ps.Value.LastPasswordChangeDateSelector); } + } + + [IgnoreDataMember] + public DateTime LastLockoutDate + { + get { return _lastLockoutDate; } + set { SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, Ps.Value.LastLockoutDateSelector); } + } + + [IgnoreDataMember] + public int FailedPasswordAttempts + { + get { return _failedLoginAttempts; } + set { SetPropertyValueAndDetectChanges(value, ref _failedLoginAttempts, Ps.Value.FailedPasswordAttemptsSelector); } + } + + //TODO: Figure out how to support all of this! - we cannot have NotImplementedExceptions because these get used by the IMembershipMemberService service so + // we'll just have them as generic get/set which don't interact with the db. + + [IgnoreDataMember] + public string PasswordQuestion { get; set; } + [IgnoreDataMember] + public string RawPasswordAnswerValue { get; set; } + [IgnoreDataMember] + public string Comments { get; set; } + + #endregion + + #region Implementation of IUser + + public UserState UserState + { + get + { + if (LastLoginDate == default(DateTime) && IsApproved == false && InvitedDate != null) + return UserState.Invited; + + if (IsLockedOut) + return UserState.LockedOut; + if (IsApproved == false) + return UserState.Disabled; + + return UserState.Active; + } + } + + [DataMember] + public string Name + { + get { return _name; } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + } + + public IEnumerable AllowedSections + { + get { return _allowedSections ?? (_allowedSections = new List(_userGroups.SelectMany(x => x.AllowedSections).Distinct())); } + } + + /// + /// This used purely for hacking backwards compatibility into this class for < 7.7 compat + /// + [DoNotClone] + [IgnoreDataMember] + internal List GroupsToSave = new List(); + + public IProfile ProfileData + { + get { return new WrappedUserProfile(this); } + } + + /// + /// The security stamp used by ASP.Net identity + /// + [IgnoreDataMember] + public string SecurityStamp + { + get { return _securityStamp; } + set { SetPropertyValueAndDetectChanges(value, ref _securityStamp, Ps.Value.SecurityStampSelector); } + } + + [DataMember] + public string Avatar + { + get { return _avatar; } + set { SetPropertyValueAndDetectChanges(value, ref _avatar, Ps.Value.AvatarSelector); } + } + + /// + /// A Json blob stored for recording tour data for a user + /// + [DataMember] + public string TourData + { + get { return _tourData; } + set { SetPropertyValueAndDetectChanges(value, ref _tourData, Ps.Value.TourDataSelector); } + } + + /// + /// Gets or sets the session timeout. + /// + /// + /// The session timeout. + /// + [DataMember] + public int SessionTimeout + { + get { return _sessionTimeout; } + set { SetPropertyValueAndDetectChanges(value, ref _sessionTimeout, Ps.Value.SessionTimeoutSelector); } + } + + /// + /// Gets or sets the start content id. + /// + /// + /// The start content id. + /// + [DataMember] + [DoNotClone] + public int[] StartContentIds + { + get { return _startContentIds; } + set { SetPropertyValueAndDetectChanges(value, ref _startContentIds, Ps.Value.StartContentIdSelector, Ps.Value.IntegerEnumerableComparer); } + } + + /// + /// Gets or sets the start media id. + /// + /// + /// The start media id. + /// + [DataMember] + [DoNotClone] + public int[] StartMediaIds + { + get { return _startMediaIds; } + set { SetPropertyValueAndDetectChanges(value, ref _startMediaIds, Ps.Value.StartMediaIdSelector, Ps.Value.IntegerEnumerableComparer); } + } + + [DataMember] + public string Language + { + get { return _language; } + set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); } + } + + [IgnoreDataMember] + internal bool DefaultToLiveEditing + { + get { return _defaultToLiveEditing; } + set { SetPropertyValueAndDetectChanges(value, ref _defaultToLiveEditing, Ps.Value.DefaultToLiveEditingSelector); } + } + + /// + /// Gets the groups that user is part of + /// + [DataMember] + public IEnumerable Groups + { + get { return _userGroups; } + } + + public void RemoveGroup(string group) + { + foreach (var userGroup in _userGroups.ToArray()) + { + if (userGroup.Alias == group) + { + _userGroups.Remove(userGroup); + //reset this flag so it's rebuilt with the assigned groups + _allowedSections = null; + OnPropertyChanged(Ps.Value.UserGroupsSelector); + } + } + } + + public void ClearGroups() + { + if (_userGroups.Count > 0) + { + _userGroups.Clear(); + //reset this flag so it's rebuilt with the assigned groups + _allowedSections = null; + OnPropertyChanged(Ps.Value.UserGroupsSelector); + } + } + + public void AddGroup(IReadOnlyUserGroup group) + { + if (_userGroups.Add(group)) + { + //reset this flag so it's rebuilt with the assigned groups + _allowedSections = null; + OnPropertyChanged(Ps.Value.UserGroupsSelector); + } + } + + #endregion + + /// + /// This is used as an internal cache for this entity - specifically for calculating start nodes so we don't re-calculated all of the time + /// + [IgnoreDataMember] + [DoNotClone] + internal IDictionary AdditionalData + { + get + { + lock (_additionalDataLock) + { + return _additionalData ?? (_additionalData = new Dictionary()); + } + } + } + + [IgnoreDataMember] + [DoNotClone] + internal object AdditionalDataLock { get { return _additionalDataLock; } } + + public override object DeepClone() + { + var clone = (User)base.DeepClone(); + //turn off change tracking + clone.DisableChangeTracking(); + //manually clone the start node props + clone._startContentIds = _startContentIds.ToArray(); + clone._startMediaIds = _startMediaIds.ToArray(); + + // this value has been cloned and points to the same object + // which obviously is bad - needs to point to a new object + clone._additionalDataLock = new object(); + + if (_additionalData != null) + { + // clone._additionalData points to the same dictionary, which is bad, because + // changing one clone impacts all of them - so we need to reset it with a fresh + // dictionary that will contain the same values - and, if some values are deep + // cloneable, they should be deep-cloned too + var cloneAdditionalData = clone._additionalData = new Dictionary(); + + lock (_additionalDataLock) + { + foreach (var kvp in _additionalData) + { + var deepCloneable = kvp.Value as IDeepCloneable; + cloneAdditionalData[kvp.Key] = deepCloneable == null ? kvp.Value : deepCloneable.DeepClone(); + } + } + } + + //need to create new collections otherwise they'll get copied by ref + clone._userGroups = new HashSet(_userGroups); + clone._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; + //re-create the event handler + //this shouldn't really be needed since we're not tracking + clone.ResetDirtyProperties(false); + //re-enable tracking + clone.EnableChangeTracking(); + + return clone; + } + + /// + /// Internal class used to wrap the user in a profile + /// + private class WrappedUserProfile : IProfile + { + private readonly IUser _user; + + public WrappedUserProfile(IUser user) + { + _user = user; + } + + public int Id + { + get { return _user.Id; } + } + + public string Name + { + get { return _user.Name; } + } + + private bool Equals(WrappedUserProfile other) + { + return _user.Equals(other._user); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((WrappedUserProfile) obj); + } + + public override int GetHashCode() + { + return _user.GetHashCode(); + } + } + + } +} diff --git a/src/Umbraco.Core/Models/PagedResult.cs b/src/Umbraco.Core/Models/PagedResult.cs index 88b2069ba4..653712d9f8 100644 --- a/src/Umbraco.Core/Models/PagedResult.cs +++ b/src/Umbraco.Core/Models/PagedResult.cs @@ -1,60 +1,60 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a paged result for a model collection - /// - /// - [DataContract(Name = "pagedCollection", Namespace = "")] - public class PagedResult - { - public PagedResult(long totalItems, long pageNumber, long pageSize) - { - TotalItems = totalItems; - PageNumber = pageNumber; - PageSize = pageSize; - - if (pageSize > 0) - { - TotalPages = (long)Math.Ceiling(totalItems / (Decimal)pageSize); - } - else - { - TotalPages = 1; - } - } - - [DataMember(Name = "pageNumber")] - public long PageNumber { get; private set; } - - [DataMember(Name = "pageSize")] - public long PageSize { get; private set; } - - [DataMember(Name = "totalPages")] - public long TotalPages { get; private set; } - - [DataMember(Name = "totalItems")] - public long TotalItems { get; private set; } - - [DataMember(Name = "items")] - public IEnumerable Items { get; set; } - - /// - /// Calculates the skip size based on the paged parameters specified - /// - /// - /// Returns 0 if the page number or page size is zero - /// - public int GetSkipSize() - { - if (PageNumber > 0 && PageSize > 0) - { - return Convert.ToInt32((PageNumber - 1) * PageSize); - } - return 0; - } - } -} +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a paged result for a model collection + /// + /// + [DataContract(Name = "pagedCollection", Namespace = "")] + public class PagedResult + { + public PagedResult(long totalItems, long pageNumber, long pageSize) + { + TotalItems = totalItems; + PageNumber = pageNumber; + PageSize = pageSize; + + if (pageSize > 0) + { + TotalPages = (long)Math.Ceiling(totalItems / (Decimal)pageSize); + } + else + { + TotalPages = 1; + } + } + + [DataMember(Name = "pageNumber")] + public long PageNumber { get; private set; } + + [DataMember(Name = "pageSize")] + public long PageSize { get; private set; } + + [DataMember(Name = "totalPages")] + public long TotalPages { get; private set; } + + [DataMember(Name = "totalItems")] + public long TotalItems { get; private set; } + + [DataMember(Name = "items")] + public IEnumerable Items { get; set; } + + /// + /// Calculates the skip size based on the paged parameters specified + /// + /// + /// Returns 0 if the page number or page size is zero + /// + public int GetSkipSize() + { + if (PageNumber > 0 && PageSize > 0) + { + return Convert.ToInt32((PageNumber - 1) * PageSize); + } + return 0; + } + } +} diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index bb4c7f338d..ac6a9b09f0 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -1,425 +1,425 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Collections; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a property. - /// - [Serializable] - [DataContract(IsReference = true)] - public class Property : EntityBase - { - private List _values = new List(); - private PropertyValue _pvalue; - private Dictionary _vvalues; - - private static readonly Lazy Ps = new Lazy(); - - protected Property() - { } - - public Property(PropertyType propertyType) - { - PropertyType = propertyType; - } - - public Property(int id, PropertyType propertyType) - { - Id = id; - PropertyType = propertyType; - } - - public class PropertyValue - { - private string _culture; - private string _segment; - - public string Culture - { - get => _culture; - internal set => _culture = value?.ToLowerInvariant(); - } - public string Segment - { - get => _segment; - internal set => _segment = value?.ToLowerInvariant(); - } - public object EditedValue { get; internal set; } - public object PublishedValue { get; internal set; } - - public PropertyValue Clone() - => new PropertyValue { _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue }; - } - - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo ValuesSelector = ExpressionHelper.GetPropertyInfo(x => x.Values); - - public readonly DelegateEqualityComparer PropertyValueComparer = new DelegateEqualityComparer( - (o, o1) => - { - if (o == null && o1 == null) return true; - - // custom comparer for strings. - // if one is null and another is empty then they are the same - if (o is string || o1 is string) - return ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) || (o != null && o1 != null && o.Equals(o1)); - - if (o == null || o1 == null) return false; - - // custom comparer for enumerable - // ReSharper disable once MergeCastWithTypeCheck - if (o is IEnumerable && o1 is IEnumerable enumerable) - return ((IEnumerable) o).Cast().UnsortedSequenceEqual(enumerable.Cast()); - - return o.Equals(o1); - }, o => o.GetHashCode()); - } - - /// - /// Returns the PropertyType, which this Property is based on - /// - [IgnoreDataMember] - public PropertyType PropertyType { get; private set; } - - /// - /// Gets the list of values. - /// - [DataMember] - public IReadOnlyCollection Values - { - get => _values; - set - { - // make sure we filter out invalid variations - // make sure we leave _vvalues null if possible - _values = value.Where(x => PropertyType.ValidateVariation(x.Culture, x.Segment, false)).ToList(); - _pvalue = _values.FirstOrDefault(x => x.Culture == null && x.Segment == null); - _vvalues = _values.Count > (_pvalue == null ? 0 : 1) - ? _values.Where(x => x != _pvalue).ToDictionary(x => new CompositeNStringNStringKey(x.Culture, x.Segment), x => x) - : null; - } - } - - /// - /// Returns the Alias of the PropertyType, which this Property is based on - /// - [DataMember] - public string Alias => PropertyType.Alias; - - /// - /// Returns the Id of the PropertyType, which this Property is based on - /// - [IgnoreDataMember] - internal int PropertyTypeId => PropertyType.Id; - - /// - /// Returns the DatabaseType that the underlaying DataType is using to store its values - /// - /// - /// Only used internally when saving the property value. - /// - [IgnoreDataMember] - internal ValueStorageType ValueStorageType => PropertyType.ValueStorageType; - - /// - /// Gets the value. - /// - public object GetValue(string culture = null, string segment = null, bool published = false) - { - if (!PropertyType.ValidateVariation(culture, segment, false)) return null; - if (culture == null && segment == null) return GetPropertyValue(_pvalue, published); - if (_vvalues == null) return null; - return _vvalues.TryGetValue(new CompositeNStringNStringKey(culture, segment), out var pvalue) - ? GetPropertyValue(pvalue, published) - : null; - } - - private object GetPropertyValue(PropertyValue pvalue, bool published) - { - if (pvalue == null) return null; - - return PropertyType.IsPublishing - ? (published ? pvalue.PublishedValue : pvalue.EditedValue) - : pvalue.EditedValue; - } - - // internal - must be invoked by the content item - // does *not* validate the value - content item must validate first - internal void PublishAllValues() - { - // if invariant-neutral is supported, publish invariant-neutral - if (PropertyType.ValidateVariation(null, null, false)) - PublishPropertyValue(_pvalue); - - // publish everything not invariant-neutral that is supported - if (_vvalues != null) - { - var pvalues = _vvalues - .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) - .Select(x => x.Value); - foreach (var pvalue in pvalues) - PublishPropertyValue(pvalue); - } - } - - // internal - must be invoked by the content item - // does *not* validate the value - content item must validate first - internal void PublishValue(string culture = null, string segment = null) - { - PropertyType.ValidateVariation(culture, segment, true); - - var (pvalue, _) = GetPValue(culture, segment, false); - if (pvalue == null) return; - PublishPropertyValue(pvalue); - } - - // internal - must be invoked by the content item - // does *not* validate the value - content item must validate first - internal void PublishCultureValues(string culture = null) - { - // if invariant and invariant-neutral is supported, publish invariant-neutral - if (culture == null && PropertyType.ValidateVariation(null, null, false)) - PublishPropertyValue(_pvalue); - - // publish everything not invariant-neutral that matches the culture and is supported - if (_vvalues != null) - { - var pvalues = _vvalues - .Where(x => x.Value.Culture.InvariantEquals(culture)) - .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) - .Select(x => x.Value); - foreach (var pvalue in pvalues) - PublishPropertyValue(pvalue); - } - } - - // internal - must be invoked by the content item - internal void ClearPublishedAllValues() - { - if (PropertyType.ValidateVariation(null, null, false)) - ClearPublishedPropertyValue(_pvalue); - - if (_vvalues != null) - { - var pvalues = _vvalues - .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) - .Select(x => x.Value); - foreach (var pvalue in pvalues) - ClearPublishedPropertyValue(pvalue); - } - } - - // internal - must be invoked by the content item - internal void ClearPublishedValue(string culture = null, string segment = null) - { - PropertyType.ValidateVariation(culture, segment, true); - var (pvalue, _) = GetPValue(culture, segment, false); - if (pvalue == null) return; - ClearPublishedPropertyValue(pvalue); - } - - // internal - must be invoked by the content item - internal void ClearPublishedCultureValues(string culture = null) - { - if (culture == null && PropertyType.ValidateVariation(null, null, false)) - ClearPublishedPropertyValue(_pvalue); - - if (_vvalues != null) - { - var pvalues = _vvalues - .Where(x => x.Value.Culture.InvariantEquals(culture)) - .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) - .Select(x => x.Value); - foreach (var pvalue in pvalues) - ClearPublishedPropertyValue(pvalue); - } - } - - private void PublishPropertyValue(PropertyValue pvalue) - { - if (pvalue == null) return; - - if (!PropertyType.IsPublishing) - throw new NotSupportedException("Property type does not support publishing."); - var origValue = pvalue.PublishedValue; - pvalue.PublishedValue = PropertyType.ConvertAssignedValue(pvalue.EditedValue); - DetectChanges(pvalue.EditedValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, false); - } - - private void ClearPublishedPropertyValue(PropertyValue pvalue) - { - if (pvalue == null) return; - - if (!PropertyType.IsPublishing) - throw new NotSupportedException("Property type does not support publishing."); - var origValue = pvalue.PublishedValue; - pvalue.PublishedValue = PropertyType.ConvertAssignedValue(null); - DetectChanges(pvalue.EditedValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, false); - } - - /// - /// Sets a value. - /// - public void SetValue(object value, string culture = null, string segment = null) - { - PropertyType.ValidateVariation(culture, segment, true); - var (pvalue, change) = GetPValue(culture, segment, true); - - var origValue = pvalue.EditedValue; - var setValue = PropertyType.ConvertAssignedValue(value); - - pvalue.EditedValue = setValue; - - DetectChanges(setValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, change); - } - - // bypasses all changes detection and is the *only* way to set the published value - internal void FactorySetValue(string culture, string segment, bool published, object value) - { - var (pvalue, _) = GetPValue(culture, segment, true); - - if (published && PropertyType.IsPublishing) - pvalue.PublishedValue = value; - else - pvalue.EditedValue = value; - } - - private (PropertyValue, bool) GetPValue(bool create) - { - var change = false; - if (_pvalue == null) - { - if (!create) return (null, false); - _pvalue = new PropertyValue(); - _values.Add(_pvalue); - change = true; - } - return (_pvalue, change); - } - - private (PropertyValue, bool) GetPValue(string culture, string segment, bool create) - { - if (culture == null && segment == null) - return GetPValue(create); - - var change = false; - if (_vvalues == null) - { - if (!create) return (null, false); - _vvalues = new Dictionary(); - change = true; - } - var k = new CompositeNStringNStringKey(culture, segment); - if (!_vvalues.TryGetValue(k, out var pvalue)) - { - if (!create) return (null, false); - pvalue = _vvalues[k] = new PropertyValue(); - pvalue.Culture = culture; - pvalue.Segment = segment; - _values.Add(pvalue); - change = true; - } - return (pvalue, change); - } - - /// - /// Gets a value indicating whether all properties are valid. - /// - /// - internal bool IsAllValid() +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Collections; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a property. + /// + [Serializable] + [DataContract(IsReference = true)] + public class Property : EntityBase + { + private List _values = new List(); + private PropertyValue _pvalue; + private Dictionary _vvalues; + + private static readonly Lazy Ps = new Lazy(); + + protected Property() + { } + + public Property(PropertyType propertyType) + { + PropertyType = propertyType; + } + + public Property(int id, PropertyType propertyType) + { + Id = id; + PropertyType = propertyType; + } + + public class PropertyValue + { + private string _culture; + private string _segment; + + public string Culture + { + get => _culture; + internal set => _culture = value?.ToLowerInvariant(); + } + public string Segment + { + get => _segment; + internal set => _segment = value?.ToLowerInvariant(); + } + public object EditedValue { get; internal set; } + public object PublishedValue { get; internal set; } + + public PropertyValue Clone() + => new PropertyValue { _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue }; + } + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo ValuesSelector = ExpressionHelper.GetPropertyInfo(x => x.Values); + + public readonly DelegateEqualityComparer PropertyValueComparer = new DelegateEqualityComparer( + (o, o1) => + { + if (o == null && o1 == null) return true; + + // custom comparer for strings. + // if one is null and another is empty then they are the same + if (o is string || o1 is string) + return ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) || (o != null && o1 != null && o.Equals(o1)); + + if (o == null || o1 == null) return false; + + // custom comparer for enumerable + // ReSharper disable once MergeCastWithTypeCheck + if (o is IEnumerable && o1 is IEnumerable enumerable) + return ((IEnumerable) o).Cast().UnsortedSequenceEqual(enumerable.Cast()); + + return o.Equals(o1); + }, o => o.GetHashCode()); + } + + /// + /// Returns the PropertyType, which this Property is based on + /// + [IgnoreDataMember] + public PropertyType PropertyType { get; private set; } + + /// + /// Gets the list of values. + /// + [DataMember] + public IReadOnlyCollection Values + { + get => _values; + set + { + // make sure we filter out invalid variations + // make sure we leave _vvalues null if possible + _values = value.Where(x => PropertyType.ValidateVariation(x.Culture, x.Segment, false)).ToList(); + _pvalue = _values.FirstOrDefault(x => x.Culture == null && x.Segment == null); + _vvalues = _values.Count > (_pvalue == null ? 0 : 1) + ? _values.Where(x => x != _pvalue).ToDictionary(x => new CompositeNStringNStringKey(x.Culture, x.Segment), x => x) + : null; + } + } + + /// + /// Returns the Alias of the PropertyType, which this Property is based on + /// + [DataMember] + public string Alias => PropertyType.Alias; + + /// + /// Returns the Id of the PropertyType, which this Property is based on + /// + [IgnoreDataMember] + internal int PropertyTypeId => PropertyType.Id; + + /// + /// Returns the DatabaseType that the underlaying DataType is using to store its values + /// + /// + /// Only used internally when saving the property value. + /// + [IgnoreDataMember] + internal ValueStorageType ValueStorageType => PropertyType.ValueStorageType; + + /// + /// Gets the value. + /// + public object GetValue(string culture = null, string segment = null, bool published = false) + { + if (!PropertyType.ValidateVariation(culture, segment, false)) return null; + if (culture == null && segment == null) return GetPropertyValue(_pvalue, published); + if (_vvalues == null) return null; + return _vvalues.TryGetValue(new CompositeNStringNStringKey(culture, segment), out var pvalue) + ? GetPropertyValue(pvalue, published) + : null; + } + + private object GetPropertyValue(PropertyValue pvalue, bool published) + { + if (pvalue == null) return null; + + return PropertyType.IsPublishing + ? (published ? pvalue.PublishedValue : pvalue.EditedValue) + : pvalue.EditedValue; + } + + // internal - must be invoked by the content item + // does *not* validate the value - content item must validate first + internal void PublishAllValues() + { + // if invariant-neutral is supported, publish invariant-neutral + if (PropertyType.ValidateVariation(null, null, false)) + PublishPropertyValue(_pvalue); + + // publish everything not invariant-neutral that is supported + if (_vvalues != null) + { + var pvalues = _vvalues + .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) + .Select(x => x.Value); + foreach (var pvalue in pvalues) + PublishPropertyValue(pvalue); + } + } + + // internal - must be invoked by the content item + // does *not* validate the value - content item must validate first + internal void PublishValue(string culture = null, string segment = null) + { + PropertyType.ValidateVariation(culture, segment, true); + + var (pvalue, _) = GetPValue(culture, segment, false); + if (pvalue == null) return; + PublishPropertyValue(pvalue); + } + + // internal - must be invoked by the content item + // does *not* validate the value - content item must validate first + internal void PublishCultureValues(string culture = null) + { + // if invariant and invariant-neutral is supported, publish invariant-neutral + if (culture == null && PropertyType.ValidateVariation(null, null, false)) + PublishPropertyValue(_pvalue); + + // publish everything not invariant-neutral that matches the culture and is supported + if (_vvalues != null) + { + var pvalues = _vvalues + .Where(x => x.Value.Culture.InvariantEquals(culture)) + .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) + .Select(x => x.Value); + foreach (var pvalue in pvalues) + PublishPropertyValue(pvalue); + } + } + + // internal - must be invoked by the content item + internal void ClearPublishedAllValues() + { + if (PropertyType.ValidateVariation(null, null, false)) + ClearPublishedPropertyValue(_pvalue); + + if (_vvalues != null) + { + var pvalues = _vvalues + .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) + .Select(x => x.Value); + foreach (var pvalue in pvalues) + ClearPublishedPropertyValue(pvalue); + } + } + + // internal - must be invoked by the content item + internal void ClearPublishedValue(string culture = null, string segment = null) + { + PropertyType.ValidateVariation(culture, segment, true); + var (pvalue, _) = GetPValue(culture, segment, false); + if (pvalue == null) return; + ClearPublishedPropertyValue(pvalue); + } + + // internal - must be invoked by the content item + internal void ClearPublishedCultureValues(string culture = null) + { + if (culture == null && PropertyType.ValidateVariation(null, null, false)) + ClearPublishedPropertyValue(_pvalue); + + if (_vvalues != null) + { + var pvalues = _vvalues + .Where(x => x.Value.Culture.InvariantEquals(culture)) + .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) + .Select(x => x.Value); + foreach (var pvalue in pvalues) + ClearPublishedPropertyValue(pvalue); + } + } + + private void PublishPropertyValue(PropertyValue pvalue) + { + if (pvalue == null) return; + + if (!PropertyType.IsPublishing) + throw new NotSupportedException("Property type does not support publishing."); + var origValue = pvalue.PublishedValue; + pvalue.PublishedValue = PropertyType.ConvertAssignedValue(pvalue.EditedValue); + DetectChanges(pvalue.EditedValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, false); + } + + private void ClearPublishedPropertyValue(PropertyValue pvalue) + { + if (pvalue == null) return; + + if (!PropertyType.IsPublishing) + throw new NotSupportedException("Property type does not support publishing."); + var origValue = pvalue.PublishedValue; + pvalue.PublishedValue = PropertyType.ConvertAssignedValue(null); + DetectChanges(pvalue.EditedValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, false); + } + + /// + /// Sets a value. + /// + public void SetValue(object value, string culture = null, string segment = null) + { + PropertyType.ValidateVariation(culture, segment, true); + var (pvalue, change) = GetPValue(culture, segment, true); + + var origValue = pvalue.EditedValue; + var setValue = PropertyType.ConvertAssignedValue(value); + + pvalue.EditedValue = setValue; + + DetectChanges(setValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, change); + } + + // bypasses all changes detection and is the *only* way to set the published value + internal void FactorySetValue(string culture, string segment, bool published, object value) + { + var (pvalue, _) = GetPValue(culture, segment, true); + + if (published && PropertyType.IsPublishing) + pvalue.PublishedValue = value; + else + pvalue.EditedValue = value; + } + + private (PropertyValue, bool) GetPValue(bool create) + { + var change = false; + if (_pvalue == null) + { + if (!create) return (null, false); + _pvalue = new PropertyValue(); + _values.Add(_pvalue); + change = true; + } + return (_pvalue, change); + } + + private (PropertyValue, bool) GetPValue(string culture, string segment, bool create) + { + if (culture == null && segment == null) + return GetPValue(create); + + var change = false; + if (_vvalues == null) + { + if (!create) return (null, false); + _vvalues = new Dictionary(); + change = true; + } + var k = new CompositeNStringNStringKey(culture, segment); + if (!_vvalues.TryGetValue(k, out var pvalue)) + { + if (!create) return (null, false); + pvalue = _vvalues[k] = new PropertyValue(); + pvalue.Culture = culture; + pvalue.Segment = segment; + _values.Add(pvalue); + change = true; + } + return (pvalue, change); + } + + /// + /// Gets a value indicating whether all properties are valid. + /// + /// + internal bool IsAllValid() { //fixme - needs API review as this is not used apart from in tests - - // invariant-neutral is supported, validate invariant-neutral - // includes mandatory validation - if (PropertyType.ValidateVariation(null, null, false) && !IsValidValue(_pvalue)) return false; - - // either invariant-neutral is not supported, or it is valid - // for anything else, validate the existing values (including mandatory), - // but we cannot validate mandatory globally (we don't know the possible cultures and segments) - - if (_vvalues == null) return true; - - var pvalues = _vvalues - .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) - .Select(x => x.Value) - .ToArray(); - - return pvalues.Length == 0 || pvalues.All(x => IsValidValue(x.EditedValue)); - } - - /// - /// Gets a value indicating whether the culture/any values are valid. - /// - /// An invalid value can be saved, but only valid values can be published. - internal bool IsCultureValid(string culture) + + // invariant-neutral is supported, validate invariant-neutral + // includes mandatory validation + if (PropertyType.ValidateVariation(null, null, false) && !IsValidValue(_pvalue)) return false; + + // either invariant-neutral is not supported, or it is valid + // for anything else, validate the existing values (including mandatory), + // but we cannot validate mandatory globally (we don't know the possible cultures and segments) + + if (_vvalues == null) return true; + + var pvalues = _vvalues + .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) + .Select(x => x.Value) + .ToArray(); + + return pvalues.Length == 0 || pvalues.All(x => IsValidValue(x.EditedValue)); + } + + /// + /// Gets a value indicating whether the culture/any values are valid. + /// + /// An invalid value can be saved, but only valid values can be published. + internal bool IsCultureValid(string culture) { //fixme - needs API review as this is not used apart from in tests - - // culture-neutral is supported, validate culture-neutral - // includes mandatory validation - if (PropertyType.ValidateVariation(culture, null, false) && !IsValidValue(GetValue(culture))) - return false; - - // either culture-neutral is not supported, or it is valid - // for anything non-neutral, validate the existing values (including mandatory), - // but we cannot validate mandatory globally (we don't know the possible segments) - - if (_vvalues == null) return true; - - var pvalues = _vvalues - .Where(x => x.Value.Culture.InvariantEquals(culture)) - .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) - .Select(x => x.Value) - .ToArray(); - - return pvalues.Length == 0 || pvalues.All(x => IsValidValue(x.EditedValue)); - } - - /// - /// Gets a value indicating whether the value is valid. - /// - /// An invalid value can be saved, but only valid values can be published. - public bool IsValid(string culture = null, string segment = null) - { - // single value -> validates mandatory - return IsValidValue(GetValue(culture, segment)); - } - - /// - /// Boolean indicating whether the passed in value is valid - /// - /// - /// True is property value is valid, otherwise false - private bool IsValidValue(object value) - { - return PropertyType.IsPropertyValueValid(value); - } - - public override object DeepClone() - { - var clone = (Property) base.DeepClone(); - - //turn off change tracking - clone.DisableChangeTracking(); - - //need to manually assign since this is a readonly property - clone.PropertyType = (PropertyType) PropertyType.DeepClone(); - - //re-enable tracking - clone.ResetDirtyProperties(false); // not needed really, since we're not tracking - clone.EnableChangeTracking(); - - return clone; - } - } -} + + // culture-neutral is supported, validate culture-neutral + // includes mandatory validation + if (PropertyType.ValidateVariation(culture, null, false) && !IsValidValue(GetValue(culture))) + return false; + + // either culture-neutral is not supported, or it is valid + // for anything non-neutral, validate the existing values (including mandatory), + // but we cannot validate mandatory globally (we don't know the possible segments) + + if (_vvalues == null) return true; + + var pvalues = _vvalues + .Where(x => x.Value.Culture.InvariantEquals(culture)) + .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) + .Select(x => x.Value) + .ToArray(); + + return pvalues.Length == 0 || pvalues.All(x => IsValidValue(x.EditedValue)); + } + + /// + /// Gets a value indicating whether the value is valid. + /// + /// An invalid value can be saved, but only valid values can be published. + public bool IsValid(string culture = null, string segment = null) + { + // single value -> validates mandatory + return IsValidValue(GetValue(culture, segment)); + } + + /// + /// Boolean indicating whether the passed in value is valid + /// + /// + /// True is property value is valid, otherwise false + private bool IsValidValue(object value) + { + return PropertyType.IsPropertyValueValid(value); + } + + public override object DeepClone() + { + var clone = (Property) base.DeepClone(); + + //turn off change tracking + clone.DisableChangeTracking(); + + //need to manually assign since this is a readonly property + clone.PropertyType = (PropertyType) PropertyType.DeepClone(); + + //re-enable tracking + clone.ResetDirtyProperties(false); // not needed really, since we're not tracking + clone.EnableChangeTracking(); + + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index 3d02c13e6c..fe2c34f042 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -1,218 +1,218 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a collection of property values. - /// - [Serializable] - [DataContract(IsReference = true)] - public class PropertyCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable - { - private readonly object _addLocker = new object(); - internal Action OnAdd; - internal Func AdditionValidator { get; set; } - - /// - /// Initializes a new instance of the class. - /// - internal PropertyCollection() - : base(StringComparer.InvariantCultureIgnoreCase) - { } - - /// - /// Initializes a new instance of the class. - /// - /// A function validating added properties. - internal PropertyCollection(Func additionValidator) - : this() - { - AdditionValidator = additionValidator; - } - - /// - /// Initializes a new instance of the class. - /// - public PropertyCollection(IEnumerable properties) - : this() - { - Reset(properties); - } - - /// - /// Replaces all properties, whilst maintaining validation delegates. - /// - internal void Reset(IEnumerable properties) - { - Clear(); - foreach (var property in properties) - Add(property); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - /// - /// Replaces the property at the specified index with the specified property. - /// - protected override void SetItem(int index, Property property) - { - base.SetItem(index, property); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property, index)); - } - - /// - /// Removes the property at the specified index. - /// - protected override void RemoveItem(int index) - { - var removed = this[index]; - base.RemoveItem(index); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); - } - - /// - /// Inserts the specified property at the specified index. - /// - protected override void InsertItem(int index, Property property) - { - base.InsertItem(index, property); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); - } - - /// - /// Removes all properties. - /// - protected override void ClearItems() - { - base.ClearItems(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - /// - /// Adds a property. - /// - internal new void Add(Property property) - { - lock (_addLocker) // fixme - why are we locking here and not everywhere else?! - { - var key = GetKeyForItem(property); - if (key != null) - { - if (Contains(key)) - { - // transfer id and values if ... - var existing = this[key]; - - if (property.Id == 0 && existing.Id != 0) - property.Id = existing.Id; - - if (property.Values.Count == 0 && existing.Values.Count > 0) - property.Values = existing.Values.Select(x => x.Clone()).ToList(); - - // replace existing with property and return, - // SetItem invokes OnCollectionChanged (but not OnAdd) - SetItem(IndexOfKey(key), property); - return; - } - } - - base.Add(property); - - OnAdd?.Invoke(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); - } - } - - /// - /// Gets the index for a specified property alias. - /// - public int IndexOfKey(string key) - { - for (var i = 0; i < Count; i++) - { - if (this[i].Alias.InvariantEquals(key)) - return i; - } - return -1; - } - - protected override string GetKeyForItem(Property item) - { - return item.Alias; - } - - /// - /// Gets the property with the specified PropertyType. - /// - internal Property this[PropertyType propertyType] - { - get - { - return this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyType.Alias)); - } - } - - public bool TryGetValue(string propertyTypeAlias, out Property property) - { - property = this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); - return property != null; - } - - /// - /// Occurs when the collection changes. - /// - public event NotifyCollectionChangedEventHandler CollectionChanged; - - protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - { - CollectionChanged?.Invoke(this, args); - } - - /// - /// Ensures that the collection contains properties for the specified property types. - /// - protected internal void EnsurePropertyTypes(IEnumerable propertyTypes) - { - if (propertyTypes == null) - return; - - foreach (var propertyType in propertyTypes) - Add(new Property(propertyType)); - } - - /// - /// Ensures that the collection does not contain properties not in the specified property types. - /// - protected internal void EnsureCleanPropertyTypes(IEnumerable propertyTypes) - { - if (propertyTypes == null) - return; - - var propertyTypesA = propertyTypes.ToArray(); - - var thisAliases = this.Select(x => x.Alias); - var typeAliases = propertyTypesA.Select(x => x.Alias); - var remove = thisAliases.Except(typeAliases).ToArray(); - foreach (var alias in remove) - Remove(alias); - - foreach (var propertyType in propertyTypesA) - Add(new Property(propertyType)); - } - - /// - /// Deep clones. - /// - public object DeepClone() - { - var clone = new PropertyCollection(); - foreach (var property in this) - clone.Add((Property) property.DeepClone()); - return clone; - } - } -} +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a collection of property values. + /// + [Serializable] + [DataContract(IsReference = true)] + public class PropertyCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable + { + private readonly object _addLocker = new object(); + internal Action OnAdd; + internal Func AdditionValidator { get; set; } + + /// + /// Initializes a new instance of the class. + /// + internal PropertyCollection() + : base(StringComparer.InvariantCultureIgnoreCase) + { } + + /// + /// Initializes a new instance of the class. + /// + /// A function validating added properties. + internal PropertyCollection(Func additionValidator) + : this() + { + AdditionValidator = additionValidator; + } + + /// + /// Initializes a new instance of the class. + /// + public PropertyCollection(IEnumerable properties) + : this() + { + Reset(properties); + } + + /// + /// Replaces all properties, whilst maintaining validation delegates. + /// + internal void Reset(IEnumerable properties) + { + Clear(); + foreach (var property in properties) + Add(property); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + /// + /// Replaces the property at the specified index with the specified property. + /// + protected override void SetItem(int index, Property property) + { + base.SetItem(index, property); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property, index)); + } + + /// + /// Removes the property at the specified index. + /// + protected override void RemoveItem(int index) + { + var removed = this[index]; + base.RemoveItem(index); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + } + + /// + /// Inserts the specified property at the specified index. + /// + protected override void InsertItem(int index, Property property) + { + base.InsertItem(index, property); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); + } + + /// + /// Removes all properties. + /// + protected override void ClearItems() + { + base.ClearItems(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + /// + /// Adds a property. + /// + internal new void Add(Property property) + { + lock (_addLocker) // fixme - why are we locking here and not everywhere else?! + { + var key = GetKeyForItem(property); + if (key != null) + { + if (Contains(key)) + { + // transfer id and values if ... + var existing = this[key]; + + if (property.Id == 0 && existing.Id != 0) + property.Id = existing.Id; + + if (property.Values.Count == 0 && existing.Values.Count > 0) + property.Values = existing.Values.Select(x => x.Clone()).ToList(); + + // replace existing with property and return, + // SetItem invokes OnCollectionChanged (but not OnAdd) + SetItem(IndexOfKey(key), property); + return; + } + } + + base.Add(property); + + OnAdd?.Invoke(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); + } + } + + /// + /// Gets the index for a specified property alias. + /// + public int IndexOfKey(string key) + { + for (var i = 0; i < Count; i++) + { + if (this[i].Alias.InvariantEquals(key)) + return i; + } + return -1; + } + + protected override string GetKeyForItem(Property item) + { + return item.Alias; + } + + /// + /// Gets the property with the specified PropertyType. + /// + internal Property this[PropertyType propertyType] + { + get + { + return this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyType.Alias)); + } + } + + public bool TryGetValue(string propertyTypeAlias, out Property property) + { + property = this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + return property != null; + } + + /// + /// Occurs when the collection changes. + /// + public event NotifyCollectionChangedEventHandler CollectionChanged; + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + CollectionChanged?.Invoke(this, args); + } + + /// + /// Ensures that the collection contains properties for the specified property types. + /// + protected internal void EnsurePropertyTypes(IEnumerable propertyTypes) + { + if (propertyTypes == null) + return; + + foreach (var propertyType in propertyTypes) + Add(new Property(propertyType)); + } + + /// + /// Ensures that the collection does not contain properties not in the specified property types. + /// + protected internal void EnsureCleanPropertyTypes(IEnumerable propertyTypes) + { + if (propertyTypes == null) + return; + + var propertyTypesA = propertyTypes.ToArray(); + + var thisAliases = this.Select(x => x.Alias); + var typeAliases = propertyTypesA.Select(x => x.Alias); + var remove = thisAliases.Except(typeAliases).ToArray(); + foreach (var alias in remove) + Remove(alias); + + foreach (var propertyType in propertyTypesA) + Add(new Property(propertyType)); + } + + /// + /// Deep clones. + /// + public object DeepClone() + { + var clone = new PropertyCollection(); + foreach (var property in this) + clone.Add((Property) property.DeepClone()); + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 02007c34fb..286e165764 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -1,99 +1,99 @@ -using System; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// A group of property types, which corresponds to the properties grouped under a Tab. - /// - [Serializable] - [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}, Name: {Name}")] - public class PropertyGroup : EntityBase, IEquatable - { - private static readonly Lazy Ps = new Lazy(); - - private string _name; - private int _sortOrder; - private PropertyTypeCollection _propertyTypes; - - public PropertyGroup(bool isPublishing) - : this(new PropertyTypeCollection(isPublishing)) - { } - - public PropertyGroup(PropertyTypeCollection propertyTypeCollection) - { - PropertyTypes = propertyTypeCollection; - } - - // ReSharper disable once ClassNeverInstantiated.Local - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyTypes); - } - - private void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); - } - - /// - /// Gets or sets the Name of the Group, which corresponds to the Tab-name in the UI - /// - [DataMember] - public string Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); - } - - /// - /// Gets or sets the Sort Order of the Group - /// - [DataMember] - public int SortOrder - { - get => _sortOrder; - set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); - } - - /// - /// Gets or sets a collection of PropertyTypes for this PropertyGroup - /// - [DataMember] - public PropertyTypeCollection PropertyTypes - { - get => _propertyTypes; - set - { - _propertyTypes = value; - - // since we're adding this collection to this group, - // we need to ensure that all the lazy values are set. - foreach (var propertyType in _propertyTypes) - propertyType.PropertyGroupId = new Lazy(() => Id); - - _propertyTypes.CollectionChanged += PropertyTypesChanged; - } - } - - public bool Equals(PropertyGroup other) - { - if (base.Equals(other)) return true; - return other != null && Name.InvariantEquals(other.Name); - } - - public override int GetHashCode() - { - var baseHash = base.GetHashCode(); - var nameHash = Name.ToLowerInvariant().GetHashCode(); - return baseHash ^ nameHash; - } - } -} +using System; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// A group of property types, which corresponds to the properties grouped under a Tab. + /// + [Serializable] + [DataContract(IsReference = true)] + [DebuggerDisplay("Id: {Id}, Name: {Name}")] + public class PropertyGroup : EntityBase, IEquatable + { + private static readonly Lazy Ps = new Lazy(); + + private string _name; + private int _sortOrder; + private PropertyTypeCollection _propertyTypes; + + public PropertyGroup(bool isPublishing) + : this(new PropertyTypeCollection(isPublishing)) + { } + + public PropertyGroup(PropertyTypeCollection propertyTypeCollection) + { + PropertyTypes = propertyTypeCollection; + } + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyTypes); + } + + private void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); + } + + /// + /// Gets or sets the Name of the Group, which corresponds to the Tab-name in the UI + /// + [DataMember] + public string Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); + } + + /// + /// Gets or sets the Sort Order of the Group + /// + [DataMember] + public int SortOrder + { + get => _sortOrder; + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); + } + + /// + /// Gets or sets a collection of PropertyTypes for this PropertyGroup + /// + [DataMember] + public PropertyTypeCollection PropertyTypes + { + get => _propertyTypes; + set + { + _propertyTypes = value; + + // since we're adding this collection to this group, + // we need to ensure that all the lazy values are set. + foreach (var propertyType in _propertyTypes) + propertyType.PropertyGroupId = new Lazy(() => Id); + + _propertyTypes.CollectionChanged += PropertyTypesChanged; + } + } + + public bool Equals(PropertyGroup other) + { + if (base.Equals(other)) return true; + return other != null && Name.InvariantEquals(other.Name); + } + + public override int GetHashCode() + { + var baseHash = base.GetHashCode(); + var nameHash = Name.ToLowerInvariant().GetHashCode(); + return baseHash ^ nameHash; + } + } +} diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index 0083bdb502..d10b375285 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -1,176 +1,176 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using System.Runtime.Serialization; -using System.Threading; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a collection of objects - /// - [Serializable] - [DataContract] - public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable - { - private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); - - internal Action OnAdd; - - internal PropertyGroupCollection() - { } - - public PropertyGroupCollection(IEnumerable groups) - { - Reset(groups); - } - - /// - /// Resets the collection to only contain the instances referenced in the parameter. - /// - /// The property groups. - /// - internal void Reset(IEnumerable groups) - { - Clear(); - foreach (var group in groups) - Add(group); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - protected override void SetItem(int index, PropertyGroup item) - { - base.SetItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); - } - - protected override void RemoveItem(int index) - { - var removed = this[index]; - base.RemoveItem(index); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); - } - - protected override void InsertItem(int index, PropertyGroup item) - { - base.InsertItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); - } - - protected override void ClearItems() - { - base.ClearItems(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - internal new void Add(PropertyGroup item) - { - try - { - _addLocker.EnterWriteLock(); - - //Note this is done to ensure existig groups can be renamed - if (item.HasIdentity && item.Id > 0) - { - var exists = Contains(item.Id); - if (exists) - { - var keyExists = Contains(item.Name); - if (keyExists) - throw new Exception($"Naming conflict: Changing the name of PropertyGroup '{item.Name}' would result in duplicates"); - - SetItem(IndexOfKey(item.Id), item); - return; - } - } - else - { - var key = GetKeyForItem(item); - if (key != null) - { - var exists = Contains(key); - if (exists) - { - SetItem(IndexOfKey(key), item); - return; - } - } - } - - base.Add(item); - OnAdd?.Invoke(); - - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); - } - finally - { +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a collection of objects + /// + [Serializable] + [DataContract] + public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable + { + private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); + + internal Action OnAdd; + + internal PropertyGroupCollection() + { } + + public PropertyGroupCollection(IEnumerable groups) + { + Reset(groups); + } + + /// + /// Resets the collection to only contain the instances referenced in the parameter. + /// + /// The property groups. + /// + internal void Reset(IEnumerable groups) + { + Clear(); + foreach (var group in groups) + Add(group); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + protected override void SetItem(int index, PropertyGroup item) + { + base.SetItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + } + + protected override void RemoveItem(int index) + { + var removed = this[index]; + base.RemoveItem(index); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + } + + protected override void InsertItem(int index, PropertyGroup item) + { + base.InsertItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + } + + protected override void ClearItems() + { + base.ClearItems(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + internal new void Add(PropertyGroup item) + { + try + { + _addLocker.EnterWriteLock(); + + //Note this is done to ensure existig groups can be renamed + if (item.HasIdentity && item.Id > 0) + { + var exists = Contains(item.Id); + if (exists) + { + var keyExists = Contains(item.Name); + if (keyExists) + throw new Exception($"Naming conflict: Changing the name of PropertyGroup '{item.Name}' would result in duplicates"); + + SetItem(IndexOfKey(item.Id), item); + return; + } + } + else + { + var key = GetKeyForItem(item); + if (key != null) + { + var exists = Contains(key); + if (exists) + { + SetItem(IndexOfKey(key), item); + return; + } + } + } + + base.Add(item); + OnAdd?.Invoke(); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + } + finally + { if (_addLocker.IsWriteLockHeld) - _addLocker.ExitWriteLock(); - } - } - - /// - /// Determines whether this collection contains a whose name matches the specified parameter. - /// - /// Name of the PropertyGroup. - /// true if the collection contains the specified name; otherwise, false. - /// - public new bool Contains(string groupName) - { - return this.Any(x => x.Name == groupName); - } - - public bool Contains(int id) - { - return this.Any(x => x.Id == id); - } - - public void RemoveItem(string propertyGroupName) - { - var key = IndexOfKey(propertyGroupName); - //Only removes an item if the key was found - if (key != -1) - RemoveItem(key); - } - - public int IndexOfKey(string key) - { - for (var i = 0; i < Count; i++) - if (this[i].Name == key) - return i; - return -1; - } - - public int IndexOfKey(int id) - { - for (var i = 0; i < Count; i++) - if (this[i].Id == id) - return i; - return -1; - } - - protected override string GetKeyForItem(PropertyGroup item) - { - return item.Name; - } - - public event NotifyCollectionChangedEventHandler CollectionChanged; - - protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - { - CollectionChanged?.Invoke(this, args); - } - - public object DeepClone() - { - var clone = new PropertyGroupCollection(); - foreach (var group in this) - { - clone.Add((PropertyGroup) group.DeepClone()); - } - return clone; - } - } -} + _addLocker.ExitWriteLock(); + } + } + + /// + /// Determines whether this collection contains a whose name matches the specified parameter. + /// + /// Name of the PropertyGroup. + /// true if the collection contains the specified name; otherwise, false. + /// + public new bool Contains(string groupName) + { + return this.Any(x => x.Name == groupName); + } + + public bool Contains(int id) + { + return this.Any(x => x.Id == id); + } + + public void RemoveItem(string propertyGroupName) + { + var key = IndexOfKey(propertyGroupName); + //Only removes an item if the key was found + if (key != -1) + RemoveItem(key); + } + + public int IndexOfKey(string key) + { + for (var i = 0; i < Count; i++) + if (this[i].Name == key) + return i; + return -1; + } + + public int IndexOfKey(int id) + { + for (var i = 0; i < Count; i++) + if (this[i].Id == id) + return i; + return -1; + } + + protected override string GetKeyForItem(PropertyGroup item) + { + return item.Name; + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + CollectionChanged?.Invoke(this, args); + } + + public object DeepClone() + { + var clone = new PropertyGroupCollection(); + foreach (var group in this) + { + clone.Add((PropertyGroup) group.DeepClone()); + } + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 66299e7749..587af74aa2 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -1,460 +1,460 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using System.Text.RegularExpressions; -using Umbraco.Core.Composing; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Strings; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a property type. - /// - [Serializable] - [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] - public class PropertyType : EntityBase, IEquatable +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text.RegularExpressions; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Strings; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a property type. + /// + [Serializable] + [DataContract(IsReference = true)] + [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] + public class PropertyType : EntityBase, IEquatable { - private static PropertySelectors _selectors; - - private readonly bool _forceValueStorageType; - private string _name; - private string _alias; - private string _description; - private int _dataTypeId; - private Lazy _propertyGroupId; - private string _propertyEditorAlias; - private ValueStorageType _valueStorageType; - private bool _mandatory; - private int _sortOrder; - private string _validationRegExp; - private ContentVariation _variations; - - /// - /// Initializes a new instance of the class. - /// - public PropertyType(IDataType dataType) - { - if (dataType == null) throw new ArgumentNullException(nameof(dataType)); - - if(dataType.HasIdentity) - _dataTypeId = dataType.Id; - - _propertyEditorAlias = dataType.EditorAlias; - _valueStorageType = dataType.DatabaseType; - _variations = ContentVariation.InvariantNeutral; - } - - /// - /// Initializes a new instance of the class. - /// - public PropertyType(IDataType dataType, string propertyTypeAlias) - : this(dataType) - { - _alias = SanitizeAlias(propertyTypeAlias); - } - - /// - /// Initializes a new instance of the class. - /// - public PropertyType(string propertyEditorAlias, ValueStorageType valueStorageType) - : this(propertyEditorAlias, valueStorageType, false) - { } - - /// - /// Initializes a new instance of the class. - /// - public PropertyType(string propertyEditorAlias, ValueStorageType valueStorageType, string propertyTypeAlias) - : this(propertyEditorAlias, valueStorageType, false, propertyTypeAlias) - { } - - /// - /// Initializes a new instance of the class. - /// - /// Set to true to force the value storage type. Values assigned to - /// the property, eg from the underlying datatype, will be ignored. - internal PropertyType(string propertyEditorAlias, ValueStorageType valueStorageType, bool forceValueStorageType, string propertyTypeAlias = null) - { - _propertyEditorAlias = propertyEditorAlias; - _valueStorageType = valueStorageType; - _forceValueStorageType = forceValueStorageType; - _alias = propertyTypeAlias == null ? null : SanitizeAlias(propertyTypeAlias); - _variations = ContentVariation.InvariantNeutral; - } - - private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); - - private class PropertySelectors - { - public readonly PropertyInfo Name = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo Alias = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo Description = ExpressionHelper.GetPropertyInfo(x => x.Description); - public readonly PropertyInfo DataTypeId = ExpressionHelper.GetPropertyInfo(x => x.DataTypeId); - public readonly PropertyInfo PropertyEditorAlias = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); - public readonly PropertyInfo ValueStorageType = ExpressionHelper.GetPropertyInfo(x => x.ValueStorageType); - public readonly PropertyInfo Mandatory = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); - public readonly PropertyInfo SortOrder = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo ValidationRegExp = ExpressionHelper.GetPropertyInfo(x => x.ValidationRegExp); - public readonly PropertyInfo PropertyGroupId = ExpressionHelper.GetPropertyInfo>(x => x.PropertyGroupId); - public readonly PropertyInfo VaryBy = ExpressionHelper.GetPropertyInfo(x => x.Variations); - } - - /// - /// Gets a value indicating whether the content type, owning this property type, is publishing. - /// - public bool IsPublishing { get; internal set; } - - /// - /// Gets of sets the name of the property type. - /// - [DataMember] - public string Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, Selectors.Name); - } - - /// - /// Gets of sets the alias of the property type. - /// - [DataMember] - public string Alias - { - get => _alias; - set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias, Selectors.Alias); - } - - /// - /// Gets of sets the description of the property type. - /// - [DataMember] - public string Description - { - get => _description; - set => SetPropertyValueAndDetectChanges(value, ref _description, Selectors.Description); - } - - /// - /// Gets or sets the identifier of the datatype for this property type. - /// - [DataMember] - public int DataTypeId - { - get => _dataTypeId; - set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, Selectors.DataTypeId); - } - - /// - /// Gets or sets the alias of the property editor for this property type. - /// - [DataMember] - public string PropertyEditorAlias - { - get => _propertyEditorAlias; - set => SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Selectors.PropertyEditorAlias); - } - - /// - /// Gets or sets the database type for storing value for this property type. - /// - [DataMember] - internal ValueStorageType ValueStorageType - { - get => _valueStorageType; - set - { - if (_forceValueStorageType) return; // ignore changes - SetPropertyValueAndDetectChanges(value, ref _valueStorageType, Selectors.ValueStorageType); - } - } - - /// - /// Gets or sets the identifier of the property group this property type belongs to. - /// - /// For generic properties, the value is null. - [DataMember] - internal Lazy PropertyGroupId - { - get => _propertyGroupId; - set => SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, Selectors.PropertyGroupId); - } - - /// - /// Gets of sets a value indicating whether a value for this property type is required. - /// - [DataMember] - public bool Mandatory - { - get => _mandatory; - set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Selectors.Mandatory); - } - - /// - /// Gets of sets the sort order of the property type. - /// - [DataMember] - public int SortOrder - { - get => _sortOrder; - set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Selectors.SortOrder); - } - - /// - /// Gets or sets the regular expression validating the property values. - /// - [DataMember] - public string ValidationRegExp - { - get => _validationRegExp; - set => SetPropertyValueAndDetectChanges(value, ref _validationRegExp, Selectors.ValidationRegExp); - } - - /// - /// Gets or sets the content variation of the property type. - /// - public ContentVariation Variations - { - get => _variations; - set => SetPropertyValueAndDetectChanges(value, ref _variations, Selectors.VaryBy); - } - - /// - /// Validates that a variation is valid for the property type. - /// - public bool ValidateVariation(string culture, string segment, bool throwIfInvalid) - { - ContentVariation variation; - if (culture != null) - { - variation = segment != null - ? ContentVariation.CultureSegment - : ContentVariation.CultureNeutral; - } - else if (segment != null) - { - variation = ContentVariation.InvariantSegment; - } - else - { - variation = ContentVariation.InvariantNeutral; - } - if (!Variations.Has(variation)) - { - if (throwIfInvalid) - throw new NotSupportedException($"Variation {variation} is invalid for property type \"{Alias}\"."); - return false; - } - return true; - } - - /// - /// Creates a new property of this property type. - /// - public Property CreateProperty() - { - return new Property(this); - } - - /// - /// Determines whether a value is of the expected type for this property type. - /// - /// - /// If the value is of the expected type, it can be directly assigned to the property. - /// Otherwise, some conversion is required. - /// - public bool IsOfExpectedPropertyType(object value) - { - // null values are assumed to be ok - if (value == null) - return true; - - // check if the type of the value matches the type from the DataType/PropertyEditor - // then it can be directly assigned, anything else requires conversion - var valueType = value.GetType(); - switch (ValueStorageType) - { - case ValueStorageType.Integer: - return valueType == typeof(int); - case ValueStorageType.Decimal: - return valueType == typeof(decimal); - case ValueStorageType.Date: - return valueType == typeof(DateTime); - case ValueStorageType.Nvarchar: - return valueType == typeof(string); - case ValueStorageType.Ntext: - return valueType == typeof(string); - default: - throw new NotSupportedException($"Not supported storage type \"{ValueStorageType}\"."); - } - } - - /// - /// Determines whether a value can be assigned to a property. - /// - public bool IsValueAssignable(object value) => TryConvertAssignedValue(value, false, out _); - - /// - /// Converts a value assigned to a property. - /// - /// - /// The input value can be pretty much anything, and is converted to the actual Clr type - /// expected by the property (eg an integer if the property values are integers). - /// Throws if the value cannot be converted. - /// - public object ConvertAssignedValue(object value) => TryConvertAssignedValue(value, true, out var converted) ? converted : null; - - /// - /// Tries to convert a value assigned to a property. - /// - /// - /// - /// - public bool TryConvertAssignedValue(object value, out object converted) => TryConvertAssignedValue(value, false, out converted); - - private bool TryConvertAssignedValue(object value, bool throwOnError, out object converted) - { - var isOfExpectedType = IsOfExpectedPropertyType(value); - if (isOfExpectedType) - { - converted = value; - return true; - } - - // isOfExpectedType is true if value is null - so if false, value is *not* null - // "garbage-in", accept what we can & convert - // throw only if conversion is not possible - - var s = value.ToString(); - converted = null; - - switch (ValueStorageType) - { - case ValueStorageType.Nvarchar: - case ValueStorageType.Ntext: - { - converted = s; - return true; - } - - case ValueStorageType.Integer: - if (s.IsNullOrWhiteSpace()) - return true; // assume empty means null - var convInt = value.TryConvertTo(); - if (convInt) - { - converted = convInt.Result; - return true; - } - if (throwOnError) - ThrowTypeException(value, typeof(int), Alias); - return false; - - case ValueStorageType.Decimal: - if (s.IsNullOrWhiteSpace()) - return true; // assume empty means null - var convDecimal = value.TryConvertTo(); - if (convDecimal) - { - // need to normalize the value (change the scaling factor and remove trailing zeroes) - // because the underlying database is going to mess with the scaling factor anyways. - converted = convDecimal.Result.Normalize(); - return true; - } - if (throwOnError) - ThrowTypeException(value, typeof(decimal), Alias); - return false; - - case ValueStorageType.Date: - if (s.IsNullOrWhiteSpace()) - return true; // assume empty means null - var convDateTime = value.TryConvertTo(); - if (convDateTime) - { - converted = convDateTime.Result; - return true; - } - if (throwOnError) - ThrowTypeException(value, typeof(DateTime), Alias); - return false; - - default: - throw new NotSupportedException($"Not supported storage type \"{ValueStorageType}\"."); - } - } - - private static void ThrowTypeException(object value, Type expected, string alias) - { - throw new InvalidOperationException($"Cannot assign value \"{value}\" of type \"{value.GetType()}\" to property \"{alias}\" expecting type \"{expected}\"."); - } + private static PropertySelectors _selectors; + + private readonly bool _forceValueStorageType; + private string _name; + private string _alias; + private string _description; + private int _dataTypeId; + private Lazy _propertyGroupId; + private string _propertyEditorAlias; + private ValueStorageType _valueStorageType; + private bool _mandatory; + private int _sortOrder; + private string _validationRegExp; + private ContentVariation _variations; + + /// + /// Initializes a new instance of the class. + /// + public PropertyType(IDataType dataType) + { + if (dataType == null) throw new ArgumentNullException(nameof(dataType)); + + if(dataType.HasIdentity) + _dataTypeId = dataType.Id; + + _propertyEditorAlias = dataType.EditorAlias; + _valueStorageType = dataType.DatabaseType; + _variations = ContentVariation.InvariantNeutral; + } + + /// + /// Initializes a new instance of the class. + /// + public PropertyType(IDataType dataType, string propertyTypeAlias) + : this(dataType) + { + _alias = SanitizeAlias(propertyTypeAlias); + } + + /// + /// Initializes a new instance of the class. + /// + public PropertyType(string propertyEditorAlias, ValueStorageType valueStorageType) + : this(propertyEditorAlias, valueStorageType, false) + { } + + /// + /// Initializes a new instance of the class. + /// + public PropertyType(string propertyEditorAlias, ValueStorageType valueStorageType, string propertyTypeAlias) + : this(propertyEditorAlias, valueStorageType, false, propertyTypeAlias) + { } + + /// + /// Initializes a new instance of the class. + /// + /// Set to true to force the value storage type. Values assigned to + /// the property, eg from the underlying datatype, will be ignored. + internal PropertyType(string propertyEditorAlias, ValueStorageType valueStorageType, bool forceValueStorageType, string propertyTypeAlias = null) + { + _propertyEditorAlias = propertyEditorAlias; + _valueStorageType = valueStorageType; + _forceValueStorageType = forceValueStorageType; + _alias = propertyTypeAlias == null ? null : SanitizeAlias(propertyTypeAlias); + _variations = ContentVariation.InvariantNeutral; + } + + private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); + + private class PropertySelectors + { + public readonly PropertyInfo Name = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo Alias = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo Description = ExpressionHelper.GetPropertyInfo(x => x.Description); + public readonly PropertyInfo DataTypeId = ExpressionHelper.GetPropertyInfo(x => x.DataTypeId); + public readonly PropertyInfo PropertyEditorAlias = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); + public readonly PropertyInfo ValueStorageType = ExpressionHelper.GetPropertyInfo(x => x.ValueStorageType); + public readonly PropertyInfo Mandatory = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); + public readonly PropertyInfo SortOrder = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo ValidationRegExp = ExpressionHelper.GetPropertyInfo(x => x.ValidationRegExp); + public readonly PropertyInfo PropertyGroupId = ExpressionHelper.GetPropertyInfo>(x => x.PropertyGroupId); + public readonly PropertyInfo VaryBy = ExpressionHelper.GetPropertyInfo(x => x.Variations); + } + + /// + /// Gets a value indicating whether the content type, owning this property type, is publishing. + /// + public bool IsPublishing { get; internal set; } + + /// + /// Gets of sets the name of the property type. + /// + [DataMember] + public string Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, Selectors.Name); + } + + /// + /// Gets of sets the alias of the property type. + /// + [DataMember] + public string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias, Selectors.Alias); + } + + /// + /// Gets of sets the description of the property type. + /// + [DataMember] + public string Description + { + get => _description; + set => SetPropertyValueAndDetectChanges(value, ref _description, Selectors.Description); + } + + /// + /// Gets or sets the identifier of the datatype for this property type. + /// + [DataMember] + public int DataTypeId + { + get => _dataTypeId; + set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, Selectors.DataTypeId); + } + + /// + /// Gets or sets the alias of the property editor for this property type. + /// + [DataMember] + public string PropertyEditorAlias + { + get => _propertyEditorAlias; + set => SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Selectors.PropertyEditorAlias); + } + + /// + /// Gets or sets the database type for storing value for this property type. + /// + [DataMember] + internal ValueStorageType ValueStorageType + { + get => _valueStorageType; + set + { + if (_forceValueStorageType) return; // ignore changes + SetPropertyValueAndDetectChanges(value, ref _valueStorageType, Selectors.ValueStorageType); + } + } + + /// + /// Gets or sets the identifier of the property group this property type belongs to. + /// + /// For generic properties, the value is null. + [DataMember] + internal Lazy PropertyGroupId + { + get => _propertyGroupId; + set => SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, Selectors.PropertyGroupId); + } + + /// + /// Gets of sets a value indicating whether a value for this property type is required. + /// + [DataMember] + public bool Mandatory + { + get => _mandatory; + set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Selectors.Mandatory); + } + + /// + /// Gets of sets the sort order of the property type. + /// + [DataMember] + public int SortOrder + { + get => _sortOrder; + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Selectors.SortOrder); + } + + /// + /// Gets or sets the regular expression validating the property values. + /// + [DataMember] + public string ValidationRegExp + { + get => _validationRegExp; + set => SetPropertyValueAndDetectChanges(value, ref _validationRegExp, Selectors.ValidationRegExp); + } + + /// + /// Gets or sets the content variation of the property type. + /// + public ContentVariation Variations + { + get => _variations; + set => SetPropertyValueAndDetectChanges(value, ref _variations, Selectors.VaryBy); + } + + /// + /// Validates that a variation is valid for the property type. + /// + public bool ValidateVariation(string culture, string segment, bool throwIfInvalid) + { + ContentVariation variation; + if (culture != null) + { + variation = segment != null + ? ContentVariation.CultureSegment + : ContentVariation.CultureNeutral; + } + else if (segment != null) + { + variation = ContentVariation.InvariantSegment; + } + else + { + variation = ContentVariation.InvariantNeutral; + } + if (!Variations.Has(variation)) + { + if (throwIfInvalid) + throw new NotSupportedException($"Variation {variation} is invalid for property type \"{Alias}\"."); + return false; + } + return true; + } + + /// + /// Creates a new property of this property type. + /// + public Property CreateProperty() + { + return new Property(this); + } + + /// + /// Determines whether a value is of the expected type for this property type. + /// + /// + /// If the value is of the expected type, it can be directly assigned to the property. + /// Otherwise, some conversion is required. + /// + public bool IsOfExpectedPropertyType(object value) + { + // null values are assumed to be ok + if (value == null) + return true; + + // check if the type of the value matches the type from the DataType/PropertyEditor + // then it can be directly assigned, anything else requires conversion + var valueType = value.GetType(); + switch (ValueStorageType) + { + case ValueStorageType.Integer: + return valueType == typeof(int); + case ValueStorageType.Decimal: + return valueType == typeof(decimal); + case ValueStorageType.Date: + return valueType == typeof(DateTime); + case ValueStorageType.Nvarchar: + return valueType == typeof(string); + case ValueStorageType.Ntext: + return valueType == typeof(string); + default: + throw new NotSupportedException($"Not supported storage type \"{ValueStorageType}\"."); + } + } + + /// + /// Determines whether a value can be assigned to a property. + /// + public bool IsValueAssignable(object value) => TryConvertAssignedValue(value, false, out _); + + /// + /// Converts a value assigned to a property. + /// + /// + /// The input value can be pretty much anything, and is converted to the actual Clr type + /// expected by the property (eg an integer if the property values are integers). + /// Throws if the value cannot be converted. + /// + public object ConvertAssignedValue(object value) => TryConvertAssignedValue(value, true, out var converted) ? converted : null; + + /// + /// Tries to convert a value assigned to a property. + /// + /// + /// + /// + public bool TryConvertAssignedValue(object value, out object converted) => TryConvertAssignedValue(value, false, out converted); + + private bool TryConvertAssignedValue(object value, bool throwOnError, out object converted) + { + var isOfExpectedType = IsOfExpectedPropertyType(value); + if (isOfExpectedType) + { + converted = value; + return true; + } + + // isOfExpectedType is true if value is null - so if false, value is *not* null + // "garbage-in", accept what we can & convert + // throw only if conversion is not possible + + var s = value.ToString(); + converted = null; + + switch (ValueStorageType) + { + case ValueStorageType.Nvarchar: + case ValueStorageType.Ntext: + { + converted = s; + return true; + } + + case ValueStorageType.Integer: + if (s.IsNullOrWhiteSpace()) + return true; // assume empty means null + var convInt = value.TryConvertTo(); + if (convInt) + { + converted = convInt.Result; + return true; + } + if (throwOnError) + ThrowTypeException(value, typeof(int), Alias); + return false; + + case ValueStorageType.Decimal: + if (s.IsNullOrWhiteSpace()) + return true; // assume empty means null + var convDecimal = value.TryConvertTo(); + if (convDecimal) + { + // need to normalize the value (change the scaling factor and remove trailing zeroes) + // because the underlying database is going to mess with the scaling factor anyways. + converted = convDecimal.Result.Normalize(); + return true; + } + if (throwOnError) + ThrowTypeException(value, typeof(decimal), Alias); + return false; + + case ValueStorageType.Date: + if (s.IsNullOrWhiteSpace()) + return true; // assume empty means null + var convDateTime = value.TryConvertTo(); + if (convDateTime) + { + converted = convDateTime.Result; + return true; + } + if (throwOnError) + ThrowTypeException(value, typeof(DateTime), Alias); + return false; + + default: + throw new NotSupportedException($"Not supported storage type \"{ValueStorageType}\"."); + } + } + + private static void ThrowTypeException(object value, Type expected, string alias) + { + throw new InvalidOperationException($"Cannot assign value \"{value}\" of type \"{value.GetType()}\" to property \"{alias}\" expecting type \"{expected}\"."); + } - //fixme - perhaps this and other validation methods should be a service level (not a model) thing? - /// - /// Determines whether a value is valid for this property type. - /// - public bool IsPropertyValueValid(object value) - { - var editor = Current.PropertyEditors[_propertyEditorAlias]; // fixme inject? - var configuration = Current.Services.DataTypeService.GetDataType(_dataTypeId).Configuration; // fixme inject? - var valueEditor = editor.GetValueEditor(configuration); - return !valueEditor.Validate(value, Mandatory, ValidationRegExp).Any(); - } - - /// - /// Sanitizes a property type alias. - /// - private static string SanitizeAlias(string value) - { - //NOTE: WE are doing this because we don't want to do a ToSafeAlias when the alias is the special case of - // being prefixed with Constants.PropertyEditors.InternalGenericPropertiesPrefix - // which is used internally - - return value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) - ? value - : value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); - } - - /// - public bool Equals(PropertyType other) - { - return other != null && (base.Equals(other) || Alias.InvariantEquals(other.Alias)); - } - - /// - public override int GetHashCode() - { - //Get hash code for the Name field if it is not null. - int baseHash = base.GetHashCode(); - - //Get hash code for the Alias field. - int hashAlias = Alias.ToLowerInvariant().GetHashCode(); - - //Calculate the hash code for the product. - return baseHash ^ hashAlias; - } - - /// - public override object DeepClone() - { - var clone = (PropertyType)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //need to manually assign the Lazy value as it will not be automatically mapped - if (PropertyGroupId != null) - { - clone._propertyGroupId = new Lazy(() => PropertyGroupId.Value); - } - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; - } - } -} + //fixme - perhaps this and other validation methods should be a service level (not a model) thing? + /// + /// Determines whether a value is valid for this property type. + /// + public bool IsPropertyValueValid(object value) + { + var editor = Current.PropertyEditors[_propertyEditorAlias]; // fixme inject? + var configuration = Current.Services.DataTypeService.GetDataType(_dataTypeId).Configuration; // fixme inject? + var valueEditor = editor.GetValueEditor(configuration); + return !valueEditor.Validate(value, Mandatory, ValidationRegExp).Any(); + } + + /// + /// Sanitizes a property type alias. + /// + private static string SanitizeAlias(string value) + { + //NOTE: WE are doing this because we don't want to do a ToSafeAlias when the alias is the special case of + // being prefixed with Constants.PropertyEditors.InternalGenericPropertiesPrefix + // which is used internally + + return value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) + ? value + : value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); + } + + /// + public bool Equals(PropertyType other) + { + return other != null && (base.Equals(other) || Alias.InvariantEquals(other.Alias)); + } + + /// + public override int GetHashCode() + { + //Get hash code for the Name field if it is not null. + int baseHash = base.GetHashCode(); + + //Get hash code for the Alias field. + int hashAlias = Alias.ToLowerInvariant().GetHashCode(); + + //Calculate the hash code for the product. + return baseHash ^ hashAlias; + } + + /// + public override object DeepClone() + { + var clone = (PropertyType)base.DeepClone(); + //turn off change tracking + clone.DisableChangeTracking(); + //need to manually assign the Lazy value as it will not be automatically mapped + if (PropertyGroupId != null) + { + clone._propertyGroupId = new Lazy(() => PropertyGroupId.Value); + } + //this shouldn't really be needed since we're not tracking + clone.ResetDirtyProperties(false); + //re-enable tracking + clone.EnableChangeTracking(); + + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index dbe97248f9..53b75f7e48 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -1,161 +1,161 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using System.Runtime.Serialization; -using System.Threading; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a collection of objects. - /// - [Serializable] - [DataContract] - public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable - { - [IgnoreDataMember] - private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); - - [IgnoreDataMember] - internal Action OnAdd; - - internal PropertyTypeCollection(bool isPublishing) - { - IsPublishing = isPublishing; - } - - public PropertyTypeCollection(bool isPublishing, IEnumerable properties) - : this(isPublishing) - { - Reset(properties); - } - - public bool IsPublishing { get; } - - /// - /// Resets the collection to only contain the instances referenced in the parameter. - /// - /// The properties. - /// - internal void Reset(IEnumerable properties) - { - Clear(); - foreach (var property in properties) - Add(property); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - protected override void SetItem(int index, PropertyType item) - { - item.IsPublishing = IsPublishing; - base.SetItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); - } - - protected override void RemoveItem(int index) - { - var removed = this[index]; - base.RemoveItem(index); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); - } - - protected override void InsertItem(int index, PropertyType item) - { - item.IsPublishing = IsPublishing; - base.InsertItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); - } - - protected override void ClearItems() - { - base.ClearItems(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - //TODO: Instead of 'new' this should explicitly implement one of the collection interfaces members - internal new void Add(PropertyType item) - { - item.IsPublishing = IsPublishing; - - // fixme redo this entirely!!! - try - { - _addLocker.EnterWriteLock(); - var key = GetKeyForItem(item); - if (key != null) - { - var exists = Contains(key); - if (exists) - { - SetItem(IndexOfKey(key), item); - return; - } - } - - //check if the item's sort order is already in use - if (this.Any(x => x.SortOrder == item.SortOrder)) - { - //make it the next iteration - item.SortOrder = this.Max(x => x.SortOrder) + 1; - } - - base.Add(item); - OnAdd?.Invoke(); - - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); - } - finally - { +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a collection of objects. + /// + [Serializable] + [DataContract] + public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable + { + [IgnoreDataMember] + private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); + + [IgnoreDataMember] + internal Action OnAdd; + + internal PropertyTypeCollection(bool isPublishing) + { + IsPublishing = isPublishing; + } + + public PropertyTypeCollection(bool isPublishing, IEnumerable properties) + : this(isPublishing) + { + Reset(properties); + } + + public bool IsPublishing { get; } + + /// + /// Resets the collection to only contain the instances referenced in the parameter. + /// + /// The properties. + /// + internal void Reset(IEnumerable properties) + { + Clear(); + foreach (var property in properties) + Add(property); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + protected override void SetItem(int index, PropertyType item) + { + item.IsPublishing = IsPublishing; + base.SetItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + } + + protected override void RemoveItem(int index) + { + var removed = this[index]; + base.RemoveItem(index); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + } + + protected override void InsertItem(int index, PropertyType item) + { + item.IsPublishing = IsPublishing; + base.InsertItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + } + + protected override void ClearItems() + { + base.ClearItems(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + //TODO: Instead of 'new' this should explicitly implement one of the collection interfaces members + internal new void Add(PropertyType item) + { + item.IsPublishing = IsPublishing; + + // fixme redo this entirely!!! + try + { + _addLocker.EnterWriteLock(); + var key = GetKeyForItem(item); + if (key != null) + { + var exists = Contains(key); + if (exists) + { + SetItem(IndexOfKey(key), item); + return; + } + } + + //check if the item's sort order is already in use + if (this.Any(x => x.SortOrder == item.SortOrder)) + { + //make it the next iteration + item.SortOrder = this.Max(x => x.SortOrder) + 1; + } + + base.Add(item); + OnAdd?.Invoke(); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + } + finally + { if (_addLocker.IsWriteLockHeld) - _addLocker.ExitWriteLock(); - } - } - - /// - /// Determines whether this collection contains a whose alias matches the specified PropertyType. - /// - /// Alias of the PropertyType. - /// true if the collection contains the specified alias; otherwise, false. - /// - public new bool Contains(string propertyAlias) - { - return this.Any(x => x.Alias == propertyAlias); - } - - public void RemoveItem(string propertyTypeAlias) - { - var key = IndexOfKey(propertyTypeAlias); - if (key != -1) RemoveItem(key); - } - - public int IndexOfKey(string key) - { - for (var i = 0; i < Count; i++) - if (this[i].Alias == key) - return i; - return -1; - } - - protected override string GetKeyForItem(PropertyType item) - { - return item.Alias; - } - - public event NotifyCollectionChangedEventHandler CollectionChanged; - - protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - { - CollectionChanged?.Invoke(this, args); - } - - public object DeepClone() - { - var clone = new PropertyTypeCollection(IsPublishing); - foreach (var propertyType in this) - clone.Add((PropertyType) propertyType.DeepClone()); - return clone; - } - } -} + _addLocker.ExitWriteLock(); + } + } + + /// + /// Determines whether this collection contains a whose alias matches the specified PropertyType. + /// + /// Alias of the PropertyType. + /// true if the collection contains the specified alias; otherwise, false. + /// + public new bool Contains(string propertyAlias) + { + return this.Any(x => x.Alias == propertyAlias); + } + + public void RemoveItem(string propertyTypeAlias) + { + var key = IndexOfKey(propertyTypeAlias); + if (key != -1) RemoveItem(key); + } + + public int IndexOfKey(string key) + { + for (var i = 0; i < Count; i++) + if (this[i].Alias == key) + return i; + return -1; + } + + protected override string GetKeyForItem(PropertyType item) + { + return item.Alias; + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + CollectionChanged?.Invoke(this, args); + } + + public object DeepClone() + { + var clone = new PropertyTypeCollection(IsPublishing); + foreach (var propertyType in this) + clone.Add((PropertyType) propertyType.DeepClone()); + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs index f1d2a5f208..43f6160250 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs @@ -1,19 +1,19 @@ -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Represents a strongly-typed published content. - /// - /// Every strongly-typed published content class should inherit from PublishedContentModel - /// (or inherit from a class that inherits from... etc.) so they are picked by the factory. - public abstract class PublishedContentModel : PublishedContentWrapped - { - /// - /// Initializes a new instance of the class with - /// an original instance. - /// - /// The original content. - protected PublishedContentModel(IPublishedContent content) - : base(content) - { } - } -} +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents a strongly-typed published content. + /// + /// Every strongly-typed published content class should inherit from PublishedContentModel + /// (or inherit from a class that inherits from... etc.) so they are picked by the factory. + public abstract class PublishedContentModel : PublishedContentWrapped + { + /// + /// Initializes a new instance of the class with + /// an original instance. + /// + /// The original content. + protected PublishedContentModel(IPublishedContent content) + : base(content) + { } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 56d500167c..bc403b904d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -1,171 +1,171 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Represents an type. - /// - /// Instances of the class are immutable, ie - /// if the content type changes, then a new class needs to be created. - public class PublishedContentType - { - private readonly PublishedPropertyType[] _propertyTypes; - - // fast alias-to-index xref containing both the raw alias and its lowercase version - private readonly Dictionary _indexes = new Dictionary(); - - /// - /// Initializes a new instance of the class with a content type. - /// - public PublishedContentType(IContentTypeComposition contentType, IPublishedContentTypeFactory factory) - : this(contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations) - { - var propertyTypes = contentType.CompositionPropertyTypes - .Select(x => factory.CreatePropertyType(this, x)) - .ToList(); - - if (ItemType == PublishedItemType.Member) - EnsureMemberProperties(propertyTypes, factory); - - _propertyTypes = propertyTypes.ToArray(); - - InitializeIndexes(); - } - - /// - /// Initializes a new instance of the with specific values. - /// - /// - /// This constructor is for tests and is not intended to be used directly from application code. - /// Values are assumed to be consisted and are not checked. - /// - public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations) - : this (id, alias, itemType, compositionAliases, variations) - { - var propertyTypesA = propertyTypes.ToArray(); - foreach (var propertyType in propertyTypesA) - propertyType.ContentType = this; - _propertyTypes = propertyTypesA; - - InitializeIndexes(); - } - - private PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations) - { - Id = id; - Alias = alias; - ItemType = itemType; - CompositionAliases = new HashSet(compositionAliases, StringComparer.InvariantCultureIgnoreCase); - Variations = variations; - } - - private void InitializeIndexes() - { - for (var i = 0; i < _propertyTypes.Length; i++) - { - var propertyType = _propertyTypes[i]; - _indexes[propertyType.Alias] = i; - _indexes[propertyType.Alias.ToLowerInvariant()] = i; - } - } - - // Members have properties such as IMember LastLoginDate which are plain C# properties and not content - // properties; they are exposed as pseudo content properties, as long as a content property with the - // same alias does not exist already. - private void EnsureMemberProperties(List propertyTypes, IPublishedContentTypeFactory factory) - { - var aliases = new HashSet(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase); - - foreach ((var alias, (var dataTypeId, var editorAlias)) in BuiltinMemberProperties) - { - if (aliases.Contains(alias)) continue; - propertyTypes.Add(factory.CreatePropertyType(this, alias, dataTypeId, ContentVariation.InvariantNeutral)); - } - } - - // fixme - this list somehow also exists in constants, see memberTypeRepository => remove duplicate! - private static readonly Dictionary BuiltinMemberProperties = new Dictionary - { - { "Email", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, - { "Username", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, - { "PasswordQuestion", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, - { "Comments", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, - { "IsApproved", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) }, - { "IsLockedOut", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) }, - { "LastLockoutDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, - { "CreateDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, - { "LastLoginDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, - { "LastPasswordChangeDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, - }; - - #region Content type - - /// - /// Gets the content type identifier. - /// - public int Id { get; } - - /// - /// Gets the content type alias. - /// - public string Alias { get; } - - /// - /// Gets the content item type. - /// - public PublishedItemType ItemType { get; } - - /// - /// Gets the aliases of the content types participating in the composition. - /// - public HashSet CompositionAliases { get; } - - /// - /// Gets the content variations of the content type. - /// - public ContentVariation Variations { get; } - - #endregion - - #region Properties - - /// - /// Gets the content type properties. - /// - public IEnumerable PropertyTypes => _propertyTypes; - - /// - /// Gets a property type index. - /// - /// The alias is case-insensitive. This is the only place where alias strings are compared. - public int GetPropertyIndex(string alias) - { - if (_indexes.TryGetValue(alias, out var index)) return index; // fastest - if (_indexes.TryGetValue(alias.ToLowerInvariant(), out index)) return index; // slower - return -1; - } - - // virtual for unit tests - fixme explain - /// - /// Gets a property type. - /// - public virtual PublishedPropertyType GetPropertyType(string alias) - { - var index = GetPropertyIndex(alias); - return GetPropertyType(index); - } - - // virtual for unit tests - fixme explain - /// - /// Gets a property type. - /// - public virtual PublishedPropertyType GetPropertyType(int index) - { - return index >= 0 && index < _propertyTypes.Length ? _propertyTypes[index] : null; - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents an type. + /// + /// Instances of the class are immutable, ie + /// if the content type changes, then a new class needs to be created. + public class PublishedContentType + { + private readonly PublishedPropertyType[] _propertyTypes; + + // fast alias-to-index xref containing both the raw alias and its lowercase version + private readonly Dictionary _indexes = new Dictionary(); + + /// + /// Initializes a new instance of the class with a content type. + /// + public PublishedContentType(IContentTypeComposition contentType, IPublishedContentTypeFactory factory) + : this(contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations) + { + var propertyTypes = contentType.CompositionPropertyTypes + .Select(x => factory.CreatePropertyType(this, x)) + .ToList(); + + if (ItemType == PublishedItemType.Member) + EnsureMemberProperties(propertyTypes, factory); + + _propertyTypes = propertyTypes.ToArray(); + + InitializeIndexes(); + } + + /// + /// Initializes a new instance of the with specific values. + /// + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// Values are assumed to be consisted and are not checked. + /// + public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations) + : this (id, alias, itemType, compositionAliases, variations) + { + var propertyTypesA = propertyTypes.ToArray(); + foreach (var propertyType in propertyTypesA) + propertyType.ContentType = this; + _propertyTypes = propertyTypesA; + + InitializeIndexes(); + } + + private PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations) + { + Id = id; + Alias = alias; + ItemType = itemType; + CompositionAliases = new HashSet(compositionAliases, StringComparer.InvariantCultureIgnoreCase); + Variations = variations; + } + + private void InitializeIndexes() + { + for (var i = 0; i < _propertyTypes.Length; i++) + { + var propertyType = _propertyTypes[i]; + _indexes[propertyType.Alias] = i; + _indexes[propertyType.Alias.ToLowerInvariant()] = i; + } + } + + // Members have properties such as IMember LastLoginDate which are plain C# properties and not content + // properties; they are exposed as pseudo content properties, as long as a content property with the + // same alias does not exist already. + private void EnsureMemberProperties(List propertyTypes, IPublishedContentTypeFactory factory) + { + var aliases = new HashSet(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase); + + foreach ((var alias, (var dataTypeId, var editorAlias)) in BuiltinMemberProperties) + { + if (aliases.Contains(alias)) continue; + propertyTypes.Add(factory.CreatePropertyType(this, alias, dataTypeId, ContentVariation.InvariantNeutral)); + } + } + + // fixme - this list somehow also exists in constants, see memberTypeRepository => remove duplicate! + private static readonly Dictionary BuiltinMemberProperties = new Dictionary + { + { "Email", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, + { "Username", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, + { "PasswordQuestion", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, + { "Comments", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, + { "IsApproved", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) }, + { "IsLockedOut", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) }, + { "LastLockoutDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, + { "CreateDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, + { "LastLoginDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, + { "LastPasswordChangeDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, + }; + + #region Content type + + /// + /// Gets the content type identifier. + /// + public int Id { get; } + + /// + /// Gets the content type alias. + /// + public string Alias { get; } + + /// + /// Gets the content item type. + /// + public PublishedItemType ItemType { get; } + + /// + /// Gets the aliases of the content types participating in the composition. + /// + public HashSet CompositionAliases { get; } + + /// + /// Gets the content variations of the content type. + /// + public ContentVariation Variations { get; } + + #endregion + + #region Properties + + /// + /// Gets the content type properties. + /// + public IEnumerable PropertyTypes => _propertyTypes; + + /// + /// Gets a property type index. + /// + /// The alias is case-insensitive. This is the only place where alias strings are compared. + public int GetPropertyIndex(string alias) + { + if (_indexes.TryGetValue(alias, out var index)) return index; // fastest + if (_indexes.TryGetValue(alias.ToLowerInvariant(), out index)) return index; // slower + return -1; + } + + // virtual for unit tests - fixme explain + /// + /// Gets a property type. + /// + public virtual PublishedPropertyType GetPropertyType(string alias) + { + var index = GetPropertyIndex(alias); + return GetPropertyType(index); + } + + // virtual for unit tests - fixme explain + /// + /// Gets a property type. + /// + public virtual PublishedPropertyType GetPropertyType(int index) + { + return index >= 0 && index < _propertyTypes.Length ? _propertyTypes[index] : null; + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index eb99401d39..5bdeb3685d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -1,139 +1,139 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Models.PublishedContent -{ - // - // we cannot implement strongly-typed content by inheriting from some sort - // of "master content" because that master content depends on the actual content cache - // that is being used. It can be an XmlPublishedContent with the XmlPublishedCache, - // or just anything else. - // - // So we implement strongly-typed content by encapsulating whatever content is - // returned by the content cache, and providing extra properties (mostly) or - // methods or whatever. This class provides the base for such encapsulation. - // - - /// - /// Provides an abstract base class for IPublishedContent implementations that - /// wrap and extend another IPublishedContent. - /// - public abstract class PublishedContentWrapped : IPublishedContent - { - private readonly IPublishedContent _content; - - /// - /// Initialize a new instance of the class - /// with an IPublishedContent instance to wrap. - /// - /// The content to wrap. - protected PublishedContentWrapped(IPublishedContent content) - { - _content = content; - } - - /// - /// Gets the wrapped content. - /// - /// The wrapped content, that was passed as an argument to the constructor. - public IPublishedContent Unwrap() => _content; - - #region ContentType +using System; +using System.Collections.Generic; - /// - public virtual PublishedContentType ContentType => _content.ContentType; - - #endregion +namespace Umbraco.Core.Models.PublishedContent +{ + // + // we cannot implement strongly-typed content by inheriting from some sort + // of "master content" because that master content depends on the actual content cache + // that is being used. It can be an XmlPublishedContent with the XmlPublishedCache, + // or just anything else. + // + // So we implement strongly-typed content by encapsulating whatever content is + // returned by the content cache, and providing extra properties (mostly) or + // methods or whatever. This class provides the base for such encapsulation. + // + + /// + /// Provides an abstract base class for IPublishedContent implementations that + /// wrap and extend another IPublishedContent. + /// + public abstract class PublishedContentWrapped : IPublishedContent + { + private readonly IPublishedContent _content; + + /// + /// Initialize a new instance of the class + /// with an IPublishedContent instance to wrap. + /// + /// The content to wrap. + protected PublishedContentWrapped(IPublishedContent content) + { + _content = content; + } + + /// + /// Gets the wrapped content. + /// + /// The wrapped content, that was passed as an argument to the constructor. + public IPublishedContent Unwrap() => _content; + + #region ContentType + + /// + public virtual PublishedContentType ContentType => _content.ContentType; - #region PublishedElement - - /// - public Guid Key => _content.Key; - #endregion - - #region PublishedContent - - /// - public virtual int Id => _content.Id; - - /// - public virtual string Name => _content.Name; - - /// - public virtual string UrlSegment => _content.UrlSegment; - - /// - public virtual int SortOrder => _content.SortOrder; - - /// - public virtual int Level => _content.Level; - - /// - public virtual string Path => _content.Path; - - /// - public virtual int TemplateId => _content.TemplateId; - /// - public virtual int CreatorId => _content.CreatorId; - - /// - public virtual string CreatorName => _content.CreatorName; - - /// - public virtual DateTime CreateDate => _content.CreateDate; - - /// - public virtual int WriterId => _content.WriterId; - - /// - public virtual string WriterName => _content.WriterName; - - /// - public virtual DateTime UpdateDate => _content.UpdateDate; - - /// - public virtual string Url => _content.Url; - - /// - public virtual string GetUrl(string culture = null) => _content.GetUrl(culture); - - /// - public PublishedCultureInfo GetCulture(string culture = null) => _content.GetCulture(culture); - - /// - public IReadOnlyDictionary Cultures => _content.Cultures; - - /// - public virtual PublishedItemType ItemType => _content.ItemType; - - /// - public virtual bool IsDraft => _content.IsDraft; - - #endregion - - #region Tree - - /// - public virtual IPublishedContent Parent => _content.Parent; - - /// - public virtual IEnumerable Children => _content.Children; - - #endregion - - #region Properties - - /// - public virtual IEnumerable Properties => _content.Properties; - - /// - public virtual IPublishedProperty GetProperty(string alias) - { - return _content.GetProperty(alias); - } - - #endregion - } -} + #region PublishedElement + + /// + public Guid Key => _content.Key; + + #endregion + + #region PublishedContent + + /// + public virtual int Id => _content.Id; + + /// + public virtual string Name => _content.Name; + + /// + public virtual string UrlSegment => _content.UrlSegment; + + /// + public virtual int SortOrder => _content.SortOrder; + + /// + public virtual int Level => _content.Level; + + /// + public virtual string Path => _content.Path; + + /// + public virtual int TemplateId => _content.TemplateId; + + /// + public virtual int CreatorId => _content.CreatorId; + + /// + public virtual string CreatorName => _content.CreatorName; + + /// + public virtual DateTime CreateDate => _content.CreateDate; + + /// + public virtual int WriterId => _content.WriterId; + + /// + public virtual string WriterName => _content.WriterName; + + /// + public virtual DateTime UpdateDate => _content.UpdateDate; + + /// + public virtual string Url => _content.Url; + + /// + public virtual string GetUrl(string culture = null) => _content.GetUrl(culture); + + /// + public PublishedCultureInfo GetCulture(string culture = null) => _content.GetCulture(culture); + + /// + public IReadOnlyDictionary Cultures => _content.Cultures; + + /// + public virtual PublishedItemType ItemType => _content.ItemType; + + /// + public virtual bool IsDraft => _content.IsDraft; + + #endregion + + #region Tree + + /// + public virtual IPublishedContent Parent => _content.Parent; + + /// + public virtual IEnumerable Children => _content.Children; + + #endregion + + #region Properties + + /// + public virtual IEnumerable Properties => _content.Properties; + + /// + public virtual IPublishedProperty GetProperty(string alias) + { + return _content.GetProperty(alias); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs index 7e2a5b5498..5f374f8bc8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs @@ -1,67 +1,67 @@ -using System; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Provides a base class for IPublishedProperty implementations which converts and caches - /// the value source to the actual value to use when rendering content. - /// - internal abstract class PublishedPropertyBase : IPublishedProperty - { - /// - /// Initializes a new instance of the class. - /// - protected PublishedPropertyBase(PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel) - { - PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType)); - ReferenceCacheLevel = referenceCacheLevel; - - ValidateCacheLevel(ReferenceCacheLevel, true); - ValidateCacheLevel(PropertyType.CacheLevel, false); - } - - // validates the cache level - private static void ValidateCacheLevel(PropertyCacheLevel cacheLevel, bool validateUnknown) - { - switch (cacheLevel) - { - case PropertyCacheLevel.Element: - case PropertyCacheLevel.Elements: - case PropertyCacheLevel.Snapshot: - case PropertyCacheLevel.None: - break; - case PropertyCacheLevel.Unknown: - if (!validateUnknown) goto default; - break; - default: - throw new Exception($"Invalid cache level \"{cacheLevel}\"."); - } - } - - /// - /// Gets the property type. - /// - public PublishedPropertyType PropertyType { get; } - - /// - /// Gets the property reference cache level. - /// - public PropertyCacheLevel ReferenceCacheLevel { get; } - - /// - public string Alias => PropertyType.Alias; - - /// - public abstract bool HasValue(string culture = null, string segment = null); - - /// - public abstract object GetSourceValue(string culture = null, string segment = null); - - /// - public abstract object GetValue(string culture = null, string segment = null); - - /// - public abstract object GetXPathValue(string culture = null, string segment = null); - } -} +using System; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides a base class for IPublishedProperty implementations which converts and caches + /// the value source to the actual value to use when rendering content. + /// + internal abstract class PublishedPropertyBase : IPublishedProperty + { + /// + /// Initializes a new instance of the class. + /// + protected PublishedPropertyBase(PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel) + { + PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType)); + ReferenceCacheLevel = referenceCacheLevel; + + ValidateCacheLevel(ReferenceCacheLevel, true); + ValidateCacheLevel(PropertyType.CacheLevel, false); + } + + // validates the cache level + private static void ValidateCacheLevel(PropertyCacheLevel cacheLevel, bool validateUnknown) + { + switch (cacheLevel) + { + case PropertyCacheLevel.Element: + case PropertyCacheLevel.Elements: + case PropertyCacheLevel.Snapshot: + case PropertyCacheLevel.None: + break; + case PropertyCacheLevel.Unknown: + if (!validateUnknown) goto default; + break; + default: + throw new Exception($"Invalid cache level \"{cacheLevel}\"."); + } + } + + /// + /// Gets the property type. + /// + public PublishedPropertyType PropertyType { get; } + + /// + /// Gets the property reference cache level. + /// + public PropertyCacheLevel ReferenceCacheLevel { get; } + + /// + public string Alias => PropertyType.Alias; + + /// + public abstract bool HasValue(string culture = null, string segment = null); + + /// + public abstract object GetSourceValue(string culture = null, string segment = null); + + /// + public abstract object GetValue(string culture = null, string segment = null); + + /// + public abstract object GetXPathValue(string culture = null, string segment = null); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index bf1d940dbe..f43e5c4916 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -1,304 +1,304 @@ -using System; -using System.Xml.Linq; -using System.Xml.XPath; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Represents a published property type. - /// - /// Instances of the class are immutable, ie - /// if the property type changes, then a new class needs to be created. - public class PublishedPropertyType +using System; +using System.Xml.Linq; +using System.Xml.XPath; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents a published property type. + /// + /// Instances of the class are immutable, ie + /// if the property type changes, then a new class needs to be created. + public class PublishedPropertyType { //fixme - API design review, should this be an interface? - - private readonly IPublishedModelFactory _publishedModelFactory; - private readonly PropertyValueConverterCollection _propertyValueConverters; - private readonly object _locker = new object(); - private volatile bool _initialized; - private IPropertyValueConverter _converter; - private PropertyCacheLevel _cacheLevel; - - private Type _modelClrType; - private Type _clrType; - - #region Constructors - - /// - /// Initialize a new instance of the class with a property type. - /// - /// - /// The new published property type belongs to the published content type. - /// - public PublishedPropertyType(PublishedContentType contentType, PropertyType propertyType, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) - : this(propertyType.Alias, propertyType.DataTypeId, true, propertyType.Variations, propertyValueConverters, publishedModelFactory, factory) - { - ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - } - - /// - /// This constructor is for tests and is not intended to be used directly from application code. - /// - /// - /// Values are assumed to be consisted and are not checked. - /// The new published property type belongs to the published content type. - /// - public PublishedPropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) - : this(propertyTypeAlias, dataTypeId, isUserProperty, variations, propertyValueConverters, publishedModelFactory, factory) - { - ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - } - - /// - /// This constructor is for tests and is not intended to be used directly from application code. - /// - /// - /// Values are assumed to be consistent and are not checked. - /// The new published property type does not belong to a published content type. - /// - public PublishedPropertyType(string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) - { - _publishedModelFactory = publishedModelFactory ?? throw new ArgumentNullException(nameof(publishedModelFactory)); - _propertyValueConverters = propertyValueConverters ?? throw new ArgumentNullException(nameof(propertyValueConverters)); - - Alias = propertyTypeAlias; - - IsUserProperty = isUserProperty; - Variations = variations; - - DataType = factory.GetDataType(dataTypeId); - } - - #endregion - - #region Property type - - /// - /// Gets the published content type containing the property type. - /// - public PublishedContentType ContentType { get; internal set; } // internally set by PublishedContentType constructor - - /// - /// Gets the data type. - /// - public PublishedDataType DataType { get; } - - /// - /// Gets property type alias. - /// - public string Alias { get; } - - /// - /// Gets the property editor alias. - /// - public string EditorAlias => DataType.EditorAlias; - - /// - /// Gets a value indicating whether the property is a user content property. - /// - /// A non-user content property is a property that has been added to a - /// published content type by Umbraco but does not corresponds to a user-defined - /// published property. - public bool IsUserProperty { get; } - - /// - /// Gets the content variations of the property type. - /// - public ContentVariation Variations { get; } - - #endregion - - #region Converters - - private void Initialize() - { - if (_initialized) return; - lock (_locker) - { - if (_initialized) return; - InitializeLocked(); - _initialized = true; - } - } - - private void InitializeLocked() - { - _converter = null; - var isdefault = false; - - foreach (var converter in _propertyValueConverters) - { - if (!converter.IsConverter(this)) - continue; - - if (_converter == null) - { - _converter = converter; - isdefault = _propertyValueConverters.IsDefault(converter); - continue; - } - - if (isdefault) - { - if (_propertyValueConverters.IsDefault(converter)) - { - // previous was default, and got another default - if (_propertyValueConverters.Shadows(_converter, converter)) - { - // previous shadows, ignore - } - else if (_propertyValueConverters.Shadows(converter, _converter)) - { - // shadows previous, replace - _converter = converter; - } - else - { - // no shadow - bad - throw new InvalidOperationException(string.Format("Type '{2}' cannot be an IPropertyValueConverter" - + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" - + " for that property, and only one converter can exist for a property.", - ContentType.Alias, Alias, - converter.GetType().FullName, _converter.GetType().FullName)); - } - } - else - { - // previous was default, replaced by non-default - _converter = converter; - isdefault = false; - } - } - else - { - if (_propertyValueConverters.IsDefault(converter)) - { - // previous was non-default, ignore default - } - else - { - // previous was non-default, and got another non-default - bad - throw new InvalidOperationException(string.Format("Type '{2}' cannot be an IPropertyValueConverter" - + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" - + " for that property, and only one converter can exist for a property.", - ContentType.Alias, Alias, - converter.GetType().FullName, _converter.GetType().FullName)); - } - } - } - - _cacheLevel = _converter?.GetPropertyCacheLevel(this) ?? PropertyCacheLevel.Snapshot; - _modelClrType = _converter == null ? typeof (object) : _converter.GetPropertyValueType(this); - } - - /// - /// Gets the property cache level. - /// - public PropertyCacheLevel CacheLevel - { - get - { - if (!_initialized) Initialize(); - return _cacheLevel; - } - } - - /// - /// Converts the source value into the intermediate value. - /// - /// The published element owning the property. - /// The source value. - /// A value indicating whether content should be considered draft. - /// The intermediate value. - public object ConvertSourceToInter(IPublishedElement owner, object source, bool preview) - { - if (!_initialized) Initialize(); - - // use the converter if any, else just return the source value - return _converter != null - ? _converter.ConvertSourceToIntermediate(owner, this, source, preview) - : source; - } - - /// - /// Converts the intermediate value into the object value. - /// - /// The published element owning the property. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether content should be considered draft. - /// The object value. - public object ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - if (!_initialized) Initialize(); - - // use the converter if any, else just return the inter value - return _converter != null - ? _converter.ConvertIntermediateToObject(owner, this, referenceCacheLevel, inter, preview) - : inter; - } - - /// - /// Converts the intermediate value into the XPath value. - /// - /// The published element owning the property. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether content should be considered draft. - /// The XPath value. - /// - /// The XPath value can be either a string or an XPathNavigator. - /// - public object ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - if (!_initialized) Initialize(); - - // use the converter if any - if (_converter != null) - return _converter.ConvertIntermediateToXPath(owner, this, referenceCacheLevel, inter, preview); - - // else just return the inter value as a string or an XPathNavigator - if (inter == null) return null; - if (inter is XElement xElement) - return xElement.CreateNavigator(); - return inter.ToString().Trim(); - } - - /// - /// Gets the property model Clr type. - /// - /// - /// The model Clr type may be a type, or may contain types. - /// For the actual Clr type, see . - /// - public Type ModelClrType - { - get - { - if (!_initialized) Initialize(); - return _modelClrType; - } - } - - /// - /// Gets the property Clr type. - /// - /// - /// Returns the actual Clr type which does not contain types. - /// Mapping from may throw if some instances - /// could not be mapped to actual Clr types. - /// - public Type ClrType - { - get - { - if (!_initialized) Initialize(); - return _clrType ?? (_clrType = _publishedModelFactory.MapModelType(_modelClrType)); - } - } - - #endregion - } -} + + private readonly IPublishedModelFactory _publishedModelFactory; + private readonly PropertyValueConverterCollection _propertyValueConverters; + private readonly object _locker = new object(); + private volatile bool _initialized; + private IPropertyValueConverter _converter; + private PropertyCacheLevel _cacheLevel; + + private Type _modelClrType; + private Type _clrType; + + #region Constructors + + /// + /// Initialize a new instance of the class with a property type. + /// + /// + /// The new published property type belongs to the published content type. + /// + public PublishedPropertyType(PublishedContentType contentType, PropertyType propertyType, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + : this(propertyType.Alias, propertyType.DataTypeId, true, propertyType.Variations, propertyValueConverters, publishedModelFactory, factory) + { + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + } + + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consisted and are not checked. + /// The new published property type belongs to the published content type. + /// + public PublishedPropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + : this(propertyTypeAlias, dataTypeId, isUserProperty, variations, propertyValueConverters, publishedModelFactory, factory) + { + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + } + + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consistent and are not checked. + /// The new published property type does not belong to a published content type. + /// + public PublishedPropertyType(string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + { + _publishedModelFactory = publishedModelFactory ?? throw new ArgumentNullException(nameof(publishedModelFactory)); + _propertyValueConverters = propertyValueConverters ?? throw new ArgumentNullException(nameof(propertyValueConverters)); + + Alias = propertyTypeAlias; + + IsUserProperty = isUserProperty; + Variations = variations; + + DataType = factory.GetDataType(dataTypeId); + } + + #endregion + + #region Property type + + /// + /// Gets the published content type containing the property type. + /// + public PublishedContentType ContentType { get; internal set; } // internally set by PublishedContentType constructor + + /// + /// Gets the data type. + /// + public PublishedDataType DataType { get; } + + /// + /// Gets property type alias. + /// + public string Alias { get; } + + /// + /// Gets the property editor alias. + /// + public string EditorAlias => DataType.EditorAlias; + + /// + /// Gets a value indicating whether the property is a user content property. + /// + /// A non-user content property is a property that has been added to a + /// published content type by Umbraco but does not corresponds to a user-defined + /// published property. + public bool IsUserProperty { get; } + + /// + /// Gets the content variations of the property type. + /// + public ContentVariation Variations { get; } + + #endregion + + #region Converters + + private void Initialize() + { + if (_initialized) return; + lock (_locker) + { + if (_initialized) return; + InitializeLocked(); + _initialized = true; + } + } + + private void InitializeLocked() + { + _converter = null; + var isdefault = false; + + foreach (var converter in _propertyValueConverters) + { + if (!converter.IsConverter(this)) + continue; + + if (_converter == null) + { + _converter = converter; + isdefault = _propertyValueConverters.IsDefault(converter); + continue; + } + + if (isdefault) + { + if (_propertyValueConverters.IsDefault(converter)) + { + // previous was default, and got another default + if (_propertyValueConverters.Shadows(_converter, converter)) + { + // previous shadows, ignore + } + else if (_propertyValueConverters.Shadows(converter, _converter)) + { + // shadows previous, replace + _converter = converter; + } + else + { + // no shadow - bad + throw new InvalidOperationException(string.Format("Type '{2}' cannot be an IPropertyValueConverter" + + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" + + " for that property, and only one converter can exist for a property.", + ContentType.Alias, Alias, + converter.GetType().FullName, _converter.GetType().FullName)); + } + } + else + { + // previous was default, replaced by non-default + _converter = converter; + isdefault = false; + } + } + else + { + if (_propertyValueConverters.IsDefault(converter)) + { + // previous was non-default, ignore default + } + else + { + // previous was non-default, and got another non-default - bad + throw new InvalidOperationException(string.Format("Type '{2}' cannot be an IPropertyValueConverter" + + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" + + " for that property, and only one converter can exist for a property.", + ContentType.Alias, Alias, + converter.GetType().FullName, _converter.GetType().FullName)); + } + } + } + + _cacheLevel = _converter?.GetPropertyCacheLevel(this) ?? PropertyCacheLevel.Snapshot; + _modelClrType = _converter == null ? typeof (object) : _converter.GetPropertyValueType(this); + } + + /// + /// Gets the property cache level. + /// + public PropertyCacheLevel CacheLevel + { + get + { + if (!_initialized) Initialize(); + return _cacheLevel; + } + } + + /// + /// Converts the source value into the intermediate value. + /// + /// The published element owning the property. + /// The source value. + /// A value indicating whether content should be considered draft. + /// The intermediate value. + public object ConvertSourceToInter(IPublishedElement owner, object source, bool preview) + { + if (!_initialized) Initialize(); + + // use the converter if any, else just return the source value + return _converter != null + ? _converter.ConvertSourceToIntermediate(owner, this, source, preview) + : source; + } + + /// + /// Converts the intermediate value into the object value. + /// + /// The published element owning the property. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether content should be considered draft. + /// The object value. + public object ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + if (!_initialized) Initialize(); + + // use the converter if any, else just return the inter value + return _converter != null + ? _converter.ConvertIntermediateToObject(owner, this, referenceCacheLevel, inter, preview) + : inter; + } + + /// + /// Converts the intermediate value into the XPath value. + /// + /// The published element owning the property. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether content should be considered draft. + /// The XPath value. + /// + /// The XPath value can be either a string or an XPathNavigator. + /// + public object ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + if (!_initialized) Initialize(); + + // use the converter if any + if (_converter != null) + return _converter.ConvertIntermediateToXPath(owner, this, referenceCacheLevel, inter, preview); + + // else just return the inter value as a string or an XPathNavigator + if (inter == null) return null; + if (inter is XElement xElement) + return xElement.CreateNavigator(); + return inter.ToString().Trim(); + } + + /// + /// Gets the property model Clr type. + /// + /// + /// The model Clr type may be a type, or may contain types. + /// For the actual Clr type, see . + /// + public Type ModelClrType + { + get + { + if (!_initialized) Initialize(); + return _modelClrType; + } + } + + /// + /// Gets the property Clr type. + /// + /// + /// Returns the actual Clr type which does not contain types. + /// Mapping from may throw if some instances + /// could not be mapped to actual Clr types. + /// + public Type ClrType + { + get + { + if (!_initialized) Initialize(); + return _clrType ?? (_clrType = _publishedModelFactory.MapModelType(_modelClrType)); + } + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/PublishedState.cs b/src/Umbraco.Core/Models/PublishedState.cs index b7da2a25b1..ace7cbdebc 100644 --- a/src/Umbraco.Core/Models/PublishedState.cs +++ b/src/Umbraco.Core/Models/PublishedState.cs @@ -1,62 +1,62 @@ -using System; - -namespace Umbraco.Core.Models +using System; + +namespace Umbraco.Core.Models { - - /// - /// The states of a content item. - /// - public enum PublishedState - { - // versions management in repo: - // - // - published = the content is published - // repo: saving draft values - // update current version (draft) values - // - // - unpublished = the content is not published - // repo: saving draft values - // update current version (draft) values - // - // - publishing = the content is being published (transitory) - // if currently published: - // delete all draft values from current version, not current anymore - // create new version with published+draft values - // - // - unpublishing = the content is being unpublished (transitory) - // if currently published (just in case): - // delete all draft values from current version, not current anymore - // create new version with published+draft values (should be managed by service) - - // when a content item is loaded, its state is one of those two: - - /// - /// The content item is published. - /// - Published, - // also: handled over to repo to save draft values for a published content - - /// - /// The content item is not published. - /// - Unpublished, - // also: handled over to repo to save draft values for an unpublished content - - // when it is handled over to the repository, its state can also be one of those: - - /// - /// The version is being saved, in order to publish the content. - /// - /// The Publishing state is transitional. Once the version - /// is saved, its state changes to Published. - Publishing, - - /// - /// The version is being saved, in order to unpublish the content. - /// - /// The Unpublishing state is transitional. Once the version - /// is saved, its state changes to Unpublished. - Unpublishing - - } -} + + /// + /// The states of a content item. + /// + public enum PublishedState + { + // versions management in repo: + // + // - published = the content is published + // repo: saving draft values + // update current version (draft) values + // + // - unpublished = the content is not published + // repo: saving draft values + // update current version (draft) values + // + // - publishing = the content is being published (transitory) + // if currently published: + // delete all draft values from current version, not current anymore + // create new version with published+draft values + // + // - unpublishing = the content is being unpublished (transitory) + // if currently published (just in case): + // delete all draft values from current version, not current anymore + // create new version with published+draft values (should be managed by service) + + // when a content item is loaded, its state is one of those two: + + /// + /// The content item is published. + /// + Published, + // also: handled over to repo to save draft values for a published content + + /// + /// The content item is not published. + /// + Unpublished, + // also: handled over to repo to save draft values for an unpublished content + + // when it is handled over to the repository, its state can also be one of those: + + /// + /// The version is being saved, in order to publish the content. + /// + /// The Publishing state is transitional. Once the version + /// is saved, its state changes to Published. + Publishing, + + /// + /// The version is being saved, in order to unpublish the content. + /// + /// The Unpublishing state is transitional. Once the version + /// is saved, its state changes to Unpublished. + Unpublishing + + } +} diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index a06ca44441..2d2b05dbd6 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -1,89 +1,89 @@ -using System; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Mappers; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Relation between two items - /// - [Serializable] - [DataContract(IsReference = true)] - public class Relation : EntityBase, IRelation - { - //NOTE: The datetime column from umbracoRelation is set on CreateDate on the Entity - private int _parentId; - private int _childId; - private IRelationType _relationType; - private string _comment; - - public Relation(int parentId, int childId, IRelationType relationType) - { - _parentId = parentId; - _childId = childId; - _relationType = relationType; - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo ChildIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildId); - public readonly PropertyInfo RelationTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RelationType); - public readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); - } - - /// - /// Gets or sets the Parent Id of the Relation (Source) - /// - [DataMember] - public int ParentId - { - get { return _parentId; } - set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } - } - - /// - /// Gets or sets the Child Id of the Relation (Destination) - /// - [DataMember] - public int ChildId - { - get { return _childId; } - set { SetPropertyValueAndDetectChanges(value, ref _childId, Ps.Value.ChildIdSelector); } - } - - /// - /// Gets or sets the for the Relation - /// - [DataMember] - public IRelationType RelationType - { - get { return _relationType; } - set { SetPropertyValueAndDetectChanges(value, ref _relationType, Ps.Value.RelationTypeSelector); } - } - - /// - /// Gets or sets a comment for the Relation - /// - [DataMember] - public string Comment - { - get { return _comment; } - set { SetPropertyValueAndDetectChanges(value, ref _comment, Ps.Value.CommentSelector); } - } - - /// - /// Gets the Id of the that this Relation is based on. - /// - [IgnoreDataMember] - public int RelationTypeId - { - get { return _relationType.Id; } - } - - } -} +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Mappers; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Relation between two items + /// + [Serializable] + [DataContract(IsReference = true)] + public class Relation : EntityBase, IRelation + { + //NOTE: The datetime column from umbracoRelation is set on CreateDate on the Entity + private int _parentId; + private int _childId; + private IRelationType _relationType; + private string _comment; + + public Relation(int parentId, int childId, IRelationType relationType) + { + _parentId = parentId; + _childId = childId; + _relationType = relationType; + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo ChildIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildId); + public readonly PropertyInfo RelationTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RelationType); + public readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); + } + + /// + /// Gets or sets the Parent Id of the Relation (Source) + /// + [DataMember] + public int ParentId + { + get { return _parentId; } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } + } + + /// + /// Gets or sets the Child Id of the Relation (Destination) + /// + [DataMember] + public int ChildId + { + get { return _childId; } + set { SetPropertyValueAndDetectChanges(value, ref _childId, Ps.Value.ChildIdSelector); } + } + + /// + /// Gets or sets the for the Relation + /// + [DataMember] + public IRelationType RelationType + { + get { return _relationType; } + set { SetPropertyValueAndDetectChanges(value, ref _relationType, Ps.Value.RelationTypeSelector); } + } + + /// + /// Gets or sets a comment for the Relation + /// + [DataMember] + public string Comment + { + get { return _comment; } + set { SetPropertyValueAndDetectChanges(value, ref _comment, Ps.Value.CommentSelector); } + } + + /// + /// Gets the Id of the that this Relation is based on. + /// + [IgnoreDataMember] + public int RelationTypeId + { + get { return _relationType.Id; } + } + + } +} diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index 65d44d43d9..5aa2c19ce3 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -1,103 +1,103 @@ -using System; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Mappers; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a RelationType - /// - [Serializable] - [DataContract(IsReference = true)] - public class RelationType : EntityBase, IRelationType - { - private string _name; - private string _alias; - private bool _isBidrectional; - private Guid _parentObjectType; - private Guid _childObjectType; - - public RelationType(Guid childObjectType, Guid parentObjectType, string alias) - { - if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentNullOrEmptyException(nameof(alias)); - _childObjectType = childObjectType; - _parentObjectType = parentObjectType; - _alias = alias; - Name = _alias; - } - - public RelationType(Guid childObjectType, Guid parentObjectType, string alias, string name) - : this(childObjectType, parentObjectType, alias) - { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - Name = name; - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo IsBidirectionalSelector = ExpressionHelper.GetPropertyInfo(x => x.IsBidirectional); - public readonly PropertyInfo ParentObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentObjectType); - public readonly PropertyInfo ChildObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildObjectType); - } - - /// - /// Gets or sets the Name of the RelationType - /// - [DataMember] - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// Gets or sets the Alias of the RelationType - /// - [DataMember] - public string Alias - { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } - } - - /// - /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) - /// - [DataMember] - public bool IsBidirectional - { - get { return _isBidrectional; } - set { SetPropertyValueAndDetectChanges(value, ref _isBidrectional, Ps.Value.IsBidirectionalSelector); } - } - - /// - /// Gets or sets the Parents object type id - /// - /// Corresponds to the NodeObjectType in the umbracoNode table - [DataMember] - public Guid ParentObjectType - { - get { return _parentObjectType; } - set { SetPropertyValueAndDetectChanges(value, ref _parentObjectType, Ps.Value.ParentObjectTypeSelector); } - } - - /// - /// Gets or sets the Childs object type id - /// - /// Corresponds to the NodeObjectType in the umbracoNode table - [DataMember] - public Guid ChildObjectType - { - get { return _childObjectType; } - set { SetPropertyValueAndDetectChanges(value, ref _childObjectType, Ps.Value.ChildObjectTypeSelector); } - } - - } -} +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Mappers; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a RelationType + /// + [Serializable] + [DataContract(IsReference = true)] + public class RelationType : EntityBase, IRelationType + { + private string _name; + private string _alias; + private bool _isBidrectional; + private Guid _parentObjectType; + private Guid _childObjectType; + + public RelationType(Guid childObjectType, Guid parentObjectType, string alias) + { + if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentNullOrEmptyException(nameof(alias)); + _childObjectType = childObjectType; + _parentObjectType = parentObjectType; + _alias = alias; + Name = _alias; + } + + public RelationType(Guid childObjectType, Guid parentObjectType, string alias, string name) + : this(childObjectType, parentObjectType, alias) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + Name = name; + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo IsBidirectionalSelector = ExpressionHelper.GetPropertyInfo(x => x.IsBidirectional); + public readonly PropertyInfo ParentObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentObjectType); + public readonly PropertyInfo ChildObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildObjectType); + } + + /// + /// Gets or sets the Name of the RelationType + /// + [DataMember] + public string Name + { + get { return _name; } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + } + + /// + /// Gets or sets the Alias of the RelationType + /// + [DataMember] + public string Alias + { + get { return _alias; } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } + } + + /// + /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) + /// + [DataMember] + public bool IsBidirectional + { + get { return _isBidrectional; } + set { SetPropertyValueAndDetectChanges(value, ref _isBidrectional, Ps.Value.IsBidirectionalSelector); } + } + + /// + /// Gets or sets the Parents object type id + /// + /// Corresponds to the NodeObjectType in the umbracoNode table + [DataMember] + public Guid ParentObjectType + { + get { return _parentObjectType; } + set { SetPropertyValueAndDetectChanges(value, ref _parentObjectType, Ps.Value.ParentObjectTypeSelector); } + } + + /// + /// Gets or sets the Childs object type id + /// + /// Corresponds to the NodeObjectType in the umbracoNode table + [DataMember] + public Guid ChildObjectType + { + get { return _childObjectType; } + set { SetPropertyValueAndDetectChanges(value, ref _childObjectType, Ps.Value.ChildObjectTypeSelector); } + } + + } +} diff --git a/src/Umbraco.Core/Models/Script.cs b/src/Umbraco.Core/Models/Script.cs index ee481b7fe5..bd8f71299c 100644 --- a/src/Umbraco.Core/Models/Script.cs +++ b/src/Umbraco.Core/Models/Script.cs @@ -1,34 +1,34 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Script file - /// - [Serializable] - [DataContract(IsReference = true)] - public class Script : File - { - public Script(string path) - : this(path, (Func) null) - { } - - internal Script(string path, Func getFileContent) - : base(path, getFileContent) - { } - - /// - /// Indicates whether the current entity has an identity, which in this case is a path/name. - /// - /// - /// Overrides the default Entity identity check. - /// - public override bool HasIdentity - { - get { return string.IsNullOrEmpty(Path) == false; } - } - } -} +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Script file + /// + [Serializable] + [DataContract(IsReference = true)] + public class Script : File + { + public Script(string path) + : this(path, (Func) null) + { } + + internal Script(string path, Func getFileContent) + : base(path, getFileContent) + { } + + /// + /// Indicates whether the current entity has an identity, which in this case is a path/name. + /// + /// + /// Overrides the default Entity identity check. + /// + public override bool HasIdentity + { + get { return string.IsNullOrEmpty(Path) == false; } + } + } +} diff --git a/src/Umbraco.Core/Models/Section.cs b/src/Umbraco.Core/Models/Section.cs index f1ca9c7a5d..cbd9dfe7dc 100644 --- a/src/Umbraco.Core/Models/Section.cs +++ b/src/Umbraco.Core/Models/Section.cs @@ -1,26 +1,26 @@ -namespace Umbraco.Core.Models -{ - /// - /// Represents a section defined in the app.config file - /// - public class Section - { - public Section(string name, string @alias, string icon, int sortOrder) - { - Name = name; - Alias = alias; - Icon = icon; - SortOrder = sortOrder; - } - - public Section() - { - - } - - public string Name { get; set; } - public string Alias { get; set; } - public string Icon { get; set; } - public int SortOrder { get; set; } - } -} +namespace Umbraco.Core.Models +{ + /// + /// Represents a section defined in the app.config file + /// + public class Section + { + public Section(string name, string @alias, string icon, int sortOrder) + { + Name = name; + Alias = alias; + Icon = icon; + SortOrder = sortOrder; + } + + public Section() + { + + } + + public string Name { get; set; } + public string Alias { get; set; } + public string Icon { get; set; } + public int SortOrder { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs index 7e65111f7d..0e135f34d6 100644 --- a/src/Umbraco.Core/Models/ServerRegistration.cs +++ b/src/Umbraco.Core/Models/ServerRegistration.cs @@ -1,126 +1,126 @@ -using System; -using System.Globalization; -using System.Reflection; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a registered server in a multiple-servers environment. - /// - public class ServerRegistration : EntityBase, IServerRegistration - { - private string _serverAddress; - private string _serverIdentity; - private bool _isActive; - private bool _isMaster; - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ServerAddressSelector = ExpressionHelper.GetPropertyInfo(x => x.ServerAddress); - public readonly PropertyInfo ServerIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.ServerIdentity); - public readonly PropertyInfo IsActiveSelector = ExpressionHelper.GetPropertyInfo(x => x.IsActive); - public readonly PropertyInfo IsMasterSelector = ExpressionHelper.GetPropertyInfo(x => x.IsMaster); - } - - /// - /// Initialiazes a new instance of the class. - /// - public ServerRegistration() - { } - - /// - /// Initialiazes a new instance of the class. - /// - /// The unique id of the server registration. - /// The server url. - /// The unique server identity. - /// The date and time the registration was created. - /// The date and time the registration was last accessed. - /// A value indicating whether the registration is active. - /// A value indicating whether the registration is master. - public ServerRegistration(int id, string serverAddress, string serverIdentity, DateTime registered, DateTime accessed, bool isActive, bool isMaster) - { - UpdateDate = accessed; - CreateDate = registered; - Key = id.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); - Id = id; - ServerAddress = serverAddress; - ServerIdentity = serverIdentity; - IsActive = isActive; - IsMaster = isMaster; - } - - /// - /// Initialiazes a new instance of the class. - /// - /// The server url. - /// The unique server identity. - /// The date and time the registration was created. - public ServerRegistration(string serverAddress, string serverIdentity, DateTime registered) - { - CreateDate = registered; - UpdateDate = registered; - Key = 0.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); - ServerAddress = serverAddress; - ServerIdentity = serverIdentity; - } - - /// - /// Gets or sets the server url. - /// - public string ServerAddress - { - get { return _serverAddress; } - set { SetPropertyValueAndDetectChanges(value, ref _serverAddress, Ps.Value.ServerAddressSelector); } - } - - /// - /// Gets or sets the server unique identity. - /// - public string ServerIdentity - { - get { return _serverIdentity; } - set { SetPropertyValueAndDetectChanges(value, ref _serverIdentity, Ps.Value.ServerIdentitySelector); } - } - - /// - /// Gets or sets a value indicating whether the server is active. - /// - public bool IsActive - { - get { return _isActive; } - set { SetPropertyValueAndDetectChanges(value, ref _isActive, Ps.Value.IsActiveSelector); } - } - - /// - /// Gets or sets a value indicating whether the server is master. - /// - public bool IsMaster - { - get { return _isMaster; } - set { SetPropertyValueAndDetectChanges(value, ref _isMaster, Ps.Value.IsMasterSelector); } - } - - /// - /// Gets the date and time the registration was created. - /// - public DateTime Registered { get { return CreateDate; } set { CreateDate = value; }} - - /// - /// Gets the date and time the registration was last accessed. - /// - public DateTime Accessed { get { return UpdateDate; } set { UpdateDate = value; }} - - /// - /// Converts the value of this instance to its equivalent string representation. - /// - /// - public override string ToString() - { - return string.Format("{{\"{0}\", \"{1}\", {2}active, {3}master}}", ServerAddress, ServerIdentity, IsActive ? "" : "!", IsMaster ? "" : "!"); - } - } -} +using System; +using System.Globalization; +using System.Reflection; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a registered server in a multiple-servers environment. + /// + public class ServerRegistration : EntityBase, IServerRegistration + { + private string _serverAddress; + private string _serverIdentity; + private bool _isActive; + private bool _isMaster; + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ServerAddressSelector = ExpressionHelper.GetPropertyInfo(x => x.ServerAddress); + public readonly PropertyInfo ServerIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.ServerIdentity); + public readonly PropertyInfo IsActiveSelector = ExpressionHelper.GetPropertyInfo(x => x.IsActive); + public readonly PropertyInfo IsMasterSelector = ExpressionHelper.GetPropertyInfo(x => x.IsMaster); + } + + /// + /// Initialiazes a new instance of the class. + /// + public ServerRegistration() + { } + + /// + /// Initialiazes a new instance of the class. + /// + /// The unique id of the server registration. + /// The server url. + /// The unique server identity. + /// The date and time the registration was created. + /// The date and time the registration was last accessed. + /// A value indicating whether the registration is active. + /// A value indicating whether the registration is master. + public ServerRegistration(int id, string serverAddress, string serverIdentity, DateTime registered, DateTime accessed, bool isActive, bool isMaster) + { + UpdateDate = accessed; + CreateDate = registered; + Key = id.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); + Id = id; + ServerAddress = serverAddress; + ServerIdentity = serverIdentity; + IsActive = isActive; + IsMaster = isMaster; + } + + /// + /// Initialiazes a new instance of the class. + /// + /// The server url. + /// The unique server identity. + /// The date and time the registration was created. + public ServerRegistration(string serverAddress, string serverIdentity, DateTime registered) + { + CreateDate = registered; + UpdateDate = registered; + Key = 0.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); + ServerAddress = serverAddress; + ServerIdentity = serverIdentity; + } + + /// + /// Gets or sets the server url. + /// + public string ServerAddress + { + get { return _serverAddress; } + set { SetPropertyValueAndDetectChanges(value, ref _serverAddress, Ps.Value.ServerAddressSelector); } + } + + /// + /// Gets or sets the server unique identity. + /// + public string ServerIdentity + { + get { return _serverIdentity; } + set { SetPropertyValueAndDetectChanges(value, ref _serverIdentity, Ps.Value.ServerIdentitySelector); } + } + + /// + /// Gets or sets a value indicating whether the server is active. + /// + public bool IsActive + { + get { return _isActive; } + set { SetPropertyValueAndDetectChanges(value, ref _isActive, Ps.Value.IsActiveSelector); } + } + + /// + /// Gets or sets a value indicating whether the server is master. + /// + public bool IsMaster + { + get { return _isMaster; } + set { SetPropertyValueAndDetectChanges(value, ref _isMaster, Ps.Value.IsMasterSelector); } + } + + /// + /// Gets the date and time the registration was created. + /// + public DateTime Registered { get { return CreateDate; } set { CreateDate = value; }} + + /// + /// Gets the date and time the registration was last accessed. + /// + public DateTime Accessed { get { return UpdateDate; } set { UpdateDate = value; }} + + /// + /// Converts the value of this instance to its equivalent string representation. + /// + /// + public override string ToString() + { + return string.Format("{{\"{0}\", \"{1}\", {2}active, {3}master}}", ServerAddress, ServerIdentity, IsActive ? "" : "!", IsMaster ? "" : "!"); + } + } +} diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index 0c697eb51f..a228b70105 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -1,175 +1,175 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Linq; -using System.Runtime.Serialization; -using Umbraco.Core.IO; -using Umbraco.Core.Strings.Css; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Stylesheet file - /// - [Serializable] - [DataContract(IsReference = true)] - public class Stylesheet : File - { - public Stylesheet(string path) - : this(path, null) - { } - - internal Stylesheet(string path, Func getFileContent) - : base(path.EnsureEndsWith(".css"), getFileContent) - { - InitializeProperties(); - } - - private Lazy> _properties; - - private void InitializeProperties() - { - //if the value is already created, we need to be created and update the collection according to - //what is now in the content - if (_properties != null && _properties.IsValueCreated) - { - //re-parse it so we can check what properties are different and adjust the event handlers - var parsed = StylesheetHelper.ParseRules(Content).ToArray(); - var names = parsed.Select(x => x.Name).ToArray(); - var existing = _properties.Value.Where(x => names.InvariantContains(x.Name)).ToArray(); - //update existing - foreach (var stylesheetProperty in existing) - { - var updateFrom = parsed.Single(x => x.Name.InvariantEquals(stylesheetProperty.Name)); - //remove current event handler while we update, we'll reset it after - stylesheetProperty.PropertyChanged -= Property_PropertyChanged; - stylesheetProperty.Alias = updateFrom.Selector; - stylesheetProperty.Value = updateFrom.Styles; - //re-add - stylesheetProperty.PropertyChanged += Property_PropertyChanged; - } - //remove no longer existing - var nonExisting = _properties.Value.Where(x => names.InvariantContains(x.Name) == false).ToArray(); - foreach (var stylesheetProperty in nonExisting) - { - stylesheetProperty.PropertyChanged -= Property_PropertyChanged; - _properties.Value.Remove(stylesheetProperty); - } - //add new ones - var newItems = parsed.Where(x => _properties.Value.Select(p => p.Name).InvariantContains(x.Name) == false); - foreach (var stylesheetRule in newItems) - { - var prop = new StylesheetProperty(stylesheetRule.Name, stylesheetRule.Selector, stylesheetRule.Styles); - prop.PropertyChanged += Property_PropertyChanged; - _properties.Value.Add(prop); - } - } - - //we haven't read the properties yet so create the lazy delegate - _properties = new Lazy>(() => - { - var parsed = StylesheetHelper.ParseRules(Content); - return parsed.Select(statement => - { - var property = new StylesheetProperty(statement.Name, statement.Selector, statement.Styles); - property.PropertyChanged += Property_PropertyChanged; - return property; - - }).ToList(); - }); - } - - /// - /// If the property has changed then we need to update the content - /// - /// - /// - void Property_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - var prop = (StylesheetProperty) sender; - - //Ensure we are setting base.Content here so that the properties don't get reset and thus any event handlers would get reset too - base.Content = StylesheetHelper.ReplaceRule(Content, prop.Name, new StylesheetRule - { - Name = prop.Name, - Selector = prop.Alias, - Styles = prop.Value - }); - } - - /// - /// Gets or sets the Content of a File - /// - public override string Content - { - get { return base.Content; } - set - { - base.Content = value; - //re-set the properties so they are re-read from the content - InitializeProperties(); - } - } - - /// - /// Returns a list of umbraco back office enabled stylesheet properties - /// - /// - /// An umbraco back office enabled stylesheet property has a special prefix, for example: - /// - /// /** umb_name: MyPropertyName */ p { font-size: 1em; } - /// - [IgnoreDataMember] - public IEnumerable Properties - { - get { return _properties.Value; } - } - - /// - /// Adds an Umbraco stylesheet property for use in the back office - /// - /// - public void AddProperty(StylesheetProperty property) - { - if (Properties.Any(x => x.Name.InvariantEquals(property.Name))) - { - throw new DuplicateNameException("The property with the name " + property.Name + " already exists in the collection"); - } - - //now we need to serialize out the new property collection over-top of the string Content. - Content = StylesheetHelper.AppendRule(Content, new StylesheetRule - { - Name = property.Name, - Selector = property.Alias, - Styles = property.Value - }); - - //re-set lazy collection - InitializeProperties(); - } - - /// - /// Removes an Umbraco stylesheet property - /// - /// - public void RemoveProperty(string name) - { - if (Properties.Any(x => x.Name.InvariantEquals(name))) - { - Content = StylesheetHelper.ReplaceRule(Content, name, null); - } - } - - /// - /// Indicates whether the current entity has an identity, which in this case is a path/name. - /// - /// - /// Overrides the default Entity identity check. - /// - public override bool HasIdentity - { - get { return string.IsNullOrEmpty(Path) == false; } - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Linq; +using System.Runtime.Serialization; +using Umbraco.Core.IO; +using Umbraco.Core.Strings.Css; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Stylesheet file + /// + [Serializable] + [DataContract(IsReference = true)] + public class Stylesheet : File + { + public Stylesheet(string path) + : this(path, null) + { } + + internal Stylesheet(string path, Func getFileContent) + : base(path.EnsureEndsWith(".css"), getFileContent) + { + InitializeProperties(); + } + + private Lazy> _properties; + + private void InitializeProperties() + { + //if the value is already created, we need to be created and update the collection according to + //what is now in the content + if (_properties != null && _properties.IsValueCreated) + { + //re-parse it so we can check what properties are different and adjust the event handlers + var parsed = StylesheetHelper.ParseRules(Content).ToArray(); + var names = parsed.Select(x => x.Name).ToArray(); + var existing = _properties.Value.Where(x => names.InvariantContains(x.Name)).ToArray(); + //update existing + foreach (var stylesheetProperty in existing) + { + var updateFrom = parsed.Single(x => x.Name.InvariantEquals(stylesheetProperty.Name)); + //remove current event handler while we update, we'll reset it after + stylesheetProperty.PropertyChanged -= Property_PropertyChanged; + stylesheetProperty.Alias = updateFrom.Selector; + stylesheetProperty.Value = updateFrom.Styles; + //re-add + stylesheetProperty.PropertyChanged += Property_PropertyChanged; + } + //remove no longer existing + var nonExisting = _properties.Value.Where(x => names.InvariantContains(x.Name) == false).ToArray(); + foreach (var stylesheetProperty in nonExisting) + { + stylesheetProperty.PropertyChanged -= Property_PropertyChanged; + _properties.Value.Remove(stylesheetProperty); + } + //add new ones + var newItems = parsed.Where(x => _properties.Value.Select(p => p.Name).InvariantContains(x.Name) == false); + foreach (var stylesheetRule in newItems) + { + var prop = new StylesheetProperty(stylesheetRule.Name, stylesheetRule.Selector, stylesheetRule.Styles); + prop.PropertyChanged += Property_PropertyChanged; + _properties.Value.Add(prop); + } + } + + //we haven't read the properties yet so create the lazy delegate + _properties = new Lazy>(() => + { + var parsed = StylesheetHelper.ParseRules(Content); + return parsed.Select(statement => + { + var property = new StylesheetProperty(statement.Name, statement.Selector, statement.Styles); + property.PropertyChanged += Property_PropertyChanged; + return property; + + }).ToList(); + }); + } + + /// + /// If the property has changed then we need to update the content + /// + /// + /// + void Property_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + var prop = (StylesheetProperty) sender; + + //Ensure we are setting base.Content here so that the properties don't get reset and thus any event handlers would get reset too + base.Content = StylesheetHelper.ReplaceRule(Content, prop.Name, new StylesheetRule + { + Name = prop.Name, + Selector = prop.Alias, + Styles = prop.Value + }); + } + + /// + /// Gets or sets the Content of a File + /// + public override string Content + { + get { return base.Content; } + set + { + base.Content = value; + //re-set the properties so they are re-read from the content + InitializeProperties(); + } + } + + /// + /// Returns a list of umbraco back office enabled stylesheet properties + /// + /// + /// An umbraco back office enabled stylesheet property has a special prefix, for example: + /// + /// /** umb_name: MyPropertyName */ p { font-size: 1em; } + /// + [IgnoreDataMember] + public IEnumerable Properties + { + get { return _properties.Value; } + } + + /// + /// Adds an Umbraco stylesheet property for use in the back office + /// + /// + public void AddProperty(StylesheetProperty property) + { + if (Properties.Any(x => x.Name.InvariantEquals(property.Name))) + { + throw new DuplicateNameException("The property with the name " + property.Name + " already exists in the collection"); + } + + //now we need to serialize out the new property collection over-top of the string Content. + Content = StylesheetHelper.AppendRule(Content, new StylesheetRule + { + Name = property.Name, + Selector = property.Alias, + Styles = property.Value + }); + + //re-set lazy collection + InitializeProperties(); + } + + /// + /// Removes an Umbraco stylesheet property + /// + /// + public void RemoveProperty(string name) + { + if (Properties.Any(x => x.Name.InvariantEquals(name))) + { + Content = StylesheetHelper.ReplaceRule(Content, name, null); + } + } + + /// + /// Indicates whether the current entity has an identity, which in this case is a path/name. + /// + /// + /// Overrides the default Entity identity check. + /// + public override bool HasIdentity + { + get { return string.IsNullOrEmpty(Path) == false; } + } + } +} diff --git a/src/Umbraco.Core/Models/StylesheetProperty.cs b/src/Umbraco.Core/Models/StylesheetProperty.cs index 2793cd0414..1601ca3e76 100644 --- a/src/Umbraco.Core/Models/StylesheetProperty.cs +++ b/src/Umbraco.Core/Models/StylesheetProperty.cs @@ -1,60 +1,60 @@ -using System; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Stylesheet Property - /// - /// - /// Properties are always formatted to have a single selector, so it can be used in the backoffice - /// - [Serializable] - [DataContract(IsReference = true)] - public class StylesheetProperty : BeingDirtyBase, IValueObject - { - private string _alias; - private string _value; - - public StylesheetProperty(string name, string @alias, string value) - { - Name = name; - _alias = alias; - _value = value; - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); - } - - /// - /// The CSS rule name that can be used by Umbraco in the back office - /// - public string Name { get; private set; } - - /// - /// This is the CSS Selector - /// - public string Alias - { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } - } - - /// - /// The CSS value for the selector - /// - public string Value - { - get { return _value; } - set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } - } - - } -} +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Stylesheet Property + /// + /// + /// Properties are always formatted to have a single selector, so it can be used in the backoffice + /// + [Serializable] + [DataContract(IsReference = true)] + public class StylesheetProperty : BeingDirtyBase, IValueObject + { + private string _alias; + private string _value; + + public StylesheetProperty(string name, string @alias, string value) + { + Name = name; + _alias = alias; + _value = value; + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + } + + /// + /// The CSS rule name that can be used by Umbraco in the back office + /// + public string Name { get; private set; } + + /// + /// This is the CSS Selector + /// + public string Alias + { + get { return _alias; } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } + } + + /// + /// The CSS value for the selector + /// + public string Value + { + get { return _value; } + set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } + } + + } +} diff --git a/src/Umbraco.Core/Models/Tag.cs b/src/Umbraco.Core/Models/Tag.cs index 5309d1f2ea..867d43c257 100644 --- a/src/Umbraco.Core/Models/Tag.cs +++ b/src/Umbraco.Core/Models/Tag.cs @@ -1,61 +1,61 @@ -using System; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a tag entity. - /// - [Serializable] - [DataContract(IsReference = true)] - public class Tag : EntityBase, ITag - { - private static PropertySelectors _selectors; - - private string _group; - private string _text; - - /// - /// Initializes a new instance of the class. - /// - public Tag() - { } - - /// - /// Initializes a new instance of the class. - /// - public Tag(int id, string group, string text) - { - Id = id; - Text = text; - Group = group; - } - - private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); - - private class PropertySelectors - { - public readonly PropertyInfo Group = ExpressionHelper.GetPropertyInfo(x => x.Group); - public readonly PropertyInfo Text = ExpressionHelper.GetPropertyInfo(x => x.Text); - } - - /// - public string Group - { - get => _group; - set => SetPropertyValueAndDetectChanges(value, ref _group, Selectors.Group); - } - - /// - public string Text - { - get => _text; - set => SetPropertyValueAndDetectChanges(value, ref _text, Selectors.Text); - } - - /// - public int NodeCount { get; internal set; } - } -} +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a tag entity. + /// + [Serializable] + [DataContract(IsReference = true)] + public class Tag : EntityBase, ITag + { + private static PropertySelectors _selectors; + + private string _group; + private string _text; + + /// + /// Initializes a new instance of the class. + /// + public Tag() + { } + + /// + /// Initializes a new instance of the class. + /// + public Tag(int id, string group, string text) + { + Id = id; + Text = text; + Group = group; + } + + private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); + + private class PropertySelectors + { + public readonly PropertyInfo Group = ExpressionHelper.GetPropertyInfo(x => x.Group); + public readonly PropertyInfo Text = ExpressionHelper.GetPropertyInfo(x => x.Text); + } + + /// + public string Group + { + get => _group; + set => SetPropertyValueAndDetectChanges(value, ref _group, Selectors.Group); + } + + /// + public string Text + { + get => _text; + set => SetPropertyValueAndDetectChanges(value, ref _text, Selectors.Text); + } + + /// + public int NodeCount { get; internal set; } + } +} diff --git a/src/Umbraco.Core/Models/TaggableObjectTypes.cs b/src/Umbraco.Core/Models/TaggableObjectTypes.cs index e669fc3af0..fbd75e2100 100644 --- a/src/Umbraco.Core/Models/TaggableObjectTypes.cs +++ b/src/Umbraco.Core/Models/TaggableObjectTypes.cs @@ -1,13 +1,13 @@ -namespace Umbraco.Core.Models -{ - /// - /// Enum representing the taggable object types - /// - public enum TaggableObjectTypes - { - All, - Content, - Media, - Member - } -} +namespace Umbraco.Core.Models +{ + /// + /// Enum representing the taggable object types + /// + public enum TaggableObjectTypes + { + All, + Content, + Media, + Member + } +} diff --git a/src/Umbraco.Core/Models/Task.cs b/src/Umbraco.Core/Models/Task.cs index 1899463c49..cb35813619 100644 --- a/src/Umbraco.Core/Models/Task.cs +++ b/src/Umbraco.Core/Models/Task.cs @@ -1,100 +1,100 @@ -using System; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Task - /// - [Serializable] - [DataContract(IsReference = true)] - public class Task : EntityBase - { - private bool _closed; - private TaskType _taskType; - private int _entityId; - private int _ownerUserId; - private int _assigneeUserId; - private string _comment; - - public Task(TaskType taskType) - { - _taskType = taskType; - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ClosedSelector = ExpressionHelper.GetPropertyInfo(x => x.Closed); - public readonly PropertyInfo TaskTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.TaskType); - public readonly PropertyInfo EntityIdSelector = ExpressionHelper.GetPropertyInfo(x => x.EntityId); - public readonly PropertyInfo OwnerUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.OwnerUserId); - public readonly PropertyInfo AssigneeUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.AssigneeUserId); - public readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); - } - - /// - /// Gets or sets a boolean indicating whether the task is closed - /// - [DataMember] - public bool Closed - { - get { return _closed; } - set { SetPropertyValueAndDetectChanges(value, ref _closed, Ps.Value.ClosedSelector); } - } - - /// - /// Gets or sets the TaskType of the Task - /// - [DataMember] - public TaskType TaskType - { - get { return _taskType; } - set { SetPropertyValueAndDetectChanges(value, ref _taskType, Ps.Value.TaskTypeSelector); } - } - - /// - /// Gets or sets the Id of the entity, which this task is associated to - /// - [DataMember] - public int EntityId - { - get { return _entityId; } - set { SetPropertyValueAndDetectChanges(value, ref _entityId, Ps.Value.EntityIdSelector); } - } - - /// - /// Gets or sets the Id of the user, who owns this task - /// - [DataMember] - public int OwnerUserId - { - get { return _ownerUserId; } - set { SetPropertyValueAndDetectChanges(value, ref _ownerUserId, Ps.Value.OwnerUserIdSelector); } - } - - /// - /// Gets or sets the Id of the user, who is assigned to this task - /// - [DataMember] - public int AssigneeUserId - { - get { return _assigneeUserId; } - set { SetPropertyValueAndDetectChanges(value, ref _assigneeUserId, Ps.Value.AssigneeUserIdSelector); } - } - - /// - /// Gets or sets the Comment for the Task - /// - [DataMember] - public string Comment - { - get { return _comment; } - set { SetPropertyValueAndDetectChanges(value, ref _comment, Ps.Value.CommentSelector); } - } - - } -} +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Task + /// + [Serializable] + [DataContract(IsReference = true)] + public class Task : EntityBase + { + private bool _closed; + private TaskType _taskType; + private int _entityId; + private int _ownerUserId; + private int _assigneeUserId; + private string _comment; + + public Task(TaskType taskType) + { + _taskType = taskType; + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ClosedSelector = ExpressionHelper.GetPropertyInfo(x => x.Closed); + public readonly PropertyInfo TaskTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.TaskType); + public readonly PropertyInfo EntityIdSelector = ExpressionHelper.GetPropertyInfo(x => x.EntityId); + public readonly PropertyInfo OwnerUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.OwnerUserId); + public readonly PropertyInfo AssigneeUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.AssigneeUserId); + public readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); + } + + /// + /// Gets or sets a boolean indicating whether the task is closed + /// + [DataMember] + public bool Closed + { + get { return _closed; } + set { SetPropertyValueAndDetectChanges(value, ref _closed, Ps.Value.ClosedSelector); } + } + + /// + /// Gets or sets the TaskType of the Task + /// + [DataMember] + public TaskType TaskType + { + get { return _taskType; } + set { SetPropertyValueAndDetectChanges(value, ref _taskType, Ps.Value.TaskTypeSelector); } + } + + /// + /// Gets or sets the Id of the entity, which this task is associated to + /// + [DataMember] + public int EntityId + { + get { return _entityId; } + set { SetPropertyValueAndDetectChanges(value, ref _entityId, Ps.Value.EntityIdSelector); } + } + + /// + /// Gets or sets the Id of the user, who owns this task + /// + [DataMember] + public int OwnerUserId + { + get { return _ownerUserId; } + set { SetPropertyValueAndDetectChanges(value, ref _ownerUserId, Ps.Value.OwnerUserIdSelector); } + } + + /// + /// Gets or sets the Id of the user, who is assigned to this task + /// + [DataMember] + public int AssigneeUserId + { + get { return _assigneeUserId; } + set { SetPropertyValueAndDetectChanges(value, ref _assigneeUserId, Ps.Value.AssigneeUserIdSelector); } + } + + /// + /// Gets or sets the Comment for the Task + /// + [DataMember] + public string Comment + { + get { return _comment; } + set { SetPropertyValueAndDetectChanges(value, ref _comment, Ps.Value.CommentSelector); } + } + + } +} diff --git a/src/Umbraco.Core/Models/TaskType.cs b/src/Umbraco.Core/Models/TaskType.cs index 3b1b3ecff2..f5e6621239 100644 --- a/src/Umbraco.Core/Models/TaskType.cs +++ b/src/Umbraco.Core/Models/TaskType.cs @@ -1,39 +1,39 @@ -using System; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Task Type - /// - [Serializable] - [DataContract(IsReference = true)] - public class TaskType : EntityBase - { - private string _alias; - - public TaskType(string @alias) - { - _alias = alias; - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - } - - /// - /// Gets or sets the Alias of the TaskType - /// - [DataMember] - public string Alias - { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } - } - } -} +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Task Type + /// + [Serializable] + [DataContract(IsReference = true)] + public class TaskType : EntityBase + { + private string _alias; + + public TaskType(string @alias) + { + _alias = alias; + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + } + + /// + /// Gets or sets the Alias of the TaskType + /// + [DataMember] + public string Alias + { + get { return _alias; } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } + } + } +} diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index f7d74b2d5f..9cdbee1951 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -1,102 +1,102 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Reflection; -using System.Runtime.Serialization; -using System.Text; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Template file. - /// - [Serializable] - [DataContract(IsReference = true)] - public class Template : File, ITemplate - { - private string _alias; - private string _name; - private string _masterTemplateAlias; - private Lazy _masterTemplateId; - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo MasterTemplateAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateAlias); - public readonly PropertyInfo MasterTemplateIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.MasterTemplateId); - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - } - - public Template(string name, string alias) - : this(name, alias, (Func) null) - { } - - internal Template(string name, string alias, Func getFileContent) - : base(string.Empty, getFileContent) - { - _name = name; - _alias = alias.ToCleanString(CleanStringType.UnderscoreAlias); - _masterTemplateId = new Lazy(() => -1); - } - - [DataMember] - public Lazy MasterTemplateId - { - get { return _masterTemplateId; } - set { SetPropertyValueAndDetectChanges(value, ref _masterTemplateId, Ps.Value.MasterTemplateIdSelector); } - } - - public string MasterTemplateAlias - { - get { return _masterTemplateAlias; } - set { SetPropertyValueAndDetectChanges(value, ref _masterTemplateAlias, Ps.Value.MasterTemplateAliasSelector); } - } - - [DataMember] - public new string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - [DataMember] - public new string Alias - { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.UnderscoreAlias), ref _alias, Ps.Value.AliasSelector); } - } - - /// - /// Returns true if the template is used as a layout for other templates (i.e. it has 'children') - /// - public bool IsMasterTemplate { get; internal set; } - - public void SetMasterTemplate(ITemplate masterTemplate) - { - if (masterTemplate == null) - { - MasterTemplateId = new Lazy(() => -1); - MasterTemplateAlias = null; - } - else - { - MasterTemplateId = new Lazy(() => masterTemplate.Id); - MasterTemplateAlias = masterTemplate.Alias; - } - - } - - protected override void DeepCloneNameAndAlias(File clone) - { - // do nothing - prevents File from doing its stuff - } - } -} +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a Template file. + /// + [Serializable] + [DataContract(IsReference = true)] + public class Template : File, ITemplate + { + private string _alias; + private string _name; + private string _masterTemplateAlias; + private Lazy _masterTemplateId; + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo MasterTemplateAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateAlias); + public readonly PropertyInfo MasterTemplateIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.MasterTemplateId); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + } + + public Template(string name, string alias) + : this(name, alias, (Func) null) + { } + + internal Template(string name, string alias, Func getFileContent) + : base(string.Empty, getFileContent) + { + _name = name; + _alias = alias.ToCleanString(CleanStringType.UnderscoreAlias); + _masterTemplateId = new Lazy(() => -1); + } + + [DataMember] + public Lazy MasterTemplateId + { + get { return _masterTemplateId; } + set { SetPropertyValueAndDetectChanges(value, ref _masterTemplateId, Ps.Value.MasterTemplateIdSelector); } + } + + public string MasterTemplateAlias + { + get { return _masterTemplateAlias; } + set { SetPropertyValueAndDetectChanges(value, ref _masterTemplateAlias, Ps.Value.MasterTemplateAliasSelector); } + } + + [DataMember] + public new string Name + { + get { return _name; } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + } + + [DataMember] + public new string Alias + { + get { return _alias; } + set { SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.UnderscoreAlias), ref _alias, Ps.Value.AliasSelector); } + } + + /// + /// Returns true if the template is used as a layout for other templates (i.e. it has 'children') + /// + public bool IsMasterTemplate { get; internal set; } + + public void SetMasterTemplate(ITemplate masterTemplate) + { + if (masterTemplate == null) + { + MasterTemplateId = new Lazy(() => -1); + MasterTemplateAlias = null; + } + else + { + MasterTemplateId = new Lazy(() => masterTemplate.Id); + MasterTemplateAlias = masterTemplate.Alias; + } + + } + + protected override void DeepCloneNameAndAlias(File clone) + { + // do nothing - prevents File from doing its stuff + } + } +} diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index 817ef3ace8..ff080f2cc1 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -1,188 +1,188 @@ -using System; -using System.ComponentModel; -using Umbraco.Core.CodeAnnotations; - -namespace Umbraco.Core.Models -{ - /// - /// Enum used to represent the Umbraco Object Types and thier associated GUIDs - /// - public enum UmbracoObjectTypes - { - /// - /// Default value - /// - Unknown, - - - /// - /// Root - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.SystemRoot)] - [FriendlyName("Root")] - ROOT, - - /// - /// Document - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Document, typeof(IContent))] - [FriendlyName("Document")] - [UmbracoUdiType(Constants.UdiEntityType.Document)] - Document, - - /// - /// Media - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Media, typeof(IMedia))] - [FriendlyName("Media")] - [UmbracoUdiType(Constants.UdiEntityType.Media)] - Media, - - /// - /// Member Type - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberType, typeof(IMemberType))] - [FriendlyName("Member Type")] - [UmbracoUdiType(Constants.UdiEntityType.MemberType)] - MemberType, - - /// - /// Template - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Template, typeof(ITemplate))] - [FriendlyName("Template")] - [UmbracoUdiType(Constants.UdiEntityType.Template)] - Template, - - /// - /// Member Group - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup)] - [FriendlyName("Member Group")] - [UmbracoUdiType(Constants.UdiEntityType.MemberGroup)] - MemberGroup, - - /// - /// "Media Type - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaType, typeof(IMediaType))] - [FriendlyName("Media Type")] - [UmbracoUdiType(Constants.UdiEntityType.MediaType)] - MediaType, - - /// - /// Document Type - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentType, typeof(IContentType))] - [FriendlyName("Document Type")] - [UmbracoUdiType(Constants.UdiEntityType.DocumentType)] - DocumentType, - - /// - /// Recycle Bin - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.ContentRecycleBin)] - [FriendlyName("Recycle Bin")] - RecycleBin, - - /// - /// Stylesheet - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Stylesheet)] - [FriendlyName("Stylesheet")] - [UmbracoUdiType(Constants.UdiEntityType.Stylesheet)] - Stylesheet, - - /// - /// Member - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Member, typeof(IMember))] - [FriendlyName("Member")] - [UmbracoUdiType(Constants.UdiEntityType.Member)] - Member, - - /// - /// Data Type - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.DataType, typeof(IDataType))] - [FriendlyName("Data Type")] - [UmbracoUdiType(Constants.UdiEntityType.DataType)] - DataType, - - /// - /// Document type container - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentTypeContainer)] - [FriendlyName("Document Type Container")] - [UmbracoUdiType(Constants.UdiEntityType.DocumentTypeContainer)] - DocumentTypeContainer, - - /// - /// Media type container - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaTypeContainer)] - [FriendlyName("Media Type Container")] - [UmbracoUdiType(Constants.UdiEntityType.MediaTypeContainer)] - MediaTypeContainer, - - /// - /// Media type container - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.DataTypeContainer)] - [FriendlyName("Data Type Container")] - [UmbracoUdiType(Constants.UdiEntityType.DataTypeContainer)] - DataTypeContainer, - - /// - /// Relation type - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.RelationType)] - [FriendlyName("Relation Type")] - [UmbracoUdiType(Constants.UdiEntityType.RelationType)] - RelationType, - - /// - /// Forms Form - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsForm)] - [FriendlyName("Form")] - FormsForm, - - /// - /// Forms PreValue - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsPreValue)] - [FriendlyName("PreValue")] - FormsPreValue, - - /// - /// Forms DataSource - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsDataSource)] - [FriendlyName("DataSource")] - FormsDataSource, - - /// - /// Language - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Language)] - [FriendlyName("Language")] - Language, - - /// - /// Document Blueprint - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentBlueprint, typeof(IContent))] - [FriendlyName("DocumentBlueprint")] - [UmbracoUdiType(Constants.UdiEntityType.DocumentBlueprint)] - DocumentBlueprint, - - /// - /// Reserved Identifier - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.IdReservation)] - [FriendlyName("Identifier Reservation")] - IdReservation - - } -} +using System; +using System.ComponentModel; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Core.Models +{ + /// + /// Enum used to represent the Umbraco Object Types and thier associated GUIDs + /// + public enum UmbracoObjectTypes + { + /// + /// Default value + /// + Unknown, + + + /// + /// Root + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.SystemRoot)] + [FriendlyName("Root")] + ROOT, + + /// + /// Document + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.Document, typeof(IContent))] + [FriendlyName("Document")] + [UmbracoUdiType(Constants.UdiEntityType.Document)] + Document, + + /// + /// Media + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.Media, typeof(IMedia))] + [FriendlyName("Media")] + [UmbracoUdiType(Constants.UdiEntityType.Media)] + Media, + + /// + /// Member Type + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberType, typeof(IMemberType))] + [FriendlyName("Member Type")] + [UmbracoUdiType(Constants.UdiEntityType.MemberType)] + MemberType, + + /// + /// Template + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.Template, typeof(ITemplate))] + [FriendlyName("Template")] + [UmbracoUdiType(Constants.UdiEntityType.Template)] + Template, + + /// + /// Member Group + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup)] + [FriendlyName("Member Group")] + [UmbracoUdiType(Constants.UdiEntityType.MemberGroup)] + MemberGroup, + + /// + /// "Media Type + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaType, typeof(IMediaType))] + [FriendlyName("Media Type")] + [UmbracoUdiType(Constants.UdiEntityType.MediaType)] + MediaType, + + /// + /// Document Type + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentType, typeof(IContentType))] + [FriendlyName("Document Type")] + [UmbracoUdiType(Constants.UdiEntityType.DocumentType)] + DocumentType, + + /// + /// Recycle Bin + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.ContentRecycleBin)] + [FriendlyName("Recycle Bin")] + RecycleBin, + + /// + /// Stylesheet + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.Stylesheet)] + [FriendlyName("Stylesheet")] + [UmbracoUdiType(Constants.UdiEntityType.Stylesheet)] + Stylesheet, + + /// + /// Member + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.Member, typeof(IMember))] + [FriendlyName("Member")] + [UmbracoUdiType(Constants.UdiEntityType.Member)] + Member, + + /// + /// Data Type + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.DataType, typeof(IDataType))] + [FriendlyName("Data Type")] + [UmbracoUdiType(Constants.UdiEntityType.DataType)] + DataType, + + /// + /// Document type container + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentTypeContainer)] + [FriendlyName("Document Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.DocumentTypeContainer)] + DocumentTypeContainer, + + /// + /// Media type container + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaTypeContainer)] + [FriendlyName("Media Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.MediaTypeContainer)] + MediaTypeContainer, + + /// + /// Media type container + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.DataTypeContainer)] + [FriendlyName("Data Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.DataTypeContainer)] + DataTypeContainer, + + /// + /// Relation type + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.RelationType)] + [FriendlyName("Relation Type")] + [UmbracoUdiType(Constants.UdiEntityType.RelationType)] + RelationType, + + /// + /// Forms Form + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsForm)] + [FriendlyName("Form")] + FormsForm, + + /// + /// Forms PreValue + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsPreValue)] + [FriendlyName("PreValue")] + FormsPreValue, + + /// + /// Forms DataSource + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsDataSource)] + [FriendlyName("DataSource")] + FormsDataSource, + + /// + /// Language + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.Language)] + [FriendlyName("Language")] + Language, + + /// + /// Document Blueprint + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentBlueprint, typeof(IContent))] + [FriendlyName("DocumentBlueprint")] + [UmbracoUdiType(Constants.UdiEntityType.DocumentBlueprint)] + DocumentBlueprint, + + /// + /// Reserved Identifier + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.IdReservation)] + [FriendlyName("Identifier Reservation")] + IdReservation + + } +} diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 1b26d28764..82e4935616 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -1,429 +1,429 @@ -using System; -using System.Collections.Generic; -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.Entities; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Models -{ - public static class UserExtensions - { - public static IEnumerable GetPermissions(this IUser user, string path, IUserService userService) - { - return userService.GetPermissionsForPath(user, path).GetAllPermissions(); - } - - public static bool HasSectionAccess(this IUser user, string app) - { - var apps = user.AllowedSections; - return apps.Any(uApp => uApp.InvariantEquals(app)); - } - - /// - /// Determines whether this user is the 'super' user. - /// - public static bool IsSuper(this IUser user) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - return user.Id == Constants.Security.SuperUserId; - } - - /// - /// Determines whether this user belongs to the administrators group. - /// - /// The 'super' user does not automatically belongs to the administrators group. - public static bool IsAdmin(this IUser user) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); - } - - /// - /// Tries to lookup the user's gravatar to see if the endpoint can be reached, if so it returns the valid URL - /// - /// - /// - /// - /// A list of 5 different sized avatar URLs - /// - internal static string[] GetUserAvatarUrls(this IUser user, ICacheProvider staticCache) - { - //check if the user has explicitly removed all avatars including a gravatar, this will be possible and the value will be "none" - if (user.Avatar == "none") - { - return new string[0]; - } - - if (user.Avatar.IsNullOrWhiteSpace()) - { - var gravatarHash = user.Email.ToMd5(); - var gravatarUrl = "https://www.gravatar.com/avatar/" + gravatarHash + "?d=404"; - - //try gravatar - var gravatarAccess = staticCache.GetCacheItem("UserAvatar" + user.Id, () => - { - // Test if we can reach this URL, will fail when there's network or firewall errors - var request = (HttpWebRequest)WebRequest.Create(gravatarUrl); - // Require response within 10 seconds - request.Timeout = 10000; - try - { - using ((HttpWebResponse)request.GetResponse()) { } - } - catch (Exception) - { - // There was an HTTP or other error, return an null instead - return false; - } - return true; - }); - - if (gravatarAccess) - { - return new[] - { - gravatarUrl + "&s=30", - gravatarUrl + "&s=60", - gravatarUrl + "&s=90", - gravatarUrl + "&s=150", - gravatarUrl + "&s=300" - }; - } - - return new string[0]; - } - - //use the custom avatar - var avatarUrl = Current.FileSystems.MediaFileSystem.GetUrl(user.Avatar); - return new[] - { - avatarUrl + "?width=30&height=30&mode=crop", - avatarUrl + "?width=60&height=60&mode=crop", - avatarUrl + "?width=90&height=90&mode=crop", - avatarUrl + "?width=150&height=150&mode=crop", - avatarUrl + "?width=300&height=300&mode=crop" - }; - - } - - /// - /// Returns the culture info associated with this user, based on the language they're assigned to in the back office - /// - /// - /// - /// - /// - public static CultureInfo GetUserCulture(this IUser user, ILocalizedTextService textService, IGlobalSettings globalSettings) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - if (textService == null) throw new ArgumentNullException(nameof(textService)); - return GetUserCulture(user.Language, textService, globalSettings); - } - - internal static CultureInfo GetUserCulture(string userLanguage, ILocalizedTextService textService, IGlobalSettings globalSettings) - { - try - { - var culture = CultureInfo.GetCultureInfo(userLanguage.Replace("_", "-")); - //TODO: This is a hack because we store the user language as 2 chars instead of the full culture - // which is actually stored in the language files (which are also named with 2 chars!) so we need to attempt - // to convert to a supported full culture - var result = textService.ConvertToSupportedCultureWithRegionCode(culture); - return result; - } - catch (CultureNotFoundException) - { - //return the default one - return CultureInfo.GetCultureInfo(globalSettings.DefaultUILanguage); - } - } - - internal static bool HasContentRootAccess(this IUser user, IEntityService entityService) - { - return HasPathAccess(Constants.System.Root.ToInvariantString(), user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); - } - - internal static bool HasContentBinAccess(this IUser user, IEntityService entityService) - { - return HasPathAccess(Constants.System.RecycleBinContent.ToInvariantString(), user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); - } - - internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService) - { - return HasPathAccess(Constants.System.Root.ToInvariantString(), user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); - } - - internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService) - { - return HasPathAccess(Constants.System.RecycleBinMedia.ToInvariantString(), user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); - } - - internal static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService) - { - return HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); - } - - internal static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService) - { - return HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); - } - - internal static bool HasPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId) - { - switch (recycleBinId) - { - case Constants.System.RecycleBinMedia: - return HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), recycleBinId); - case Constants.System.RecycleBinContent: - return HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService), recycleBinId); - default: - 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)); - - // check for no access - if (startNodeIds.Length == 0) - return false; - - // check for root access - if (startNodeIds.Contains(Constants.System.Root)) - return true; - - 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(string.Concat(",", recycleBinId, ","))) - return false; - - // check for a start node in the path - 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.", nameof(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; - } - - /// - /// Determines whether this user has access to view sensitive data - /// - /// - public static bool HasAccessToSensitiveData(this IUser user) - { - if (user == null) throw new ArgumentNullException("user"); - return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); - } - - // calc. start nodes, combining groups' and user's, and excluding what's in the bin - public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService) - { - const string cacheKey = "AllContentStartNodes"; - //try to look them up from cache so we don't recalculate - 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(); - var usn = user.StartContentIds; - var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService); - ToUserCache(user, cacheKey, vals); - return vals; - } - - // calc. start nodes, combining groups' and user's, and excluding what's in the bin - public static int[] CalculateMediaStartNodeIds(this IUser user, IEntityService entityService) - { - const string cacheKey = "AllMediaStartNodes"; - //try to look them up from cache so we don't recalculate - 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(); - var usn = user.StartMediaIds; - var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService); - ToUserCache(user, cacheKey, vals); - return vals; - } - - 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 - { - if (!(user is User entityUser)) return null; - - lock (entityUser.AdditionalDataLock) - { - return entityUser.AdditionalData.TryGetValue(cacheKey, out var allContentStartNodes) - ? allContentStartNodes as T - : null; - } - } - - private static void ToUserCache(IUser user, string cacheKey, T vals) - where T: class - { - if (!(user is User entityUser)) return; - - lock (entityUser.AdditionalDataLock) - { - entityUser.AdditionalData[cacheKey] = vals; - } - } - - private static bool StartsWithPath(string test, string path) - { - return test.StartsWith(path) && test.Length > path.Length && test[path.Length] == ','; - } - - private static string GetBinPath(UmbracoObjectTypes objectType) - { - var binPath = Constants.System.Root + ","; - switch (objectType) - { - case UmbracoObjectTypes.Document: - binPath += Constants.System.RecycleBinContent; - break; - case UmbracoObjectTypes.Media: - binPath += Constants.System.RecycleBinMedia; - break; - default: - throw new ArgumentOutOfRangeException(nameof(objectType)); - } - return binPath; - } - - internal static int[] CombineStartNodes(UmbracoObjectTypes objectType, int[] groupSn, int[] userSn, IEntityService entityService) - { - // assume groupSn and userSn each don't contain duplicates - - var asn = groupSn.Concat(userSn).Distinct().ToArray(); - var paths = asn.Length > 0 - ? entityService.GetAllPaths(objectType, asn).ToDictionary(x => x.Id, x => x.Path) - : new Dictionary(); - - paths[Constants.System.Root] = Constants.System.Root.ToString(); // entityService does not get that one - - var binPath = GetBinPath(objectType); - - var lsn = new List(); - foreach (var sn in groupSn) - { - if (paths.TryGetValue(sn, out var snp) == false) continue; // ignore rogue node (no path) - - if (StartsWithPath(snp, binPath)) continue; // ignore bin - - if (lsn.Any(x => StartsWithPath(snp, paths[x]))) continue; // skip if something above this sn - lsn.RemoveAll(x => StartsWithPath(paths[x], snp)); // remove anything below this sn - lsn.Add(sn); - } - - var usn = new List(); - foreach (var sn in userSn) - { - if (paths.TryGetValue(sn, out var snp) == false) continue; // ignore rogue node (no path) - - if (StartsWithPath(snp, binPath)) continue; // ignore bin - - if (usn.Any(x => StartsWithPath(paths[x], snp))) continue; // skip if something below this sn - usn.RemoveAll(x => StartsWithPath(snp, paths[x])); // remove anything above this sn - usn.Add(sn); - } - - foreach (var sn in usn) - { - var snp = paths[sn]; // has to be here now - lsn.RemoveAll(x => StartsWithPath(snp, paths[x]) || StartsWithPath(paths[x], snp)); // remove anything above or below this sn - lsn.Add(sn); - } - - return lsn.ToArray(); - } - } -} +using System; +using System.Collections.Generic; +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.Entities; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Models +{ + public static class UserExtensions + { + public static IEnumerable GetPermissions(this IUser user, string path, IUserService userService) + { + return userService.GetPermissionsForPath(user, path).GetAllPermissions(); + } + + public static bool HasSectionAccess(this IUser user, string app) + { + var apps = user.AllowedSections; + return apps.Any(uApp => uApp.InvariantEquals(app)); + } + + /// + /// Determines whether this user is the 'super' user. + /// + public static bool IsSuper(this IUser user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return user.Id == Constants.Security.SuperUserId; + } + + /// + /// Determines whether this user belongs to the administrators group. + /// + /// The 'super' user does not automatically belongs to the administrators group. + public static bool IsAdmin(this IUser user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); + } + + /// + /// Tries to lookup the user's gravatar to see if the endpoint can be reached, if so it returns the valid URL + /// + /// + /// + /// + /// A list of 5 different sized avatar URLs + /// + internal static string[] GetUserAvatarUrls(this IUser user, ICacheProvider staticCache) + { + //check if the user has explicitly removed all avatars including a gravatar, this will be possible and the value will be "none" + if (user.Avatar == "none") + { + return new string[0]; + } + + if (user.Avatar.IsNullOrWhiteSpace()) + { + var gravatarHash = user.Email.ToMd5(); + var gravatarUrl = "https://www.gravatar.com/avatar/" + gravatarHash + "?d=404"; + + //try gravatar + var gravatarAccess = staticCache.GetCacheItem("UserAvatar" + user.Id, () => + { + // Test if we can reach this URL, will fail when there's network or firewall errors + var request = (HttpWebRequest)WebRequest.Create(gravatarUrl); + // Require response within 10 seconds + request.Timeout = 10000; + try + { + using ((HttpWebResponse)request.GetResponse()) { } + } + catch (Exception) + { + // There was an HTTP or other error, return an null instead + return false; + } + return true; + }); + + if (gravatarAccess) + { + return new[] + { + gravatarUrl + "&s=30", + gravatarUrl + "&s=60", + gravatarUrl + "&s=90", + gravatarUrl + "&s=150", + gravatarUrl + "&s=300" + }; + } + + return new string[0]; + } + + //use the custom avatar + var avatarUrl = Current.FileSystems.MediaFileSystem.GetUrl(user.Avatar); + return new[] + { + avatarUrl + "?width=30&height=30&mode=crop", + avatarUrl + "?width=60&height=60&mode=crop", + avatarUrl + "?width=90&height=90&mode=crop", + avatarUrl + "?width=150&height=150&mode=crop", + avatarUrl + "?width=300&height=300&mode=crop" + }; + + } + + /// + /// Returns the culture info associated with this user, based on the language they're assigned to in the back office + /// + /// + /// + /// + /// + public static CultureInfo GetUserCulture(this IUser user, ILocalizedTextService textService, IGlobalSettings globalSettings) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + if (textService == null) throw new ArgumentNullException(nameof(textService)); + return GetUserCulture(user.Language, textService, globalSettings); + } + + internal static CultureInfo GetUserCulture(string userLanguage, ILocalizedTextService textService, IGlobalSettings globalSettings) + { + try + { + var culture = CultureInfo.GetCultureInfo(userLanguage.Replace("_", "-")); + //TODO: This is a hack because we store the user language as 2 chars instead of the full culture + // which is actually stored in the language files (which are also named with 2 chars!) so we need to attempt + // to convert to a supported full culture + var result = textService.ConvertToSupportedCultureWithRegionCode(culture); + return result; + } + catch (CultureNotFoundException) + { + //return the default one + return CultureInfo.GetCultureInfo(globalSettings.DefaultUILanguage); + } + } + + internal static bool HasContentRootAccess(this IUser user, IEntityService entityService) + { + return HasPathAccess(Constants.System.Root.ToInvariantString(), user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); + } + + internal static bool HasContentBinAccess(this IUser user, IEntityService entityService) + { + return HasPathAccess(Constants.System.RecycleBinContent.ToInvariantString(), user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); + } + + internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService) + { + return HasPathAccess(Constants.System.Root.ToInvariantString(), user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); + } + + internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService) + { + return HasPathAccess(Constants.System.RecycleBinMedia.ToInvariantString(), user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); + } + + internal static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService) + { + return HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent); + } + + internal static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService) + { + return HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); + } + + internal static bool HasPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId) + { + switch (recycleBinId) + { + case Constants.System.RecycleBinMedia: + return HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), recycleBinId); + case Constants.System.RecycleBinContent: + return HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService), recycleBinId); + default: + 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)); + + // check for no access + if (startNodeIds.Length == 0) + return false; + + // check for root access + if (startNodeIds.Contains(Constants.System.Root)) + return true; + + 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(string.Concat(",", recycleBinId, ","))) + return false; + + // check for a start node in the path + 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.", nameof(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; + } + + /// + /// Determines whether this user has access to view sensitive data + /// + /// + public static bool HasAccessToSensitiveData(this IUser user) + { + if (user == null) throw new ArgumentNullException("user"); + return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); + } + + // calc. start nodes, combining groups' and user's, and excluding what's in the bin + public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService) + { + const string cacheKey = "AllContentStartNodes"; + //try to look them up from cache so we don't recalculate + 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(); + var usn = user.StartContentIds; + var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService); + ToUserCache(user, cacheKey, vals); + return vals; + } + + // calc. start nodes, combining groups' and user's, and excluding what's in the bin + public static int[] CalculateMediaStartNodeIds(this IUser user, IEntityService entityService) + { + const string cacheKey = "AllMediaStartNodes"; + //try to look them up from cache so we don't recalculate + 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(); + var usn = user.StartMediaIds; + var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService); + ToUserCache(user, cacheKey, vals); + return vals; + } + + 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 + { + if (!(user is User entityUser)) return null; + + lock (entityUser.AdditionalDataLock) + { + return entityUser.AdditionalData.TryGetValue(cacheKey, out var allContentStartNodes) + ? allContentStartNodes as T + : null; + } + } + + private static void ToUserCache(IUser user, string cacheKey, T vals) + where T: class + { + if (!(user is User entityUser)) return; + + lock (entityUser.AdditionalDataLock) + { + entityUser.AdditionalData[cacheKey] = vals; + } + } + + private static bool StartsWithPath(string test, string path) + { + return test.StartsWith(path) && test.Length > path.Length && test[path.Length] == ','; + } + + private static string GetBinPath(UmbracoObjectTypes objectType) + { + var binPath = Constants.System.Root + ","; + switch (objectType) + { + case UmbracoObjectTypes.Document: + binPath += Constants.System.RecycleBinContent; + break; + case UmbracoObjectTypes.Media: + binPath += Constants.System.RecycleBinMedia; + break; + default: + throw new ArgumentOutOfRangeException(nameof(objectType)); + } + return binPath; + } + + internal static int[] CombineStartNodes(UmbracoObjectTypes objectType, int[] groupSn, int[] userSn, IEntityService entityService) + { + // assume groupSn and userSn each don't contain duplicates + + var asn = groupSn.Concat(userSn).Distinct().ToArray(); + var paths = asn.Length > 0 + ? entityService.GetAllPaths(objectType, asn).ToDictionary(x => x.Id, x => x.Path) + : new Dictionary(); + + paths[Constants.System.Root] = Constants.System.Root.ToString(); // entityService does not get that one + + var binPath = GetBinPath(objectType); + + var lsn = new List(); + foreach (var sn in groupSn) + { + if (paths.TryGetValue(sn, out var snp) == false) continue; // ignore rogue node (no path) + + if (StartsWithPath(snp, binPath)) continue; // ignore bin + + if (lsn.Any(x => StartsWithPath(snp, paths[x]))) continue; // skip if something above this sn + lsn.RemoveAll(x => StartsWithPath(paths[x], snp)); // remove anything below this sn + lsn.Add(sn); + } + + var usn = new List(); + foreach (var sn in userSn) + { + if (paths.TryGetValue(sn, out var snp) == false) continue; // ignore rogue node (no path) + + if (StartsWithPath(snp, binPath)) continue; // ignore bin + + if (usn.Any(x => StartsWithPath(paths[x], snp))) continue; // skip if something below this sn + usn.RemoveAll(x => StartsWithPath(snp, paths[x])); // remove anything above this sn + usn.Add(sn); + } + + foreach (var sn in usn) + { + var snp = paths[sn]; // has to be here now + lsn.RemoveAll(x => StartsWithPath(snp, paths[x]) || StartsWithPath(paths[x], snp)); // remove anything above or below this sn + lsn.Add(sn); + } + + return lsn.ToArray(); + } + } +} diff --git a/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs index c75b0496f3..f54741e489 100644 --- a/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs +++ b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs @@ -1,31 +1,31 @@ -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Reflection; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Models.Validation -{ - /// - /// Specifies that a data field value is required in order to persist an object. - /// - /// - /// There are two levels of validation in Umbraco. (1) value validation is performed by - /// instances; it can prevent a content item from being published, but not from being saved. (2) required validation - /// of properties marked with ; it does prevent an object from being saved - /// and is used for properties that are absolutely mandatory, such as the name of a content item. - /// - public class RequiredForPersistenceAttribute : RequiredAttribute - { - /// - /// Determines whether an object has all required values for persistence. - /// - internal static bool HasRequiredValuesForPersistence(object model) - { - return model.GetType().GetProperties().All(x => - { - var a = x.GetCustomAttribute(); - return a == null || a.IsValid(x.GetValue(model)); - }); - } - } -} +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Models.Validation +{ + /// + /// Specifies that a data field value is required in order to persist an object. + /// + /// + /// There are two levels of validation in Umbraco. (1) value validation is performed by + /// instances; it can prevent a content item from being published, but not from being saved. (2) required validation + /// of properties marked with ; it does prevent an object from being saved + /// and is used for properties that are absolutely mandatory, such as the name of a content item. + /// + public class RequiredForPersistenceAttribute : RequiredAttribute + { + /// + /// Determines whether an object has all required values for persistence. + /// + internal static bool HasRequiredValuesForPersistence(object model) + { + return model.GetType().GetProperties().All(x => + { + var a = x.GetCustomAttribute(); + return a == null || a.IsValid(x.GetValue(model)); + }); + } + } +} diff --git a/src/Umbraco.Core/NameValueCollectionExtensions.cs b/src/Umbraco.Core/NameValueCollectionExtensions.cs index 463d247f51..3c52989019 100644 --- a/src/Umbraco.Core/NameValueCollectionExtensions.cs +++ b/src/Umbraco.Core/NameValueCollectionExtensions.cs @@ -1,42 +1,42 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Text; - -namespace Umbraco.Core -{ - internal static class NameValueCollectionExtensions - { - public static IEnumerable> AsEnumerable(this NameValueCollection nvc) - { - foreach (string key in nvc.AllKeys) - { - yield return new KeyValuePair(key, nvc[key]); - } - } - - public static bool ContainsKey(this NameValueCollection collection, string key) - { - return collection.Keys.Cast().Any(k => (string) k == key); - } - - public static T GetValue(this NameValueCollection collection, string key, T defaultIfNotFound) - { - if (collection.ContainsKey(key) == false) - { - return defaultIfNotFound; - } - - var val = collection[key]; - if (val == null) - { - return defaultIfNotFound; - } - - var result = val.TryConvertTo(); - - return result.Success ? result.Result : defaultIfNotFound; - } - } -} +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; + +namespace Umbraco.Core +{ + internal static class NameValueCollectionExtensions + { + public static IEnumerable> AsEnumerable(this NameValueCollection nvc) + { + foreach (string key in nvc.AllKeys) + { + yield return new KeyValuePair(key, nvc[key]); + } + } + + public static bool ContainsKey(this NameValueCollection collection, string key) + { + return collection.Keys.Cast().Any(k => (string) k == key); + } + + public static T GetValue(this NameValueCollection collection, string key, T defaultIfNotFound) + { + if (collection.ContainsKey(key) == false) + { + return defaultIfNotFound; + } + + var val = collection[key]; + if (val == null) + { + return defaultIfNotFound; + } + + var result = val.TryConvertTo(); + + return result.Success ? result.Result : defaultIfNotFound; + } + } +} diff --git a/src/Umbraco.Core/NetworkHelper.cs b/src/Umbraco.Core/NetworkHelper.cs index 5f0bae70c4..e5e153966a 100644 --- a/src/Umbraco.Core/NetworkHelper.cs +++ b/src/Umbraco.Core/NetworkHelper.cs @@ -1,50 +1,50 @@ -using System; - -namespace Umbraco.Core -{ - /// - /// Currently just used to get the machine name in med trust and to format a machine name for use with file names - /// - internal class NetworkHelper - { - /// - /// Returns the machine name that is safe to use in file paths. - /// - /// - /// see: https://github.com/Shandem/ClientDependency/issues/4 - /// - public static string FileSafeMachineName - { - get { return MachineName.ReplaceNonAlphanumericChars('-'); } - } - - /// - /// Returns the current machine name - /// - /// - /// Tries to resolve the machine name, if it cannot it uses the config section. - /// - public static string MachineName - { - get - { - try - { - return Environment.MachineName; - } - catch - { - try - { - return System.Net.Dns.GetHostName(); - } - catch - { - //if we get here it means we cannot access the machine name - throw new ApplicationException("Cannot resolve the current machine name eithe by Environment.MachineName or by Dns.GetHostname()"); - } - } - } - } - } -} +using System; + +namespace Umbraco.Core +{ + /// + /// Currently just used to get the machine name in med trust and to format a machine name for use with file names + /// + internal class NetworkHelper + { + /// + /// Returns the machine name that is safe to use in file paths. + /// + /// + /// see: https://github.com/Shandem/ClientDependency/issues/4 + /// + public static string FileSafeMachineName + { + get { return MachineName.ReplaceNonAlphanumericChars('-'); } + } + + /// + /// Returns the current machine name + /// + /// + /// Tries to resolve the machine name, if it cannot it uses the config section. + /// + public static string MachineName + { + get + { + try + { + return Environment.MachineName; + } + catch + { + try + { + return System.Net.Dns.GetHostName(); + } + catch + { + //if we get here it means we cannot access the machine name + throw new ApplicationException("Cannot resolve the current machine name eithe by Environment.MachineName or by Dns.GetHostname()"); + } + } + } + } + } +} diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index fb994f92f5..44e5968a9f 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -1,792 +1,792 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Xml; -using Newtonsoft.Json; -using Umbraco.Core.Collections; - -namespace Umbraco.Core -{ - /// - /// Provides object extension methods. - /// - public static class ObjectExtensions - { - private static readonly ConcurrentDictionary> ToObjectTypes = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary NullableGenericCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary InputTypeConverterCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary DestinationTypeConverterCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary AssignableTypeCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary BoolConvertCache = new ConcurrentDictionary(); - - private static readonly char[] NumberDecimalSeparatorsToNormalize = { '.', ',' }; - private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new CustomBooleanTypeConverter(); - - //private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); - - /// - /// - /// - /// - /// - /// - public static IEnumerable AsEnumerableOfOne(this T input) - { - return Enumerable.Repeat(input, 1); - } - - /// - /// - /// - /// - public static void DisposeIfDisposable(this object input) - { - if (input is IDisposable disposable) - disposable.Dispose(); - } - - /// - /// Provides a shortcut way of safely casting an input when you cannot guarantee the is - /// an instance type (i.e., when the C# AS keyword is not applicable). - /// - /// - /// The input. - /// - internal static T SafeCast(this object input) - { - if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default; - if (input is T variable) return variable; - return default; - } - - /// - /// Attempts to convert the input object to the output type. - /// - /// This code is an optimized version of the original Umbraco method - /// The type to convert to - /// The input. - /// The - public static Attempt TryConvertTo(this object input) - { - var result = TryConvertTo(input, typeof(T)); - - if (result.Success) - return Attempt.Succeed((T)result.Result); - - // just try to cast - try - { - return Attempt.Succeed((T)input); - } - catch (Exception e) - { - return Attempt.Fail(e); - } - } - - /// - /// Attempts to convert the input object to the output type. - /// - /// This code is an optimized version of the original Umbraco method - /// The input. - /// The type to convert to - /// The - public static Attempt TryConvertTo(this object input, Type target) - { - if (target == null) - { - return Attempt.Fail(); - } - - try - { - if (input == null) - { - // Nullable is ok - if (target.IsGenericType && GetCachedGenericNullableType(target) != null) - { - return Attempt.Succeed(null); - } - - // Reference types are ok - return Attempt.If(target.IsValueType == false, null); - } - - var inputType = input.GetType(); - - // Easy - if (target == typeof(object) || inputType == target) - { - return Attempt.Succeed(input); - } - - // Check for string so that overloaders of ToString() can take advantage of the conversion. - if (target == typeof(string)) - { - return Attempt.Succeed(input.ToString()); - } - - // If we've got a nullable of something, we try to convert directly to that thing. - // We cache the destination type and underlying nullable types - // Any other generic types need to fall through - if (target.IsGenericType) - { - var underlying = GetCachedGenericNullableType(target); - if (underlying != null) - { - // Special case for empty strings for bools/dates which should return null if an empty string. - if (input is string inputString) - { - //TODO: Why the check against only bool/date when a string is null/empty? In what scenario can we convert to another type when the string is null or empty other than just being null? - if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool))) - { - return Attempt.Succeed(null); - } - } - - // Recursively call into this method with the inner (not-nullable) type and handle the outcome - var inner = input.TryConvertTo(underlying); - - // And if sucessful, fall on through to rewrap in a nullable; if failed, pass on the exception - if (inner.Success) - { - input = inner.Result; // Now fall on through... - } - else - { - return Attempt.Fail(inner.Exception); - } - } - } - else - { - // target is not a generic type - - if (input is string inputString) - { - // Try convert from string, returns an Attempt if the string could be - // processed (either succeeded or failed), else null if we need to try - // other methods - var result = TryConvertToFromString(inputString, target); - if (result.HasValue) - { - return result.Value; - } - } - - // TODO: Do a check for destination type being IEnumerable and source type implementing IEnumerable with - // the same 'T', then we'd have to find the extension method for the type AsEnumerable() and execute it. - if (GetCachedCanAssign(input, inputType, target)) - { - return Attempt.Succeed(Convert.ChangeType(input, target)); - } - } - - if (target == typeof(bool)) - { - if (GetCachedCanConvertToBoolean(inputType)) - { - return Attempt.Succeed(CustomBooleanTypeConverter.ConvertFrom(input)); - } - } - - var inputConverter = GetCachedSourceTypeConverter(inputType, target); - if (inputConverter != null) - { - return Attempt.Succeed(inputConverter.ConvertTo(input, target)); - } - - var outputConverter = GetCachedTargetTypeConverter(inputType, target); - if (outputConverter != null) - { - return Attempt.Succeed(outputConverter.ConvertFrom(input)); - } - - if (target.IsGenericType && GetCachedGenericNullableType(target) != null) - { - // cannot Convert.ChangeType as that does not work with nullable - // input has already been converted to the underlying type - just - // return input, there's an implicit conversion from T to T? anyways - return Attempt.Succeed(input); - } - - // Re-check convertables since we altered the input through recursion - if (input is IConvertible convertible2) - { - return Attempt.Succeed(Convert.ChangeType(convertible2, target)); - } - } - catch (Exception e) - { - return Attempt.Fail(e); - } - - return Attempt.Fail(); - } - - /// - /// Attempts to convert the input string to the output type. - /// - /// This code is an optimized version of the original Umbraco method - /// The input. - /// The type to convert to - /// The - private static Attempt? TryConvertToFromString(this string input, Type target) - { - // Easy - if (target == typeof(string)) - { - return Attempt.Succeed(input); - } - - // Null, empty, whitespaces - if (string.IsNullOrWhiteSpace(input)) - { - if (target == typeof(bool)) - { - // null/empty = bool false - return Attempt.Succeed(false); - } - - if (target == typeof(DateTime)) - { - // null/empty = min DateTime value - return Attempt.Succeed(DateTime.MinValue); - } - - // Cannot decide here, - // Any of the types below will fail parsing and will return a failed attempt - // but anything else will not be processed and will return null - // so even though the string is null/empty we have to proceed. - } - - // Look for type conversions in the expected order of frequency of use. - // - // By using a mixture of ordered if statements and switches we can optimize both for - // fast conditional checking for most frequently used types and the branching - // that does not depend on previous values available to switch statements. - if (target.IsPrimitive) - { - if (target == typeof(int)) - { - if (int.TryParse(input, out var value)) - { - return Attempt.Succeed(value); - } - - // Because decimal 100.01m will happily convert to integer 100, it - // makes sense that string "100.01" *also* converts to integer 100. - var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.If(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2)); - } - - if (target == typeof(long)) - { - if (long.TryParse(input, out var value)) - { - return Attempt.Succeed(value); - } - - // Same as int - var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.If(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2)); - } - - // TODO: Should we do the decimal trick for short, byte, unsigned? - - if (target == typeof(bool)) - { - if (bool.TryParse(input, out var value)) - { - return Attempt.Succeed(value); - } - - // Don't declare failure so the CustomBooleanTypeConverter can try - return null; - } - - // Calling this method directly is faster than any attempt to cache it. - switch (Type.GetTypeCode(target)) - { - case TypeCode.Int16: - return Attempt.If(short.TryParse(input, out var value), value); - - case TypeCode.Double: - var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.If(double.TryParse(input2, out var valueD), valueD); - - case TypeCode.Single: - var input3 = NormalizeNumberDecimalSeparator(input); - return Attempt.If(float.TryParse(input3, out var valueF), valueF); - - case TypeCode.Char: - return Attempt.If(char.TryParse(input, out var valueC), valueC); - - case TypeCode.Byte: - return Attempt.If(byte.TryParse(input, out var valueB), valueB); - - case TypeCode.SByte: - return Attempt.If(sbyte.TryParse(input, out var valueSb), valueSb); - - case TypeCode.UInt32: - return Attempt.If(uint.TryParse(input, out var valueU), valueU); - - case TypeCode.UInt16: - return Attempt.If(ushort.TryParse(input, out var valueUs), valueUs); - - case TypeCode.UInt64: - return Attempt.If(ulong.TryParse(input, out var valueUl), valueUl); - } - } - else if (target == typeof(Guid)) - { - return Attempt.If(Guid.TryParse(input, out var value), value); - } - else if (target == typeof(DateTime)) - { - if (DateTime.TryParse(input, out var value)) - { - switch (value.Kind) - { - case DateTimeKind.Unspecified: - case DateTimeKind.Utc: - return Attempt.Succeed(value); - - case DateTimeKind.Local: - return Attempt.Succeed(value.ToUniversalTime()); - - default: - throw new ArgumentOutOfRangeException(); - } - } - - return Attempt.Fail(); - } - else if (target == typeof(DateTimeOffset)) - { - return Attempt.If(DateTimeOffset.TryParse(input, out var value), value); - } - else if (target == typeof(TimeSpan)) - { - return Attempt.If(TimeSpan.TryParse(input, out var value), value); - } - else if (target == typeof(decimal)) - { - var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.If(decimal.TryParse(input2, out var value), value); - } - else if (input != null && target == typeof(Version)) - { - return Attempt.If(Version.TryParse(input, out var value), value); - } - - // E_NOTIMPL IPAddress, BigInteger - return null; // we can't decide... - } - internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) - { - //TODO: Localise this exception - if (isDisposed) - throw new ObjectDisposedException(objectname); - } - - //public enum PropertyNamesCaseType - //{ - // CamelCase, - // CaseInsensitive - //} - - ///// - ///// Convert an object to a JSON string with camelCase formatting - ///// - ///// - ///// - //public static string ToJsonString(this object obj) - //{ - // return obj.ToJsonString(PropertyNamesCaseType.CamelCase); - //} - - ///// - ///// Convert an object to a JSON string with the specified formatting - ///// - ///// The obj. - ///// Type of the property names case. - ///// - //public static string ToJsonString(this object obj, PropertyNamesCaseType propertyNamesCaseType) - //{ - // var type = obj.GetType(); - // var dateTimeStyle = "yyyy-MM-dd HH:mm:ss"; - - // if (type.IsPrimitive || typeof(string).IsAssignableFrom(type)) - // { - // return obj.ToString(); - // } - - // if (typeof(DateTime).IsAssignableFrom(type) || typeof(DateTimeOffset).IsAssignableFrom(type)) - // { - // return Convert.ToDateTime(obj).ToString(dateTimeStyle); - // } - - // var serializer = new JsonSerializer(); - - // switch (propertyNamesCaseType) - // { - // case PropertyNamesCaseType.CamelCase: - // serializer.ContractResolver = new CamelCasePropertyNamesContractResolver(); - // break; - // } - - // var dateTimeConverter = new IsoDateTimeConverter - // { - // DateTimeStyles = System.Globalization.DateTimeStyles.None, - // DateTimeFormat = dateTimeStyle - // }; - - // if (typeof(IDictionary).IsAssignableFrom(type)) - // { - // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); - // } - - // if (type.IsArray || (typeof(IEnumerable).IsAssignableFrom(type))) - // { - // return JArray.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); - // } - - // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); - //} - - /// - /// Converts an object into a dictionary - /// - /// - /// - /// - /// - /// - /// - public static IDictionary ToDictionary(this T o, params Expression>[] ignoreProperties) - { - return o.ToDictionary(ignoreProperties.Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray()); - } - - /// - /// Turns object into dictionary - /// - /// - /// Properties to ignore - /// - public static IDictionary ToDictionary(this object o, params string[] ignoreProperties) - { - if (o != null) - { - var props = TypeDescriptor.GetProperties(o); - var d = new Dictionary(); - foreach (var prop in props.Cast().Where(x => ignoreProperties.Contains(x.Name) == false)) - { - var val = prop.GetValue(o); - if (val != null) - { - d.Add(prop.Name, (TVal)val); - } - } - return d; - } - return new Dictionary(); - } - - /// - /// Converts an object's properties into a dictionary. - /// - /// The object to convert. - /// A property namer function. - /// A dictionary containing each properties. - public static Dictionary ToObjectDictionary(T obj, Func namer = null) - { - if (obj == null) return new Dictionary(); - - string DefaultNamer(PropertyInfo property) - { - var jsonProperty = property.GetCustomAttribute(); - return jsonProperty?.PropertyName ?? property.Name; - } - - var t = obj.GetType(); - - if (namer == null) namer = DefaultNamer; - - if (!ToObjectTypes.TryGetValue(t, out var properties)) - { - properties = new Dictionary(); - - foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)) - properties[namer(p)] = ReflectionUtilities.EmitPropertyGetter(p); - - ToObjectTypes[t] = properties; - } - - return properties.ToDictionary(x => x.Key, x => ((Func) x.Value)(obj)); - } - - internal static string ToDebugString(this object obj, int levels = 0) - { - if (obj == null) return "{null}"; - try - { - if (obj is string) - { - return "\"{0}\"".InvariantFormat(obj); - } - if (obj is int || obj is Int16 || obj is Int64 || obj is float || obj is double || obj is bool || obj is int? || obj is Int16? || obj is Int64? || obj is float? || obj is double? || obj is bool?) - { - return "{0}".InvariantFormat(obj); - } - if (obj is Enum) - { - return "[{0}]".InvariantFormat(obj); - } - if (obj is IEnumerable) - { - var enumerable = (obj as IEnumerable); - - var items = (from object enumItem in enumerable let value = GetEnumPropertyDebugString(enumItem, levels) where value != null select value).Take(10).ToList(); - - return items.Any() - ? "{{ {0} }}".InvariantFormat(string.Join(", ", items)) - : null; - } - - var props = obj.GetType().GetProperties(); - if ((props.Length == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2) - { - try - { - var key = props[0].GetValue(obj, null) as string; - var value = props[1].GetValue(obj, null).ToDebugString(levels - 1); - return "{0}={1}".InvariantFormat(key, value); - } - catch (Exception) - { - return "[KeyValuePropertyException]"; - } - } - if (levels > -1) - { - var items = - (from propertyInfo in props - let value = GetPropertyDebugString(propertyInfo, obj, levels) - where value != null - select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray(); - - return items.Any() - ? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, String.Join(", ", items)) - : null; - } - } - catch (Exception ex) - { - return "[Exception:{0}]".InvariantFormat(ex.Message); - } - return null; - } - - - /// - /// Attempts to serialize the value to an XmlString using ToXmlString - /// - /// - /// - /// - internal static Attempt TryConvertToXmlString(this object value, Type type) - { - try - { - var output = value.ToXmlString(type); - return Attempt.Succeed(output); - } - catch (NotSupportedException ex) - { - return Attempt.Fail(ex); - } - } - - /// - /// Returns an XmlSerialized safe string representation for the value - /// - /// - /// The Type can only be a primitive type or Guid and byte[] otherwise an exception is thrown - /// - internal static string ToXmlString(this object value, Type type) - { - if (value == null) return string.Empty; - if (type == typeof(string)) return (value.ToString().IsNullOrWhiteSpace() ? "" : value.ToString()); - if (type == typeof(bool)) return XmlConvert.ToString((bool)value); - if (type == typeof(byte)) return XmlConvert.ToString((byte)value); - if (type == typeof(char)) return XmlConvert.ToString((char)value); - if (type == typeof(DateTime)) return XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.Unspecified); - if (type == typeof(DateTimeOffset)) return XmlConvert.ToString((DateTimeOffset)value); - if (type == typeof(decimal)) return XmlConvert.ToString((decimal)value); - if (type == typeof(double)) return XmlConvert.ToString((double)value); - if (type == typeof(float)) return XmlConvert.ToString((float)value); - if (type == typeof(Guid)) return XmlConvert.ToString((Guid)value); - if (type == typeof(int)) return XmlConvert.ToString((int)value); - if (type == typeof(long)) return XmlConvert.ToString((long)value); - if (type == typeof(sbyte)) return XmlConvert.ToString((sbyte)value); - if (type == typeof(short)) return XmlConvert.ToString((short)value); - if (type == typeof(TimeSpan)) return XmlConvert.ToString((TimeSpan)value); - if (type == typeof(bool)) return XmlConvert.ToString((bool)value); - if (type == typeof(uint)) return XmlConvert.ToString((uint)value); - if (type == typeof(ulong)) return XmlConvert.ToString((ulong)value); - if (type == typeof(ushort)) return XmlConvert.ToString((ushort)value); - - throw new NotSupportedException("Cannot convert type " + type.FullName + " to a string using ToXmlString as it is not supported by XmlConvert"); - } - - /// - /// Returns an XmlSerialized safe string representation for the value and type - /// - /// - /// - /// - internal static string ToXmlString(this object value) - { - return value.ToXmlString(typeof (T)); - } - - private static string GetEnumPropertyDebugString(object enumItem, int levels) - { - try - { - return enumItem.ToDebugString(levels - 1); - } - catch (Exception) - { - return "[GetEnumPartException]"; - } - } - - private static string GetPropertyDebugString(PropertyInfo propertyInfo, object obj, int levels) - { - try - { - return propertyInfo.GetValue(obj, null).ToDebugString(levels - 1); - } - catch (Exception) - { - return "[GetPropertyValueException]"; - } - } - - internal static Guid AsGuid(this object value) - { - return value is Guid guid ? guid : Guid.Empty; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string NormalizeNumberDecimalSeparator(string s) - { - var normalized = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0]; - return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized); - } - - // gets a converter for source, that can convert to target, or null if none exists - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TypeConverter GetCachedSourceTypeConverter(Type source, Type target) - { - var key = new CompositeTypeTypeKey(source, target); - - if (InputTypeConverterCache.TryGetValue(key, out var typeConverter)) - { - return typeConverter; - } - - var converter = TypeDescriptor.GetConverter(source); - if (converter.CanConvertTo(target)) - { - return InputTypeConverterCache[key] = converter; - } - - return InputTypeConverterCache[key] = null; - } - - // gets a converter for target, that can convert from source, or null if none exists - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TypeConverter GetCachedTargetTypeConverter(Type source, Type target) - { - var key = new CompositeTypeTypeKey(source, target); - - if (DestinationTypeConverterCache.TryGetValue(key, out var typeConverter)) - { - return typeConverter; - } - - TypeConverter converter = TypeDescriptor.GetConverter(target); - if (converter.CanConvertFrom(source)) - { - return DestinationTypeConverterCache[key] = converter; - } - - return DestinationTypeConverterCache[key] = null; - } - - // gets the underlying type of a nullable type, or null if the type is not nullable - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Type GetCachedGenericNullableType(Type type) - { - if (NullableGenericCache.TryGetValue(type, out var underlyingType)) - { - return underlyingType; - } - - if (type.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - Type underlying = Nullable.GetUnderlyingType(type); - return NullableGenericCache[type] = underlying; - } - - return NullableGenericCache[type] = null; - } - - // gets an IConvertible from source to target type, or null if none exists - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool GetCachedCanAssign(object input, Type source, Type target) - { - var key = new CompositeTypeTypeKey(source, target); - if (AssignableTypeCache.TryGetValue(key, out var canConvert)) - { - return canConvert; - } - - // "object is" is faster than "Type.IsAssignableFrom. - // We can use it to very quickly determine whether true/false - if (input is IConvertible && target.IsAssignableFrom(source)) - { - return AssignableTypeCache[key] = true; - } - - return AssignableTypeCache[key] = false; - } - - // determines whether a type can be converted to boolean - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool GetCachedCanConvertToBoolean(Type type) - { - if (BoolConvertCache.TryGetValue(type, out var result)) - { - return result; - } - - if (CustomBooleanTypeConverter.CanConvertFrom(type)) - { - return BoolConvertCache[type] = true; - } - - return BoolConvertCache[type] = false; - } - } -} +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Xml; +using Newtonsoft.Json; +using Umbraco.Core.Collections; + +namespace Umbraco.Core +{ + /// + /// Provides object extension methods. + /// + public static class ObjectExtensions + { + private static readonly ConcurrentDictionary> ToObjectTypes = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary NullableGenericCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary InputTypeConverterCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary DestinationTypeConverterCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary AssignableTypeCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary BoolConvertCache = new ConcurrentDictionary(); + + private static readonly char[] NumberDecimalSeparatorsToNormalize = { '.', ',' }; + private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new CustomBooleanTypeConverter(); + + //private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); + + /// + /// + /// + /// + /// + /// + public static IEnumerable AsEnumerableOfOne(this T input) + { + return Enumerable.Repeat(input, 1); + } + + /// + /// + /// + /// + public static void DisposeIfDisposable(this object input) + { + if (input is IDisposable disposable) + disposable.Dispose(); + } + + /// + /// Provides a shortcut way of safely casting an input when you cannot guarantee the is + /// an instance type (i.e., when the C# AS keyword is not applicable). + /// + /// + /// The input. + /// + internal static T SafeCast(this object input) + { + if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default; + if (input is T variable) return variable; + return default; + } + + /// + /// Attempts to convert the input object to the output type. + /// + /// This code is an optimized version of the original Umbraco method + /// The type to convert to + /// The input. + /// The + public static Attempt TryConvertTo(this object input) + { + var result = TryConvertTo(input, typeof(T)); + + if (result.Success) + return Attempt.Succeed((T)result.Result); + + // just try to cast + try + { + return Attempt.Succeed((T)input); + } + catch (Exception e) + { + return Attempt.Fail(e); + } + } + + /// + /// Attempts to convert the input object to the output type. + /// + /// This code is an optimized version of the original Umbraco method + /// The input. + /// The type to convert to + /// The + public static Attempt TryConvertTo(this object input, Type target) + { + if (target == null) + { + return Attempt.Fail(); + } + + try + { + if (input == null) + { + // Nullable is ok + if (target.IsGenericType && GetCachedGenericNullableType(target) != null) + { + return Attempt.Succeed(null); + } + + // Reference types are ok + return Attempt.If(target.IsValueType == false, null); + } + + var inputType = input.GetType(); + + // Easy + if (target == typeof(object) || inputType == target) + { + return Attempt.Succeed(input); + } + + // Check for string so that overloaders of ToString() can take advantage of the conversion. + if (target == typeof(string)) + { + return Attempt.Succeed(input.ToString()); + } + + // If we've got a nullable of something, we try to convert directly to that thing. + // We cache the destination type and underlying nullable types + // Any other generic types need to fall through + if (target.IsGenericType) + { + var underlying = GetCachedGenericNullableType(target); + if (underlying != null) + { + // Special case for empty strings for bools/dates which should return null if an empty string. + if (input is string inputString) + { + //TODO: Why the check against only bool/date when a string is null/empty? In what scenario can we convert to another type when the string is null or empty other than just being null? + if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool))) + { + return Attempt.Succeed(null); + } + } + + // Recursively call into this method with the inner (not-nullable) type and handle the outcome + var inner = input.TryConvertTo(underlying); + + // And if sucessful, fall on through to rewrap in a nullable; if failed, pass on the exception + if (inner.Success) + { + input = inner.Result; // Now fall on through... + } + else + { + return Attempt.Fail(inner.Exception); + } + } + } + else + { + // target is not a generic type + + if (input is string inputString) + { + // Try convert from string, returns an Attempt if the string could be + // processed (either succeeded or failed), else null if we need to try + // other methods + var result = TryConvertToFromString(inputString, target); + if (result.HasValue) + { + return result.Value; + } + } + + // TODO: Do a check for destination type being IEnumerable and source type implementing IEnumerable with + // the same 'T', then we'd have to find the extension method for the type AsEnumerable() and execute it. + if (GetCachedCanAssign(input, inputType, target)) + { + return Attempt.Succeed(Convert.ChangeType(input, target)); + } + } + + if (target == typeof(bool)) + { + if (GetCachedCanConvertToBoolean(inputType)) + { + return Attempt.Succeed(CustomBooleanTypeConverter.ConvertFrom(input)); + } + } + + var inputConverter = GetCachedSourceTypeConverter(inputType, target); + if (inputConverter != null) + { + return Attempt.Succeed(inputConverter.ConvertTo(input, target)); + } + + var outputConverter = GetCachedTargetTypeConverter(inputType, target); + if (outputConverter != null) + { + return Attempt.Succeed(outputConverter.ConvertFrom(input)); + } + + if (target.IsGenericType && GetCachedGenericNullableType(target) != null) + { + // cannot Convert.ChangeType as that does not work with nullable + // input has already been converted to the underlying type - just + // return input, there's an implicit conversion from T to T? anyways + return Attempt.Succeed(input); + } + + // Re-check convertables since we altered the input through recursion + if (input is IConvertible convertible2) + { + return Attempt.Succeed(Convert.ChangeType(convertible2, target)); + } + } + catch (Exception e) + { + return Attempt.Fail(e); + } + + return Attempt.Fail(); + } + + /// + /// Attempts to convert the input string to the output type. + /// + /// This code is an optimized version of the original Umbraco method + /// The input. + /// The type to convert to + /// The + private static Attempt? TryConvertToFromString(this string input, Type target) + { + // Easy + if (target == typeof(string)) + { + return Attempt.Succeed(input); + } + + // Null, empty, whitespaces + if (string.IsNullOrWhiteSpace(input)) + { + if (target == typeof(bool)) + { + // null/empty = bool false + return Attempt.Succeed(false); + } + + if (target == typeof(DateTime)) + { + // null/empty = min DateTime value + return Attempt.Succeed(DateTime.MinValue); + } + + // Cannot decide here, + // Any of the types below will fail parsing and will return a failed attempt + // but anything else will not be processed and will return null + // so even though the string is null/empty we have to proceed. + } + + // Look for type conversions in the expected order of frequency of use. + // + // By using a mixture of ordered if statements and switches we can optimize both for + // fast conditional checking for most frequently used types and the branching + // that does not depend on previous values available to switch statements. + if (target.IsPrimitive) + { + if (target == typeof(int)) + { + if (int.TryParse(input, out var value)) + { + return Attempt.Succeed(value); + } + + // Because decimal 100.01m will happily convert to integer 100, it + // makes sense that string "100.01" *also* converts to integer 100. + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.If(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2)); + } + + if (target == typeof(long)) + { + if (long.TryParse(input, out var value)) + { + return Attempt.Succeed(value); + } + + // Same as int + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.If(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2)); + } + + // TODO: Should we do the decimal trick for short, byte, unsigned? + + if (target == typeof(bool)) + { + if (bool.TryParse(input, out var value)) + { + return Attempt.Succeed(value); + } + + // Don't declare failure so the CustomBooleanTypeConverter can try + return null; + } + + // Calling this method directly is faster than any attempt to cache it. + switch (Type.GetTypeCode(target)) + { + case TypeCode.Int16: + return Attempt.If(short.TryParse(input, out var value), value); + + case TypeCode.Double: + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.If(double.TryParse(input2, out var valueD), valueD); + + case TypeCode.Single: + var input3 = NormalizeNumberDecimalSeparator(input); + return Attempt.If(float.TryParse(input3, out var valueF), valueF); + + case TypeCode.Char: + return Attempt.If(char.TryParse(input, out var valueC), valueC); + + case TypeCode.Byte: + return Attempt.If(byte.TryParse(input, out var valueB), valueB); + + case TypeCode.SByte: + return Attempt.If(sbyte.TryParse(input, out var valueSb), valueSb); + + case TypeCode.UInt32: + return Attempt.If(uint.TryParse(input, out var valueU), valueU); + + case TypeCode.UInt16: + return Attempt.If(ushort.TryParse(input, out var valueUs), valueUs); + + case TypeCode.UInt64: + return Attempt.If(ulong.TryParse(input, out var valueUl), valueUl); + } + } + else if (target == typeof(Guid)) + { + return Attempt.If(Guid.TryParse(input, out var value), value); + } + else if (target == typeof(DateTime)) + { + if (DateTime.TryParse(input, out var value)) + { + switch (value.Kind) + { + case DateTimeKind.Unspecified: + case DateTimeKind.Utc: + return Attempt.Succeed(value); + + case DateTimeKind.Local: + return Attempt.Succeed(value.ToUniversalTime()); + + default: + throw new ArgumentOutOfRangeException(); + } + } + + return Attempt.Fail(); + } + else if (target == typeof(DateTimeOffset)) + { + return Attempt.If(DateTimeOffset.TryParse(input, out var value), value); + } + else if (target == typeof(TimeSpan)) + { + return Attempt.If(TimeSpan.TryParse(input, out var value), value); + } + else if (target == typeof(decimal)) + { + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.If(decimal.TryParse(input2, out var value), value); + } + else if (input != null && target == typeof(Version)) + { + return Attempt.If(Version.TryParse(input, out var value), value); + } + + // E_NOTIMPL IPAddress, BigInteger + return null; // we can't decide... + } + internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) + { + //TODO: Localise this exception + if (isDisposed) + throw new ObjectDisposedException(objectname); + } + + //public enum PropertyNamesCaseType + //{ + // CamelCase, + // CaseInsensitive + //} + + ///// + ///// Convert an object to a JSON string with camelCase formatting + ///// + ///// + ///// + //public static string ToJsonString(this object obj) + //{ + // return obj.ToJsonString(PropertyNamesCaseType.CamelCase); + //} + + ///// + ///// Convert an object to a JSON string with the specified formatting + ///// + ///// The obj. + ///// Type of the property names case. + ///// + //public static string ToJsonString(this object obj, PropertyNamesCaseType propertyNamesCaseType) + //{ + // var type = obj.GetType(); + // var dateTimeStyle = "yyyy-MM-dd HH:mm:ss"; + + // if (type.IsPrimitive || typeof(string).IsAssignableFrom(type)) + // { + // return obj.ToString(); + // } + + // if (typeof(DateTime).IsAssignableFrom(type) || typeof(DateTimeOffset).IsAssignableFrom(type)) + // { + // return Convert.ToDateTime(obj).ToString(dateTimeStyle); + // } + + // var serializer = new JsonSerializer(); + + // switch (propertyNamesCaseType) + // { + // case PropertyNamesCaseType.CamelCase: + // serializer.ContractResolver = new CamelCasePropertyNamesContractResolver(); + // break; + // } + + // var dateTimeConverter = new IsoDateTimeConverter + // { + // DateTimeStyles = System.Globalization.DateTimeStyles.None, + // DateTimeFormat = dateTimeStyle + // }; + + // if (typeof(IDictionary).IsAssignableFrom(type)) + // { + // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); + // } + + // if (type.IsArray || (typeof(IEnumerable).IsAssignableFrom(type))) + // { + // return JArray.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); + // } + + // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); + //} + + /// + /// Converts an object into a dictionary + /// + /// + /// + /// + /// + /// + /// + public static IDictionary ToDictionary(this T o, params Expression>[] ignoreProperties) + { + return o.ToDictionary(ignoreProperties.Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray()); + } + + /// + /// Turns object into dictionary + /// + /// + /// Properties to ignore + /// + public static IDictionary ToDictionary(this object o, params string[] ignoreProperties) + { + if (o != null) + { + var props = TypeDescriptor.GetProperties(o); + var d = new Dictionary(); + foreach (var prop in props.Cast().Where(x => ignoreProperties.Contains(x.Name) == false)) + { + var val = prop.GetValue(o); + if (val != null) + { + d.Add(prop.Name, (TVal)val); + } + } + return d; + } + return new Dictionary(); + } + + /// + /// Converts an object's properties into a dictionary. + /// + /// The object to convert. + /// A property namer function. + /// A dictionary containing each properties. + public static Dictionary ToObjectDictionary(T obj, Func namer = null) + { + if (obj == null) return new Dictionary(); + + string DefaultNamer(PropertyInfo property) + { + var jsonProperty = property.GetCustomAttribute(); + return jsonProperty?.PropertyName ?? property.Name; + } + + var t = obj.GetType(); + + if (namer == null) namer = DefaultNamer; + + if (!ToObjectTypes.TryGetValue(t, out var properties)) + { + properties = new Dictionary(); + + foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)) + properties[namer(p)] = ReflectionUtilities.EmitPropertyGetter(p); + + ToObjectTypes[t] = properties; + } + + return properties.ToDictionary(x => x.Key, x => ((Func) x.Value)(obj)); + } + + internal static string ToDebugString(this object obj, int levels = 0) + { + if (obj == null) return "{null}"; + try + { + if (obj is string) + { + return "\"{0}\"".InvariantFormat(obj); + } + if (obj is int || obj is Int16 || obj is Int64 || obj is float || obj is double || obj is bool || obj is int? || obj is Int16? || obj is Int64? || obj is float? || obj is double? || obj is bool?) + { + return "{0}".InvariantFormat(obj); + } + if (obj is Enum) + { + return "[{0}]".InvariantFormat(obj); + } + if (obj is IEnumerable) + { + var enumerable = (obj as IEnumerable); + + var items = (from object enumItem in enumerable let value = GetEnumPropertyDebugString(enumItem, levels) where value != null select value).Take(10).ToList(); + + return items.Any() + ? "{{ {0} }}".InvariantFormat(string.Join(", ", items)) + : null; + } + + var props = obj.GetType().GetProperties(); + if ((props.Length == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2) + { + try + { + var key = props[0].GetValue(obj, null) as string; + var value = props[1].GetValue(obj, null).ToDebugString(levels - 1); + return "{0}={1}".InvariantFormat(key, value); + } + catch (Exception) + { + return "[KeyValuePropertyException]"; + } + } + if (levels > -1) + { + var items = + (from propertyInfo in props + let value = GetPropertyDebugString(propertyInfo, obj, levels) + where value != null + select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray(); + + return items.Any() + ? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, String.Join(", ", items)) + : null; + } + } + catch (Exception ex) + { + return "[Exception:{0}]".InvariantFormat(ex.Message); + } + return null; + } + + + /// + /// Attempts to serialize the value to an XmlString using ToXmlString + /// + /// + /// + /// + internal static Attempt TryConvertToXmlString(this object value, Type type) + { + try + { + var output = value.ToXmlString(type); + return Attempt.Succeed(output); + } + catch (NotSupportedException ex) + { + return Attempt.Fail(ex); + } + } + + /// + /// Returns an XmlSerialized safe string representation for the value + /// + /// + /// The Type can only be a primitive type or Guid and byte[] otherwise an exception is thrown + /// + internal static string ToXmlString(this object value, Type type) + { + if (value == null) return string.Empty; + if (type == typeof(string)) return (value.ToString().IsNullOrWhiteSpace() ? "" : value.ToString()); + if (type == typeof(bool)) return XmlConvert.ToString((bool)value); + if (type == typeof(byte)) return XmlConvert.ToString((byte)value); + if (type == typeof(char)) return XmlConvert.ToString((char)value); + if (type == typeof(DateTime)) return XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.Unspecified); + if (type == typeof(DateTimeOffset)) return XmlConvert.ToString((DateTimeOffset)value); + if (type == typeof(decimal)) return XmlConvert.ToString((decimal)value); + if (type == typeof(double)) return XmlConvert.ToString((double)value); + if (type == typeof(float)) return XmlConvert.ToString((float)value); + if (type == typeof(Guid)) return XmlConvert.ToString((Guid)value); + if (type == typeof(int)) return XmlConvert.ToString((int)value); + if (type == typeof(long)) return XmlConvert.ToString((long)value); + if (type == typeof(sbyte)) return XmlConvert.ToString((sbyte)value); + if (type == typeof(short)) return XmlConvert.ToString((short)value); + if (type == typeof(TimeSpan)) return XmlConvert.ToString((TimeSpan)value); + if (type == typeof(bool)) return XmlConvert.ToString((bool)value); + if (type == typeof(uint)) return XmlConvert.ToString((uint)value); + if (type == typeof(ulong)) return XmlConvert.ToString((ulong)value); + if (type == typeof(ushort)) return XmlConvert.ToString((ushort)value); + + throw new NotSupportedException("Cannot convert type " + type.FullName + " to a string using ToXmlString as it is not supported by XmlConvert"); + } + + /// + /// Returns an XmlSerialized safe string representation for the value and type + /// + /// + /// + /// + internal static string ToXmlString(this object value) + { + return value.ToXmlString(typeof (T)); + } + + private static string GetEnumPropertyDebugString(object enumItem, int levels) + { + try + { + return enumItem.ToDebugString(levels - 1); + } + catch (Exception) + { + return "[GetEnumPartException]"; + } + } + + private static string GetPropertyDebugString(PropertyInfo propertyInfo, object obj, int levels) + { + try + { + return propertyInfo.GetValue(obj, null).ToDebugString(levels - 1); + } + catch (Exception) + { + return "[GetPropertyValueException]"; + } + } + + internal static Guid AsGuid(this object value) + { + return value is Guid guid ? guid : Guid.Empty; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string NormalizeNumberDecimalSeparator(string s) + { + var normalized = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0]; + return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized); + } + + // gets a converter for source, that can convert to target, or null if none exists + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TypeConverter GetCachedSourceTypeConverter(Type source, Type target) + { + var key = new CompositeTypeTypeKey(source, target); + + if (InputTypeConverterCache.TryGetValue(key, out var typeConverter)) + { + return typeConverter; + } + + var converter = TypeDescriptor.GetConverter(source); + if (converter.CanConvertTo(target)) + { + return InputTypeConverterCache[key] = converter; + } + + return InputTypeConverterCache[key] = null; + } + + // gets a converter for target, that can convert from source, or null if none exists + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TypeConverter GetCachedTargetTypeConverter(Type source, Type target) + { + var key = new CompositeTypeTypeKey(source, target); + + if (DestinationTypeConverterCache.TryGetValue(key, out var typeConverter)) + { + return typeConverter; + } + + TypeConverter converter = TypeDescriptor.GetConverter(target); + if (converter.CanConvertFrom(source)) + { + return DestinationTypeConverterCache[key] = converter; + } + + return DestinationTypeConverterCache[key] = null; + } + + // gets the underlying type of a nullable type, or null if the type is not nullable + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Type GetCachedGenericNullableType(Type type) + { + if (NullableGenericCache.TryGetValue(type, out var underlyingType)) + { + return underlyingType; + } + + if (type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + Type underlying = Nullable.GetUnderlyingType(type); + return NullableGenericCache[type] = underlying; + } + + return NullableGenericCache[type] = null; + } + + // gets an IConvertible from source to target type, or null if none exists + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool GetCachedCanAssign(object input, Type source, Type target) + { + var key = new CompositeTypeTypeKey(source, target); + if (AssignableTypeCache.TryGetValue(key, out var canConvert)) + { + return canConvert; + } + + // "object is" is faster than "Type.IsAssignableFrom. + // We can use it to very quickly determine whether true/false + if (input is IConvertible && target.IsAssignableFrom(source)) + { + return AssignableTypeCache[key] = true; + } + + return AssignableTypeCache[key] = false; + } + + // determines whether a type can be converted to boolean + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool GetCachedCanConvertToBoolean(Type type) + { + if (BoolConvertCache.TryGetValue(type, out var result)) + { + return result; + } + + if (CustomBooleanTypeConverter.CanConvertFrom(type)) + { + return BoolConvertCache[type] = true; + } + + return BoolConvertCache[type] = false; + } + } +} diff --git a/src/Umbraco.Core/Packaging/IPackageExtraction.cs b/src/Umbraco.Core/Packaging/IPackageExtraction.cs index c19ea733df..21a5198f55 100644 --- a/src/Umbraco.Core/Packaging/IPackageExtraction.cs +++ b/src/Umbraco.Core/Packaging/IPackageExtraction.cs @@ -1,62 +1,62 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Packaging -{ - /// - /// Used to access an umbraco package file - /// Remeber that filenames must be unique - /// use "FindDubletFileNames" for sanitycheck - /// - internal interface IPackageExtraction - { - /// - /// Returns the content of the file with the given filename - /// - /// Full path to the umbraco package file - /// filename of the file for wich to get the text content - /// this is the relative directory for the location of the file in the package - /// I dont know why umbraco packages contains directories in the first place?? - /// text content of the file - string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage); - - /// - /// Copies a file from package to given destination - /// - /// Full path to the ubraco package file - /// filename of the file to copy - /// destination path (including destination filename) - /// True a file was overwritten - void CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath); - - /// - /// Copies a file from package to given destination - /// - /// Full path to the ubraco package file - /// Key: Source file in package. Value: Destination path inclusive file name - void CopyFilesFromArchive(string packageFilePath, IEnumerable> sourceDestination); - - /// - /// Check if given list of files can be found in the package - /// - /// Full path to the umbraco package file - /// a list of files you would like to find in the package - /// a subset if any of the files in "expectedFiles" that could not be found in the package - IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles); - - - /// - /// Sanitycheck - should return en empty collection if package is valid - /// - /// Full path to the umbraco package file - /// list of files that are found more than ones (accross directories) in the package - IEnumerable FindDubletFileNames(string packageFilePath); - - /// - /// Reads the given files from archive and returns them as a collection of byte arrays - /// - /// - /// - /// - IEnumerable ReadFilesFromArchive(string packageFilePath, IEnumerable filesToGet); - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Packaging +{ + /// + /// Used to access an umbraco package file + /// Remeber that filenames must be unique + /// use "FindDubletFileNames" for sanitycheck + /// + internal interface IPackageExtraction + { + /// + /// Returns the content of the file with the given filename + /// + /// Full path to the umbraco package file + /// filename of the file for wich to get the text content + /// this is the relative directory for the location of the file in the package + /// I dont know why umbraco packages contains directories in the first place?? + /// text content of the file + string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage); + + /// + /// Copies a file from package to given destination + /// + /// Full path to the ubraco package file + /// filename of the file to copy + /// destination path (including destination filename) + /// True a file was overwritten + void CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath); + + /// + /// Copies a file from package to given destination + /// + /// Full path to the ubraco package file + /// Key: Source file in package. Value: Destination path inclusive file name + void CopyFilesFromArchive(string packageFilePath, IEnumerable> sourceDestination); + + /// + /// Check if given list of files can be found in the package + /// + /// Full path to the umbraco package file + /// a list of files you would like to find in the package + /// a subset if any of the files in "expectedFiles" that could not be found in the package + IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles); + + + /// + /// Sanitycheck - should return en empty collection if package is valid + /// + /// Full path to the umbraco package file + /// list of files that are found more than ones (accross directories) in the package + IEnumerable FindDubletFileNames(string packageFilePath); + + /// + /// Reads the given files from archive and returns them as a collection of byte arrays + /// + /// + /// + /// + IEnumerable ReadFilesFromArchive(string packageFilePath, IEnumerable filesToGet); + } +} diff --git a/src/Umbraco.Core/Packaging/IPackageInstallation.cs b/src/Umbraco.Core/Packaging/IPackageInstallation.cs index dac05c47f1..1d0d46355c 100644 --- a/src/Umbraco.Core/Packaging/IPackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/IPackageInstallation.cs @@ -1,13 +1,13 @@ -using System.Xml.Linq; -using Umbraco.Core.Models.Packaging; - -namespace Umbraco.Core.Packaging -{ - internal interface IPackageInstallation - { - InstallationSummary InstallPackage(string packageFilePath, int userId); - MetaData GetMetaData(string packageFilePath); - PreInstallWarnings GetPreInstallWarnings(string packageFilePath); - XElement GetConfigXmlElement(string packageFilePath); - } -} +using System.Xml.Linq; +using Umbraco.Core.Models.Packaging; + +namespace Umbraco.Core.Packaging +{ + internal interface IPackageInstallation + { + InstallationSummary InstallPackage(string packageFilePath, int userId); + MetaData GetMetaData(string packageFilePath); + PreInstallWarnings GetPreInstallWarnings(string packageFilePath); + XElement GetConfigXmlElement(string packageFilePath); + } +} diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs index 484a67dd9b..b392aca289 100644 --- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs +++ b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs @@ -1,295 +1,295 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security; -using System.Security.Permissions; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Packaging -{ - // Note - // That class uses ReflectionOnlyLoad which does NOT handle policies (bindingRedirect) and - // therefore raised warnings when installing a package, if an exact dependency could not be - // found, though it would be found via policies. So we have to explicitely apply policies - // where appropriate. - - internal class PackageBinaryInspector : MarshalByRefObject - { - /// - /// Entry point to call from your code - /// - /// - /// - /// - /// - /// - /// Will perform the assembly scan in a separate app domain - /// - public static IEnumerable ScanAssembliesForTypeReference(IEnumerable assemblys, out string[] errorReport) - { - // need to wrap in a safe call context in order to prevent whatever Umbraco stores - // in the logical call context from flowing to the created app domain (eg, the - // UmbracoDatabase is *not* serializable and cannot and should not flow). - using (new SafeCallContext()) - { - var appDomain = GetTempAppDomain(); - var type = typeof(PackageBinaryInspector); - try - { - var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( - type.Assembly.FullName, - type.FullName); - // do NOT turn PerformScan into static (even if ReSharper says so)! - var result = value.PerformScan(assemblys.ToArray(), out errorReport); - return result; - } - finally - { - AppDomain.Unload(appDomain); - } - } - } - - /// - /// Entry point to call from your code - /// - /// - /// - /// - /// - /// - /// Will perform the assembly scan in a separate app domain - /// - public static IEnumerable ScanAssembliesForTypeReference(string dllPath, out string[] errorReport) - { - var appDomain = GetTempAppDomain(); - var type = typeof(PackageBinaryInspector); - try - { - var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( - type.Assembly.FullName, - type.FullName); - // do NOT turn PerformScan into static (even if ReSharper says so)! - var result = value.PerformScan(dllPath, out errorReport); - return result; - } - finally - { - AppDomain.Unload(appDomain); - } - } - - /// - /// Performs the assembly scanning - /// - /// - /// - /// - /// - /// - /// This method is executed in a separate app domain - /// - private IEnumerable PerformScan(IEnumerable assemblies, out string[] errorReport) - { - //we need this handler to resolve assembly dependencies when loading below - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) => - { - var name = AppDomain.CurrentDomain.ApplyPolicy(e.Name); - var a = Assembly.ReflectionOnlyLoad(name); - if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name); - return a; - }; - - //First load each dll file into the context - // do NOT apply policy here: we want to scan the dlls that are in the binaries - var loaded = assemblies.Select(Assembly.ReflectionOnlyLoad).ToList(); - - //scan - return PerformScan(loaded, out errorReport); - } - - /// - /// Performs the assembly scanning - /// - /// - /// - /// - /// - /// - /// This method is executed in a separate app domain - /// - private IEnumerable PerformScan(string dllPath, out string[] errorReport) - { - if (Directory.Exists(dllPath) == false) - { - throw new DirectoryNotFoundException("Could not find directory " + dllPath); - } - - //we need this handler to resolve assembly dependencies when loading below - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) => - { - var name = AppDomain.CurrentDomain.ApplyPolicy(e.Name); - var a = Assembly.ReflectionOnlyLoad(name); - if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name); - return a; - }; - - //First load each dll file into the context - // do NOT apply policy here: we want to scan the dlls that are in the path - var files = Directory.GetFiles(dllPath, "*.dll"); - var loaded = files.Select(Assembly.ReflectionOnlyLoadFrom).ToList(); - - //scan - return PerformScan(loaded, out errorReport); - } - - private static IEnumerable PerformScan(IList loaded, out string[] errorReport) - { - var dllsWithReference = new List(); - var errors = new List(); - var assembliesWithErrors = new List(); - - //load each of the LoadFrom assemblies into the Load context too - foreach (var a in loaded) - { - var name = AppDomain.CurrentDomain.ApplyPolicy(a.FullName); - Assembly.ReflectionOnlyLoad(name); - } - - //get the list of assembly names to compare below - var loadedNames = loaded.Select(x => x.GetName().Name).ToArray(); - - //Then load each referenced assembly into the context - foreach (var a in loaded) - { - //don't load any referenced assemblies that are already found in the loaded array - this is based on name - // regardless of version. We'll assume that if the assembly found in the folder matches the assembly name - // being looked for, that is the version the user has shipped their package with and therefore it 'must' be correct - foreach (var assemblyName in a.GetReferencedAssemblies().Where(ass => loadedNames.Contains(ass.Name) == false)) - { - try - { - var name = AppDomain.CurrentDomain.ApplyPolicy(assemblyName.FullName); - Assembly.ReflectionOnlyLoad(name); - } - catch (FileNotFoundException) - { - //if an exception occurs it means that a referenced assembly could not be found - errors.Add( - string.Concat("This package references the assembly '", - assemblyName.Name, - "' which was not found")); - assembliesWithErrors.Add(a); - } - catch (Exception ex) - { - //if an exception occurs it means that a referenced assembly could not be found - errors.Add( - string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '", - assemblyName.Name, - "' see error log for full details.")); - assembliesWithErrors.Add(a); - Current.Logger.Error("An error occurred scanning package assemblies", ex); - } - } - } - - var contractType = GetLoadFromContractType(); - - //now that we have all referenced types into the context we can look up stuff - foreach (var a in loaded.Except(assembliesWithErrors)) - { - //now we need to see if they contain any type 'T' - var reflectedAssembly = a; - - try - { - var found = reflectedAssembly.GetExportedTypes() - .Where(contractType.IsAssignableFrom); - - if (found.Any()) - { - dllsWithReference.Add(reflectedAssembly.FullName); - } - } - catch (Exception ex) - { - //This is a hack that nobody can seem to get around, I've read everything and it seems that - // this is quite a common thing when loading types into reflection only load context, so - // we're just going to ignore this specific one for now - var typeLoadEx = ex as TypeLoadException; - if (typeLoadEx != null) - { - if (typeLoadEx.Message.InvariantContains("does not have an implementation")) - { - //ignore - continue; - } - } - else - { - errors.Add( - string.Concat("This package could not be verified for compatibility. An error occurred while scanning a packaged assembly '", - a.GetName().Name, - "' see error log for full details.")); - assembliesWithErrors.Add(a); - Current.Logger.Error("An error occurred scanning package assemblies", ex); - } - } - - } - - errorReport = errors.ToArray(); - return dllsWithReference; - } - - /// - /// In order to compare types, the types must be in the same context, this method will return the type that - /// we are checking against but from the Load context. - /// - /// - /// - private static Type GetLoadFromContractType() - { - var name = AppDomain.CurrentDomain.ApplyPolicy(typeof(T).Assembly.FullName); - var contractAssemblyLoadFrom = Assembly.ReflectionOnlyLoad(name); - - var contractType = contractAssemblyLoadFrom.GetExportedTypes() - .FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName); - - if (contractType == null) - { - throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies"); - } - return contractType; - } - - /// - /// Create an app domain - /// - /// - private static AppDomain GetTempAppDomain() - { - //copy the current app domain setup but don't shadow copy files - var appName = "TempDomain" + Guid.NewGuid(); - var domainSetup = new AppDomainSetup - { - ApplicationName = appName, - ShadowCopyFiles = "false", - ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase, - ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, - DynamicBase = AppDomain.CurrentDomain.SetupInformation.DynamicBase, - LicenseFile = AppDomain.CurrentDomain.SetupInformation.LicenseFile, - LoaderOptimization = AppDomain.CurrentDomain.SetupInformation.LoaderOptimization, - PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, - PrivateBinPathProbe = AppDomain.CurrentDomain.SetupInformation.PrivateBinPathProbe - }; - - // create new domain with full trust - return AppDomain.CreateDomain(appName, AppDomain.CurrentDomain.Evidence, domainSetup, new PermissionSet(PermissionState.Unrestricted)); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Packaging +{ + // Note + // That class uses ReflectionOnlyLoad which does NOT handle policies (bindingRedirect) and + // therefore raised warnings when installing a package, if an exact dependency could not be + // found, though it would be found via policies. So we have to explicitely apply policies + // where appropriate. + + internal class PackageBinaryInspector : MarshalByRefObject + { + /// + /// Entry point to call from your code + /// + /// + /// + /// + /// + /// + /// Will perform the assembly scan in a separate app domain + /// + public static IEnumerable ScanAssembliesForTypeReference(IEnumerable assemblys, out string[] errorReport) + { + // need to wrap in a safe call context in order to prevent whatever Umbraco stores + // in the logical call context from flowing to the created app domain (eg, the + // UmbracoDatabase is *not* serializable and cannot and should not flow). + using (new SafeCallContext()) + { + var appDomain = GetTempAppDomain(); + var type = typeof(PackageBinaryInspector); + try + { + var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( + type.Assembly.FullName, + type.FullName); + // do NOT turn PerformScan into static (even if ReSharper says so)! + var result = value.PerformScan(assemblys.ToArray(), out errorReport); + return result; + } + finally + { + AppDomain.Unload(appDomain); + } + } + } + + /// + /// Entry point to call from your code + /// + /// + /// + /// + /// + /// + /// Will perform the assembly scan in a separate app domain + /// + public static IEnumerable ScanAssembliesForTypeReference(string dllPath, out string[] errorReport) + { + var appDomain = GetTempAppDomain(); + var type = typeof(PackageBinaryInspector); + try + { + var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( + type.Assembly.FullName, + type.FullName); + // do NOT turn PerformScan into static (even if ReSharper says so)! + var result = value.PerformScan(dllPath, out errorReport); + return result; + } + finally + { + AppDomain.Unload(appDomain); + } + } + + /// + /// Performs the assembly scanning + /// + /// + /// + /// + /// + /// + /// This method is executed in a separate app domain + /// + private IEnumerable PerformScan(IEnumerable assemblies, out string[] errorReport) + { + //we need this handler to resolve assembly dependencies when loading below + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) => + { + var name = AppDomain.CurrentDomain.ApplyPolicy(e.Name); + var a = Assembly.ReflectionOnlyLoad(name); + if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name); + return a; + }; + + //First load each dll file into the context + // do NOT apply policy here: we want to scan the dlls that are in the binaries + var loaded = assemblies.Select(Assembly.ReflectionOnlyLoad).ToList(); + + //scan + return PerformScan(loaded, out errorReport); + } + + /// + /// Performs the assembly scanning + /// + /// + /// + /// + /// + /// + /// This method is executed in a separate app domain + /// + private IEnumerable PerformScan(string dllPath, out string[] errorReport) + { + if (Directory.Exists(dllPath) == false) + { + throw new DirectoryNotFoundException("Could not find directory " + dllPath); + } + + //we need this handler to resolve assembly dependencies when loading below + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) => + { + var name = AppDomain.CurrentDomain.ApplyPolicy(e.Name); + var a = Assembly.ReflectionOnlyLoad(name); + if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name); + return a; + }; + + //First load each dll file into the context + // do NOT apply policy here: we want to scan the dlls that are in the path + var files = Directory.GetFiles(dllPath, "*.dll"); + var loaded = files.Select(Assembly.ReflectionOnlyLoadFrom).ToList(); + + //scan + return PerformScan(loaded, out errorReport); + } + + private static IEnumerable PerformScan(IList loaded, out string[] errorReport) + { + var dllsWithReference = new List(); + var errors = new List(); + var assembliesWithErrors = new List(); + + //load each of the LoadFrom assemblies into the Load context too + foreach (var a in loaded) + { + var name = AppDomain.CurrentDomain.ApplyPolicy(a.FullName); + Assembly.ReflectionOnlyLoad(name); + } + + //get the list of assembly names to compare below + var loadedNames = loaded.Select(x => x.GetName().Name).ToArray(); + + //Then load each referenced assembly into the context + foreach (var a in loaded) + { + //don't load any referenced assemblies that are already found in the loaded array - this is based on name + // regardless of version. We'll assume that if the assembly found in the folder matches the assembly name + // being looked for, that is the version the user has shipped their package with and therefore it 'must' be correct + foreach (var assemblyName in a.GetReferencedAssemblies().Where(ass => loadedNames.Contains(ass.Name) == false)) + { + try + { + var name = AppDomain.CurrentDomain.ApplyPolicy(assemblyName.FullName); + Assembly.ReflectionOnlyLoad(name); + } + catch (FileNotFoundException) + { + //if an exception occurs it means that a referenced assembly could not be found + errors.Add( + string.Concat("This package references the assembly '", + assemblyName.Name, + "' which was not found")); + assembliesWithErrors.Add(a); + } + catch (Exception ex) + { + //if an exception occurs it means that a referenced assembly could not be found + errors.Add( + string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '", + assemblyName.Name, + "' see error log for full details.")); + assembliesWithErrors.Add(a); + Current.Logger.Error("An error occurred scanning package assemblies", ex); + } + } + } + + var contractType = GetLoadFromContractType(); + + //now that we have all referenced types into the context we can look up stuff + foreach (var a in loaded.Except(assembliesWithErrors)) + { + //now we need to see if they contain any type 'T' + var reflectedAssembly = a; + + try + { + var found = reflectedAssembly.GetExportedTypes() + .Where(contractType.IsAssignableFrom); + + if (found.Any()) + { + dllsWithReference.Add(reflectedAssembly.FullName); + } + } + catch (Exception ex) + { + //This is a hack that nobody can seem to get around, I've read everything and it seems that + // this is quite a common thing when loading types into reflection only load context, so + // we're just going to ignore this specific one for now + var typeLoadEx = ex as TypeLoadException; + if (typeLoadEx != null) + { + if (typeLoadEx.Message.InvariantContains("does not have an implementation")) + { + //ignore + continue; + } + } + else + { + errors.Add( + string.Concat("This package could not be verified for compatibility. An error occurred while scanning a packaged assembly '", + a.GetName().Name, + "' see error log for full details.")); + assembliesWithErrors.Add(a); + Current.Logger.Error("An error occurred scanning package assemblies", ex); + } + } + + } + + errorReport = errors.ToArray(); + return dllsWithReference; + } + + /// + /// In order to compare types, the types must be in the same context, this method will return the type that + /// we are checking against but from the Load context. + /// + /// + /// + private static Type GetLoadFromContractType() + { + var name = AppDomain.CurrentDomain.ApplyPolicy(typeof(T).Assembly.FullName); + var contractAssemblyLoadFrom = Assembly.ReflectionOnlyLoad(name); + + var contractType = contractAssemblyLoadFrom.GetExportedTypes() + .FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName); + + if (contractType == null) + { + throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies"); + } + return contractType; + } + + /// + /// Create an app domain + /// + /// + private static AppDomain GetTempAppDomain() + { + //copy the current app domain setup but don't shadow copy files + var appName = "TempDomain" + Guid.NewGuid(); + var domainSetup = new AppDomainSetup + { + ApplicationName = appName, + ShadowCopyFiles = "false", + ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase, + ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, + DynamicBase = AppDomain.CurrentDomain.SetupInformation.DynamicBase, + LicenseFile = AppDomain.CurrentDomain.SetupInformation.LicenseFile, + LoaderOptimization = AppDomain.CurrentDomain.SetupInformation.LoaderOptimization, + PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, + PrivateBinPathProbe = AppDomain.CurrentDomain.SetupInformation.PrivateBinPathProbe + }; + + // create new domain with full trust + return AppDomain.CreateDomain(appName, AppDomain.CurrentDomain.Evidence, domainSetup, new PermissionSet(PermissionState.Unrestricted)); + } + } +} diff --git a/src/Umbraco.Core/Packaging/PackageExtraction.cs b/src/Umbraco.Core/Packaging/PackageExtraction.cs index 752cc80697..cc3c394732 100644 --- a/src/Umbraco.Core/Packaging/PackageExtraction.cs +++ b/src/Umbraco.Core/Packaging/PackageExtraction.cs @@ -1,191 +1,191 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.IO.Compression; - -namespace Umbraco.Core.Packaging -{ - internal class PackageExtraction : IPackageExtraction - { - public string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage) - { - string retVal = null; - bool fileFound = false; - string foundDir = null; - - ReadZipfileEntries(packageFilePath, entry => - { - string fileName = Path.GetFileName(entry.Name); - - if (string.IsNullOrEmpty(fileName) == false && fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase)) - { - - foundDir = entry.Name.Substring(0, entry.Name.Length - fileName.Length); - fileFound = true; - using (var entryStream = entry.Open()) - using (var reader = new StreamReader(entryStream)) - { - retVal = reader.ReadToEnd(); - return false; - } - } - return true; - }); - - if (fileFound == false) - { - directoryInPackage = null; - throw new FileNotFoundException(string.Format("Could not find file in package {0}", packageFilePath), fileToRead); - } - directoryInPackage = foundDir; - return retVal; - } - - private static void CheckPackageExists(string packageFilePath) - { - if (string.IsNullOrEmpty(packageFilePath)) - { - throw new ArgumentNullException("packageFilePath"); - } - - if (File.Exists(packageFilePath) == false) - { - if (File.Exists(packageFilePath) == false) - throw new ArgumentException(string.Format("Package file: {0} could not be found", packageFilePath)); - } - - string extension = Path.GetExtension(packageFilePath).ToLower(); - - var alowedExtension = new[] { ".umb", ".zip" }; - - // Check if the file is a valid package - if (alowedExtension.All(ae => ae.Equals(extension) == false)) - { - throw new ArgumentException( - string.Format("Error - file isn't a package. only extentions: \"{0}\" is allowed", string.Join(", ", alowedExtension))); - } - } - - public void CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath) - { - CopyFilesFromArchive(packageFilePath, new[]{new KeyValuePair(fileInPackageName, destinationfilePath) } ); - } - - public void CopyFilesFromArchive(string packageFilePath, IEnumerable> sourceDestination) - { - var d = sourceDestination.ToDictionary(k => k.Key.ToLower(), v => v.Value); - - - ReadZipfileEntries(packageFilePath, entry => - { - string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); - if (fileName == string.Empty) { return true; } - - string destination; - if (string.IsNullOrEmpty(fileName) == false && d.TryGetValue(fileName, out destination)) - { - using (var streamWriter = File.Open(destination, FileMode.Create)) - using (var entryStream = entry.Open()) - { - entryStream.CopyTo(streamWriter); - } - - d.Remove(fileName); - return d.Any(); - } - return true; - }); - - if (d.Any()) - { - throw new ArgumentException(string.Format("The following source file(s): \"{0}\" could not be found in archive: \"{1}\"", string.Join("\", \"",d.Keys), packageFilePath)); - } - } - - public IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles) - { - var retVal = expectedFiles.ToList(); - - ReadZipfileEntries(packageFilePath, zipEntry => - { - string fileName = Path.GetFileName(zipEntry.Name); - - int index = retVal.FindIndex(f => f.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)); - - if (index != -1) { retVal.RemoveAt(index); } - - return retVal.Any(); - }); - return retVal; - - } - - public IEnumerable FindDubletFileNames(string packageFilePath) - { - var dictionary = new Dictionary>(); - - - ReadZipfileEntries(packageFilePath, entry => - { - string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); - - List list; - if (dictionary.TryGetValue(fileName, out list) == false) - { - list = new List(); - dictionary.Add(fileName, list); - } - - list.Add(entry.Name); - - return true; - }); - - return dictionary.Values.Where(v => v.Count > 1).SelectMany(v => v); - } - - public IEnumerable ReadFilesFromArchive(string packageFilePath, IEnumerable filesToGet) - { - CheckPackageExists(packageFilePath); - - var files = new HashSet(filesToGet.Select(f => f.ToLowerInvariant())); - - using (var fs = File.OpenRead(packageFilePath)) - using (var zipArchive = new ZipArchive(fs)) - { - foreach (var zipEntry in zipArchive.Entries) - { - if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/")) continue; - - if (files.Contains(zipEntry.Name.ToLowerInvariant())) - { - using (var memStream = new MemoryStream()) - using (var entryStream = zipEntry.Open()) - { - entryStream.CopyTo(memStream); - memStream.Close(); - yield return memStream.ToArray(); - } - } - } - } - } - - private void ReadZipfileEntries(string packageFilePath, Func entryFunc, bool skipsDirectories = true) - { - CheckPackageExists(packageFilePath); - - using (var fs = File.OpenRead(packageFilePath)) - using (var zipArchive = new ZipArchive(fs)) - { - foreach (var zipEntry in zipArchive.Entries) - { - if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/") && skipsDirectories) - continue; - if (entryFunc(zipEntry) == false) break; - } - } - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.IO.Compression; + +namespace Umbraco.Core.Packaging +{ + internal class PackageExtraction : IPackageExtraction + { + public string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage) + { + string retVal = null; + bool fileFound = false; + string foundDir = null; + + ReadZipfileEntries(packageFilePath, entry => + { + string fileName = Path.GetFileName(entry.Name); + + if (string.IsNullOrEmpty(fileName) == false && fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase)) + { + + foundDir = entry.Name.Substring(0, entry.Name.Length - fileName.Length); + fileFound = true; + using (var entryStream = entry.Open()) + using (var reader = new StreamReader(entryStream)) + { + retVal = reader.ReadToEnd(); + return false; + } + } + return true; + }); + + if (fileFound == false) + { + directoryInPackage = null; + throw new FileNotFoundException(string.Format("Could not find file in package {0}", packageFilePath), fileToRead); + } + directoryInPackage = foundDir; + return retVal; + } + + private static void CheckPackageExists(string packageFilePath) + { + if (string.IsNullOrEmpty(packageFilePath)) + { + throw new ArgumentNullException("packageFilePath"); + } + + if (File.Exists(packageFilePath) == false) + { + if (File.Exists(packageFilePath) == false) + throw new ArgumentException(string.Format("Package file: {0} could not be found", packageFilePath)); + } + + string extension = Path.GetExtension(packageFilePath).ToLower(); + + var alowedExtension = new[] { ".umb", ".zip" }; + + // Check if the file is a valid package + if (alowedExtension.All(ae => ae.Equals(extension) == false)) + { + throw new ArgumentException( + string.Format("Error - file isn't a package. only extentions: \"{0}\" is allowed", string.Join(", ", alowedExtension))); + } + } + + public void CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath) + { + CopyFilesFromArchive(packageFilePath, new[]{new KeyValuePair(fileInPackageName, destinationfilePath) } ); + } + + public void CopyFilesFromArchive(string packageFilePath, IEnumerable> sourceDestination) + { + var d = sourceDestination.ToDictionary(k => k.Key.ToLower(), v => v.Value); + + + ReadZipfileEntries(packageFilePath, entry => + { + string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); + if (fileName == string.Empty) { return true; } + + string destination; + if (string.IsNullOrEmpty(fileName) == false && d.TryGetValue(fileName, out destination)) + { + using (var streamWriter = File.Open(destination, FileMode.Create)) + using (var entryStream = entry.Open()) + { + entryStream.CopyTo(streamWriter); + } + + d.Remove(fileName); + return d.Any(); + } + return true; + }); + + if (d.Any()) + { + throw new ArgumentException(string.Format("The following source file(s): \"{0}\" could not be found in archive: \"{1}\"", string.Join("\", \"",d.Keys), packageFilePath)); + } + } + + public IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles) + { + var retVal = expectedFiles.ToList(); + + ReadZipfileEntries(packageFilePath, zipEntry => + { + string fileName = Path.GetFileName(zipEntry.Name); + + int index = retVal.FindIndex(f => f.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)); + + if (index != -1) { retVal.RemoveAt(index); } + + return retVal.Any(); + }); + return retVal; + + } + + public IEnumerable FindDubletFileNames(string packageFilePath) + { + var dictionary = new Dictionary>(); + + + ReadZipfileEntries(packageFilePath, entry => + { + string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); + + List list; + if (dictionary.TryGetValue(fileName, out list) == false) + { + list = new List(); + dictionary.Add(fileName, list); + } + + list.Add(entry.Name); + + return true; + }); + + return dictionary.Values.Where(v => v.Count > 1).SelectMany(v => v); + } + + public IEnumerable ReadFilesFromArchive(string packageFilePath, IEnumerable filesToGet) + { + CheckPackageExists(packageFilePath); + + var files = new HashSet(filesToGet.Select(f => f.ToLowerInvariant())); + + using (var fs = File.OpenRead(packageFilePath)) + using (var zipArchive = new ZipArchive(fs)) + { + foreach (var zipEntry in zipArchive.Entries) + { + if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/")) continue; + + if (files.Contains(zipEntry.Name.ToLowerInvariant())) + { + using (var memStream = new MemoryStream()) + using (var entryStream = zipEntry.Open()) + { + entryStream.CopyTo(memStream); + memStream.Close(); + yield return memStream.ToArray(); + } + } + } + } + } + + private void ReadZipfileEntries(string packageFilePath, Func entryFunc, bool skipsDirectories = true) + { + CheckPackageExists(packageFilePath); + + using (var fs = File.OpenRead(packageFilePath)) + using (var zipArchive = new ZipArchive(fs)) + { + foreach (var zipEntry in zipArchive.Entries) + { + if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/") && skipsDirectories) + continue; + if (entryFunc(zipEntry) == false) break; + } + } + } + } +} diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index 0d95a6096b..a6f1dd0f25 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -1,586 +1,586 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using System.Xml.XPath; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Packaging; -using Umbraco.Core.Services; -using File = System.IO.File; - -namespace Umbraco.Core.Packaging -{ - internal class PackageInstallation : IPackageInstallation - { - private readonly IFileService _fileService; - private readonly IMacroService _macroService; - private readonly IPackagingService _packagingService; - private IConflictingPackageData _conflictingPackageData; - private readonly IPackageExtraction _packageExtraction; - private string _fullPathToRoot; - - public PackageInstallation(IPackagingService packagingService, IMacroService macroService, - IFileService fileService, IPackageExtraction packageExtraction) - : this(packagingService, macroService, fileService, packageExtraction, IOHelper.GetRootDirectorySafe()) - {} - - public PackageInstallation(IPackagingService packagingService, IMacroService macroService, - IFileService fileService, IPackageExtraction packageExtraction, string fullPathToRoot) - { - if (packageExtraction != null) _packageExtraction = packageExtraction; - else throw new ArgumentNullException("packageExtraction"); - - if (macroService != null) _macroService = macroService; - else throw new ArgumentNullException("macroService"); - - if (fileService != null) _fileService = fileService; - else throw new ArgumentNullException("fileService"); - - if (packagingService != null) _packagingService = packagingService; - else throw new ArgumentNullException("packagingService"); - - _fullPathToRoot = fullPathToRoot; - } - - public IConflictingPackageData ConflictingPackageData - { - private get - { - return _conflictingPackageData ?? - (_conflictingPackageData = new ConflictingPackageData(_macroService, _fileService)); - } - set - { - if (_conflictingPackageData != null) - { - throw new PropertyConstraintException("This property already have a value"); - } - _conflictingPackageData = value; - } - } - - public string FullPathToRoot - { - private get { return _fullPathToRoot; } - set - { - - if (_fullPathToRoot != null) - { - throw new PropertyConstraintException("This property already have a value"); - } - - _fullPathToRoot = value; - } - } - - public MetaData GetMetaData(string packageFilePath) - { - try - { - XElement rootElement = GetConfigXmlElement(packageFilePath); - return GetMetaData(rootElement); - } - catch (Exception e) - { - throw new Exception("Error reading " + packageFilePath, e); - } - } - - public PreInstallWarnings GetPreInstallWarnings(string packageFilePath) - { - try - { - XElement rootElement = GetConfigXmlElement(packageFilePath); - return GetPreInstallWarnings(rootElement); - } - catch (Exception e) - { - throw new Exception("Error reading " + packageFilePath, e); - } - } - - public InstallationSummary InstallPackage(string packageFile, int userId) - { - XElement dataTypes; - XElement languages; - XElement dictionaryItems; - XElement macroes; - XElement files; - XElement templates; - XElement documentTypes; - XElement styleSheets; - XElement documentSet; - XElement documents; - XElement actions; - MetaData metaData; - InstallationSummary installationSummary; - - try - { - XElement rootElement = GetConfigXmlElement(packageFile); - PackageSupportedCheck(rootElement); - PackageStructureSanetyCheck(packageFile, rootElement); - dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); - languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); - dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryItemsNodeName); - macroes = rootElement.Element(Constants.Packaging.MacrosNodeName); - files = rootElement.Element(Constants.Packaging.FilesNodeName); - templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); - documentTypes = rootElement.Element(Constants.Packaging.DocumentTypesNodeName); - styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); - documentSet = rootElement.Element(Constants.Packaging.DocumentSetNodeName); - documents = rootElement.Element(Constants.Packaging.DocumentsNodeName); - actions = rootElement.Element(Constants.Packaging.ActionsNodeName); - - metaData = GetMetaData(rootElement); - installationSummary = new InstallationSummary {MetaData = metaData}; - } - catch (Exception e) - { - throw new Exception("Error reading " + packageFile, e); - } - - try - { - var dataTypeDefinitions = EmptyEnumerableIfNull(dataTypes) ?? InstallDataTypes(dataTypes, userId); - installationSummary.DataTypesInstalled = dataTypeDefinitions; - - var languagesInstalled = EmptyEnumerableIfNull(languages) ?? InstallLanguages(languages, userId); - installationSummary.LanguagesInstalled = languagesInstalled; - - var dictionaryInstalled = EmptyEnumerableIfNull(dictionaryItems) ?? InstallDictionaryItems(dictionaryItems); - installationSummary.DictionaryItemsInstalled = dictionaryInstalled; - - var macros = EmptyEnumerableIfNull(macroes) ?? InstallMacros(macroes, userId); - installationSummary.MacrosInstalled = macros; - - var keyValuePairs = EmptyEnumerableIfNull(packageFile) ?? InstallFiles(packageFile, files); - installationSummary.FilesInstalled = keyValuePairs; - - var templatesInstalled = EmptyEnumerableIfNull(templates) ?? InstallTemplats(templates, userId); - installationSummary.TemplatesInstalled = templatesInstalled; - - var documentTypesInstalled = EmptyEnumerableIfNull(documentTypes) ?? InstallDocumentTypes(documentTypes, userId); - installationSummary.ContentTypesInstalled =documentTypesInstalled; - - var stylesheetsInstalled = EmptyEnumerableIfNull(styleSheets) ?? InstallStylesheets(styleSheets); - installationSummary.StylesheetsInstalled = stylesheetsInstalled; - - var documentsInstalled = documents != null ? InstallDocuments(documents, userId) - : EmptyEnumerableIfNull(documentSet) - ?? InstallDocuments(documentSet, userId); - installationSummary.ContentInstalled = documentsInstalled; - - var packageActions = EmptyEnumerableIfNull(actions) ?? GetPackageActions(actions, metaData.Name); - installationSummary.Actions = packageActions; - - installationSummary.PackageInstalled = true; - - return installationSummary; - } - catch (Exception e) - { - throw new Exception("Error installing package " + packageFile, e); - } - } - - /// - /// Temperary check to test that we support stylesheets - /// - /// - private void PackageSupportedCheck(XElement rootElement) - { - XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); - if (styleSheets != null && styleSheets.Elements().Any()) - throw new NotSupportedException("Stylesheets is not suported in this version of umbraco"); - } - - private static T[] EmptyArrayIfNull(object obj) - { - return obj == null ? new T[0] : null; - } - - private static IEnumerable EmptyEnumerableIfNull(object obj) - { - return obj == null ? Enumerable.Empty() : null; - } - - private XDocument GetConfigXmlDoc(string packageFilePath) - { - var configXmlContent = _packageExtraction.ReadTextFileFromArchive(packageFilePath, - Constants.Packaging.PackageXmlFileName, out _); - - return XDocument.Parse(configXmlContent); - } - - public XElement GetConfigXmlElement(string packageFilePath) - { - XDocument document = GetConfigXmlDoc(packageFilePath); - if (document.Root == null || - document.Root.Name.LocalName.Equals(Constants.Packaging.UmbPackageNodeName) == false) - { - throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); - } - return document.Root; - } - - internal void PackageStructureSanetyCheck(string packageFilePath) - { - XElement rootElement = GetConfigXmlElement(packageFilePath); - PackageStructureSanetyCheck(packageFilePath, rootElement); - } - - private void PackageStructureSanetyCheck(string packageFilePath, XElement rootElement) - { - XElement filesElement = rootElement.Element(Constants.Packaging.FilesNodeName); - if (filesElement != null) - { - var sourceDestination = ExtractSourceDestinationFileInformation(filesElement).ToArray(); - - var missingFiles = _packageExtraction.FindMissingFiles(packageFilePath, sourceDestination.Select(i => i.Key)).ToArray(); - - if (missingFiles.Any()) - { - throw new Exception("The following file(s) are missing in the package: " + - string.Join(", ", missingFiles.Select( - mf => - { - var sd = sourceDestination.Single(fi => fi.Key == mf); - return string.Format("source: \"{0}\" destination: \"{1}\"", - sd.Key, sd.Value); - }))); - } - - IEnumerable dubletFileNames = _packageExtraction.FindDubletFileNames(packageFilePath).ToArray(); - - if (dubletFileNames.Any()) - { - throw new Exception("The following filename(s) are found more than one time in the package, since the filename is used ad primary key, this is not allowed: " + - string.Join(", ", dubletFileNames)); - } - } - } - - private static IEnumerable GetPackageActions(XElement actionsElement, string packageName) - { - if (actionsElement == null) { return new PackageAction[0]; } - - if (string.Equals(Constants.Packaging.ActionsNodeName, actionsElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.ActionsNodeName + "\" as root", - "actionsElement"); - } - - return actionsElement.Elements(Constants.Packaging.ActionNodeName) - .Select(elemet => - { - XAttribute aliasAttr = elemet.Attribute(Constants.Packaging.AliasNodeNameCapital); - if (aliasAttr == null) - throw new ArgumentException( - "missing \"" + Constants.Packaging.AliasNodeNameCapital + "\" atribute in alias element", - "actionsElement"); - - var packageAction = new PackageAction - { - XmlData = elemet, - Alias = aliasAttr.Value, - PackageName = packageName, - }; - - - var attr = elemet.Attribute(Constants.Packaging.RunatNodeAttribute); - - if (attr != null && Enum.TryParse(attr.Value, true, out ActionRunAt runAt)) { packageAction.RunAt = runAt; } - - attr = elemet.Attribute(Constants.Packaging.UndoNodeAttribute); - - if (attr != null && bool.TryParse(attr.Value, out var undo)) { packageAction.Undo = undo; } - - - return packageAction; - }).ToArray(); - } - - private IEnumerable InstallDocuments(XElement documentsElement, int userId = 0) - { - if ((string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName) == false) - && (string.Equals(Constants.Packaging.DocumentsNodeName, documentsElement.Name.LocalName) == false)) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentsNodeName + "\" as root", - "documentsElement"); - } - - if (string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName)) - return _packagingService.ImportContent(documentsElement, -1, userId); - - return - documentsElement.Elements(Constants.Packaging.DocumentSetNodeName) - .SelectMany(documentSetElement => _packagingService.ImportContent(documentSetElement, -1, userId)) - .ToArray(); - } - - private IEnumerable InstallStylesheets(XElement styleSheetsElement) - { - if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.StylesheetsNodeName + "\" as root", - "styleSheetsElement"); - } - - // TODO: Call _packagingService when import stylesheets import has been implimentet - if (styleSheetsElement.HasElements == false) { return new List(); } - - throw new NotImplementedException("The packaging service do not yes have a method for importing stylesheets"); - } - - private IEnumerable InstallDocumentTypes(XElement documentTypes, int userId = 0) - { - if (string.Equals(Constants.Packaging.DocumentTypesNodeName, documentTypes.Name.LocalName) == false) - { - if (string.Equals(Constants.Packaging.DocumentTypeNodeName, documentTypes.Name.LocalName) == false) - throw new ArgumentException( - "Must be \"" + Constants.Packaging.DocumentTypesNodeName + "\" as root", "documentTypes"); - - documentTypes = new XElement(Constants.Packaging.DocumentTypesNodeName, documentTypes); - } - - return _packagingService.ImportContentTypes(documentTypes, userId); - } - - private IEnumerable InstallTemplats(XElement templateElement, int userId = 0) - { - if (string.Equals(Constants.Packaging.TemplatesNodeName, templateElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.TemplatesNodeName + "\" as root", - "templateElement"); - } - return _packagingService.ImportTemplates(templateElement, userId); - } - - private IEnumerable InstallFiles(string packageFilePath, XElement filesElement) - { - var sourceDestination = ExtractSourceDestinationFileInformation(filesElement); - sourceDestination = AppendRootToDestination(FullPathToRoot, sourceDestination); - - _packageExtraction.CopyFilesFromArchive(packageFilePath, sourceDestination); - - return sourceDestination.Select(sd => sd.Value).ToArray(); - } - - private KeyValuePair[] AppendRootToDestination(string fullpathToRoot, IEnumerable> sourceDestination) - { - return - sourceDestination.Select( - sd => new KeyValuePair(sd.Key, Path.Combine(fullpathToRoot, sd.Value))).ToArray(); - } - - private IEnumerable InstallMacros(XElement macroElements, int userId = 0) - { - if (string.Equals(Constants.Packaging.MacrosNodeName, macroElements.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.MacrosNodeName + "\" as root", - "macroElements"); - } - return _packagingService.ImportMacros(macroElements, userId); - } - - private IEnumerable InstallDictionaryItems(XElement dictionaryItemsElement) - { - if (string.Equals(Constants.Packaging.DictionaryItemsNodeName, dictionaryItemsElement.Name.LocalName) == - false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.DictionaryItemsNodeName + "\" as root", - "dictionaryItemsElement"); - } - return _packagingService.ImportDictionaryItems(dictionaryItemsElement); - } - - private IEnumerable InstallLanguages(XElement languageElement, int userId = 0) - { - if (string.Equals(Constants.Packaging.LanguagesNodeName, languageElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.LanguagesNodeName + "\" as root", "languageElement"); - } - return _packagingService.ImportLanguages(languageElement, userId); - } - - private IEnumerable InstallDataTypes(XElement dataTypeElements, int userId = 0) - { - if (string.Equals(Constants.Packaging.DataTypesNodeName, dataTypeElements.Name.LocalName) == false) - { - if (string.Equals(Constants.Packaging.DataTypeNodeName, dataTypeElements.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.DataTypeNodeName + "\" as root", "dataTypeElements"); - } - } - return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId); - } - - private PreInstallWarnings GetPreInstallWarnings(XElement rootElement) - { - XElement files = rootElement.Element(Constants.Packaging.FilesNodeName); - XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); - XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); - XElement alias = rootElement.Element(Constants.Packaging.MacrosNodeName); - - var sourceDestination = EmptyArrayIfNull>(files) ?? ExtractSourceDestinationFileInformation(files); - - var installWarnings = new PreInstallWarnings(); - - var macroAliases = EmptyEnumerableIfNull(alias) ?? ConflictingPackageData.FindConflictingMacros(alias); - installWarnings.ConflictingMacroAliases = macroAliases; - - var templateAliases = EmptyEnumerableIfNull(templates) ?? ConflictingPackageData.FindConflictingTemplates(templates); - installWarnings.ConflictingTemplateAliases = templateAliases; - - var stylesheetNames = EmptyEnumerableIfNull(styleSheets) ?? ConflictingPackageData.FindConflictingStylesheets(styleSheets); - installWarnings.ConflictingStylesheetNames = stylesheetNames; - - installWarnings.UnsecureFiles = FindUnsecureFiles(sourceDestination); - installWarnings.FilesReplaced = FindFilesToBeReplaced(sourceDestination); - - return installWarnings; - } - - private KeyValuePair[] FindFilesToBeReplaced(IEnumerable> sourceDestination) - { - return sourceDestination.Where(sd => File.Exists(Path.Combine(FullPathToRoot, sd.Value))).ToArray(); - } - - private KeyValuePair[] FindUnsecureFiles(IEnumerable> sourceDestinationPair) - { - return sourceDestinationPair.Where(sd => IsFileDestinationUnsecure(sd.Value)).ToArray(); - } - - private bool IsFileDestinationUnsecure(string destination) - { - var unsecureDirNames = new[] {"bin", "app_code"}; - if(unsecureDirNames.Any(ud => destination.StartsWith(ud, StringComparison.InvariantCultureIgnoreCase))) - return true; - - string extension = Path.GetExtension(destination); - return extension != null && extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase); - } - - private KeyValuePair[] ExtractSourceDestinationFileInformation(XElement filesElement) - { - if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName) == false) - { - throw new ArgumentException("the root element must be \"Files\"", "filesElement"); - } - - return filesElement.Elements(Constants.Packaging.FileNodeName) - .Select(e => - { - XElement guidElement = e.Element(Constants.Packaging.GuidNodeName); - if (guidElement == null) - { - throw new ArgumentException("Missing element \"" + Constants.Packaging.GuidNodeName + "\"", - "filesElement"); - } - - XElement orgPathElement = e.Element(Constants.Packaging.OrgPathNodeName); - if (orgPathElement == null) - { - throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgPathNodeName + "\"", - "filesElement"); - } - - XElement orgNameElement = e.Element(Constants.Packaging.OrgNameNodeName); - if (orgNameElement == null) - { - throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgNameNodeName + "\"", - "filesElement"); - } - - var fileName = PrepareAsFilePathElement(orgNameElement.Value); - var relativeDir = UpdatePathPlaceholders(PrepareAsFilePathElement(orgPathElement.Value)); - - var relativePath = Path.Combine(relativeDir, fileName); - - - return new KeyValuePair(guidElement.Value, relativePath); - }).ToArray(); - } - - private static string PrepareAsFilePathElement(string pathElement) - { - return pathElement.TrimStart(new[] {'\\', '/', '~'}).Replace("/", "\\"); - } - - private MetaData GetMetaData(XElement xRootElement) - { - XElement infoElement = xRootElement.Element(Constants.Packaging.InfoNodeName); - - if (infoElement == null) - { - throw new ArgumentException("Did not hold a \"" + Constants.Packaging.InfoNodeName + "\" element", - "xRootElement"); - } - - var majorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMajorXpath); - var minorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMinorXpath); - var patchElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsPatchXpath); - var nameElement = infoElement.XPathSelectElement(Constants.Packaging.PackageNameXpath); - var versionElement = infoElement.XPathSelectElement(Constants.Packaging.PackageVersionXpath); - var urlElement = infoElement.XPathSelectElement(Constants.Packaging.PackageUrlXpath); - var licenseElement = infoElement.XPathSelectElement(Constants.Packaging.PackageLicenseXpath); - var authorNameElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorNameXpath); - var authorUrlElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorWebsiteXpath); - var readmeElement = infoElement.XPathSelectElement(Constants.Packaging.ReadmeXpath); - - XElement controlElement = xRootElement.Element(Constants.Packaging.ControlNodeName); - - return new MetaData - { - Name = StringValue(nameElement), - Version = StringValue(versionElement), - Url = StringValue(urlElement), - License = StringValue(licenseElement), - LicenseUrl = StringAttribute(licenseElement, Constants.Packaging.PackageLicenseXpathUrlAttribute), - AuthorName = StringValue(authorNameElement), - AuthorUrl = StringValue(authorUrlElement), - Readme = StringValue(readmeElement), - Control = StringValue(controlElement), - ReqMajor = IntValue(majorElement), - ReqMinor = IntValue(minorElement), - ReqPatch = IntValue(patchElement) - }; - } - - private static string StringValue(XElement xElement, string defaultValue = "") - { - return xElement == null ? defaultValue : xElement.Value; - } - - private static string StringAttribute(XElement xElement, string attribute, string defaultValue = "") - { - return xElement == null - ? defaultValue - : xElement.HasAttributes ? xElement.AttributeValue(attribute) : defaultValue; - } - - private static int IntValue(XElement xElement, int defaultValue = 0) - { - return xElement == null ? defaultValue : int.TryParse(xElement.Value, out var val) ? val : defaultValue; - } - - private static string UpdatePathPlaceholders(string path) - { - if (path.Contains("[$")) - { - //this is experimental and undocumented... - path = path.Replace("[$UMBRACO]", SystemDirectories.Umbraco); - path = path.Replace("[$UMBRACOCLIENT]", SystemDirectories.UmbracoClient); - path = path.Replace("[$CONFIG]", SystemDirectories.Config); - path = path.Replace("[$DATA]", SystemDirectories.Data); - } - return path; - } - } -} +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Packaging; +using Umbraco.Core.Services; +using File = System.IO.File; + +namespace Umbraco.Core.Packaging +{ + internal class PackageInstallation : IPackageInstallation + { + private readonly IFileService _fileService; + private readonly IMacroService _macroService; + private readonly IPackagingService _packagingService; + private IConflictingPackageData _conflictingPackageData; + private readonly IPackageExtraction _packageExtraction; + private string _fullPathToRoot; + + public PackageInstallation(IPackagingService packagingService, IMacroService macroService, + IFileService fileService, IPackageExtraction packageExtraction) + : this(packagingService, macroService, fileService, packageExtraction, IOHelper.GetRootDirectorySafe()) + {} + + public PackageInstallation(IPackagingService packagingService, IMacroService macroService, + IFileService fileService, IPackageExtraction packageExtraction, string fullPathToRoot) + { + if (packageExtraction != null) _packageExtraction = packageExtraction; + else throw new ArgumentNullException("packageExtraction"); + + if (macroService != null) _macroService = macroService; + else throw new ArgumentNullException("macroService"); + + if (fileService != null) _fileService = fileService; + else throw new ArgumentNullException("fileService"); + + if (packagingService != null) _packagingService = packagingService; + else throw new ArgumentNullException("packagingService"); + + _fullPathToRoot = fullPathToRoot; + } + + public IConflictingPackageData ConflictingPackageData + { + private get + { + return _conflictingPackageData ?? + (_conflictingPackageData = new ConflictingPackageData(_macroService, _fileService)); + } + set + { + if (_conflictingPackageData != null) + { + throw new PropertyConstraintException("This property already have a value"); + } + _conflictingPackageData = value; + } + } + + public string FullPathToRoot + { + private get { return _fullPathToRoot; } + set + { + + if (_fullPathToRoot != null) + { + throw new PropertyConstraintException("This property already have a value"); + } + + _fullPathToRoot = value; + } + } + + public MetaData GetMetaData(string packageFilePath) + { + try + { + XElement rootElement = GetConfigXmlElement(packageFilePath); + return GetMetaData(rootElement); + } + catch (Exception e) + { + throw new Exception("Error reading " + packageFilePath, e); + } + } + + public PreInstallWarnings GetPreInstallWarnings(string packageFilePath) + { + try + { + XElement rootElement = GetConfigXmlElement(packageFilePath); + return GetPreInstallWarnings(rootElement); + } + catch (Exception e) + { + throw new Exception("Error reading " + packageFilePath, e); + } + } + + public InstallationSummary InstallPackage(string packageFile, int userId) + { + XElement dataTypes; + XElement languages; + XElement dictionaryItems; + XElement macroes; + XElement files; + XElement templates; + XElement documentTypes; + XElement styleSheets; + XElement documentSet; + XElement documents; + XElement actions; + MetaData metaData; + InstallationSummary installationSummary; + + try + { + XElement rootElement = GetConfigXmlElement(packageFile); + PackageSupportedCheck(rootElement); + PackageStructureSanetyCheck(packageFile, rootElement); + dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); + languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); + dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryItemsNodeName); + macroes = rootElement.Element(Constants.Packaging.MacrosNodeName); + files = rootElement.Element(Constants.Packaging.FilesNodeName); + templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); + documentTypes = rootElement.Element(Constants.Packaging.DocumentTypesNodeName); + styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); + documentSet = rootElement.Element(Constants.Packaging.DocumentSetNodeName); + documents = rootElement.Element(Constants.Packaging.DocumentsNodeName); + actions = rootElement.Element(Constants.Packaging.ActionsNodeName); + + metaData = GetMetaData(rootElement); + installationSummary = new InstallationSummary {MetaData = metaData}; + } + catch (Exception e) + { + throw new Exception("Error reading " + packageFile, e); + } + + try + { + var dataTypeDefinitions = EmptyEnumerableIfNull(dataTypes) ?? InstallDataTypes(dataTypes, userId); + installationSummary.DataTypesInstalled = dataTypeDefinitions; + + var languagesInstalled = EmptyEnumerableIfNull(languages) ?? InstallLanguages(languages, userId); + installationSummary.LanguagesInstalled = languagesInstalled; + + var dictionaryInstalled = EmptyEnumerableIfNull(dictionaryItems) ?? InstallDictionaryItems(dictionaryItems); + installationSummary.DictionaryItemsInstalled = dictionaryInstalled; + + var macros = EmptyEnumerableIfNull(macroes) ?? InstallMacros(macroes, userId); + installationSummary.MacrosInstalled = macros; + + var keyValuePairs = EmptyEnumerableIfNull(packageFile) ?? InstallFiles(packageFile, files); + installationSummary.FilesInstalled = keyValuePairs; + + var templatesInstalled = EmptyEnumerableIfNull(templates) ?? InstallTemplats(templates, userId); + installationSummary.TemplatesInstalled = templatesInstalled; + + var documentTypesInstalled = EmptyEnumerableIfNull(documentTypes) ?? InstallDocumentTypes(documentTypes, userId); + installationSummary.ContentTypesInstalled =documentTypesInstalled; + + var stylesheetsInstalled = EmptyEnumerableIfNull(styleSheets) ?? InstallStylesheets(styleSheets); + installationSummary.StylesheetsInstalled = stylesheetsInstalled; + + var documentsInstalled = documents != null ? InstallDocuments(documents, userId) + : EmptyEnumerableIfNull(documentSet) + ?? InstallDocuments(documentSet, userId); + installationSummary.ContentInstalled = documentsInstalled; + + var packageActions = EmptyEnumerableIfNull(actions) ?? GetPackageActions(actions, metaData.Name); + installationSummary.Actions = packageActions; + + installationSummary.PackageInstalled = true; + + return installationSummary; + } + catch (Exception e) + { + throw new Exception("Error installing package " + packageFile, e); + } + } + + /// + /// Temperary check to test that we support stylesheets + /// + /// + private void PackageSupportedCheck(XElement rootElement) + { + XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); + if (styleSheets != null && styleSheets.Elements().Any()) + throw new NotSupportedException("Stylesheets is not suported in this version of umbraco"); + } + + private static T[] EmptyArrayIfNull(object obj) + { + return obj == null ? new T[0] : null; + } + + private static IEnumerable EmptyEnumerableIfNull(object obj) + { + return obj == null ? Enumerable.Empty() : null; + } + + private XDocument GetConfigXmlDoc(string packageFilePath) + { + var configXmlContent = _packageExtraction.ReadTextFileFromArchive(packageFilePath, + Constants.Packaging.PackageXmlFileName, out _); + + return XDocument.Parse(configXmlContent); + } + + public XElement GetConfigXmlElement(string packageFilePath) + { + XDocument document = GetConfigXmlDoc(packageFilePath); + if (document.Root == null || + document.Root.Name.LocalName.Equals(Constants.Packaging.UmbPackageNodeName) == false) + { + throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); + } + return document.Root; + } + + internal void PackageStructureSanetyCheck(string packageFilePath) + { + XElement rootElement = GetConfigXmlElement(packageFilePath); + PackageStructureSanetyCheck(packageFilePath, rootElement); + } + + private void PackageStructureSanetyCheck(string packageFilePath, XElement rootElement) + { + XElement filesElement = rootElement.Element(Constants.Packaging.FilesNodeName); + if (filesElement != null) + { + var sourceDestination = ExtractSourceDestinationFileInformation(filesElement).ToArray(); + + var missingFiles = _packageExtraction.FindMissingFiles(packageFilePath, sourceDestination.Select(i => i.Key)).ToArray(); + + if (missingFiles.Any()) + { + throw new Exception("The following file(s) are missing in the package: " + + string.Join(", ", missingFiles.Select( + mf => + { + var sd = sourceDestination.Single(fi => fi.Key == mf); + return string.Format("source: \"{0}\" destination: \"{1}\"", + sd.Key, sd.Value); + }))); + } + + IEnumerable dubletFileNames = _packageExtraction.FindDubletFileNames(packageFilePath).ToArray(); + + if (dubletFileNames.Any()) + { + throw new Exception("The following filename(s) are found more than one time in the package, since the filename is used ad primary key, this is not allowed: " + + string.Join(", ", dubletFileNames)); + } + } + } + + private static IEnumerable GetPackageActions(XElement actionsElement, string packageName) + { + if (actionsElement == null) { return new PackageAction[0]; } + + if (string.Equals(Constants.Packaging.ActionsNodeName, actionsElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.ActionsNodeName + "\" as root", + "actionsElement"); + } + + return actionsElement.Elements(Constants.Packaging.ActionNodeName) + .Select(elemet => + { + XAttribute aliasAttr = elemet.Attribute(Constants.Packaging.AliasNodeNameCapital); + if (aliasAttr == null) + throw new ArgumentException( + "missing \"" + Constants.Packaging.AliasNodeNameCapital + "\" atribute in alias element", + "actionsElement"); + + var packageAction = new PackageAction + { + XmlData = elemet, + Alias = aliasAttr.Value, + PackageName = packageName, + }; + + + var attr = elemet.Attribute(Constants.Packaging.RunatNodeAttribute); + + if (attr != null && Enum.TryParse(attr.Value, true, out ActionRunAt runAt)) { packageAction.RunAt = runAt; } + + attr = elemet.Attribute(Constants.Packaging.UndoNodeAttribute); + + if (attr != null && bool.TryParse(attr.Value, out var undo)) { packageAction.Undo = undo; } + + + return packageAction; + }).ToArray(); + } + + private IEnumerable InstallDocuments(XElement documentsElement, int userId = 0) + { + if ((string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName) == false) + && (string.Equals(Constants.Packaging.DocumentsNodeName, documentsElement.Name.LocalName) == false)) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentsNodeName + "\" as root", + "documentsElement"); + } + + if (string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName)) + return _packagingService.ImportContent(documentsElement, -1, userId); + + return + documentsElement.Elements(Constants.Packaging.DocumentSetNodeName) + .SelectMany(documentSetElement => _packagingService.ImportContent(documentSetElement, -1, userId)) + .ToArray(); + } + + private IEnumerable InstallStylesheets(XElement styleSheetsElement) + { + if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.StylesheetsNodeName + "\" as root", + "styleSheetsElement"); + } + + // TODO: Call _packagingService when import stylesheets import has been implimentet + if (styleSheetsElement.HasElements == false) { return new List(); } + + throw new NotImplementedException("The packaging service do not yes have a method for importing stylesheets"); + } + + private IEnumerable InstallDocumentTypes(XElement documentTypes, int userId = 0) + { + if (string.Equals(Constants.Packaging.DocumentTypesNodeName, documentTypes.Name.LocalName) == false) + { + if (string.Equals(Constants.Packaging.DocumentTypeNodeName, documentTypes.Name.LocalName) == false) + throw new ArgumentException( + "Must be \"" + Constants.Packaging.DocumentTypesNodeName + "\" as root", "documentTypes"); + + documentTypes = new XElement(Constants.Packaging.DocumentTypesNodeName, documentTypes); + } + + return _packagingService.ImportContentTypes(documentTypes, userId); + } + + private IEnumerable InstallTemplats(XElement templateElement, int userId = 0) + { + if (string.Equals(Constants.Packaging.TemplatesNodeName, templateElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.TemplatesNodeName + "\" as root", + "templateElement"); + } + return _packagingService.ImportTemplates(templateElement, userId); + } + + private IEnumerable InstallFiles(string packageFilePath, XElement filesElement) + { + var sourceDestination = ExtractSourceDestinationFileInformation(filesElement); + sourceDestination = AppendRootToDestination(FullPathToRoot, sourceDestination); + + _packageExtraction.CopyFilesFromArchive(packageFilePath, sourceDestination); + + return sourceDestination.Select(sd => sd.Value).ToArray(); + } + + private KeyValuePair[] AppendRootToDestination(string fullpathToRoot, IEnumerable> sourceDestination) + { + return + sourceDestination.Select( + sd => new KeyValuePair(sd.Key, Path.Combine(fullpathToRoot, sd.Value))).ToArray(); + } + + private IEnumerable InstallMacros(XElement macroElements, int userId = 0) + { + if (string.Equals(Constants.Packaging.MacrosNodeName, macroElements.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.MacrosNodeName + "\" as root", + "macroElements"); + } + return _packagingService.ImportMacros(macroElements, userId); + } + + private IEnumerable InstallDictionaryItems(XElement dictionaryItemsElement) + { + if (string.Equals(Constants.Packaging.DictionaryItemsNodeName, dictionaryItemsElement.Name.LocalName) == + false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.DictionaryItemsNodeName + "\" as root", + "dictionaryItemsElement"); + } + return _packagingService.ImportDictionaryItems(dictionaryItemsElement); + } + + private IEnumerable InstallLanguages(XElement languageElement, int userId = 0) + { + if (string.Equals(Constants.Packaging.LanguagesNodeName, languageElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.LanguagesNodeName + "\" as root", "languageElement"); + } + return _packagingService.ImportLanguages(languageElement, userId); + } + + private IEnumerable InstallDataTypes(XElement dataTypeElements, int userId = 0) + { + if (string.Equals(Constants.Packaging.DataTypesNodeName, dataTypeElements.Name.LocalName) == false) + { + if (string.Equals(Constants.Packaging.DataTypeNodeName, dataTypeElements.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.DataTypeNodeName + "\" as root", "dataTypeElements"); + } + } + return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId); + } + + private PreInstallWarnings GetPreInstallWarnings(XElement rootElement) + { + XElement files = rootElement.Element(Constants.Packaging.FilesNodeName); + XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); + XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); + XElement alias = rootElement.Element(Constants.Packaging.MacrosNodeName); + + var sourceDestination = EmptyArrayIfNull>(files) ?? ExtractSourceDestinationFileInformation(files); + + var installWarnings = new PreInstallWarnings(); + + var macroAliases = EmptyEnumerableIfNull(alias) ?? ConflictingPackageData.FindConflictingMacros(alias); + installWarnings.ConflictingMacroAliases = macroAliases; + + var templateAliases = EmptyEnumerableIfNull(templates) ?? ConflictingPackageData.FindConflictingTemplates(templates); + installWarnings.ConflictingTemplateAliases = templateAliases; + + var stylesheetNames = EmptyEnumerableIfNull(styleSheets) ?? ConflictingPackageData.FindConflictingStylesheets(styleSheets); + installWarnings.ConflictingStylesheetNames = stylesheetNames; + + installWarnings.UnsecureFiles = FindUnsecureFiles(sourceDestination); + installWarnings.FilesReplaced = FindFilesToBeReplaced(sourceDestination); + + return installWarnings; + } + + private KeyValuePair[] FindFilesToBeReplaced(IEnumerable> sourceDestination) + { + return sourceDestination.Where(sd => File.Exists(Path.Combine(FullPathToRoot, sd.Value))).ToArray(); + } + + private KeyValuePair[] FindUnsecureFiles(IEnumerable> sourceDestinationPair) + { + return sourceDestinationPair.Where(sd => IsFileDestinationUnsecure(sd.Value)).ToArray(); + } + + private bool IsFileDestinationUnsecure(string destination) + { + var unsecureDirNames = new[] {"bin", "app_code"}; + if(unsecureDirNames.Any(ud => destination.StartsWith(ud, StringComparison.InvariantCultureIgnoreCase))) + return true; + + string extension = Path.GetExtension(destination); + return extension != null && extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase); + } + + private KeyValuePair[] ExtractSourceDestinationFileInformation(XElement filesElement) + { + if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName) == false) + { + throw new ArgumentException("the root element must be \"Files\"", "filesElement"); + } + + return filesElement.Elements(Constants.Packaging.FileNodeName) + .Select(e => + { + XElement guidElement = e.Element(Constants.Packaging.GuidNodeName); + if (guidElement == null) + { + throw new ArgumentException("Missing element \"" + Constants.Packaging.GuidNodeName + "\"", + "filesElement"); + } + + XElement orgPathElement = e.Element(Constants.Packaging.OrgPathNodeName); + if (orgPathElement == null) + { + throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgPathNodeName + "\"", + "filesElement"); + } + + XElement orgNameElement = e.Element(Constants.Packaging.OrgNameNodeName); + if (orgNameElement == null) + { + throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgNameNodeName + "\"", + "filesElement"); + } + + var fileName = PrepareAsFilePathElement(orgNameElement.Value); + var relativeDir = UpdatePathPlaceholders(PrepareAsFilePathElement(orgPathElement.Value)); + + var relativePath = Path.Combine(relativeDir, fileName); + + + return new KeyValuePair(guidElement.Value, relativePath); + }).ToArray(); + } + + private static string PrepareAsFilePathElement(string pathElement) + { + return pathElement.TrimStart(new[] {'\\', '/', '~'}).Replace("/", "\\"); + } + + private MetaData GetMetaData(XElement xRootElement) + { + XElement infoElement = xRootElement.Element(Constants.Packaging.InfoNodeName); + + if (infoElement == null) + { + throw new ArgumentException("Did not hold a \"" + Constants.Packaging.InfoNodeName + "\" element", + "xRootElement"); + } + + var majorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMajorXpath); + var minorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMinorXpath); + var patchElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsPatchXpath); + var nameElement = infoElement.XPathSelectElement(Constants.Packaging.PackageNameXpath); + var versionElement = infoElement.XPathSelectElement(Constants.Packaging.PackageVersionXpath); + var urlElement = infoElement.XPathSelectElement(Constants.Packaging.PackageUrlXpath); + var licenseElement = infoElement.XPathSelectElement(Constants.Packaging.PackageLicenseXpath); + var authorNameElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorNameXpath); + var authorUrlElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorWebsiteXpath); + var readmeElement = infoElement.XPathSelectElement(Constants.Packaging.ReadmeXpath); + + XElement controlElement = xRootElement.Element(Constants.Packaging.ControlNodeName); + + return new MetaData + { + Name = StringValue(nameElement), + Version = StringValue(versionElement), + Url = StringValue(urlElement), + License = StringValue(licenseElement), + LicenseUrl = StringAttribute(licenseElement, Constants.Packaging.PackageLicenseXpathUrlAttribute), + AuthorName = StringValue(authorNameElement), + AuthorUrl = StringValue(authorUrlElement), + Readme = StringValue(readmeElement), + Control = StringValue(controlElement), + ReqMajor = IntValue(majorElement), + ReqMinor = IntValue(minorElement), + ReqPatch = IntValue(patchElement) + }; + } + + private static string StringValue(XElement xElement, string defaultValue = "") + { + return xElement == null ? defaultValue : xElement.Value; + } + + private static string StringAttribute(XElement xElement, string attribute, string defaultValue = "") + { + return xElement == null + ? defaultValue + : xElement.HasAttributes ? xElement.AttributeValue(attribute) : defaultValue; + } + + private static int IntValue(XElement xElement, int defaultValue = 0) + { + return xElement == null ? defaultValue : int.TryParse(xElement.Value, out var val) ? val : defaultValue; + } + + private static string UpdatePathPlaceholders(string path) + { + if (path.Contains("[$")) + { + //this is experimental and undocumented... + path = path.Replace("[$UMBRACO]", SystemDirectories.Umbraco); + path = path.Replace("[$UMBRACOCLIENT]", SystemDirectories.UmbracoClient); + path = path.Replace("[$CONFIG]", SystemDirectories.Config); + path = path.Replace("[$DATA]", SystemDirectories.Data); + } + return path; + } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs index 529fb54ec7..c191ebe6f0 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs @@ -1,25 +1,25 @@ -using System; - -namespace Umbraco.Core.Persistence.DatabaseAnnotations -{ - /// - /// Attribute that represents a db constraint - /// - [AttributeUsage(AttributeTargets.Property)] - public class ConstraintAttribute : Attribute - { - /// - /// Gets or sets the name of the constraint - /// - /// - /// Overrides the default naming of a property constraint: - /// DF_tableName_propertyName - /// - public string Name { get; set; } - - /// - /// Gets or sets the Default value - /// - public object Default { get; set; } - } -} +using System; + +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + /// + /// Attribute that represents a db constraint + /// + [AttributeUsage(AttributeTargets.Property)] + public class ConstraintAttribute : Attribute + { + /// + /// Gets or sets the name of the constraint + /// + /// + /// Overrides the default naming of a property constraint: + /// DF_tableName_propertyName + /// + public string Name { get; set; } + + /// + /// Gets or sets the Default value + /// + public object Default { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs index dc80cd5925..35cb4a8087 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs @@ -1,33 +1,33 @@ -using System; - -namespace Umbraco.Core.Persistence.DatabaseAnnotations -{ - /// - /// Attribute that represents a Foreign Key reference - /// - [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] - public class ForeignKeyAttribute : ReferencesAttribute - { - public ForeignKeyAttribute(Type type) : base(type) - { - } - - internal string OnDelete { get; set; } - internal string OnUpdate { get; set; } - - /// - /// Gets or sets the name of the foreign key refence - /// - /// - /// Overrides the default naming of a foreign key reference: - /// FK_thisTableName_refTableName - /// - public string Name { get; set; } - - /// - /// Gets or sets the name of the Column that this foreign key should reference. - /// - /// PrimaryKey column is used by default - public string Column { get; set; } - } -} +using System; + +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + /// + /// Attribute that represents a Foreign Key reference + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class ForeignKeyAttribute : ReferencesAttribute + { + public ForeignKeyAttribute(Type type) : base(type) + { + } + + internal string OnDelete { get; set; } + internal string OnUpdate { get; set; } + + /// + /// Gets or sets the name of the foreign key refence + /// + /// + /// Overrides the default naming of a foreign key reference: + /// FK_thisTableName_refTableName + /// + public string Name { get; set; } + + /// + /// Gets or sets the name of the Column that this foreign key should reference. + /// + /// PrimaryKey column is used by default + public string Column { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs index fd4a4026f2..138dceff09 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs @@ -1,35 +1,35 @@ -using System; - -namespace Umbraco.Core.Persistence.DatabaseAnnotations -{ - /// - /// Attribute that represents an Index - /// - [AttributeUsage(AttributeTargets.Property)] - public class IndexAttribute : Attribute - { - public IndexAttribute(IndexTypes indexType) - { - IndexType = indexType; - } - - /// - /// Gets or sets the name of the Index - /// - /// - /// Overrides default naming of indexes: - /// IX_tableName - /// - public string Name { get; set; }//Overrides default naming of indexes: IX_tableName - - /// - /// Gets or sets the type of index to create - /// - public IndexTypes IndexType { get; private set; } - - /// - /// Gets or sets the column name(s) for the current index - /// - public string ForColumns { get; set; } - } -} +using System; + +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + /// + /// Attribute that represents an Index + /// + [AttributeUsage(AttributeTargets.Property)] + public class IndexAttribute : Attribute + { + public IndexAttribute(IndexTypes indexType) + { + IndexType = indexType; + } + + /// + /// Gets or sets the name of the Index + /// + /// + /// Overrides default naming of indexes: + /// IX_tableName + /// + public string Name { get; set; }//Overrides default naming of indexes: IX_tableName + + /// + /// Gets or sets the type of index to create + /// + public IndexTypes IndexType { get; private set; } + + /// + /// Gets or sets the column name(s) for the current index + /// + public string ForColumns { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexTypes.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexTypes.cs index b99e18f96a..e2cdd529c4 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexTypes.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexTypes.cs @@ -1,12 +1,12 @@ -namespace Umbraco.Core.Persistence.DatabaseAnnotations -{ - /// - /// Enum for the 3 types of indexes that can be created - /// - public enum IndexTypes - { - Clustered, - NonClustered, - UniqueNonClustered - } -} +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + /// + /// Enum for the 3 types of indexes that can be created + /// + public enum IndexTypes + { + Clustered, + NonClustered, + UniqueNonClustered + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/LengthAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/LengthAttribute.cs index a82f66ea64..50e91e35de 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/LengthAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/LengthAttribute.cs @@ -1,22 +1,22 @@ -using System; - -namespace Umbraco.Core.Persistence.DatabaseAnnotations -{ - /// - /// Attribute that represents the length of a column - /// - /// Used to define the length of fixed sized columns - typically used for nvarchar - [AttributeUsage(AttributeTargets.Property)] - public class LengthAttribute : Attribute - { - public LengthAttribute(int length) - { - Length = length; - } - - /// - /// Gets or sets the length of a column - /// - public int Length { get; private set; } - } -} +using System; + +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + /// + /// Attribute that represents the length of a column + /// + /// Used to define the length of fixed sized columns - typically used for nvarchar + [AttributeUsage(AttributeTargets.Property)] + public class LengthAttribute : Attribute + { + public LengthAttribute(int length) + { + Length = length; + } + + /// + /// Gets or sets the length of a column + /// + public int Length { get; private set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettingAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettingAttribute.cs index b75b8650c7..d13b803c33 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettingAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettingAttribute.cs @@ -1,20 +1,20 @@ -using System; - -namespace Umbraco.Core.Persistence.DatabaseAnnotations -{ - /// - /// Attribute that represents the Null-setting of a column - /// - /// - /// This should only be used for Columns that can be Null. - /// By convention the Columns will be "NOT NULL". - /// - [AttributeUsage(AttributeTargets.Property)] - public class NullSettingAttribute : Attribute - { - /// - /// Gets or sets the for a column - /// - public NullSettings NullSetting { get; set; } - } -} +using System; + +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + /// + /// Attribute that represents the Null-setting of a column + /// + /// + /// This should only be used for Columns that can be Null. + /// By convention the Columns will be "NOT NULL". + /// + [AttributeUsage(AttributeTargets.Property)] + public class NullSettingAttribute : Attribute + { + /// + /// Gets or sets the for a column + /// + public NullSettings NullSetting { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettings.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettings.cs index d07b0ebf85..eea7141bb1 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettings.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettings.cs @@ -1,11 +1,11 @@ -namespace Umbraco.Core.Persistence.DatabaseAnnotations -{ - /// - /// Enum with the 2 possible Null settings: Null or Not Null - /// - public enum NullSettings - { - Null, - NotNull - } -} +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + /// + /// Enum with the 2 possible Null settings: Null or Not Null + /// + public enum NullSettings + { + Null, + NotNull + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/PrimaryKeyColumnAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/PrimaryKeyColumnAttribute.cs index b8cbf27c77..526464abb7 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/PrimaryKeyColumnAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/PrimaryKeyColumnAttribute.cs @@ -1,59 +1,59 @@ -using System; - -namespace Umbraco.Core.Persistence.DatabaseAnnotations -{ - /// - /// Attribute that represents a Primary Key - /// - /// - /// By default, Clustered and AutoIncrement is set to true. - /// - [AttributeUsage(AttributeTargets.Property)] - public class PrimaryKeyColumnAttribute : Attribute - { - public PrimaryKeyColumnAttribute() - { - Clustered = true; - AutoIncrement = true; - } - - /// - /// Gets or sets a boolean indicating whether the primary key is clustered. - /// - /// Defaults to true - public bool Clustered { get; set; } - - /// - /// Gets or sets a boolean indicating whether the primary key is auto incremented. - /// - /// Defaults to true - public bool AutoIncrement { get; set; } - - /// - /// Gets or sets the name of the PrimaryKey. - /// - /// - /// Overrides the default naming of a PrimaryKey constraint: - /// PK_tableName - /// - public string Name { get; set; } - - /// - /// Gets or sets the names of the columns for this PrimaryKey. - /// - /// - /// Should only be used if the PrimaryKey spans over multiple columns. - /// Usage: [nodeId], [otherColumn] - /// - public string OnColumns { get; set; } - - /// - /// Gets or sets the Identity Seed, which is used for Sql Ce databases. - /// - /// - /// We'll only look for changes to seeding and apply them if the configured database - /// is an Sql Ce database. - /// - public int IdentitySeed { get; set; } - } -} +using System; + +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + /// + /// Attribute that represents a Primary Key + /// + /// + /// By default, Clustered and AutoIncrement is set to true. + /// + [AttributeUsage(AttributeTargets.Property)] + public class PrimaryKeyColumnAttribute : Attribute + { + public PrimaryKeyColumnAttribute() + { + Clustered = true; + AutoIncrement = true; + } + + /// + /// Gets or sets a boolean indicating whether the primary key is clustered. + /// + /// Defaults to true + public bool Clustered { get; set; } + + /// + /// Gets or sets a boolean indicating whether the primary key is auto incremented. + /// + /// Defaults to true + public bool AutoIncrement { get; set; } + + /// + /// Gets or sets the name of the PrimaryKey. + /// + /// + /// Overrides the default naming of a PrimaryKey constraint: + /// PK_tableName + /// + public string Name { get; set; } + + /// + /// Gets or sets the names of the columns for this PrimaryKey. + /// + /// + /// Should only be used if the PrimaryKey spans over multiple columns. + /// Usage: [nodeId], [otherColumn] + /// + public string OnColumns { get; set; } + + /// + /// Gets or sets the Identity Seed, which is used for Sql Ce databases. + /// + /// + /// We'll only look for changes to seeding and apply them if the configured database + /// is an Sql Ce database. + /// + public int IdentitySeed { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ReferencesAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ReferencesAttribute.cs index cd700cc8ba..87b2887258 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ReferencesAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ReferencesAttribute.cs @@ -1,21 +1,21 @@ -using System; - -namespace Umbraco.Core.Persistence.DatabaseAnnotations -{ - /// - /// Attribute that represents a reference between two tables/DTOs - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] - public class ReferencesAttribute : Attribute - { - public ReferencesAttribute(Type type) - { - Type = type; - } - - /// - /// Gets or sets the Type of the referenced DTO/table - /// - public Type Type { get; set; } - } -} +using System; + +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + /// + /// Attribute that represents a reference between two tables/DTOs + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] + public class ReferencesAttribute : Attribute + { + public ReferencesAttribute(Type type) + { + Type = type; + } + + /// + /// Gets or sets the Type of the referenced DTO/table + /// + public Type Type { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs index a54925dfc3..0568e5cd19 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs @@ -1,24 +1,24 @@ -using System; - -namespace Umbraco.Core.Persistence.DatabaseAnnotations -{ - /// - /// Attribute that represents the usage of a special type - /// - /// - /// Should only be used when the .NET type can't be directly translated to a DbType. - /// - [AttributeUsage(AttributeTargets.Property)] - public class SpecialDbTypeAttribute : Attribute - { - public SpecialDbTypeAttribute(SpecialDbTypes databaseType) - { - DatabaseType = databaseType; - } - - /// - /// Gets or sets the for this column - /// - public SpecialDbTypes DatabaseType { get; private set; } - } -} +using System; + +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + /// + /// Attribute that represents the usage of a special type + /// + /// + /// Should only be used when the .NET type can't be directly translated to a DbType. + /// + [AttributeUsage(AttributeTargets.Property)] + public class SpecialDbTypeAttribute : Attribute + { + public SpecialDbTypeAttribute(SpecialDbTypes databaseType) + { + DatabaseType = databaseType; + } + + /// + /// Gets or sets the for this column + /// + public SpecialDbTypes DatabaseType { get; private set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs index efb9fc6ea1..4b606bc0f5 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs @@ -1,12 +1,12 @@ -namespace Umbraco.Core.Persistence.DatabaseAnnotations -{ - /// - /// Enum with the two special types that has to be supported because - /// of the current umbraco db schema. - /// - public enum SpecialDbTypes - { - NTEXT, - NCHAR - } -} +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + /// + /// Enum with the two special types that has to be supported because + /// of the current umbraco db schema. + /// + public enum SpecialDbTypes + { + NTEXT, + NCHAR + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs index de29c25314..7504dc2fb7 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs @@ -1,35 +1,35 @@ -using System; -using System.Data; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public class ColumnDefinition - { - public virtual string Name { get; set; } - //This type is typically used as part of a migration - public virtual DbType? Type { get; set; } - //When DbType isn't set explicitly the Type will be used to find the right DbType in the SqlSyntaxProvider. - //This type is typically used as part of an initial table creation - public Type PropertyType { get; set; } - //Only used for special cases as part of an initial table creation - public bool HasSpecialDbType { get; set; } - public SpecialDbTypes DbType { get; set; } - public virtual int Seeding { get; set; } - public virtual int Size { get; set; } - public virtual int Precision { get; set; } - public virtual string CustomType { get; set; } - public virtual object DefaultValue { get; set; } - public virtual string ConstraintName { get; set; } - public virtual bool IsForeignKey { get; set; } - public virtual bool IsIdentity { get; set; } - public virtual bool IsIndexed { get; set; }//Clustered? - public virtual bool IsPrimaryKey { get; set; } - public virtual string PrimaryKeyName { get; set; } - public virtual string PrimaryKeyColumns { get; set; }//When the primary key spans multiple columns - public virtual bool IsNullable { get; set; } - public virtual bool IsUnique { get; set; } - public virtual string TableName { get; set; } - public virtual ModificationType ModificationType { get; set; } - } -} +using System; +using System.Data; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public class ColumnDefinition + { + public virtual string Name { get; set; } + //This type is typically used as part of a migration + public virtual DbType? Type { get; set; } + //When DbType isn't set explicitly the Type will be used to find the right DbType in the SqlSyntaxProvider. + //This type is typically used as part of an initial table creation + public Type PropertyType { get; set; } + //Only used for special cases as part of an initial table creation + public bool HasSpecialDbType { get; set; } + public SpecialDbTypes DbType { get; set; } + public virtual int Seeding { get; set; } + public virtual int Size { get; set; } + public virtual int Precision { get; set; } + public virtual string CustomType { get; set; } + public virtual object DefaultValue { get; set; } + public virtual string ConstraintName { get; set; } + public virtual bool IsForeignKey { get; set; } + public virtual bool IsIdentity { get; set; } + public virtual bool IsIndexed { get; set; }//Clustered? + public virtual bool IsPrimaryKey { get; set; } + public virtual string PrimaryKeyName { get; set; } + public virtual string PrimaryKeyColumns { get; set; }//When the primary key spans multiple columns + public virtual bool IsNullable { get; set; } + public virtual bool IsUnique { get; set; } + public virtual string TableName { get; set; } + public virtual ModificationType ModificationType { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintDefinition.cs index 8cda7e1a28..8b618a6619 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintDefinition.cs @@ -1,22 +1,22 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public class ConstraintDefinition - { - public ConstraintDefinition(ConstraintType type) - { - constraintType = type; - } - - private ConstraintType constraintType; - public bool IsPrimaryKeyConstraint { get { return ConstraintType.PrimaryKey == constraintType; } } - public bool IsUniqueConstraint { get { return ConstraintType.Unique == constraintType; } } - public bool IsNonUniqueConstraint { get { return ConstraintType.NonUnique == constraintType; } } - - public string SchemaName { get; set; } - public string ConstraintName { get; set; } - public string TableName { get; set; } - public ICollection Columns = new HashSet(); - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public class ConstraintDefinition + { + public ConstraintDefinition(ConstraintType type) + { + constraintType = type; + } + + private ConstraintType constraintType; + public bool IsPrimaryKeyConstraint { get { return ConstraintType.PrimaryKey == constraintType; } } + public bool IsUniqueConstraint { get { return ConstraintType.Unique == constraintType; } } + public bool IsNonUniqueConstraint { get { return ConstraintType.NonUnique == constraintType; } } + + public string SchemaName { get; set; } + public string ConstraintName { get; set; } + public string TableName { get; set; } + public ICollection Columns = new HashSet(); + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintType.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintType.cs index b47500f265..9cb2a518fe 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintType.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintType.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public enum ConstraintType - { - PrimaryKey, - Unique, - NonUnique - } -} +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public enum ConstraintType + { + PrimaryKey, + Unique, + NonUnique + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs index b1ae542659..97f995e99d 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs @@ -1,178 +1,178 @@ -using System; -using System.Data; -using System.Linq; -using System.Reflection; -using NPoco; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - internal static class DefinitionFactory - { - public static TableDefinition GetTableDefinition(Type modelType, ISqlSyntaxProvider sqlSyntax) - { - //Looks for NPoco's TableNameAtribute for the name of the table - //If no attribute is set we use the name of the Type as the default convention - var tableNameAttribute = modelType.FirstAttribute(); - string tableName = tableNameAttribute == null ? modelType.Name : tableNameAttribute.Value; - - var tableDefinition = new TableDefinition {Name = tableName}; - var objProperties = modelType.GetProperties().ToList(); - foreach (var propertyInfo in objProperties) - { - //If current property has an IgnoreAttribute then skip it - var ignoreAttribute = propertyInfo.FirstAttribute(); - if (ignoreAttribute != null) continue; - - //If current property has a ResultColumnAttribute then skip it - var resultColumnAttribute = propertyInfo.FirstAttribute(); - if (resultColumnAttribute != null) continue; - - //Looks for ColumnAttribute with the name of the column, which would exist with ExplicitColumns - //Otherwise use the name of the property itself as the default convention - var columnAttribute = propertyInfo.FirstAttribute(); - string columnName = columnAttribute != null ? columnAttribute.Name : propertyInfo.Name; - var columnDefinition = GetColumnDefinition(modelType, propertyInfo, columnName, tableName, sqlSyntax); - tableDefinition.Columns.Add(columnDefinition); - - //Creates a foreignkey definition and adds it to the collection on the table definition - var foreignKeyAttributes = propertyInfo.MultipleAttribute(); - if (foreignKeyAttributes != null) - { - foreach (var foreignKeyAttribute in foreignKeyAttributes) - { - var foreignKeyDefinition = GetForeignKeyDefinition(modelType, propertyInfo, foreignKeyAttribute, columnName, tableName); - tableDefinition.ForeignKeys.Add(foreignKeyDefinition); - } - } - - //Creates an index definition and adds it to the collection on the table definition - var indexAttribute = propertyInfo.FirstAttribute(); - if (indexAttribute != null) - { - var indexDefinition = GetIndexDefinition(modelType, propertyInfo, indexAttribute, columnName, tableName); - tableDefinition.Indexes.Add(indexDefinition); - } - } - - return tableDefinition; - } - - public static ColumnDefinition GetColumnDefinition(Type modelType, PropertyInfo propertyInfo, string columnName, string tableName, ISqlSyntaxProvider sqlSyntax) - { - var definition = new ColumnDefinition{ Name = columnName, TableName = tableName, ModificationType = ModificationType.Create }; - - //Look for specific Null setting attributed a column - var nullSettingAttribute = propertyInfo.FirstAttribute(); - if (nullSettingAttribute != null) - { - definition.IsNullable = nullSettingAttribute.NullSetting == NullSettings.Null; - } - - //Look for specific DbType attributed a column - var databaseTypeAttribute = propertyInfo.FirstAttribute(); - if (databaseTypeAttribute != null) - { - definition.HasSpecialDbType = true; - definition.DbType = databaseTypeAttribute.DatabaseType; - } - else - { - definition.PropertyType = propertyInfo.PropertyType; - } - - //Look for Primary Key for the current column - var primaryKeyColumnAttribute = propertyInfo.FirstAttribute(); - if (primaryKeyColumnAttribute != null) - { - string primaryKeyName = string.IsNullOrEmpty(primaryKeyColumnAttribute.Name) - ? string.Format("PK_{0}", tableName) - : primaryKeyColumnAttribute.Name; - - definition.IsPrimaryKey = true; - definition.IsIdentity = primaryKeyColumnAttribute.AutoIncrement; - definition.IsIndexed = primaryKeyColumnAttribute.Clustered; - definition.PrimaryKeyName = primaryKeyName; - definition.PrimaryKeyColumns = primaryKeyColumnAttribute.OnColumns ?? string.Empty; - definition.Seeding = primaryKeyColumnAttribute.IdentitySeed; - } - - //Look for Size/Length of DbType - var lengthAttribute = propertyInfo.FirstAttribute(); - if (lengthAttribute != null) - { - definition.Size = lengthAttribute.Length; - } - - //Look for Constraint for the current column - var constraintAttribute = propertyInfo.FirstAttribute(); - if (constraintAttribute != null) - { - //Special case for MySQL as it can't have multiple default DateTime values, which - //is what the umbracoServer table definition is trying to create - if (sqlSyntax is MySqlSyntaxProvider && definition.TableName == "umbracoServer" && - definition.TableName.ToLowerInvariant() == "lastNotifiedDate".ToLowerInvariant()) - return definition; - - definition.ConstraintName = constraintAttribute.Name ?? string.Empty; - definition.DefaultValue = constraintAttribute.Default ?? string.Empty; - } - - return definition; - } - - public static ForeignKeyDefinition GetForeignKeyDefinition(Type modelType, PropertyInfo propertyInfo, - ForeignKeyAttribute attribute, string columnName, string tableName) - { - var referencedTable = attribute.Type.FirstAttribute(); - var referencedPrimaryKey = attribute.Type.FirstAttribute(); - - string referencedColumn = string.IsNullOrEmpty(attribute.Column) - ? referencedPrimaryKey.Value - : attribute.Column; - - string foreignKeyName = string.IsNullOrEmpty(attribute.Name) - ? string.Format("FK_{0}_{1}_{2}", tableName, referencedTable.Value, referencedColumn) - : attribute.Name; - - var definition = new ForeignKeyDefinition - { - Name = foreignKeyName, - ForeignTable = tableName, - PrimaryTable = referencedTable.Value - }; - definition.ForeignColumns.Add(columnName); - definition.PrimaryColumns.Add(referencedColumn); - - return definition; - } - - public static IndexDefinition GetIndexDefinition(Type modelType, PropertyInfo propertyInfo, IndexAttribute attribute, string columnName, string tableName) - { - string indexName = string.IsNullOrEmpty(attribute.Name) - ? string.Format("IX_{0}_{1}", tableName, columnName) - : attribute.Name; - - var definition = new IndexDefinition - { - Name = indexName, - IndexType = attribute.IndexType, - ColumnName = columnName, - TableName = tableName, - IsClustered = attribute.IndexType == IndexTypes.Clustered, - IsUnique = attribute.IndexType == IndexTypes.UniqueNonClustered - }; - - if (string.IsNullOrEmpty(attribute.ForColumns) == false) - { - var columns = attribute.ForColumns.Split(',').Select(p => p.Trim()); - foreach (var column in columns) - { - definition.Columns.Add(new IndexColumnDefinition {Name = column, Direction = Direction.Ascending}); - } - } - return definition; - } - } -} +using System; +using System.Data; +using System.Linq; +using System.Reflection; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + internal static class DefinitionFactory + { + public static TableDefinition GetTableDefinition(Type modelType, ISqlSyntaxProvider sqlSyntax) + { + //Looks for NPoco's TableNameAtribute for the name of the table + //If no attribute is set we use the name of the Type as the default convention + var tableNameAttribute = modelType.FirstAttribute(); + string tableName = tableNameAttribute == null ? modelType.Name : tableNameAttribute.Value; + + var tableDefinition = new TableDefinition {Name = tableName}; + var objProperties = modelType.GetProperties().ToList(); + foreach (var propertyInfo in objProperties) + { + //If current property has an IgnoreAttribute then skip it + var ignoreAttribute = propertyInfo.FirstAttribute(); + if (ignoreAttribute != null) continue; + + //If current property has a ResultColumnAttribute then skip it + var resultColumnAttribute = propertyInfo.FirstAttribute(); + if (resultColumnAttribute != null) continue; + + //Looks for ColumnAttribute with the name of the column, which would exist with ExplicitColumns + //Otherwise use the name of the property itself as the default convention + var columnAttribute = propertyInfo.FirstAttribute(); + string columnName = columnAttribute != null ? columnAttribute.Name : propertyInfo.Name; + var columnDefinition = GetColumnDefinition(modelType, propertyInfo, columnName, tableName, sqlSyntax); + tableDefinition.Columns.Add(columnDefinition); + + //Creates a foreignkey definition and adds it to the collection on the table definition + var foreignKeyAttributes = propertyInfo.MultipleAttribute(); + if (foreignKeyAttributes != null) + { + foreach (var foreignKeyAttribute in foreignKeyAttributes) + { + var foreignKeyDefinition = GetForeignKeyDefinition(modelType, propertyInfo, foreignKeyAttribute, columnName, tableName); + tableDefinition.ForeignKeys.Add(foreignKeyDefinition); + } + } + + //Creates an index definition and adds it to the collection on the table definition + var indexAttribute = propertyInfo.FirstAttribute(); + if (indexAttribute != null) + { + var indexDefinition = GetIndexDefinition(modelType, propertyInfo, indexAttribute, columnName, tableName); + tableDefinition.Indexes.Add(indexDefinition); + } + } + + return tableDefinition; + } + + public static ColumnDefinition GetColumnDefinition(Type modelType, PropertyInfo propertyInfo, string columnName, string tableName, ISqlSyntaxProvider sqlSyntax) + { + var definition = new ColumnDefinition{ Name = columnName, TableName = tableName, ModificationType = ModificationType.Create }; + + //Look for specific Null setting attributed a column + var nullSettingAttribute = propertyInfo.FirstAttribute(); + if (nullSettingAttribute != null) + { + definition.IsNullable = nullSettingAttribute.NullSetting == NullSettings.Null; + } + + //Look for specific DbType attributed a column + var databaseTypeAttribute = propertyInfo.FirstAttribute(); + if (databaseTypeAttribute != null) + { + definition.HasSpecialDbType = true; + definition.DbType = databaseTypeAttribute.DatabaseType; + } + else + { + definition.PropertyType = propertyInfo.PropertyType; + } + + //Look for Primary Key for the current column + var primaryKeyColumnAttribute = propertyInfo.FirstAttribute(); + if (primaryKeyColumnAttribute != null) + { + string primaryKeyName = string.IsNullOrEmpty(primaryKeyColumnAttribute.Name) + ? string.Format("PK_{0}", tableName) + : primaryKeyColumnAttribute.Name; + + definition.IsPrimaryKey = true; + definition.IsIdentity = primaryKeyColumnAttribute.AutoIncrement; + definition.IsIndexed = primaryKeyColumnAttribute.Clustered; + definition.PrimaryKeyName = primaryKeyName; + definition.PrimaryKeyColumns = primaryKeyColumnAttribute.OnColumns ?? string.Empty; + definition.Seeding = primaryKeyColumnAttribute.IdentitySeed; + } + + //Look for Size/Length of DbType + var lengthAttribute = propertyInfo.FirstAttribute(); + if (lengthAttribute != null) + { + definition.Size = lengthAttribute.Length; + } + + //Look for Constraint for the current column + var constraintAttribute = propertyInfo.FirstAttribute(); + if (constraintAttribute != null) + { + //Special case for MySQL as it can't have multiple default DateTime values, which + //is what the umbracoServer table definition is trying to create + if (sqlSyntax is MySqlSyntaxProvider && definition.TableName == "umbracoServer" && + definition.TableName.ToLowerInvariant() == "lastNotifiedDate".ToLowerInvariant()) + return definition; + + definition.ConstraintName = constraintAttribute.Name ?? string.Empty; + definition.DefaultValue = constraintAttribute.Default ?? string.Empty; + } + + return definition; + } + + public static ForeignKeyDefinition GetForeignKeyDefinition(Type modelType, PropertyInfo propertyInfo, + ForeignKeyAttribute attribute, string columnName, string tableName) + { + var referencedTable = attribute.Type.FirstAttribute(); + var referencedPrimaryKey = attribute.Type.FirstAttribute(); + + string referencedColumn = string.IsNullOrEmpty(attribute.Column) + ? referencedPrimaryKey.Value + : attribute.Column; + + string foreignKeyName = string.IsNullOrEmpty(attribute.Name) + ? string.Format("FK_{0}_{1}_{2}", tableName, referencedTable.Value, referencedColumn) + : attribute.Name; + + var definition = new ForeignKeyDefinition + { + Name = foreignKeyName, + ForeignTable = tableName, + PrimaryTable = referencedTable.Value + }; + definition.ForeignColumns.Add(columnName); + definition.PrimaryColumns.Add(referencedColumn); + + return definition; + } + + public static IndexDefinition GetIndexDefinition(Type modelType, PropertyInfo propertyInfo, IndexAttribute attribute, string columnName, string tableName) + { + string indexName = string.IsNullOrEmpty(attribute.Name) + ? string.Format("IX_{0}_{1}", tableName, columnName) + : attribute.Name; + + var definition = new IndexDefinition + { + Name = indexName, + IndexType = attribute.IndexType, + ColumnName = columnName, + TableName = tableName, + IsClustered = attribute.IndexType == IndexTypes.Clustered, + IsUnique = attribute.IndexType == IndexTypes.UniqueNonClustered + }; + + if (string.IsNullOrEmpty(attribute.ForColumns) == false) + { + var columns = attribute.ForColumns.Split(',').Select(p => p.Trim()); + foreach (var column in columns) + { + definition.Columns.Add(new IndexColumnDefinition {Name = column, Direction = Direction.Ascending}); + } + } + return definition; + } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DeletionDataDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DeletionDataDefinition.cs index 5afd18d6ff..b1508689dc 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DeletionDataDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DeletionDataDefinition.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public class DeletionDataDefinition : List> - { - - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public class DeletionDataDefinition : List> + { + + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/Direction.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/Direction.cs index 1425cf1dc9..5c34413763 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/Direction.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/Direction.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public enum Direction - { - Ascending = 0, - Descending = 1 - } -} +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public enum Direction + { + Ascending = 0, + Descending = 1 + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ForeignKeyDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ForeignKeyDefinition.cs index b6233b5517..22bfba88b6 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ForeignKeyDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ForeignKeyDefinition.cs @@ -1,27 +1,27 @@ -using System.Collections.Generic; -using System.Data; - -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public class ForeignKeyDefinition - { - public ForeignKeyDefinition() - { - ForeignColumns = new List(); - PrimaryColumns = new List(); - //Set to None by Default - OnDelete = Rule.None; - OnUpdate = Rule.None; - } - - public virtual string Name { get; set; } - public virtual string ForeignTable { get; set; } - public virtual string ForeignTableSchema { get; set; } - public virtual string PrimaryTable { get; set; } - public virtual string PrimaryTableSchema { get; set; } - public virtual Rule OnDelete { get; set; } - public virtual Rule OnUpdate { get; set; } - public virtual ICollection ForeignColumns { get; set; } - public virtual ICollection PrimaryColumns { get; set; } - } -} +using System.Collections.Generic; +using System.Data; + +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public class ForeignKeyDefinition + { + public ForeignKeyDefinition() + { + ForeignColumns = new List(); + PrimaryColumns = new List(); + //Set to None by Default + OnDelete = Rule.None; + OnUpdate = Rule.None; + } + + public virtual string Name { get; set; } + public virtual string ForeignTable { get; set; } + public virtual string ForeignTableSchema { get; set; } + public virtual string PrimaryTable { get; set; } + public virtual string PrimaryTableSchema { get; set; } + public virtual Rule OnDelete { get; set; } + public virtual Rule OnUpdate { get; set; } + public virtual ICollection ForeignColumns { get; set; } + public virtual ICollection PrimaryColumns { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexColumnDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexColumnDefinition.cs index 039c1cc312..3638ca9520 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexColumnDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexColumnDefinition.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public class IndexColumnDefinition - { - public virtual string Name { get; set; } - public virtual Direction Direction { get; set; } - } -} +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public class IndexColumnDefinition + { + public virtual string Name { get; set; } + public virtual Direction Direction { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs index 8ae67c3b75..d4f2a27ae6 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs @@ -1,22 +1,22 @@ -using System.Collections.Generic; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public class IndexDefinition - { - public IndexDefinition() - { - Columns = new List(); - } - - public virtual string Name { get; set; } - public virtual string SchemaName { get; set; } - public virtual string TableName { get; set; } - public virtual string ColumnName { get; set; } - public virtual bool IsUnique { get; set; } - public bool IsClustered { get; set; } - public virtual ICollection Columns { get; set; } - public IndexTypes IndexType { get; set; } - } -} +using System.Collections.Generic; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public class IndexDefinition + { + public IndexDefinition() + { + Columns = new List(); + } + + public virtual string Name { get; set; } + public virtual string SchemaName { get; set; } + public virtual string TableName { get; set; } + public virtual string ColumnName { get; set; } + public virtual bool IsUnique { get; set; } + public bool IsClustered { get; set; } + public virtual ICollection Columns { get; set; } + public IndexTypes IndexType { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/InsertionDataDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/InsertionDataDefinition.cs index 3405853cce..8f837244e9 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/InsertionDataDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/InsertionDataDefinition.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public class InsertionDataDefinition : List> - { - - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public class InsertionDataDefinition : List> + { + + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ModificationType.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ModificationType.cs index c6e2b52306..506ddcecc7 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ModificationType.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ModificationType.cs @@ -1,13 +1,13 @@ -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public enum ModificationType - { - Create, - Alter, - Drop, - Rename, - Insert, - Update, - Delete - } -} +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public enum ModificationType + { + Create, + Alter, + Drop, + Rename, + Insert, + Update, + Delete + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs index 3415ad71dd..18d0bce19d 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs @@ -1,10 +1,10 @@ -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public enum SystemMethods - { - NewGuid, - CurrentDateTime, - //NewSequentialId, - //CurrentUTCDateTime - } -} +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public enum SystemMethods + { + NewGuid, + CurrentDateTime, + //NewSequentialId, + //CurrentUTCDateTime + } +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/TableDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/TableDefinition.cs index afb7ca3d8e..e79c10d097 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/TableDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/TableDefinition.cs @@ -1,20 +1,20 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions -{ - public class TableDefinition - { - public TableDefinition() - { - Columns = new List(); - ForeignKeys = new List(); - Indexes = new List(); - } - - public virtual string Name { get; set; } - public virtual string SchemaName { get; set; } - public virtual ICollection Columns { get; set; } - public virtual ICollection ForeignKeys { get; set; } - public virtual ICollection Indexes { get; set; } - } -} +using System.Collections.Generic; + +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +{ + public class TableDefinition + { + public TableDefinition() + { + Columns = new List(); + ForeignKeys = new List(); + Indexes = new List(); + } + + public virtual string Name { get; set; } + public virtual string SchemaName { get; set; } + public virtual ICollection Columns { get; set; } + public virtual ICollection ForeignKeys { get; set; } + public virtual ICollection Indexes { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index b1b0141147..892a2a4724 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -1,162 +1,162 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - // factory for - // IContentType (document types) - // IMediaType (media types) - // IMemberType (member types) - // - internal class ContentTypeFactory - { - #region IContentType - - public IContentType BuildContentTypeEntity(ContentTypeDto dto) - { - var contentType = new ContentType(dto.NodeDto.ParentId); - - try - { - contentType.DisableChangeTracking(); - - BuildCommonEntity(contentType, dto); - - // reset dirty initial properties (U4-1946) - contentType.ResetDirtyProperties(false); - return contentType; - } - finally - { - contentType.EnableChangeTracking(); - } - } - - #endregion - - #region IMediaType - - public IMediaType BuildMediaTypeEntity(ContentTypeDto dto) - { - var contentType = new MediaType(dto.NodeDto.ParentId); - try - { - contentType.DisableChangeTracking(); - - BuildCommonEntity(contentType, dto); - - // reset dirty initial properties (U4-1946) - contentType.ResetDirtyProperties(false); - } - finally - { - contentType.EnableChangeTracking(); - } - - return contentType; - } - - #endregion - - #region IMemberType - - public IMemberType BuildMemberTypeEntity(ContentTypeDto dto) - { - throw new NotImplementedException(); - } - - public IEnumerable BuildMemberTypeDtos(IMemberType entity) - { - var memberType = entity as MemberType; - if (memberType == null || memberType.PropertyTypes.Any() == false) - return Enumerable.Empty(); - - var dtos = memberType.PropertyTypes.Select(x => new MemberTypeDto - { - NodeId = entity.Id, - PropertyTypeId = x.Id, - CanEdit = memberType.MemberCanEditProperty(x.Alias), - ViewOnProfile = memberType.MemberCanViewProperty(x.Alias), - IsSensitive = memberType.IsSensitiveProperty(x.Alias) - }).ToList(); - return dtos; - } - - #endregion - - #region Common - - private static void BuildCommonEntity(ContentTypeBase entity, ContentTypeDto dto) - { - entity.Id = dto.NodeDto.NodeId; - entity.Key = dto.NodeDto.UniqueId; - entity.Alias = dto.Alias; - entity.Name = dto.NodeDto.Text; - entity.Icon = dto.Icon; - entity.Thumbnail = dto.Thumbnail; - entity.SortOrder = dto.NodeDto.SortOrder; - entity.Description = dto.Description; - entity.CreateDate = dto.NodeDto.CreateDate; - entity.Path = dto.NodeDto.Path; - entity.Level = dto.NodeDto.Level; - entity.CreatorId = dto.NodeDto.UserId ?? Constants.Security.UnknownUserId; - entity.AllowedAsRoot = dto.AllowAtRoot; - entity.IsContainer = dto.IsContainer; - entity.Trashed = dto.NodeDto.Trashed; - entity.Variations = (ContentVariation) dto.Variations; - } - - public ContentTypeDto BuildContentTypeDto(IContentTypeBase entity) - { - Guid nodeObjectType; - if (entity is IContentType) - nodeObjectType = Constants.ObjectTypes.DocumentType; - else if (entity is IMediaType) - nodeObjectType = Constants.ObjectTypes.MediaType; - else if (entity is IMemberType) - nodeObjectType = Constants.ObjectTypes.MemberType; - else - throw new Exception("Invalid entity."); - - var contentTypeDto = new ContentTypeDto - { - Alias = entity.Alias, - Description = entity.Description, - Icon = entity.Icon, - Thumbnail = entity.Thumbnail, - NodeId = entity.Id, - AllowAtRoot = entity.AllowedAsRoot, - IsContainer = entity.IsContainer, - Variations = (byte) entity.Variations, - NodeDto = BuildNodeDto(entity, nodeObjectType) - }; - return contentTypeDto; - } - - private static NodeDto BuildNodeDto(IUmbracoEntity entity, Guid nodeObjectType) - { - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = nodeObjectType, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = entity.Name, - Trashed = false, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; - return nodeDto; - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + // factory for + // IContentType (document types) + // IMediaType (media types) + // IMemberType (member types) + // + internal class ContentTypeFactory + { + #region IContentType + + public IContentType BuildContentTypeEntity(ContentTypeDto dto) + { + var contentType = new ContentType(dto.NodeDto.ParentId); + + try + { + contentType.DisableChangeTracking(); + + BuildCommonEntity(contentType, dto); + + // reset dirty initial properties (U4-1946) + contentType.ResetDirtyProperties(false); + return contentType; + } + finally + { + contentType.EnableChangeTracking(); + } + } + + #endregion + + #region IMediaType + + public IMediaType BuildMediaTypeEntity(ContentTypeDto dto) + { + var contentType = new MediaType(dto.NodeDto.ParentId); + try + { + contentType.DisableChangeTracking(); + + BuildCommonEntity(contentType, dto); + + // reset dirty initial properties (U4-1946) + contentType.ResetDirtyProperties(false); + } + finally + { + contentType.EnableChangeTracking(); + } + + return contentType; + } + + #endregion + + #region IMemberType + + public IMemberType BuildMemberTypeEntity(ContentTypeDto dto) + { + throw new NotImplementedException(); + } + + public IEnumerable BuildMemberTypeDtos(IMemberType entity) + { + var memberType = entity as MemberType; + if (memberType == null || memberType.PropertyTypes.Any() == false) + return Enumerable.Empty(); + + var dtos = memberType.PropertyTypes.Select(x => new MemberTypeDto + { + NodeId = entity.Id, + PropertyTypeId = x.Id, + CanEdit = memberType.MemberCanEditProperty(x.Alias), + ViewOnProfile = memberType.MemberCanViewProperty(x.Alias), + IsSensitive = memberType.IsSensitiveProperty(x.Alias) + }).ToList(); + return dtos; + } + + #endregion + + #region Common + + private static void BuildCommonEntity(ContentTypeBase entity, ContentTypeDto dto) + { + entity.Id = dto.NodeDto.NodeId; + entity.Key = dto.NodeDto.UniqueId; + entity.Alias = dto.Alias; + entity.Name = dto.NodeDto.Text; + entity.Icon = dto.Icon; + entity.Thumbnail = dto.Thumbnail; + entity.SortOrder = dto.NodeDto.SortOrder; + entity.Description = dto.Description; + entity.CreateDate = dto.NodeDto.CreateDate; + entity.Path = dto.NodeDto.Path; + entity.Level = dto.NodeDto.Level; + entity.CreatorId = dto.NodeDto.UserId ?? Constants.Security.UnknownUserId; + entity.AllowedAsRoot = dto.AllowAtRoot; + entity.IsContainer = dto.IsContainer; + entity.Trashed = dto.NodeDto.Trashed; + entity.Variations = (ContentVariation) dto.Variations; + } + + public ContentTypeDto BuildContentTypeDto(IContentTypeBase entity) + { + Guid nodeObjectType; + if (entity is IContentType) + nodeObjectType = Constants.ObjectTypes.DocumentType; + else if (entity is IMediaType) + nodeObjectType = Constants.ObjectTypes.MediaType; + else if (entity is IMemberType) + nodeObjectType = Constants.ObjectTypes.MemberType; + else + throw new Exception("Invalid entity."); + + var contentTypeDto = new ContentTypeDto + { + Alias = entity.Alias, + Description = entity.Description, + Icon = entity.Icon, + Thumbnail = entity.Thumbnail, + NodeId = entity.Id, + AllowAtRoot = entity.AllowedAsRoot, + IsContainer = entity.IsContainer, + Variations = (byte) entity.Variations, + NodeDto = BuildNodeDto(entity, nodeObjectType) + }; + return contentTypeDto; + } + + private static NodeDto BuildNodeDto(IUmbracoEntity entity, Guid nodeObjectType) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = nodeObjectType, + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = entity.Name, + Trashed = false, + UniqueId = entity.Key, + UserId = entity.CreatorId + }; + return nodeDto; + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs index e84b1c96e2..ee63f37f8d 100644 --- a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs @@ -1,66 +1,66 @@ -using System.Collections.Generic; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class DictionaryItemFactory - { - #region Implementation of IEntityFactory - - public IDictionaryItem BuildEntity(DictionaryDto dto) - { - var item = new DictionaryItem(dto.Parent, dto.Key); - - try - { - item.DisableChangeTracking(); - - item.Id = dto.PrimaryKey; - item.Key = dto.UniqueId; - - // reset dirty initial properties (U4-1946) - item.ResetDirtyProperties(false); - return item; - } - finally - { - item.EnableChangeTracking(); - } - } - - public DictionaryDto BuildDto(IDictionaryItem entity) - { - return new DictionaryDto - { - UniqueId = entity.Key, - Key = entity.ItemKey, - Parent = entity.ParentId, - PrimaryKey = entity.Id, - LanguageTextDtos = BuildLanguageTextDtos(entity) - }; - } - - #endregion - - private List BuildLanguageTextDtos(IDictionaryItem entity) - { - var list = new List(); - foreach (var translation in entity.Translations) - { - var text = new LanguageTextDto - { - LanguageId = translation.LanguageId, - UniqueId = translation.Key, - Value = translation.Value - }; - - if (translation.HasIdentity) - text.PrimaryKey = translation.Id; - - list.Add(text); - } - return list; - } - } -} +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class DictionaryItemFactory + { + #region Implementation of IEntityFactory + + public IDictionaryItem BuildEntity(DictionaryDto dto) + { + var item = new DictionaryItem(dto.Parent, dto.Key); + + try + { + item.DisableChangeTracking(); + + item.Id = dto.PrimaryKey; + item.Key = dto.UniqueId; + + // reset dirty initial properties (U4-1946) + item.ResetDirtyProperties(false); + return item; + } + finally + { + item.EnableChangeTracking(); + } + } + + public DictionaryDto BuildDto(IDictionaryItem entity) + { + return new DictionaryDto + { + UniqueId = entity.Key, + Key = entity.ItemKey, + Parent = entity.ParentId, + PrimaryKey = entity.Id, + LanguageTextDtos = BuildLanguageTextDtos(entity) + }; + } + + #endregion + + private List BuildLanguageTextDtos(IDictionaryItem entity) + { + var list = new List(); + foreach (var translation in entity.Translations) + { + var text = new LanguageTextDto + { + LanguageId = translation.LanguageId, + UniqueId = translation.Key, + Value = translation.Value + }; + + if (translation.HasIdentity) + text.PrimaryKey = translation.Id; + + list.Add(text); + } + return list; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs index a4075e03e2..a995480bc2 100644 --- a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs @@ -1,55 +1,55 @@ -using System; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class DictionaryTranslationFactory - { - private readonly Guid _uniqueId; - - public DictionaryTranslationFactory(Guid uniqueId) - { - _uniqueId = uniqueId; - } - - #region Implementation of IEntityFactory - - public IDictionaryTranslation BuildEntity(LanguageTextDto dto) - { - var item = new DictionaryTranslation(dto.LanguageId, dto.Value, _uniqueId); - - try - { - item.DisableChangeTracking(); - - item.Id = dto.PrimaryKey; - - // reset dirty initial properties (U4-1946) - item.ResetDirtyProperties(false); - return item; - } - finally - { - item.EnableChangeTracking(); - } - } - - public LanguageTextDto BuildDto(IDictionaryTranslation entity) - { - var text = new LanguageTextDto - { - LanguageId = entity.LanguageId, - UniqueId = _uniqueId, - Value = entity.Value - }; - - if (entity.HasIdentity) - text.PrimaryKey = entity.Id; - - return text; - } - - #endregion - } -} +using System; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class DictionaryTranslationFactory + { + private readonly Guid _uniqueId; + + public DictionaryTranslationFactory(Guid uniqueId) + { + _uniqueId = uniqueId; + } + + #region Implementation of IEntityFactory + + public IDictionaryTranslation BuildEntity(LanguageTextDto dto) + { + var item = new DictionaryTranslation(dto.LanguageId, dto.Value, _uniqueId); + + try + { + item.DisableChangeTracking(); + + item.Id = dto.PrimaryKey; + + // reset dirty initial properties (U4-1946) + item.ResetDirtyProperties(false); + return item; + } + finally + { + item.EnableChangeTracking(); + } + } + + public LanguageTextDto BuildDto(IDictionaryTranslation entity) + { + var text = new LanguageTextDto + { + LanguageId = entity.LanguageId, + UniqueId = _uniqueId, + Value = entity.Value + }; + + if (entity.HasIdentity) + text.PrimaryKey = entity.Id; + + return text; + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs index f79dab1bbd..9355d43ed5 100644 --- a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs @@ -1,26 +1,26 @@ -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class LanguageFactory - { - public ILanguage BuildEntity(LanguageDto dto) - { - var lang = new Language(dto.IsoCode) { CultureName = dto.CultureName, Id = dto.Id, IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage, Mandatory = dto.Mandatory }; - // reset dirty initial properties (U4-1946) - lang.ResetDirtyProperties(false); - return lang; - } - - public LanguageDto BuildDto(ILanguage entity) - { - var dto = new LanguageDto { CultureName = entity.CultureName, IsoCode = entity.IsoCode, IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage, Mandatory = entity.Mandatory }; - if (entity.HasIdentity) - dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); - - return dto; - } - } -} +using System.Globalization; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class LanguageFactory + { + public ILanguage BuildEntity(LanguageDto dto) + { + var lang = new Language(dto.IsoCode) { CultureName = dto.CultureName, Id = dto.Id, IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage, Mandatory = dto.Mandatory }; + // reset dirty initial properties (U4-1946) + lang.ResetDirtyProperties(false); + return lang; + } + + public LanguageDto BuildDto(ILanguage entity) + { + var dto = new LanguageDto { CultureName = entity.CultureName, IsoCode = entity.IsoCode, IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage, Mandatory = entity.Mandatory }; + if (entity.HasIdentity) + dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); + + return dto; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs index 3963c591f0..063ac432ea 100644 --- a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs @@ -1,77 +1,77 @@ -using System.Collections.Generic; -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class MacroFactory - { - public IMacro BuildEntity(MacroDto dto) - { - var model = new Macro(dto.Id, dto.UniqueId, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.MacroSource, (MacroTypes)dto.MacroType); - - try - { - model.DisableChangeTracking(); - - foreach (var p in dto.MacroPropertyDtos.EmptyNull()) - { - model.Properties.Add(new MacroProperty(p.Id, p.UniqueId, p.Alias, p.Name, p.SortOrder, p.EditorAlias)); - } - - // reset dirty initial properties (U4-1946) - model.ResetDirtyProperties(false); - return model; - } - finally - { - model.EnableChangeTracking(); - } - } - - public MacroDto BuildDto(IMacro entity) - { - var dto = new MacroDto - { - UniqueId = entity.Key, - Alias = entity.Alias, - CacheByPage = entity.CacheByPage, - CachePersonalized = entity.CacheByMember, - DontRender = entity.DontRender, - Name = entity.Name, - MacroSource = entity.MacroSource, - RefreshRate = entity.CacheDuration, - UseInEditor = entity.UseInEditor, +using System.Collections.Generic; +using System.Globalization; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class MacroFactory + { + public IMacro BuildEntity(MacroDto dto) + { + var model = new Macro(dto.Id, dto.UniqueId, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.MacroSource, (MacroTypes)dto.MacroType); + + try + { + model.DisableChangeTracking(); + + foreach (var p in dto.MacroPropertyDtos.EmptyNull()) + { + model.Properties.Add(new MacroProperty(p.Id, p.UniqueId, p.Alias, p.Name, p.SortOrder, p.EditorAlias)); + } + + // reset dirty initial properties (U4-1946) + model.ResetDirtyProperties(false); + return model; + } + finally + { + model.EnableChangeTracking(); + } + } + + public MacroDto BuildDto(IMacro entity) + { + var dto = new MacroDto + { + UniqueId = entity.Key, + Alias = entity.Alias, + CacheByPage = entity.CacheByPage, + CachePersonalized = entity.CacheByMember, + DontRender = entity.DontRender, + Name = entity.Name, + MacroSource = entity.MacroSource, + RefreshRate = entity.CacheDuration, + UseInEditor = entity.UseInEditor, MacroPropertyDtos = BuildPropertyDtos(entity), - MacroType = (int)entity.MacroType - }; - - if (entity.HasIdentity) - dto.Id = int.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); - - return dto; - } - - private List BuildPropertyDtos(IMacro entity) - { - var list = new List(); - foreach (var p in entity.Properties) - { - var text = new MacroPropertyDto - { - UniqueId = p.Key, - Alias = p.Alias, - Name = p.Name, - Macro = entity.Id, - SortOrder = (byte)p.SortOrder, - EditorAlias = p.EditorAlias, - Id = p.Id - }; - - list.Add(text); - } - return list; - } - } -} + MacroType = (int)entity.MacroType + }; + + if (entity.HasIdentity) + dto.Id = int.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); + + return dto; + } + + private List BuildPropertyDtos(IMacro entity) + { + var list = new List(); + foreach (var p in entity.Properties) + { + var text = new MacroPropertyDto + { + UniqueId = p.Key, + Alias = p.Alias, + Name = p.Name, + Macro = entity.Id, + SortOrder = (byte)p.SortOrder, + EditorAlias = p.EditorAlias, + Id = p.Id + }; + + list.Add(text); + } + return list; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index 6f79f03c73..1865a72495 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -1,202 +1,202 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class MemberTypeReadOnlyFactory - { - public IMemberType BuildEntity(MemberTypeReadOnlyDto dto, out bool needsSaving) - { - var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); - needsSaving = false; - - var memberType = new MemberType(dto.ParentId); - - try - { - memberType.DisableChangeTracking(); - - memberType.Alias = dto.Alias; - memberType.AllowedAsRoot = dto.AllowAtRoot; - memberType.CreateDate = dto.CreateDate; - memberType.CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0; - memberType.Description = dto.Description; - memberType.Icon = dto.Icon; - memberType.Id = dto.NodeId; - memberType.IsContainer = dto.IsContainer; - memberType.Key = dto.UniqueId.Value; - memberType.Level = dto.Level; - memberType.Name = dto.Text; - memberType.Path = dto.Path; - memberType.SortOrder = dto.SortOrder; - memberType.Thumbnail = dto.Thumbnail; - memberType.Trashed = dto.Trashed; - memberType.UpdateDate = dto.CreateDate; - memberType.AllowedContentTypes = Enumerable.Empty(); - - var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType, standardPropertyTypes); - memberType.PropertyGroups = propertyTypeGroupCollection; - - var propertyTypes = GetPropertyTypes(dto, memberType, standardPropertyTypes); - - //By Convention we add 9 stnd PropertyTypes - This is only here to support loading of types that didn't have these conventions before. - foreach (var standardPropertyType in standardPropertyTypes) - { - if (dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue; - - // beware! - // means that we can return a memberType "from database" that has some property types - // that do *not* come from the database and therefore are incomplete eg have no key, - // no id, no dataTypeDefinitionId - ouch! - better notify caller of the situation - needsSaving = true; - - //Add the standard PropertyType to the current list - propertyTypes.Add(standardPropertyType.Value); - - //Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType - memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, - new MemberTypePropertyProfileAccess(false, false, false)); - } - memberType.NoGroupPropertyTypes = propertyTypes; - - return memberType; - } - finally - { - memberType.EnableChangeTracking(); - } - } - - private PropertyGroupCollection GetPropertyTypeGroupCollection(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps) - { - // see PropertyGroupFactory, repeating code here... - - var propertyGroups = new PropertyGroupCollection(); - foreach (var groupDto in dto.PropertyTypeGroups.Where(x => x.Id.HasValue)) - { - var group = new PropertyGroup(MemberType.IsPublishingConst); - - // if the group is defined on the current member type, - // assign its identifier, else it will be zero - if (groupDto.ContentTypeNodeId == memberType.Id) - { - // note: no idea why Id is nullable here, but better check - if (groupDto.Id.HasValue == false) - throw new Exception("GroupDto.Id has no value."); - group.Id = groupDto.Id.Value; - } - - group.Key = groupDto.UniqueId; - group.Name = groupDto.Text; - group.SortOrder = groupDto.SortOrder; - group.PropertyTypes = new PropertyTypeCollection(MemberType.IsPublishingConst); - - //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded - var localGroupDto = groupDto; - var typeDtos = dto.PropertyTypes.Where(x => x.Id.HasValue && x.Id > 0 && x.PropertyTypeGroupId.HasValue && x.PropertyTypeGroupId.Value == localGroupDto.Id.Value); - foreach (var typeDto in typeDtos) - { - //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType - memberType.MemberTypePropertyTypes.Add(typeDto.Alias, - new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive)); - - var tempGroupDto = groupDto; - - //ensures that any built-in membership properties have their correct dbtype assigned no matter - //what the underlying data type is - var propDbType = MemberTypeRepository.GetDbTypeForBuiltInProperty( - typeDto.Alias, - typeDto.DbType.EnumParse(true), - standardProps); - - var propertyType = new PropertyType( - typeDto.PropertyEditorAlias, - propDbType.Result, - //This flag tells the property type that it has an explicit dbtype and that it cannot be changed - // which is what we want for the built-in properties. - propDbType.Success, - typeDto.Alias) - { - DataTypeId = typeDto.DataTypeId, - Description = typeDto.Description, - Id = typeDto.Id.Value, - Name = typeDto.Name, - Mandatory = typeDto.Mandatory, - SortOrder = typeDto.SortOrder, - ValidationRegExp = typeDto.ValidationRegExp, - PropertyGroupId = new Lazy(() => tempGroupDto.Id.Value), - CreateDate = memberType.CreateDate, - UpdateDate = memberType.UpdateDate, - Key = typeDto.UniqueId - }; - - // reset dirty initial properties (U4-1946) - propertyType.ResetDirtyProperties(false); - group.PropertyTypes.Add(propertyType); - } - - // reset dirty initial properties (U4-1946) - group.ResetDirtyProperties(false); - propertyGroups.Add(group); - } - - return propertyGroups; - } - - - - private List GetPropertyTypes(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps) - { - //Find PropertyTypes that does not belong to a PropertyTypeGroup - var propertyTypes = new List(); - foreach (var typeDto in dto.PropertyTypes.Where(x => (x.PropertyTypeGroupId.HasValue == false || x.PropertyTypeGroupId.Value == 0) && x.Id.HasValue)) - { - //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType - memberType.MemberTypePropertyTypes.Add(typeDto.Alias, - new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive)); - - //ensures that any built-in membership properties have their correct dbtype assigned no matter - //what the underlying data type is - var propDbType = MemberTypeRepository.GetDbTypeForBuiltInProperty( - typeDto.Alias, - typeDto.DbType.EnumParse(true), - standardProps); - - var propertyType = new PropertyType( - typeDto.PropertyEditorAlias, - propDbType.Result, - //This flag tells the property type that it has an explicit dbtype and that it cannot be changed - // which is what we want for the built-in properties. - propDbType.Success, - typeDto.Alias) - { - DataTypeId = typeDto.DataTypeId, - Description = typeDto.Description, - Id = typeDto.Id.Value, - Mandatory = typeDto.Mandatory, - Name = typeDto.Name, - SortOrder = typeDto.SortOrder, - ValidationRegExp = typeDto.ValidationRegExp, - PropertyGroupId = null, - CreateDate = dto.CreateDate, - UpdateDate = dto.CreateDate, - Key = typeDto.UniqueId - }; - - propertyTypes.Add(propertyType); - } - return propertyTypes; - } - - public MemberTypeReadOnlyDto BuildDto(IMemberType entity) - { - throw new System.NotImplementedException(); - } - - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class MemberTypeReadOnlyFactory + { + public IMemberType BuildEntity(MemberTypeReadOnlyDto dto, out bool needsSaving) + { + var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + needsSaving = false; + + var memberType = new MemberType(dto.ParentId); + + try + { + memberType.DisableChangeTracking(); + + memberType.Alias = dto.Alias; + memberType.AllowedAsRoot = dto.AllowAtRoot; + memberType.CreateDate = dto.CreateDate; + memberType.CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0; + memberType.Description = dto.Description; + memberType.Icon = dto.Icon; + memberType.Id = dto.NodeId; + memberType.IsContainer = dto.IsContainer; + memberType.Key = dto.UniqueId.Value; + memberType.Level = dto.Level; + memberType.Name = dto.Text; + memberType.Path = dto.Path; + memberType.SortOrder = dto.SortOrder; + memberType.Thumbnail = dto.Thumbnail; + memberType.Trashed = dto.Trashed; + memberType.UpdateDate = dto.CreateDate; + memberType.AllowedContentTypes = Enumerable.Empty(); + + var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType, standardPropertyTypes); + memberType.PropertyGroups = propertyTypeGroupCollection; + + var propertyTypes = GetPropertyTypes(dto, memberType, standardPropertyTypes); + + //By Convention we add 9 stnd PropertyTypes - This is only here to support loading of types that didn't have these conventions before. + foreach (var standardPropertyType in standardPropertyTypes) + { + if (dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue; + + // beware! + // means that we can return a memberType "from database" that has some property types + // that do *not* come from the database and therefore are incomplete eg have no key, + // no id, no dataTypeDefinitionId - ouch! - better notify caller of the situation + needsSaving = true; + + //Add the standard PropertyType to the current list + propertyTypes.Add(standardPropertyType.Value); + + //Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType + memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, + new MemberTypePropertyProfileAccess(false, false, false)); + } + memberType.NoGroupPropertyTypes = propertyTypes; + + return memberType; + } + finally + { + memberType.EnableChangeTracking(); + } + } + + private PropertyGroupCollection GetPropertyTypeGroupCollection(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps) + { + // see PropertyGroupFactory, repeating code here... + + var propertyGroups = new PropertyGroupCollection(); + foreach (var groupDto in dto.PropertyTypeGroups.Where(x => x.Id.HasValue)) + { + var group = new PropertyGroup(MemberType.IsPublishingConst); + + // if the group is defined on the current member type, + // assign its identifier, else it will be zero + if (groupDto.ContentTypeNodeId == memberType.Id) + { + // note: no idea why Id is nullable here, but better check + if (groupDto.Id.HasValue == false) + throw new Exception("GroupDto.Id has no value."); + group.Id = groupDto.Id.Value; + } + + group.Key = groupDto.UniqueId; + group.Name = groupDto.Text; + group.SortOrder = groupDto.SortOrder; + group.PropertyTypes = new PropertyTypeCollection(MemberType.IsPublishingConst); + + //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded + var localGroupDto = groupDto; + var typeDtos = dto.PropertyTypes.Where(x => x.Id.HasValue && x.Id > 0 && x.PropertyTypeGroupId.HasValue && x.PropertyTypeGroupId.Value == localGroupDto.Id.Value); + foreach (var typeDto in typeDtos) + { + //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType + memberType.MemberTypePropertyTypes.Add(typeDto.Alias, + new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive)); + + var tempGroupDto = groupDto; + + //ensures that any built-in membership properties have their correct dbtype assigned no matter + //what the underlying data type is + var propDbType = MemberTypeRepository.GetDbTypeForBuiltInProperty( + typeDto.Alias, + typeDto.DbType.EnumParse(true), + standardProps); + + var propertyType = new PropertyType( + typeDto.PropertyEditorAlias, + propDbType.Result, + //This flag tells the property type that it has an explicit dbtype and that it cannot be changed + // which is what we want for the built-in properties. + propDbType.Success, + typeDto.Alias) + { + DataTypeId = typeDto.DataTypeId, + Description = typeDto.Description, + Id = typeDto.Id.Value, + Name = typeDto.Name, + Mandatory = typeDto.Mandatory, + SortOrder = typeDto.SortOrder, + ValidationRegExp = typeDto.ValidationRegExp, + PropertyGroupId = new Lazy(() => tempGroupDto.Id.Value), + CreateDate = memberType.CreateDate, + UpdateDate = memberType.UpdateDate, + Key = typeDto.UniqueId + }; + + // reset dirty initial properties (U4-1946) + propertyType.ResetDirtyProperties(false); + group.PropertyTypes.Add(propertyType); + } + + // reset dirty initial properties (U4-1946) + group.ResetDirtyProperties(false); + propertyGroups.Add(group); + } + + return propertyGroups; + } + + + + private List GetPropertyTypes(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps) + { + //Find PropertyTypes that does not belong to a PropertyTypeGroup + var propertyTypes = new List(); + foreach (var typeDto in dto.PropertyTypes.Where(x => (x.PropertyTypeGroupId.HasValue == false || x.PropertyTypeGroupId.Value == 0) && x.Id.HasValue)) + { + //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType + memberType.MemberTypePropertyTypes.Add(typeDto.Alias, + new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive)); + + //ensures that any built-in membership properties have their correct dbtype assigned no matter + //what the underlying data type is + var propDbType = MemberTypeRepository.GetDbTypeForBuiltInProperty( + typeDto.Alias, + typeDto.DbType.EnumParse(true), + standardProps); + + var propertyType = new PropertyType( + typeDto.PropertyEditorAlias, + propDbType.Result, + //This flag tells the property type that it has an explicit dbtype and that it cannot be changed + // which is what we want for the built-in properties. + propDbType.Success, + typeDto.Alias) + { + DataTypeId = typeDto.DataTypeId, + Description = typeDto.Description, + Id = typeDto.Id.Value, + Mandatory = typeDto.Mandatory, + Name = typeDto.Name, + SortOrder = typeDto.SortOrder, + ValidationRegExp = typeDto.ValidationRegExp, + PropertyGroupId = null, + CreateDate = dto.CreateDate, + UpdateDate = dto.CreateDate, + Key = typeDto.UniqueId + }; + + propertyTypes.Add(propertyType); + } + return propertyTypes; + } + + public MemberTypeReadOnlyDto BuildDto(IMemberType entity) + { + throw new System.NotImplementedException(); + } + + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/ModelFactoryConfiguration.cs b/src/Umbraco.Core/Persistence/Factories/ModelFactoryConfiguration.cs index 0a7372087d..e473965be0 100644 --- a/src/Umbraco.Core/Persistence/Factories/ModelFactoryConfiguration.cs +++ b/src/Umbraco.Core/Persistence/Factories/ModelFactoryConfiguration.cs @@ -1,20 +1,20 @@ -using AutoMapper; - -// fixme what is this?! -namespace Umbraco.Core.Persistence.Factories -{ - - ////TODO: Implement AutoMapper for the other factories! - - ///// - ///// This configures all of the AutoMapper supported mapping factories - ///// - //internal class ModelFactoryConfiguration : MapperConfiguration - //{ - // public override void ConfigureMappings(IConfiguration config) - // { - - // UserSectionFactory.ConfigureMappings(config); - // } - //} -} +using AutoMapper; + +// fixme what is this?! +namespace Umbraco.Core.Persistence.Factories +{ + + ////TODO: Implement AutoMapper for the other factories! + + ///// + ///// This configures all of the AutoMapper supported mapping factories + ///// + //internal class ModelFactoryConfiguration : MapperConfiguration + //{ + // public override void ConfigureMappings(IConfiguration config) + // { + + // UserSectionFactory.ConfigureMappings(config); + // } + //} +} diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index d25f921f1a..b0e3e2dc7d 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -1,153 +1,153 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.Repositories; - -namespace Umbraco.Core.Persistence.Factories -{ - internal static class PropertyFactory - { - public static IEnumerable BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection dtos, int publishedVersionId, ILanguageRepository languageRepository) - { - var properties = new List(); - var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable) x); - - foreach (var propertyType in propertyTypes) - { - var property = propertyType.CreateProperty(); - - try - { - property.DisableChangeTracking(); - - // see notes in BuildDtos - we always have edit+published dtos - - if (xdtos.TryGetValue(propertyType.Id, out var propDtos)) - { - foreach (var propDto in propDtos) - property.FactorySetValue(languageRepository.GetIsoCodeById(propDto.LanguageId), propDto.Segment, propDto.VersionId == publishedVersionId, propDto.Value); - } - - property.ResetDirtyProperties(false); - properties.Add(property); - } - finally - { - property.EnableChangeTracking(); - } - } - - return properties; - } - - private static PropertyDataDto BuildDto(int versionId, Property property, int? languageId, string segment, object value) - { - var dto = new PropertyDataDto { VersionId = versionId, PropertyTypeId = property.PropertyTypeId }; - - if (languageId.HasValue) - dto.LanguageId = languageId; - - if (segment != null) - dto.Segment = segment; - - if (property.ValueStorageType == ValueStorageType.Integer) - { - if (value is bool || property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.Boolean) - { - dto.IntegerValue = value != null && string.IsNullOrEmpty(value.ToString()) ? 0 : Convert.ToInt32(value); - } - else if (value != null && string.IsNullOrWhiteSpace(value.ToString()) == false && int.TryParse(value.ToString(), out var val)) - { - dto.IntegerValue = val; - } - } - else if (property.ValueStorageType == ValueStorageType.Decimal && value != null) - { - if (decimal.TryParse(value.ToString(), out var val)) - { - dto.DecimalValue = val; // property value should be normalized already - } - } - else if (property.ValueStorageType == ValueStorageType.Date && value != null && string.IsNullOrWhiteSpace(value.ToString()) == false) - { - if (DateTime.TryParse(value.ToString(), out var date)) - { - dto.DateValue = date; - } - } - else if (property.ValueStorageType == ValueStorageType.Ntext && value != null) - { - dto.TextValue = value.ToString(); - } - else if (property.ValueStorageType == ValueStorageType.Nvarchar && value != null) - { - dto.VarcharValue = value.ToString(); - } - - return dto; - } - - public static IEnumerable BuildDtos(int currentVersionId, int publishedVersionId, IEnumerable properties, ILanguageRepository languageRepository, out bool edited, out HashSet editedCultures) - { - var propertyDataDtos = new List(); - edited = false; - editedCultures = null; // don't allocate unless necessary - - foreach (var property in properties) - { - if (property.PropertyType.IsPublishing) +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Repositories; + +namespace Umbraco.Core.Persistence.Factories +{ + internal static class PropertyFactory + { + public static IEnumerable BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection dtos, int publishedVersionId, ILanguageRepository languageRepository) + { + var properties = new List(); + var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable) x); + + foreach (var propertyType in propertyTypes) + { + var property = propertyType.CreateProperty(); + + try + { + property.DisableChangeTracking(); + + // see notes in BuildDtos - we always have edit+published dtos + + if (xdtos.TryGetValue(propertyType.Id, out var propDtos)) + { + foreach (var propDto in propDtos) + property.FactorySetValue(languageRepository.GetIsoCodeById(propDto.LanguageId), propDto.Segment, propDto.VersionId == publishedVersionId, propDto.Value); + } + + property.ResetDirtyProperties(false); + properties.Add(property); + } + finally + { + property.EnableChangeTracking(); + } + } + + return properties; + } + + private static PropertyDataDto BuildDto(int versionId, Property property, int? languageId, string segment, object value) + { + var dto = new PropertyDataDto { VersionId = versionId, PropertyTypeId = property.PropertyTypeId }; + + if (languageId.HasValue) + dto.LanguageId = languageId; + + if (segment != null) + dto.Segment = segment; + + if (property.ValueStorageType == ValueStorageType.Integer) + { + if (value is bool || property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.Boolean) + { + dto.IntegerValue = value != null && string.IsNullOrEmpty(value.ToString()) ? 0 : Convert.ToInt32(value); + } + else if (value != null && string.IsNullOrWhiteSpace(value.ToString()) == false && int.TryParse(value.ToString(), out var val)) + { + dto.IntegerValue = val; + } + } + else if (property.ValueStorageType == ValueStorageType.Decimal && value != null) + { + if (decimal.TryParse(value.ToString(), out var val)) + { + dto.DecimalValue = val; // property value should be normalized already + } + } + else if (property.ValueStorageType == ValueStorageType.Date && value != null && string.IsNullOrWhiteSpace(value.ToString()) == false) + { + if (DateTime.TryParse(value.ToString(), out var date)) + { + dto.DateValue = date; + } + } + else if (property.ValueStorageType == ValueStorageType.Ntext && value != null) + { + dto.TextValue = value.ToString(); + } + else if (property.ValueStorageType == ValueStorageType.Nvarchar && value != null) + { + dto.VarcharValue = value.ToString(); + } + + return dto; + } + + public static IEnumerable BuildDtos(int currentVersionId, int publishedVersionId, IEnumerable properties, ILanguageRepository languageRepository, out bool edited, out HashSet editedCultures) + { + var propertyDataDtos = new List(); + edited = false; + editedCultures = null; // don't allocate unless necessary + + foreach (var property in properties) + { + if (property.PropertyType.IsPublishing) { // fixme // why only CultureNeutral? // then, the tree can only show when a CultureNeutral value has been modified, but not when // a CultureSegment has been modified, so if I edit some french/mobile thing, the tree will // NOT tell me that I have changes? - + var editingCultures = property.PropertyType.Variations.Has(ContentVariation.CultureNeutral); if (editingCultures && editedCultures == null) editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase); - - // publishing = deal with edit and published values - foreach (var propertyValue in property.Values) - { - // deal with published value - if (propertyValue.PublishedValue != null && publishedVersionId > 0) - propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue)); - - // deal with edit value - if (propertyValue.EditedValue != null) - propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); - - // deal with missing edit value (fix inconsistencies) - else if (propertyValue.PublishedValue != null) - propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue)); - - // use explicit equals here, else object comparison fails at comparing eg strings - var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); - edited |= !sameValues; + + // publishing = deal with edit and published values + foreach (var propertyValue in property.Values) + { + // deal with published value + if (propertyValue.PublishedValue != null && publishedVersionId > 0) + propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue)); + + // deal with edit value + if (propertyValue.EditedValue != null) + propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); + + // deal with missing edit value (fix inconsistencies) + else if (propertyValue.PublishedValue != null) + propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue)); + + // use explicit equals here, else object comparison fails at comparing eg strings + var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); + edited |= !sameValues; if (editingCultures && // cultures can be edited, ie CultureNeutral is supported propertyValue.Culture != null && propertyValue.Segment == null && // and value is CultureNeutral - !sameValues) // and edited and published are different - { - editedCultures.Add(propertyValue.Culture); // report culture as edited - } - } - } - else - { - foreach (var propertyValue in property.Values) - { - // not publishing = only deal with edit values - if (propertyValue.EditedValue != null) - propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); - } - edited = true; - } - } - - return propertyDataDtos; - } - } -} + !sameValues) // and edited and published are different + { + editedCultures.Add(propertyValue.Culture); // report culture as edited + } + } + } + else + { + foreach (var propertyValue in property.Values) + { + // not publishing = only deal with edit values + if (propertyValue.EditedValue != null) + propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); + } + edited = true; + } + } + + return propertyDataDtos; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index 59bed3cede..2adc439541 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -1,162 +1,162 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class PropertyGroupFactory - { - private readonly int _contentTypeId; - private readonly DateTime _createDate; - private readonly DateTime _updateDate; - //a callback to create a property type which can be injected via a contructor - private readonly Func _propertyTypeCtor; - - public PropertyGroupFactory(int contentTypeId) - { - _contentTypeId = contentTypeId; - _propertyTypeCtor = (propertyEditorAlias, dbType, alias) => new PropertyType(propertyEditorAlias, dbType); - } - - public PropertyGroupFactory(int contentTypeId, DateTime createDate, DateTime updateDate, Func propertyTypeCtor) - { - _contentTypeId = contentTypeId; - _createDate = createDate; - _updateDate = updateDate; - _propertyTypeCtor = propertyTypeCtor; - } - - #region Implementation of IEntityFactory,IEnumerable> - - public IEnumerable BuildEntity(IEnumerable groupDtos, bool isPublishing) - { - // groupDtos contains all the groups, those that are defined on the current - // content type, and those that are inherited from composition content types - var propertyGroups = new PropertyGroupCollection(); - foreach (var groupDto in groupDtos) - { - var group = new PropertyGroup(isPublishing); - - try - { - group.DisableChangeTracking(); - - // if the group is defined on the current content type, - // assign its identifier, else it will be zero - if (groupDto.ContentTypeNodeId == _contentTypeId) - group.Id = groupDto.Id; - - group.Name = groupDto.Text; - group.SortOrder = groupDto.SortOrder; - group.PropertyTypes = new PropertyTypeCollection(isPublishing); - group.Key = groupDto.UniqueId; - - //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded - var typeDtos = groupDto.PropertyTypeDtos.Where(x => x.Id > 0); - foreach (var typeDto in typeDtos) - { - var tempGroupDto = groupDto; - var propertyType = _propertyTypeCtor(typeDto.DataTypeDto.EditorAlias, - typeDto.DataTypeDto.DbType.EnumParse(true), - typeDto.Alias); - - try - { - propertyType.DisableChangeTracking(); - - propertyType.Alias = typeDto.Alias; - propertyType.DataTypeId = typeDto.DataTypeId; - propertyType.Description = typeDto.Description; - propertyType.Id = typeDto.Id; - propertyType.Key = typeDto.UniqueId; - propertyType.Name = typeDto.Name; - propertyType.Mandatory = typeDto.Mandatory; - propertyType.SortOrder = typeDto.SortOrder; - propertyType.ValidationRegExp = typeDto.ValidationRegExp; - propertyType.PropertyGroupId = new Lazy(() => tempGroupDto.Id); - propertyType.CreateDate = _createDate; - propertyType.UpdateDate = _updateDate; - propertyType.Variations = (ContentVariation) typeDto.Variations; - - // reset dirty initial properties (U4-1946) - propertyType.ResetDirtyProperties(false); - group.PropertyTypes.Add(propertyType); - } - finally - { - propertyType.EnableChangeTracking(); - } - } - - // reset dirty initial properties (U4-1946) - group.ResetDirtyProperties(false); - propertyGroups.Add(group); - } - finally - { - group.EnableChangeTracking(); - } - } - - return propertyGroups; - } - - public IEnumerable BuildDto(IEnumerable entity) - { - return entity.Select(BuildGroupDto).ToList(); - } - - #endregion - - internal PropertyTypeGroupDto BuildGroupDto(PropertyGroup propertyGroup) - { - var dto = new PropertyTypeGroupDto - { - ContentTypeNodeId = _contentTypeId, - SortOrder = propertyGroup.SortOrder, - Text = propertyGroup.Name, - UniqueId = propertyGroup.Key - }; - - if (propertyGroup.HasIdentity) - dto.Id = propertyGroup.Id; - - dto.PropertyTypeDtos = propertyGroup.PropertyTypes.Select(propertyType => BuildPropertyTypeDto(propertyGroup.Id, propertyType)).ToList(); - - return dto; - } - - internal PropertyTypeDto BuildPropertyTypeDto(int tabId, PropertyType propertyType) - { - var propertyTypeDto = new PropertyTypeDto - { - Alias = propertyType.Alias, - ContentTypeId = _contentTypeId, - DataTypeId = propertyType.DataTypeId, - Description = propertyType.Description, - Mandatory = propertyType.Mandatory, - Name = propertyType.Name, - SortOrder = propertyType.SortOrder, - ValidationRegExp = propertyType.ValidationRegExp, - UniqueId = propertyType.Key, - Variations = (byte) propertyType.Variations - }; - - if (tabId != default) - { - propertyTypeDto.PropertyTypeGroupId = tabId; - } - else - { - propertyTypeDto.PropertyTypeGroupId = null; - } - - if (propertyType.HasIdentity) - propertyTypeDto.Id = propertyType.Id; - - return propertyTypeDto; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class PropertyGroupFactory + { + private readonly int _contentTypeId; + private readonly DateTime _createDate; + private readonly DateTime _updateDate; + //a callback to create a property type which can be injected via a contructor + private readonly Func _propertyTypeCtor; + + public PropertyGroupFactory(int contentTypeId) + { + _contentTypeId = contentTypeId; + _propertyTypeCtor = (propertyEditorAlias, dbType, alias) => new PropertyType(propertyEditorAlias, dbType); + } + + public PropertyGroupFactory(int contentTypeId, DateTime createDate, DateTime updateDate, Func propertyTypeCtor) + { + _contentTypeId = contentTypeId; + _createDate = createDate; + _updateDate = updateDate; + _propertyTypeCtor = propertyTypeCtor; + } + + #region Implementation of IEntityFactory,IEnumerable> + + public IEnumerable BuildEntity(IEnumerable groupDtos, bool isPublishing) + { + // groupDtos contains all the groups, those that are defined on the current + // content type, and those that are inherited from composition content types + var propertyGroups = new PropertyGroupCollection(); + foreach (var groupDto in groupDtos) + { + var group = new PropertyGroup(isPublishing); + + try + { + group.DisableChangeTracking(); + + // if the group is defined on the current content type, + // assign its identifier, else it will be zero + if (groupDto.ContentTypeNodeId == _contentTypeId) + group.Id = groupDto.Id; + + group.Name = groupDto.Text; + group.SortOrder = groupDto.SortOrder; + group.PropertyTypes = new PropertyTypeCollection(isPublishing); + group.Key = groupDto.UniqueId; + + //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded + var typeDtos = groupDto.PropertyTypeDtos.Where(x => x.Id > 0); + foreach (var typeDto in typeDtos) + { + var tempGroupDto = groupDto; + var propertyType = _propertyTypeCtor(typeDto.DataTypeDto.EditorAlias, + typeDto.DataTypeDto.DbType.EnumParse(true), + typeDto.Alias); + + try + { + propertyType.DisableChangeTracking(); + + propertyType.Alias = typeDto.Alias; + propertyType.DataTypeId = typeDto.DataTypeId; + propertyType.Description = typeDto.Description; + propertyType.Id = typeDto.Id; + propertyType.Key = typeDto.UniqueId; + propertyType.Name = typeDto.Name; + propertyType.Mandatory = typeDto.Mandatory; + propertyType.SortOrder = typeDto.SortOrder; + propertyType.ValidationRegExp = typeDto.ValidationRegExp; + propertyType.PropertyGroupId = new Lazy(() => tempGroupDto.Id); + propertyType.CreateDate = _createDate; + propertyType.UpdateDate = _updateDate; + propertyType.Variations = (ContentVariation) typeDto.Variations; + + // reset dirty initial properties (U4-1946) + propertyType.ResetDirtyProperties(false); + group.PropertyTypes.Add(propertyType); + } + finally + { + propertyType.EnableChangeTracking(); + } + } + + // reset dirty initial properties (U4-1946) + group.ResetDirtyProperties(false); + propertyGroups.Add(group); + } + finally + { + group.EnableChangeTracking(); + } + } + + return propertyGroups; + } + + public IEnumerable BuildDto(IEnumerable entity) + { + return entity.Select(BuildGroupDto).ToList(); + } + + #endregion + + internal PropertyTypeGroupDto BuildGroupDto(PropertyGroup propertyGroup) + { + var dto = new PropertyTypeGroupDto + { + ContentTypeNodeId = _contentTypeId, + SortOrder = propertyGroup.SortOrder, + Text = propertyGroup.Name, + UniqueId = propertyGroup.Key + }; + + if (propertyGroup.HasIdentity) + dto.Id = propertyGroup.Id; + + dto.PropertyTypeDtos = propertyGroup.PropertyTypes.Select(propertyType => BuildPropertyTypeDto(propertyGroup.Id, propertyType)).ToList(); + + return dto; + } + + internal PropertyTypeDto BuildPropertyTypeDto(int tabId, PropertyType propertyType) + { + var propertyTypeDto = new PropertyTypeDto + { + Alias = propertyType.Alias, + ContentTypeId = _contentTypeId, + DataTypeId = propertyType.DataTypeId, + Description = propertyType.Description, + Mandatory = propertyType.Mandatory, + Name = propertyType.Name, + SortOrder = propertyType.SortOrder, + ValidationRegExp = propertyType.ValidationRegExp, + UniqueId = propertyType.Key, + Variations = (byte) propertyType.Variations + }; + + if (tabId != default) + { + propertyTypeDto.PropertyTypeGroupId = tabId; + } + else + { + propertyTypeDto.PropertyTypeGroupId = null; + } + + if (propertyType.HasIdentity) + propertyTypeDto.Id = propertyType.Id; + + return propertyTypeDto; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs index 8a556486a9..6abb858e94 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs @@ -1,59 +1,59 @@ -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class RelationFactory - { - private readonly IRelationType _relationType; - - public RelationFactory(IRelationType relationType) - { - _relationType = relationType; - } - - #region Implementation of IEntityFactory - - public IRelation BuildEntity(RelationDto dto) - { - var entity = new Relation(dto.ParentId, dto.ChildId, _relationType); - - try - { - entity.DisableChangeTracking(); - - entity.Comment = dto.Comment; - entity.CreateDate = dto.Datetime; - entity.Id = dto.Id; - entity.UpdateDate = dto.Datetime; - - // reset dirty initial properties (U4-1946) - entity.ResetDirtyProperties(false); - return entity; - } - finally - { - entity.EnableChangeTracking(); - } - } - - public RelationDto BuildDto(IRelation entity) - { - var dto = new RelationDto - { - ChildId = entity.ChildId, - Comment = string.IsNullOrEmpty(entity.Comment) ? string.Empty : entity.Comment, - Datetime = entity.CreateDate, - ParentId = entity.ParentId, - RelationType = entity.RelationType.Id - }; - - if (entity.HasIdentity) - dto.Id = entity.Id; - - return dto; - } - - #endregion - } -} +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class RelationFactory + { + private readonly IRelationType _relationType; + + public RelationFactory(IRelationType relationType) + { + _relationType = relationType; + } + + #region Implementation of IEntityFactory + + public IRelation BuildEntity(RelationDto dto) + { + var entity = new Relation(dto.ParentId, dto.ChildId, _relationType); + + try + { + entity.DisableChangeTracking(); + + entity.Comment = dto.Comment; + entity.CreateDate = dto.Datetime; + entity.Id = dto.Id; + entity.UpdateDate = dto.Datetime; + + // reset dirty initial properties (U4-1946) + entity.ResetDirtyProperties(false); + return entity; + } + finally + { + entity.EnableChangeTracking(); + } + } + + public RelationDto BuildDto(IRelation entity) + { + var dto = new RelationDto + { + ChildId = entity.ChildId, + Comment = string.IsNullOrEmpty(entity.Comment) ? string.Empty : entity.Comment, + Datetime = entity.CreateDate, + ParentId = entity.ParentId, + RelationType = entity.RelationType.Id + }; + + if (entity.HasIdentity) + dto.Id = entity.Id; + + return dto; + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs index 6b35452bfa..ecf3ac8e93 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs @@ -1,54 +1,54 @@ -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class RelationTypeFactory - { - #region Implementation of IEntityFactory - - public IRelationType BuildEntity(RelationTypeDto dto) - { - var entity = new RelationType(dto.ChildObjectType, dto.ParentObjectType, dto.Alias); - - try - { - entity.DisableChangeTracking(); - - entity.Id = dto.Id; - entity.Key = dto.UniqueId; - entity.IsBidirectional = dto.Dual; - entity.Name = dto.Name; - - // reset dirty initial properties (U4-1946) - entity.ResetDirtyProperties(false); - return entity; - } - finally - { - entity.EnableChangeTracking(); - } - } - - public RelationTypeDto BuildDto(IRelationType entity) - { - var dto = new RelationTypeDto - { - Alias = entity.Alias, - ChildObjectType = entity.ChildObjectType, - Dual = entity.IsBidirectional, - Name = entity.Name, - ParentObjectType = entity.ParentObjectType, - UniqueId = entity.Key - }; - if (entity.HasIdentity) - { - dto.Id = entity.Id; - } - - return dto; - } - - #endregion - } -} +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class RelationTypeFactory + { + #region Implementation of IEntityFactory + + public IRelationType BuildEntity(RelationTypeDto dto) + { + var entity = new RelationType(dto.ChildObjectType, dto.ParentObjectType, dto.Alias); + + try + { + entity.DisableChangeTracking(); + + entity.Id = dto.Id; + entity.Key = dto.UniqueId; + entity.IsBidirectional = dto.Dual; + entity.Name = dto.Name; + + // reset dirty initial properties (U4-1946) + entity.ResetDirtyProperties(false); + return entity; + } + finally + { + entity.EnableChangeTracking(); + } + } + + public RelationTypeDto BuildDto(IRelationType entity) + { + var dto = new RelationTypeDto + { + Alias = entity.Alias, + ChildObjectType = entity.ChildObjectType, + Dual = entity.IsBidirectional, + Name = entity.Name, + ParentObjectType = entity.ParentObjectType, + UniqueId = entity.Key + }; + if (entity.HasIdentity) + { + dto.Id = entity.Id; + } + + return dto; + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs index 5b0a42ad7b..95b28bb551 100644 --- a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs @@ -1,34 +1,34 @@ -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class ServerRegistrationFactory - { - public ServerRegistration BuildEntity(ServerRegistrationDto dto) - { - var model = new ServerRegistration(dto.Id, dto.ServerAddress, dto.ServerIdentity, dto.DateRegistered, dto.DateAccessed, dto.IsActive, dto.IsMaster); - // reset dirty initial properties (U4-1946) - model.ResetDirtyProperties(false); - return model; - } - - public ServerRegistrationDto BuildDto(IServerRegistration entity) - { - var dto = new ServerRegistrationDto - { - ServerAddress = entity.ServerAddress, - DateRegistered = entity.CreateDate, - IsActive = entity.IsActive, - IsMaster = ((ServerRegistration) entity).IsMaster, - DateAccessed = entity.UpdateDate, - ServerIdentity = entity.ServerIdentity - }; - - if (entity.HasIdentity) - dto.Id = entity.Id; - - return dto; - } - } -} +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class ServerRegistrationFactory + { + public ServerRegistration BuildEntity(ServerRegistrationDto dto) + { + var model = new ServerRegistration(dto.Id, dto.ServerAddress, dto.ServerIdentity, dto.DateRegistered, dto.DateAccessed, dto.IsActive, dto.IsMaster); + // reset dirty initial properties (U4-1946) + model.ResetDirtyProperties(false); + return model; + } + + public ServerRegistrationDto BuildDto(IServerRegistration entity) + { + var dto = new ServerRegistrationDto + { + ServerAddress = entity.ServerAddress, + DateRegistered = entity.CreateDate, + IsActive = entity.IsActive, + IsMaster = ((ServerRegistration) entity).IsMaster, + DateAccessed = entity.UpdateDate, + ServerIdentity = entity.ServerIdentity + }; + + if (entity.HasIdentity) + dto.Id = entity.Id; + + return dto; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/TagFactory.cs b/src/Umbraco.Core/Persistence/Factories/TagFactory.cs index bd93d1d3b6..867e6b0ae3 100644 --- a/src/Umbraco.Core/Persistence/Factories/TagFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TagFactory.cs @@ -1,27 +1,27 @@ -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal static class TagFactory - { - public static ITag BuildEntity(TagDto dto) - { - var entity = new Tag(dto.Id, dto.Group, dto.Text) { NodeCount = dto.NodeCount }; - // reset dirty initial properties (U4-1946) - entity.ResetDirtyProperties(false); - return entity; - } - - public static TagDto BuildDto(ITag entity) - { - return new TagDto - { - Id = entity.Id, - Group = entity.Group, - Text = entity.Text, - //Key = entity.Group + "/" + entity.Text // de-normalize - }; - } - } -} +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal static class TagFactory + { + public static ITag BuildEntity(TagDto dto) + { + var entity = new Tag(dto.Id, dto.Group, dto.Text) { NodeCount = dto.NodeCount }; + // reset dirty initial properties (U4-1946) + entity.ResetDirtyProperties(false); + return entity; + } + + public static TagDto BuildDto(ITag entity) + { + return new TagDto + { + Id = entity.Id, + Group = entity.Group, + Text = entity.Text, + //Key = entity.Group + "/" + entity.Text // de-normalize + }; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs index a5be4d05bc..720b49d1af 100644 --- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs @@ -1,107 +1,107 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class TemplateFactory - { - private readonly int _primaryKey; - private readonly Guid _nodeObjectTypeId; - - public TemplateFactory() - { - - } - - public TemplateFactory(Guid nodeObjectTypeId) - { - _nodeObjectTypeId = nodeObjectTypeId; - } - - public TemplateFactory(int primaryKey, Guid nodeObjectTypeId) - { - _primaryKey = primaryKey; - _nodeObjectTypeId = nodeObjectTypeId; - } - - #region Implementation of IEntityFactory - - public Template BuildEntity(TemplateDto dto, IEnumerable childDefinitions, Func getFileContent) - { - var template = new Template(dto.NodeDto.Text, dto.Alias, getFileContent); - - try - { - template.DisableChangeTracking(); - - template.CreateDate = dto.NodeDto.CreateDate; - template.Id = dto.NodeId; - template.Key = dto.NodeDto.UniqueId; - template.Path = dto.NodeDto.Path; - - template.IsMasterTemplate = childDefinitions.Any(x => x.ParentId == dto.NodeId); - - if (dto.NodeDto.ParentId > 0) - template.MasterTemplateId = new Lazy(() => dto.NodeDto.ParentId); - - // reset dirty initial properties (U4-1946) - template.ResetDirtyProperties(false); - return template; - } - finally - { - template.EnableChangeTracking(); - } - } - - public TemplateDto BuildDto(Template entity) - { - var dto = new TemplateDto - { - Alias = entity.Alias, - Design = entity.Content ?? string.Empty, - NodeDto = BuildNodeDto(entity) - }; - - if (entity.MasterTemplateId != null && entity.MasterTemplateId.Value > 0) - { - dto.NodeDto.ParentId = entity.MasterTemplateId.Value; - } - - if (entity.HasIdentity) - { - dto.NodeId = entity.Id; - dto.PrimaryKey = _primaryKey; - } - - return dto; - } - - #endregion - - private NodeDto BuildNodeDto(Template entity) - { - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = 1, - NodeObjectType = _nodeObjectTypeId, - ParentId = entity.MasterTemplateId.Value, - Path = entity.Path, - Text = entity.Name, - Trashed = false, - UniqueId = entity.Key - }; - - return nodeDto; - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class TemplateFactory + { + private readonly int _primaryKey; + private readonly Guid _nodeObjectTypeId; + + public TemplateFactory() + { + + } + + public TemplateFactory(Guid nodeObjectTypeId) + { + _nodeObjectTypeId = nodeObjectTypeId; + } + + public TemplateFactory(int primaryKey, Guid nodeObjectTypeId) + { + _primaryKey = primaryKey; + _nodeObjectTypeId = nodeObjectTypeId; + } + + #region Implementation of IEntityFactory + + public Template BuildEntity(TemplateDto dto, IEnumerable childDefinitions, Func getFileContent) + { + var template = new Template(dto.NodeDto.Text, dto.Alias, getFileContent); + + try + { + template.DisableChangeTracking(); + + template.CreateDate = dto.NodeDto.CreateDate; + template.Id = dto.NodeId; + template.Key = dto.NodeDto.UniqueId; + template.Path = dto.NodeDto.Path; + + template.IsMasterTemplate = childDefinitions.Any(x => x.ParentId == dto.NodeId); + + if (dto.NodeDto.ParentId > 0) + template.MasterTemplateId = new Lazy(() => dto.NodeDto.ParentId); + + // reset dirty initial properties (U4-1946) + template.ResetDirtyProperties(false); + return template; + } + finally + { + template.EnableChangeTracking(); + } + } + + public TemplateDto BuildDto(Template entity) + { + var dto = new TemplateDto + { + Alias = entity.Alias, + Design = entity.Content ?? string.Empty, + NodeDto = BuildNodeDto(entity) + }; + + if (entity.MasterTemplateId != null && entity.MasterTemplateId.Value > 0) + { + dto.NodeDto.ParentId = entity.MasterTemplateId.Value; + } + + if (entity.HasIdentity) + { + dto.NodeId = entity.Id; + dto.PrimaryKey = _primaryKey; + } + + return dto; + } + + #endregion + + private NodeDto BuildNodeDto(Template entity) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = 1, + NodeObjectType = _nodeObjectTypeId, + ParentId = entity.MasterTemplateId.Value, + Path = entity.Path, + Text = entity.Name, + Trashed = false, + UniqueId = entity.Key + }; + + return nodeDto; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index 394477cb51..97d9a5550a 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -1,104 +1,104 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal static class UserFactory - { - public static IUser BuildEntity(UserDto dto) - { - var guidId = dto.Id.ToGuid(); - - var user = new User(dto.Id, dto.UserName, dto.Email, dto.Login,dto.Password, - dto.UserGroupDtos.Select(x => x.ToReadOnlyGroup()).ToArray(), - dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Content).Select(x => x.StartNode).ToArray(), - dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Media).Select(x => x.StartNode).ToArray()); - - try - { - user.DisableChangeTracking(); - - user.Key = guidId; - user.IsLockedOut = dto.NoConsole; - user.IsApproved = dto.Disabled == false; - user.Language = dto.UserLanguage; - user.SecurityStamp = dto.SecurityStampToken; - user.FailedPasswordAttempts = dto.FailedLoginAttempts ?? 0; - user.LastLockoutDate = dto.LastLockoutDate ?? DateTime.MinValue; - user.LastLoginDate = dto.LastLoginDate ?? DateTime.MinValue; - user.LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue; - user.CreateDate = dto.CreateDate; - user.UpdateDate = dto.UpdateDate; - user.Avatar = dto.Avatar; - user.EmailConfirmedDate = dto.EmailConfirmedDate; - user.InvitedDate = dto.InvitedDate; - user.TourData = dto.TourData; - - // reset dirty initial properties (U4-1946) - user.ResetDirtyProperties(false); - - return user; - } - finally - { - user.EnableChangeTracking(); - } - } - - public static UserDto BuildDto(IUser entity) - { - var dto = new UserDto - { - Disabled = entity.IsApproved == false, - Email = entity.Email, - Login = entity.Username, - NoConsole = entity.IsLockedOut, - Password = entity.RawPasswordValue, - UserLanguage = entity.Language, - UserName = entity.Name, - SecurityStampToken = entity.SecurityStamp, - FailedLoginAttempts = entity.FailedPasswordAttempts, - LastLockoutDate = entity.LastLockoutDate == DateTime.MinValue ? (DateTime?)null : entity.LastLockoutDate, - LastLoginDate = entity.LastLoginDate == DateTime.MinValue ? (DateTime?)null : entity.LastLoginDate, - LastPasswordChangeDate = entity.LastPasswordChangeDate == DateTime.MinValue ? (DateTime?)null : entity.LastPasswordChangeDate, - CreateDate = entity.CreateDate, - UpdateDate = entity.UpdateDate, - Avatar = entity.Avatar, - EmailConfirmedDate = entity.EmailConfirmedDate, +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal static class UserFactory + { + public static IUser BuildEntity(UserDto dto) + { + var guidId = dto.Id.ToGuid(); + + var user = new User(dto.Id, dto.UserName, dto.Email, dto.Login,dto.Password, + dto.UserGroupDtos.Select(x => x.ToReadOnlyGroup()).ToArray(), + dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Content).Select(x => x.StartNode).ToArray(), + dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Media).Select(x => x.StartNode).ToArray()); + + try + { + user.DisableChangeTracking(); + + user.Key = guidId; + user.IsLockedOut = dto.NoConsole; + user.IsApproved = dto.Disabled == false; + user.Language = dto.UserLanguage; + user.SecurityStamp = dto.SecurityStampToken; + user.FailedPasswordAttempts = dto.FailedLoginAttempts ?? 0; + user.LastLockoutDate = dto.LastLockoutDate ?? DateTime.MinValue; + user.LastLoginDate = dto.LastLoginDate ?? DateTime.MinValue; + user.LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue; + user.CreateDate = dto.CreateDate; + user.UpdateDate = dto.UpdateDate; + user.Avatar = dto.Avatar; + user.EmailConfirmedDate = dto.EmailConfirmedDate; + user.InvitedDate = dto.InvitedDate; + user.TourData = dto.TourData; + + // reset dirty initial properties (U4-1946) + user.ResetDirtyProperties(false); + + return user; + } + finally + { + user.EnableChangeTracking(); + } + } + + public static UserDto BuildDto(IUser entity) + { + var dto = new UserDto + { + Disabled = entity.IsApproved == false, + Email = entity.Email, + Login = entity.Username, + NoConsole = entity.IsLockedOut, + Password = entity.RawPasswordValue, + UserLanguage = entity.Language, + UserName = entity.Name, + SecurityStampToken = entity.SecurityStamp, + FailedLoginAttempts = entity.FailedPasswordAttempts, + LastLockoutDate = entity.LastLockoutDate == DateTime.MinValue ? (DateTime?)null : entity.LastLockoutDate, + LastLoginDate = entity.LastLoginDate == DateTime.MinValue ? (DateTime?)null : entity.LastLoginDate, + LastPasswordChangeDate = entity.LastPasswordChangeDate == DateTime.MinValue ? (DateTime?)null : entity.LastPasswordChangeDate, + CreateDate = entity.CreateDate, + UpdateDate = entity.UpdateDate, + Avatar = entity.Avatar, + EmailConfirmedDate = entity.EmailConfirmedDate, InvitedDate = entity.InvitedDate, - TourData = entity.TourData - }; - - foreach (var startNodeId in entity.StartContentIds) - { - dto.UserStartNodeDtos.Add(new UserStartNodeDto - { - StartNode = startNodeId, - StartNodeType = (int)UserStartNodeDto.StartNodeTypeValue.Content, - UserId = entity.Id - }); - } - - foreach (var startNodeId in entity.StartMediaIds) - { - dto.UserStartNodeDtos.Add(new UserStartNodeDto - { - StartNode = startNodeId, - StartNodeType = (int)UserStartNodeDto.StartNodeTypeValue.Media, - UserId = entity.Id - }); - } - - if (entity.HasIdentity) - { - dto.Id = entity.Id.SafeCast(); - } - - return dto; - } - } -} + TourData = entity.TourData + }; + + foreach (var startNodeId in entity.StartContentIds) + { + dto.UserStartNodeDtos.Add(new UserStartNodeDto + { + StartNode = startNodeId, + StartNodeType = (int)UserStartNodeDto.StartNodeTypeValue.Content, + UserId = entity.Id + }); + } + + foreach (var startNodeId in entity.StartMediaIds) + { + dto.UserStartNodeDtos.Add(new UserStartNodeDto + { + StartNode = startNodeId, + StartNodeType = (int)UserStartNodeDto.StartNodeTypeValue.Media, + UserId = entity.Id + }); + } + + if (entity.HasIdentity) + { + dto.Id = entity.Id.SafeCast(); + } + + return dto; + } + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/ITransientErrorDetectionStrategy.cs b/src/Umbraco.Core/Persistence/FaultHandling/ITransientErrorDetectionStrategy.cs index 3c76d46f6a..aa5161b024 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/ITransientErrorDetectionStrategy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/ITransientErrorDetectionStrategy.cs @@ -1,17 +1,17 @@ -using System; - -namespace Umbraco.Core.Persistence.FaultHandling -{ - /// - /// Defines an interface which must be implemented by custom components responsible for detecting specific transient conditions. - /// - public interface ITransientErrorDetectionStrategy - { - /// - /// Determines whether the specified exception represents a transient failure that can be compensated by a retry. - /// - /// The exception object to be verified. - /// True if the specified exception is considered as transient, otherwise false. - bool IsTransient(Exception ex); - } -} +using System; + +namespace Umbraco.Core.Persistence.FaultHandling +{ + /// + /// Defines an interface which must be implemented by custom components responsible for detecting specific transient conditions. + /// + public interface ITransientErrorDetectionStrategy + { + /// + /// Determines whether the specified exception represents a transient failure that can be compensated by a retry. + /// + /// The exception object to be verified. + /// True if the specified exception is considered as transient, otherwise false. + bool IsTransient(Exception ex); + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs index e3a72313f0..c537281dc9 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs @@ -1,62 +1,62 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Persistence.FaultHandling -{ - /// - /// The special type of exception that provides managed exit from a retry loop. The user code can use this - /// exception to notify the retry policy that no further retry attempts are required. - /// - [Serializable] - public sealed class RetryLimitExceededException : Exception - { - /// - /// Initializes a new instance of the class with a default error message. - /// - public RetryLimitExceededException() - : this("RetryLimitExceeded") - { - } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public RetryLimitExceededException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the class with a reference to the inner exception - /// that is the cause of this exception. - /// - /// The exception that is the cause of the current exception. - public RetryLimitExceededException(Exception innerException) - : base(innerException != null ? innerException.Message : "RetryLimitExceeded", innerException) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - /// The exception that is the cause of the current exception. - public RetryLimitExceededException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// The parameter is null. - /// The class name is null or is zero (0). - private RetryLimitExceededException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Persistence.FaultHandling +{ + /// + /// The special type of exception that provides managed exit from a retry loop. The user code can use this + /// exception to notify the retry policy that no further retry attempts are required. + /// + [Serializable] + public sealed class RetryLimitExceededException : Exception + { + /// + /// Initializes a new instance of the class with a default error message. + /// + public RetryLimitExceededException() + : this("RetryLimitExceeded") + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public RetryLimitExceededException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a reference to the inner exception + /// that is the cause of this exception. + /// + /// The exception that is the cause of the current exception. + public RetryLimitExceededException(Exception innerException) + : base(innerException != null ? innerException.Message : "RetryLimitExceeded", innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. + public RetryLimitExceededException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + /// The parameter is null. + /// The class name is null or is zero (0). + private RetryLimitExceededException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicy.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicy.cs index 6d99f9f1e5..58bada0516 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicy.cs @@ -1,240 +1,240 @@ -using System; -using System.Threading; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.FaultHandling.Strategies; - -namespace Umbraco.Core.Persistence.FaultHandling -{ - /// - /// Provides the base implementation of the retry mechanism for unreliable actions and transient conditions. - /// - public class RetryPolicy - { - /// - /// Returns a default policy that does no retries, it just invokes action exactly once. - /// - public static readonly RetryPolicy NoRetry = new RetryPolicy(new TransientErrorIgnoreStrategy(), 0); - - /// - /// Returns a default policy that implements a fixed retry interval configured with the default retry strategy. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static readonly RetryPolicy DefaultFixed = new RetryPolicy(new TransientErrorCatchAllStrategy(), new FixedInterval()); - - /// - /// Returns a default policy that implements a progressive retry interval configured with the default retry strategy. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static readonly RetryPolicy DefaultProgressive = new RetryPolicy(new TransientErrorCatchAllStrategy(), new Incremental()); - - /// - /// Returns a default policy that implements a random exponential retry interval configured with the default retry strategy. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static readonly RetryPolicy DefaultExponential = new RetryPolicy(new TransientErrorCatchAllStrategy(), new ExponentialBackoff()); - - /// - /// Initializes a new instance of the RetryPolicy class with the specified number of retry attempts and parameters defining the progressive delay between retries. - /// - /// The that is responsible for detecting transient conditions. - /// The retry strategy to use for this retry policy. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, RetryStrategy retryStrategy) - { - //Guard.ArgumentNotNull(errorDetectionStrategy, "errorDetectionStrategy"); - //Guard.ArgumentNotNull(retryStrategy, "retryPolicy"); - - this.ErrorDetectionStrategy = errorDetectionStrategy; - - if (errorDetectionStrategy == null) - { - throw new InvalidOperationException("The error detection strategy type must implement the ITransientErrorDetectionStrategy interface."); - } - - this.RetryStrategy = retryStrategy; - } - - /// - /// Initializes a new instance of the RetryPolicy class with the specified number of retry attempts and default fixed time interval between retries. - /// - /// The that is responsible for detecting transient conditions. - /// The number of retry attempts. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount) - : this(errorDetectionStrategy, new FixedInterval(retryCount)) - { - } - - /// - /// Initializes a new instance of the RetryPolicy class with the specified number of retry attempts and fixed time interval between retries. - /// - /// The that is responsible for detecting transient conditions. - /// The number of retry attempts. - /// The interval between retries. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan retryInterval) - : this(errorDetectionStrategy, new FixedInterval(retryCount, retryInterval)) - { - } - - /// - /// Initializes a new instance of the RetryPolicy class with the specified number of retry attempts and back-off parameters for calculating the exponential delay between retries. - /// - /// The that is responsible for detecting transient conditions. - /// The number of retry attempts. - /// The minimum back-off time. - /// The maximum back-off time. - /// The time value that will be used for calculating a random delta in the exponential delay between retries. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) - : this(errorDetectionStrategy, new ExponentialBackoff(retryCount, minBackoff, maxBackoff, deltaBackoff)) - { - } - - /// - /// Initializes a new instance of the RetryPolicy class with the specified number of retry attempts and parameters defining the progressive delay between retries. - /// - /// The that is responsible for detecting transient conditions. - /// The number of retry attempts. - /// The initial interval that will apply for the first retry. - /// The incremental time value that will be used for calculating the progressive delay between retries. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan initialInterval, TimeSpan increment) - : this(errorDetectionStrategy, new Incremental(retryCount, initialInterval, increment)) - { - } - - /// - /// An instance of a callback delegate that will be invoked whenever a retry condition is encountered. - /// - public event EventHandler Retrying; - - /// - /// Gets the retry strategy. - /// - public RetryStrategy RetryStrategy { get; private set; } - - /// - /// Gets the instance of the error detection strategy. - /// - public ITransientErrorDetectionStrategy ErrorDetectionStrategy { get; private set; } - - /// - /// Repetitively executes the specified action while it satisfies the current retry policy. - /// - /// A delegate representing the executable action which doesn't return any results. - public virtual void ExecuteAction(Action action) - { - //Guard.ArgumentNotNull(action, "action"); - - this.ExecuteAction(() => { action(); return default(object); }); - } - - /// - /// Repetitively executes the specified action while it satisfies the current retry policy. - /// - /// The type of result expected from the executable action. - /// A delegate representing the executable action which returns the result of type R. - /// The result from the action. - public virtual TResult ExecuteAction(Func func) - { - //Guard.ArgumentNotNull(func, "func"); - - int retryCount = 0; - TimeSpan delay = TimeSpan.Zero; - Exception lastError; - - var shouldRetry = this.RetryStrategy.GetShouldRetry(); - - for (; ; ) - { - lastError = null; - - try - { - return func(); - } - catch (RetryLimitExceededException limitExceededEx) - { - // The user code can throw a RetryLimitExceededException to force the exit from the retry loop. - // The RetryLimitExceeded exception can have an inner exception attached to it. This is the exception - // which we will have to throw up the stack so that callers can handle it. - if (limitExceededEx.InnerException != null) - { - throw limitExceededEx.InnerException; - } - else - { - return default(TResult); - } - } - catch (Exception ex) - { - lastError = ex; - - if (!(this.ErrorDetectionStrategy.IsTransient(lastError) && shouldRetry(retryCount++, lastError, out delay))) - { - throw; - } - } - - // Perform an extra check in the delay interval. Should prevent from accidentally ending up with the value of -1 that will block a thread indefinitely. - // In addition, any other negative numbers will cause an ArgumentOutOfRangeException fault that will be thrown by Thread.Sleep. - if (delay.TotalMilliseconds < 0) - { - delay = TimeSpan.Zero; - } - - this.OnRetrying(retryCount, lastError, delay); - - if (retryCount > 1 || !this.RetryStrategy.FastFirstRetry) - { - Thread.Sleep(delay); - } - } - } - - /// - /// Notifies the subscribers whenever a retry condition is encountered. - /// - /// The current retry attempt count. - /// The exception which caused the retry conditions to occur. - /// The delay indicating how long the current thread will be suspended for before the next iteration will be invoked. - protected virtual void OnRetrying(int retryCount, Exception lastError, TimeSpan delay) - { - if (this.Retrying != null) - { - this.Retrying(this, new RetryingEventArgs(retryCount, delay, lastError)); - } - } - - #region Private classes - /// - /// Implements a strategy that ignores any transient errors. - /// - private sealed class TransientErrorIgnoreStrategy : ITransientErrorDetectionStrategy - { - /// - /// Always return false. - /// - /// The exception. - /// Returns false. - public bool IsTransient(Exception ex) - { - return false; - } - } - - /// - /// Implements a strategy that treats all exceptions as transient errors. - /// - private sealed class TransientErrorCatchAllStrategy : ITransientErrorDetectionStrategy - { - /// - /// Always return true. - /// - /// The exception. - /// Returns true. - public bool IsTransient(Exception ex) - { - return true; - } - } - #endregion - } -} +using System; +using System.Threading; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.FaultHandling.Strategies; + +namespace Umbraco.Core.Persistence.FaultHandling +{ + /// + /// Provides the base implementation of the retry mechanism for unreliable actions and transient conditions. + /// + public class RetryPolicy + { + /// + /// Returns a default policy that does no retries, it just invokes action exactly once. + /// + public static readonly RetryPolicy NoRetry = new RetryPolicy(new TransientErrorIgnoreStrategy(), 0); + + /// + /// Returns a default policy that implements a fixed retry interval configured with the default retry strategy. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static readonly RetryPolicy DefaultFixed = new RetryPolicy(new TransientErrorCatchAllStrategy(), new FixedInterval()); + + /// + /// Returns a default policy that implements a progressive retry interval configured with the default retry strategy. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static readonly RetryPolicy DefaultProgressive = new RetryPolicy(new TransientErrorCatchAllStrategy(), new Incremental()); + + /// + /// Returns a default policy that implements a random exponential retry interval configured with the default retry strategy. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static readonly RetryPolicy DefaultExponential = new RetryPolicy(new TransientErrorCatchAllStrategy(), new ExponentialBackoff()); + + /// + /// Initializes a new instance of the RetryPolicy class with the specified number of retry attempts and parameters defining the progressive delay between retries. + /// + /// The that is responsible for detecting transient conditions. + /// The retry strategy to use for this retry policy. + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, RetryStrategy retryStrategy) + { + //Guard.ArgumentNotNull(errorDetectionStrategy, "errorDetectionStrategy"); + //Guard.ArgumentNotNull(retryStrategy, "retryPolicy"); + + this.ErrorDetectionStrategy = errorDetectionStrategy; + + if (errorDetectionStrategy == null) + { + throw new InvalidOperationException("The error detection strategy type must implement the ITransientErrorDetectionStrategy interface."); + } + + this.RetryStrategy = retryStrategy; + } + + /// + /// Initializes a new instance of the RetryPolicy class with the specified number of retry attempts and default fixed time interval between retries. + /// + /// The that is responsible for detecting transient conditions. + /// The number of retry attempts. + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount) + : this(errorDetectionStrategy, new FixedInterval(retryCount)) + { + } + + /// + /// Initializes a new instance of the RetryPolicy class with the specified number of retry attempts and fixed time interval between retries. + /// + /// The that is responsible for detecting transient conditions. + /// The number of retry attempts. + /// The interval between retries. + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan retryInterval) + : this(errorDetectionStrategy, new FixedInterval(retryCount, retryInterval)) + { + } + + /// + /// Initializes a new instance of the RetryPolicy class with the specified number of retry attempts and back-off parameters for calculating the exponential delay between retries. + /// + /// The that is responsible for detecting transient conditions. + /// The number of retry attempts. + /// The minimum back-off time. + /// The maximum back-off time. + /// The time value that will be used for calculating a random delta in the exponential delay between retries. + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) + : this(errorDetectionStrategy, new ExponentialBackoff(retryCount, minBackoff, maxBackoff, deltaBackoff)) + { + } + + /// + /// Initializes a new instance of the RetryPolicy class with the specified number of retry attempts and parameters defining the progressive delay between retries. + /// + /// The that is responsible for detecting transient conditions. + /// The number of retry attempts. + /// The initial interval that will apply for the first retry. + /// The incremental time value that will be used for calculating the progressive delay between retries. + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan initialInterval, TimeSpan increment) + : this(errorDetectionStrategy, new Incremental(retryCount, initialInterval, increment)) + { + } + + /// + /// An instance of a callback delegate that will be invoked whenever a retry condition is encountered. + /// + public event EventHandler Retrying; + + /// + /// Gets the retry strategy. + /// + public RetryStrategy RetryStrategy { get; private set; } + + /// + /// Gets the instance of the error detection strategy. + /// + public ITransientErrorDetectionStrategy ErrorDetectionStrategy { get; private set; } + + /// + /// Repetitively executes the specified action while it satisfies the current retry policy. + /// + /// A delegate representing the executable action which doesn't return any results. + public virtual void ExecuteAction(Action action) + { + //Guard.ArgumentNotNull(action, "action"); + + this.ExecuteAction(() => { action(); return default(object); }); + } + + /// + /// Repetitively executes the specified action while it satisfies the current retry policy. + /// + /// The type of result expected from the executable action. + /// A delegate representing the executable action which returns the result of type R. + /// The result from the action. + public virtual TResult ExecuteAction(Func func) + { + //Guard.ArgumentNotNull(func, "func"); + + int retryCount = 0; + TimeSpan delay = TimeSpan.Zero; + Exception lastError; + + var shouldRetry = this.RetryStrategy.GetShouldRetry(); + + for (; ; ) + { + lastError = null; + + try + { + return func(); + } + catch (RetryLimitExceededException limitExceededEx) + { + // The user code can throw a RetryLimitExceededException to force the exit from the retry loop. + // The RetryLimitExceeded exception can have an inner exception attached to it. This is the exception + // which we will have to throw up the stack so that callers can handle it. + if (limitExceededEx.InnerException != null) + { + throw limitExceededEx.InnerException; + } + else + { + return default(TResult); + } + } + catch (Exception ex) + { + lastError = ex; + + if (!(this.ErrorDetectionStrategy.IsTransient(lastError) && shouldRetry(retryCount++, lastError, out delay))) + { + throw; + } + } + + // Perform an extra check in the delay interval. Should prevent from accidentally ending up with the value of -1 that will block a thread indefinitely. + // In addition, any other negative numbers will cause an ArgumentOutOfRangeException fault that will be thrown by Thread.Sleep. + if (delay.TotalMilliseconds < 0) + { + delay = TimeSpan.Zero; + } + + this.OnRetrying(retryCount, lastError, delay); + + if (retryCount > 1 || !this.RetryStrategy.FastFirstRetry) + { + Thread.Sleep(delay); + } + } + } + + /// + /// Notifies the subscribers whenever a retry condition is encountered. + /// + /// The current retry attempt count. + /// The exception which caused the retry conditions to occur. + /// The delay indicating how long the current thread will be suspended for before the next iteration will be invoked. + protected virtual void OnRetrying(int retryCount, Exception lastError, TimeSpan delay) + { + if (this.Retrying != null) + { + this.Retrying(this, new RetryingEventArgs(retryCount, delay, lastError)); + } + } + + #region Private classes + /// + /// Implements a strategy that ignores any transient errors. + /// + private sealed class TransientErrorIgnoreStrategy : ITransientErrorDetectionStrategy + { + /// + /// Always return false. + /// + /// The exception. + /// Returns false. + public bool IsTransient(Exception ex) + { + return false; + } + } + + /// + /// Implements a strategy that treats all exceptions as transient errors. + /// + private sealed class TransientErrorCatchAllStrategy : ITransientErrorDetectionStrategy + { + /// + /// Always return true. + /// + /// The exception. + /// Returns true. + public bool IsTransient(Exception ex) + { + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicyFactory.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicyFactory.cs index 0729d67ee0..7ce1a42ad8 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicyFactory.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicyFactory.cs @@ -1,57 +1,57 @@ -using Umbraco.Core.Persistence.FaultHandling.Strategies; - -namespace Umbraco.Core.Persistence.FaultHandling -{ - /// - /// Provides a factory class for instantiating application-specific retry policies. - /// - public static class RetryPolicyFactory - { - public static RetryPolicy GetDefaultSqlConnectionRetryPolicyByConnectionString(string connectionString) - { - //Is this really the best way to determine if the database is an Azure database? - return connectionString.Contains("database.windows.net") - ? GetDefaultSqlAzureConnectionRetryPolicy() - : GetDefaultSqlConnectionRetryPolicy(); - } - - public static RetryPolicy GetDefaultSqlConnectionRetryPolicy() - { - var retryStrategy = RetryStrategy.DefaultExponential; - var retryPolicy = new RetryPolicy(new NetworkConnectivityErrorDetectionStrategy(), retryStrategy); - - return retryPolicy; - } - - public static RetryPolicy GetDefaultSqlAzureConnectionRetryPolicy() - { - var retryStrategy = RetryStrategy.DefaultExponential; - var retryPolicy = new RetryPolicy(new SqlAzureTransientErrorDetectionStrategy(), retryStrategy); - return retryPolicy; - } - - public static RetryPolicy GetDefaultSqlCommandRetryPolicyByConnectionString(string connectionString) - { - //Is this really the best way to determine if the database is an Azure database? - return connectionString.Contains("database.windows.net") - ? GetDefaultSqlAzureCommandRetryPolicy() - : GetDefaultSqlCommandRetryPolicy(); - } - - public static RetryPolicy GetDefaultSqlCommandRetryPolicy() - { - var retryStrategy = RetryStrategy.DefaultFixed; - var retryPolicy = new RetryPolicy(new NetworkConnectivityErrorDetectionStrategy(), retryStrategy); - - return retryPolicy; - } - - public static RetryPolicy GetDefaultSqlAzureCommandRetryPolicy() - { - var retryStrategy = RetryStrategy.DefaultFixed; - var retryPolicy = new RetryPolicy(new SqlAzureTransientErrorDetectionStrategy(), retryStrategy); - - return retryPolicy; - } - } -} +using Umbraco.Core.Persistence.FaultHandling.Strategies; + +namespace Umbraco.Core.Persistence.FaultHandling +{ + /// + /// Provides a factory class for instantiating application-specific retry policies. + /// + public static class RetryPolicyFactory + { + public static RetryPolicy GetDefaultSqlConnectionRetryPolicyByConnectionString(string connectionString) + { + //Is this really the best way to determine if the database is an Azure database? + return connectionString.Contains("database.windows.net") + ? GetDefaultSqlAzureConnectionRetryPolicy() + : GetDefaultSqlConnectionRetryPolicy(); + } + + public static RetryPolicy GetDefaultSqlConnectionRetryPolicy() + { + var retryStrategy = RetryStrategy.DefaultExponential; + var retryPolicy = new RetryPolicy(new NetworkConnectivityErrorDetectionStrategy(), retryStrategy); + + return retryPolicy; + } + + public static RetryPolicy GetDefaultSqlAzureConnectionRetryPolicy() + { + var retryStrategy = RetryStrategy.DefaultExponential; + var retryPolicy = new RetryPolicy(new SqlAzureTransientErrorDetectionStrategy(), retryStrategy); + return retryPolicy; + } + + public static RetryPolicy GetDefaultSqlCommandRetryPolicyByConnectionString(string connectionString) + { + //Is this really the best way to determine if the database is an Azure database? + return connectionString.Contains("database.windows.net") + ? GetDefaultSqlAzureCommandRetryPolicy() + : GetDefaultSqlCommandRetryPolicy(); + } + + public static RetryPolicy GetDefaultSqlCommandRetryPolicy() + { + var retryStrategy = RetryStrategy.DefaultFixed; + var retryPolicy = new RetryPolicy(new NetworkConnectivityErrorDetectionStrategy(), retryStrategy); + + return retryPolicy; + } + + public static RetryPolicy GetDefaultSqlAzureCommandRetryPolicy() + { + var retryStrategy = RetryStrategy.DefaultFixed; + var retryPolicy = new RetryPolicy(new SqlAzureTransientErrorDetectionStrategy(), retryStrategy); + + return retryPolicy; + } + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryStrategy.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryStrategy.cs index 0f79e78a7f..a327d2a949 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/RetryStrategy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryStrategy.cs @@ -1,111 +1,111 @@ -using System; -using Umbraco.Core.Persistence.FaultHandling.Strategies; - -namespace Umbraco.Core.Persistence.FaultHandling -{ - /// - /// Defines a callback delegate that will be invoked whenever a retry condition is encountered. - /// - /// The current retry attempt count. - /// The exception which caused the retry conditions to occur. - /// The delay indicating how long the current thread will be suspended for before the next iteration will be invoked. - /// Returns a callback delegate that will be invoked whenever to retry should be attempt. - public delegate bool ShouldRetry(int retryCount, Exception lastException, out TimeSpan delay); - - /// - /// Represents a retry strategy that determines how many times should be retried and the interval between retries. - /// - public abstract class RetryStrategy - { - #region Public members - /// - /// The default number of retry attempts. - /// - public static readonly int DefaultClientRetryCount = 10; - - /// - /// The default amount of time used when calculating a random delta in the exponential delay between retries. - /// - public static readonly TimeSpan DefaultClientBackoff = TimeSpan.FromSeconds(10.0); - - /// - /// The default maximum amount of time used when calculating the exponential delay between retries. - /// - public static readonly TimeSpan DefaultMaxBackoff = TimeSpan.FromSeconds(30.0); - - /// - /// The default minimum amount of time used when calculating the exponential delay between retries. - /// - public static readonly TimeSpan DefaultMinBackoff = TimeSpan.FromSeconds(1.0); - - /// - /// The default amount of time defining an interval between retries. - /// - public static readonly TimeSpan DefaultRetryInterval = TimeSpan.FromSeconds(1.0); - - /// - /// The default amount of time defining a time increment between retry attempts in the progressive delay policy. - /// - public static readonly TimeSpan DefaultRetryIncrement = TimeSpan.FromSeconds(1.0); - - /// - /// The default flag indicating whether or not the very first retry attempt will be made immediately - /// whereas the subsequent retries will remain subject to retry interval. - /// - public static readonly bool DefaultFirstFastRetry = true; - - #endregion - - /// - /// Returns a default policy that does no retries, it just invokes action exactly once. - /// - public static readonly RetryStrategy NoRetry = new FixedInterval(0, DefaultRetryInterval); - - /// - /// Returns a default policy that implements a fixed retry interval configured with and parameters. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static readonly RetryStrategy DefaultFixed = new FixedInterval(DefaultClientRetryCount, DefaultRetryInterval); - - /// - /// Returns a default policy that implements a progressive retry interval configured with , and parameters. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static readonly RetryStrategy DefaultProgressive = new Incremental(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement); - - /// - /// Returns a default policy that implements a random exponential retry interval configured with , , and parameters. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static readonly RetryStrategy DefaultExponential = new ExponentialBackoff(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultClientBackoff); - - /// - /// Initializes a new instance of the class. - /// - /// The name of the retry strategy. - /// a value indicating whether or not the very first retry attempt will be made immediately - /// whereas the subsequent retries will remain subject to retry interval. - protected RetryStrategy(string name, bool firstFastRetry) - { - this.Name = name; - this.FastFirstRetry = firstFastRetry; - } - - /// - /// Gets or sets a value indicating whether or not the very first retry attempt will be made immediately - /// whereas the subsequent retries will remain subject to retry interval. - /// - public bool FastFirstRetry { get; set; } - - /// - /// Gets the name of the retry strategy. - /// - public string Name { get; private set; } - - /// - /// Returns the corresponding ShouldRetry delegate. - /// - /// The ShouldRetry delegate. - public abstract ShouldRetry GetShouldRetry(); - } -} +using System; +using Umbraco.Core.Persistence.FaultHandling.Strategies; + +namespace Umbraco.Core.Persistence.FaultHandling +{ + /// + /// Defines a callback delegate that will be invoked whenever a retry condition is encountered. + /// + /// The current retry attempt count. + /// The exception which caused the retry conditions to occur. + /// The delay indicating how long the current thread will be suspended for before the next iteration will be invoked. + /// Returns a callback delegate that will be invoked whenever to retry should be attempt. + public delegate bool ShouldRetry(int retryCount, Exception lastException, out TimeSpan delay); + + /// + /// Represents a retry strategy that determines how many times should be retried and the interval between retries. + /// + public abstract class RetryStrategy + { + #region Public members + /// + /// The default number of retry attempts. + /// + public static readonly int DefaultClientRetryCount = 10; + + /// + /// The default amount of time used when calculating a random delta in the exponential delay between retries. + /// + public static readonly TimeSpan DefaultClientBackoff = TimeSpan.FromSeconds(10.0); + + /// + /// The default maximum amount of time used when calculating the exponential delay between retries. + /// + public static readonly TimeSpan DefaultMaxBackoff = TimeSpan.FromSeconds(30.0); + + /// + /// The default minimum amount of time used when calculating the exponential delay between retries. + /// + public static readonly TimeSpan DefaultMinBackoff = TimeSpan.FromSeconds(1.0); + + /// + /// The default amount of time defining an interval between retries. + /// + public static readonly TimeSpan DefaultRetryInterval = TimeSpan.FromSeconds(1.0); + + /// + /// The default amount of time defining a time increment between retry attempts in the progressive delay policy. + /// + public static readonly TimeSpan DefaultRetryIncrement = TimeSpan.FromSeconds(1.0); + + /// + /// The default flag indicating whether or not the very first retry attempt will be made immediately + /// whereas the subsequent retries will remain subject to retry interval. + /// + public static readonly bool DefaultFirstFastRetry = true; + + #endregion + + /// + /// Returns a default policy that does no retries, it just invokes action exactly once. + /// + public static readonly RetryStrategy NoRetry = new FixedInterval(0, DefaultRetryInterval); + + /// + /// Returns a default policy that implements a fixed retry interval configured with and parameters. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static readonly RetryStrategy DefaultFixed = new FixedInterval(DefaultClientRetryCount, DefaultRetryInterval); + + /// + /// Returns a default policy that implements a progressive retry interval configured with , and parameters. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static readonly RetryStrategy DefaultProgressive = new Incremental(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement); + + /// + /// Returns a default policy that implements a random exponential retry interval configured with , , and parameters. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static readonly RetryStrategy DefaultExponential = new ExponentialBackoff(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultClientBackoff); + + /// + /// Initializes a new instance of the class. + /// + /// The name of the retry strategy. + /// a value indicating whether or not the very first retry attempt will be made immediately + /// whereas the subsequent retries will remain subject to retry interval. + protected RetryStrategy(string name, bool firstFastRetry) + { + this.Name = name; + this.FastFirstRetry = firstFastRetry; + } + + /// + /// Gets or sets a value indicating whether or not the very first retry attempt will be made immediately + /// whereas the subsequent retries will remain subject to retry interval. + /// + public bool FastFirstRetry { get; set; } + + /// + /// Gets the name of the retry strategy. + /// + public string Name { get; private set; } + + /// + /// Returns the corresponding ShouldRetry delegate. + /// + /// The ShouldRetry delegate. + public abstract ShouldRetry GetShouldRetry(); + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryingEventArgs.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryingEventArgs.cs index 99eeb29688..ae66b2ee19 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/RetryingEventArgs.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryingEventArgs.cs @@ -1,40 +1,40 @@ -using System; - -namespace Umbraco.Core.Persistence.FaultHandling -{ - /// - /// Contains information required for the event. - /// - public class RetryingEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The current retry attempt count. - /// The delay indicating how long the current thread will be suspended for before the next iteration will be invoked. - /// The exception which caused the retry conditions to occur. - public RetryingEventArgs(int currentRetryCount, TimeSpan delay, Exception lastException) - { - //Guard.ArgumentNotNull(lastException, "lastException"); - - this.CurrentRetryCount = currentRetryCount; - this.Delay = delay; - this.LastException = lastException; - } - - /// - /// Gets the current retry count. - /// - public int CurrentRetryCount { get; private set; } - - /// - /// Gets the delay indicating how long the current thread will be suspended for before the next iteration will be invoked. - /// - public TimeSpan Delay { get; private set; } - - /// - /// Gets the exception which caused the retry conditions to occur. - /// - public Exception LastException { get; private set; } - } -} +using System; + +namespace Umbraco.Core.Persistence.FaultHandling +{ + /// + /// Contains information required for the event. + /// + public class RetryingEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The current retry attempt count. + /// The delay indicating how long the current thread will be suspended for before the next iteration will be invoked. + /// The exception which caused the retry conditions to occur. + public RetryingEventArgs(int currentRetryCount, TimeSpan delay, Exception lastException) + { + //Guard.ArgumentNotNull(lastException, "lastException"); + + this.CurrentRetryCount = currentRetryCount; + this.Delay = delay; + this.LastException = lastException; + } + + /// + /// Gets the current retry count. + /// + public int CurrentRetryCount { get; private set; } + + /// + /// Gets the delay indicating how long the current thread will be suspended for before the next iteration will be invoked. + /// + public TimeSpan Delay { get; private set; } + + /// + /// Gets the exception which caused the retry conditions to occur. + /// + public Exception LastException { get; private set; } + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/ExponentialBackoff.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/ExponentialBackoff.cs index e8954d334a..88a605955f 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/ExponentialBackoff.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/ExponentialBackoff.cs @@ -1,100 +1,100 @@ -using System; - -namespace Umbraco.Core.Persistence.FaultHandling.Strategies -{ - /// - /// A retry strategy with back-off parameters for calculating the exponential delay between retries. - /// - public class ExponentialBackoff : RetryStrategy - { - private readonly int retryCount; - private readonly TimeSpan minBackoff; - private readonly TimeSpan maxBackoff; - private readonly TimeSpan deltaBackoff; - - /// - /// Initializes a new instance of the class. - /// - public ExponentialBackoff() - : this(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultClientBackoff) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum number of retry attempts. - /// The minimum back-off time - /// The maximum back-off time. - /// The value that will be used for calculating a random delta in the exponential delay between retries. - public ExponentialBackoff(int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) - : this(null, retryCount, minBackoff, maxBackoff, deltaBackoff, DefaultFirstFastRetry) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the retry strategy. - /// The maximum number of retry attempts. - /// The minimum back-off time - /// The maximum back-off time. - /// The value that will be used for calculating a random delta in the exponential delay between retries. - public ExponentialBackoff(string name, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) - : this(name, retryCount, minBackoff, maxBackoff, deltaBackoff, DefaultFirstFastRetry) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the retry strategy. - /// The maximum number of retry attempts. - /// The minimum back-off time - /// The maximum back-off time. - /// The value that will be used for calculating a random delta in the exponential delay between retries. - /// - /// Indicates whether or not the very first retry attempt will be made immediately - /// whereas the subsequent retries will remain subject to retry interval. - /// - public ExponentialBackoff(string name, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff, bool firstFastRetry) - : base(name, firstFastRetry) - { - //Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); - //Guard.ArgumentNotNegativeValue(minBackoff.Ticks, "minBackoff"); - //Guard.ArgumentNotNegativeValue(maxBackoff.Ticks, "maxBackoff"); - //Guard.ArgumentNotNegativeValue(deltaBackoff.Ticks, "deltaBackoff"); - //Guard.ArgumentNotGreaterThan(minBackoff.TotalMilliseconds, maxBackoff.TotalMilliseconds, "minBackoff"); - - this.retryCount = retryCount; - this.minBackoff = minBackoff; - this.maxBackoff = maxBackoff; - this.deltaBackoff = deltaBackoff; - } - - /// - /// Returns the corresponding ShouldRetry delegate. - /// - /// The ShouldRetry delegate. - public override ShouldRetry GetShouldRetry() - { - return delegate(int currentRetryCount, Exception lastException, out TimeSpan retryInterval) - { - if (currentRetryCount < this.retryCount) - { - var random = new Random(); - - var delta = (int)((Math.Pow(2.0, currentRetryCount) - 1.0) * random.Next((int)(this.deltaBackoff.TotalMilliseconds * 0.8), (int)(this.deltaBackoff.TotalMilliseconds * 1.2))); - var interval = (int)Math.Min(checked(this.minBackoff.TotalMilliseconds + delta), this.maxBackoff.TotalMilliseconds); - - retryInterval = TimeSpan.FromMilliseconds(interval); - - return true; - } - - retryInterval = TimeSpan.Zero; - return false; - }; - } - } -} +using System; + +namespace Umbraco.Core.Persistence.FaultHandling.Strategies +{ + /// + /// A retry strategy with back-off parameters for calculating the exponential delay between retries. + /// + public class ExponentialBackoff : RetryStrategy + { + private readonly int retryCount; + private readonly TimeSpan minBackoff; + private readonly TimeSpan maxBackoff; + private readonly TimeSpan deltaBackoff; + + /// + /// Initializes a new instance of the class. + /// + public ExponentialBackoff() + : this(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultClientBackoff) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum number of retry attempts. + /// The minimum back-off time + /// The maximum back-off time. + /// The value that will be used for calculating a random delta in the exponential delay between retries. + public ExponentialBackoff(int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) + : this(null, retryCount, minBackoff, maxBackoff, deltaBackoff, DefaultFirstFastRetry) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the retry strategy. + /// The maximum number of retry attempts. + /// The minimum back-off time + /// The maximum back-off time. + /// The value that will be used for calculating a random delta in the exponential delay between retries. + public ExponentialBackoff(string name, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) + : this(name, retryCount, minBackoff, maxBackoff, deltaBackoff, DefaultFirstFastRetry) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the retry strategy. + /// The maximum number of retry attempts. + /// The minimum back-off time + /// The maximum back-off time. + /// The value that will be used for calculating a random delta in the exponential delay between retries. + /// + /// Indicates whether or not the very first retry attempt will be made immediately + /// whereas the subsequent retries will remain subject to retry interval. + /// + public ExponentialBackoff(string name, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff, bool firstFastRetry) + : base(name, firstFastRetry) + { + //Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); + //Guard.ArgumentNotNegativeValue(minBackoff.Ticks, "minBackoff"); + //Guard.ArgumentNotNegativeValue(maxBackoff.Ticks, "maxBackoff"); + //Guard.ArgumentNotNegativeValue(deltaBackoff.Ticks, "deltaBackoff"); + //Guard.ArgumentNotGreaterThan(minBackoff.TotalMilliseconds, maxBackoff.TotalMilliseconds, "minBackoff"); + + this.retryCount = retryCount; + this.minBackoff = minBackoff; + this.maxBackoff = maxBackoff; + this.deltaBackoff = deltaBackoff; + } + + /// + /// Returns the corresponding ShouldRetry delegate. + /// + /// The ShouldRetry delegate. + public override ShouldRetry GetShouldRetry() + { + return delegate(int currentRetryCount, Exception lastException, out TimeSpan retryInterval) + { + if (currentRetryCount < this.retryCount) + { + var random = new Random(); + + var delta = (int)((Math.Pow(2.0, currentRetryCount) - 1.0) * random.Next((int)(this.deltaBackoff.TotalMilliseconds * 0.8), (int)(this.deltaBackoff.TotalMilliseconds * 1.2))); + var interval = (int)Math.Min(checked(this.minBackoff.TotalMilliseconds + delta), this.maxBackoff.TotalMilliseconds); + + retryInterval = TimeSpan.FromMilliseconds(interval); + + return true; + } + + retryInterval = TimeSpan.Zero; + return false; + }; + } + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/FixedInterval.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/FixedInterval.cs index 289d9d7a90..5e927077e8 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/FixedInterval.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/FixedInterval.cs @@ -1,96 +1,96 @@ -using System; - -namespace Umbraco.Core.Persistence.FaultHandling.Strategies -{ - /// - /// A retry strategy with a specified number of retry attempts and a default fixed time interval between retries. - /// - public class FixedInterval : RetryStrategy - { - private readonly int retryCount; - private readonly TimeSpan retryInterval; - - /// - /// Initializes a new instance of the class. - /// - public FixedInterval() - : this(DefaultClientRetryCount) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The number of retry attempts. - public FixedInterval(int retryCount) - : this(retryCount, DefaultRetryInterval) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The number of retry attempts. - /// The time interval between retries. - public FixedInterval(int retryCount, TimeSpan retryInterval) - : this(null, retryCount, retryInterval, DefaultFirstFastRetry) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The retry strategy name. - /// The number of retry attempts. - /// The time interval between retries. - public FixedInterval(string name, int retryCount, TimeSpan retryInterval) - : this(name, retryCount, retryInterval, DefaultFirstFastRetry) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The retry strategy name. - /// The number of retry attempts. - /// The time interval between retries. - /// a value indicating whether or not the very first retry attempt will be made immediately whereas the subsequent retries will remain subject to retry interval. - public FixedInterval(string name, int retryCount, TimeSpan retryInterval, bool firstFastRetry) - : base(name, firstFastRetry) - { - //Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); - //Guard.ArgumentNotNegativeValue(retryInterval.Ticks, "retryInterval"); - - this.retryCount = retryCount; - this.retryInterval = retryInterval; - } - - /// - /// Returns the corresponding ShouldRetry delegate. - /// - /// The ShouldRetry delegate. - public override ShouldRetry GetShouldRetry() - { - if (this.retryCount == 0) - { - return delegate(int currentRetryCount, Exception lastException, out TimeSpan interval) - { - interval = TimeSpan.Zero; - return false; - }; - } - - return delegate(int currentRetryCount, Exception lastException, out TimeSpan interval) - { - if (currentRetryCount < this.retryCount) - { - interval = this.retryInterval; - return true; - } - - interval = TimeSpan.Zero; - return false; - }; - } - } -} +using System; + +namespace Umbraco.Core.Persistence.FaultHandling.Strategies +{ + /// + /// A retry strategy with a specified number of retry attempts and a default fixed time interval between retries. + /// + public class FixedInterval : RetryStrategy + { + private readonly int retryCount; + private readonly TimeSpan retryInterval; + + /// + /// Initializes a new instance of the class. + /// + public FixedInterval() + : this(DefaultClientRetryCount) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of retry attempts. + public FixedInterval(int retryCount) + : this(retryCount, DefaultRetryInterval) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of retry attempts. + /// The time interval between retries. + public FixedInterval(int retryCount, TimeSpan retryInterval) + : this(null, retryCount, retryInterval, DefaultFirstFastRetry) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The retry strategy name. + /// The number of retry attempts. + /// The time interval between retries. + public FixedInterval(string name, int retryCount, TimeSpan retryInterval) + : this(name, retryCount, retryInterval, DefaultFirstFastRetry) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The retry strategy name. + /// The number of retry attempts. + /// The time interval between retries. + /// a value indicating whether or not the very first retry attempt will be made immediately whereas the subsequent retries will remain subject to retry interval. + public FixedInterval(string name, int retryCount, TimeSpan retryInterval, bool firstFastRetry) + : base(name, firstFastRetry) + { + //Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); + //Guard.ArgumentNotNegativeValue(retryInterval.Ticks, "retryInterval"); + + this.retryCount = retryCount; + this.retryInterval = retryInterval; + } + + /// + /// Returns the corresponding ShouldRetry delegate. + /// + /// The ShouldRetry delegate. + public override ShouldRetry GetShouldRetry() + { + if (this.retryCount == 0) + { + return delegate(int currentRetryCount, Exception lastException, out TimeSpan interval) + { + interval = TimeSpan.Zero; + return false; + }; + } + + return delegate(int currentRetryCount, Exception lastException, out TimeSpan interval) + { + if (currentRetryCount < this.retryCount) + { + interval = this.retryInterval; + return true; + } + + interval = TimeSpan.Zero; + return false; + }; + } + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/Incremental.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/Incremental.cs index 18f2575fea..126bdaaba2 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/Incremental.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/Incremental.cs @@ -1,86 +1,86 @@ -using System; - -namespace Umbraco.Core.Persistence.FaultHandling.Strategies -{ - /// - /// A retry strategy with a specified number of retry attempts and an incremental time interval between retries. - /// - public class Incremental : RetryStrategy - { - private readonly int retryCount; - private readonly TimeSpan initialInterval; - private readonly TimeSpan increment; - - /// - /// Initializes a new instance of the class. - /// - public Incremental() - : this(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The number of retry attempts. - /// The initial interval that will apply for the first retry. - /// The incremental time value that will be used for calculating the progressive delay between retries. - public Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment) - : this(null, retryCount, initialInterval, increment) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The retry strategy name. - /// The number of retry attempts. - /// The initial interval that will apply for the first retry. - /// The incremental time value that will be used for calculating the progressive delay between retries. - public Incremental(string name, int retryCount, TimeSpan initialInterval, TimeSpan increment) - : this(name, retryCount, initialInterval, increment, DefaultFirstFastRetry) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The retry strategy name. - /// The number of retry attempts. - /// The initial interval that will apply for the first retry. - /// The incremental time value that will be used for calculating the progressive delay between retries. - /// a value indicating whether or not the very first retry attempt will be made immediately whereas the subsequent retries will remain subject to retry interval. - public Incremental(string name, int retryCount, TimeSpan initialInterval, TimeSpan increment, bool firstFastRetry) - : base(name, firstFastRetry) - { - //Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); - //Guard.ArgumentNotNegativeValue(initialInterval.Ticks, "initialInterval"); - //Guard.ArgumentNotNegativeValue(increment.Ticks, "increment"); - - this.retryCount = retryCount; - this.initialInterval = initialInterval; - this.increment = increment; - } - - /// - /// Returns the corresponding ShouldRetry delegate. - /// - /// The ShouldRetry delegate. - public override ShouldRetry GetShouldRetry() - { - return delegate(int currentRetryCount, Exception lastException, out TimeSpan retryInterval) - { - if (currentRetryCount < this.retryCount) - { - retryInterval = TimeSpan.FromMilliseconds(this.initialInterval.TotalMilliseconds + (this.increment.TotalMilliseconds * currentRetryCount)); - - return true; - } - - retryInterval = TimeSpan.Zero; - - return false; - }; - } - } -} +using System; + +namespace Umbraco.Core.Persistence.FaultHandling.Strategies +{ + /// + /// A retry strategy with a specified number of retry attempts and an incremental time interval between retries. + /// + public class Incremental : RetryStrategy + { + private readonly int retryCount; + private readonly TimeSpan initialInterval; + private readonly TimeSpan increment; + + /// + /// Initializes a new instance of the class. + /// + public Incremental() + : this(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of retry attempts. + /// The initial interval that will apply for the first retry. + /// The incremental time value that will be used for calculating the progressive delay between retries. + public Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment) + : this(null, retryCount, initialInterval, increment) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The retry strategy name. + /// The number of retry attempts. + /// The initial interval that will apply for the first retry. + /// The incremental time value that will be used for calculating the progressive delay between retries. + public Incremental(string name, int retryCount, TimeSpan initialInterval, TimeSpan increment) + : this(name, retryCount, initialInterval, increment, DefaultFirstFastRetry) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The retry strategy name. + /// The number of retry attempts. + /// The initial interval that will apply for the first retry. + /// The incremental time value that will be used for calculating the progressive delay between retries. + /// a value indicating whether or not the very first retry attempt will be made immediately whereas the subsequent retries will remain subject to retry interval. + public Incremental(string name, int retryCount, TimeSpan initialInterval, TimeSpan increment, bool firstFastRetry) + : base(name, firstFastRetry) + { + //Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); + //Guard.ArgumentNotNegativeValue(initialInterval.Ticks, "initialInterval"); + //Guard.ArgumentNotNegativeValue(increment.Ticks, "increment"); + + this.retryCount = retryCount; + this.initialInterval = initialInterval; + this.increment = increment; + } + + /// + /// Returns the corresponding ShouldRetry delegate. + /// + /// The ShouldRetry delegate. + public override ShouldRetry GetShouldRetry() + { + return delegate(int currentRetryCount, Exception lastException, out TimeSpan retryInterval) + { + if (currentRetryCount < this.retryCount) + { + retryInterval = TimeSpan.FromMilliseconds(this.initialInterval.TotalMilliseconds + (this.increment.TotalMilliseconds * currentRetryCount)); + + return true; + } + + retryInterval = TimeSpan.Zero; + + return false; + }; + } + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs index ea28f5f8b7..c6f524eaeb 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs @@ -1,31 +1,31 @@ -using System; -using System.Data.SqlClient; - -namespace Umbraco.Core.Persistence.FaultHandling.Strategies -{ - /// - /// Implements a strategy that detects network connectivity errors such as host not found. - /// - public class NetworkConnectivityErrorDetectionStrategy : ITransientErrorDetectionStrategy - { - public bool IsTransient(Exception ex) - { - SqlException sqlException; - - if (ex != null && (sqlException = ex as SqlException) != null) - { - switch (sqlException.Number) - { - // SQL Error Code: 11001 - // A network-related or instance-specific error occurred while establishing a connection to SQL Server. - // The server was not found or was not accessible. Verify that the instance name is correct and that SQL - // Server is configured to allow remote connections. (provider: TCP Provider, error: 0 - No such host is known.) - case 11001: - return true; - } - } - - return false; - } - } -} +using System; +using System.Data.SqlClient; + +namespace Umbraco.Core.Persistence.FaultHandling.Strategies +{ + /// + /// Implements a strategy that detects network connectivity errors such as host not found. + /// + public class NetworkConnectivityErrorDetectionStrategy : ITransientErrorDetectionStrategy + { + public bool IsTransient(Exception ex) + { + SqlException sqlException; + + if (ex != null && (sqlException = ex as SqlException) != null) + { + switch (sqlException.Number) + { + // SQL Error Code: 11001 + // A network-related or instance-specific error occurred while establishing a connection to SQL Server. + // The server was not found or was not accessible. Verify that the instance name is correct and that SQL + // Server is configured to allow remote connections. (provider: TCP Provider, error: 0 - No such host is known.) + case 11001: + return true; + } + } + + return false; + } + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs index 1164719538..849fd35fad 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs @@ -1,161 +1,161 @@ -using System; -using System.Data; -using System.Data.SqlClient; - -namespace Umbraco.Core.Persistence.FaultHandling.Strategies -{ - /// - /// Provides the transient error detection logic for transient faults that are specific to SQL Azure. - /// - public class SqlAzureTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy - { - #region ProcessNetLibErrorCode enumeration - - /// - /// Error codes reported by the DBNETLIB module. - /// - private enum ProcessNetLibErrorCode - { - ZeroBytes = -3, - - Timeout = -2, /* Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. */ - - Unknown = -1, - - InsufficientMemory = 1, - - AccessDenied = 2, - - ConnectionBusy = 3, - - ConnectionBroken = 4, - - ConnectionLimit = 5, - - ServerNotFound = 6, - - NetworkNotFound = 7, - - InsufficientResources = 8, - - NetworkBusy = 9, - - NetworkAccessDenied = 10, - - GeneralError = 11, - - IncorrectMode = 12, - - NameNotFound = 13, - - InvalidConnection = 14, - - ReadWriteError = 15, - - TooManyHandles = 16, - - ServerError = 17, - - SSLError = 18, - - EncryptionError = 19, - - EncryptionNotSupported = 20 - } - - #endregion - - #region ITransientErrorDetectionStrategy implementation - - /// - /// Determines whether the specified exception represents a transient failure that can be compensated by a retry. - /// - /// The exception object to be verified. - /// True if the specified exception is considered as transient, otherwise false. - public bool IsTransient(Exception ex) - { - if (ex != null) - { - SqlException sqlException; - if ((sqlException = ex as SqlException) != null) - { - // Enumerate through all errors found in the exception. - foreach (SqlError err in sqlException.Errors) - { - switch (err.Number) - { - // SQL Error Code: 40501 - // The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded). - case ThrottlingCondition.ThrottlingErrorNumber: - // Decode the reason code from the error message to determine the grounds for throttling. - var condition = ThrottlingCondition.FromError(err); - - // Attach the decoded values as additional attributes to the original SQL exception. - sqlException.Data[condition.ThrottlingMode.GetType().Name] = - condition.ThrottlingMode.ToString(); - sqlException.Data[condition.GetType().Name] = condition; - - return true; - - // SQL Error Code: 40197 - // The service has encountered an error processing your request. Please try again. - case 40197: - // SQL Error Code: 10053 - // A transport-level error has occurred when receiving results from the server. - // An established connection was aborted by the software in your host machine. - case 10053: - // SQL Error Code: 10054 - // A transport-level error has occurred when sending the request to the server. - // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) - case 10054: - // SQL Error Code: 10060 - // A network-related or instance-specific error occurred while establishing a connection to SQL Server. - // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server - // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed - // because the connected party did not properly respond after a period of time, or established connection failed - // because connected host has failed to respond.)"} - case 10060: - // SQL Error Code: 40613 - // Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer - // support, and provide them the session tracing ID of ZZZZZ. - case 40613: - // SQL Error Code: 40143 - // The service has encountered an error processing your request. Please try again. - case 40143: - // SQL Error Code: 233 - // The client was unable to establish a connection because of an error during connection initialization process before login. - // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy - // to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server. - // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) - case 233: - // SQL Error Code: 64 - // A connection was successfully established with the server, but then an error occurred during the login process. - // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) - case 64: - // DBNETLIB Error Code: 20 - // The instance of SQL Server you attempted to connect to does not support encryption. - case (int)ProcessNetLibErrorCode.EncryptionNotSupported: - return true; - } - } - } - else if (ex is TimeoutException) - { - return true; - } - else - { - EntityException entityException; - if ((entityException = ex as EntityException) != null) - { - return this.IsTransient(entityException.InnerException); - } - } - } - - return false; - } - - #endregion - } -} +using System; +using System.Data; +using System.Data.SqlClient; + +namespace Umbraco.Core.Persistence.FaultHandling.Strategies +{ + /// + /// Provides the transient error detection logic for transient faults that are specific to SQL Azure. + /// + public class SqlAzureTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy + { + #region ProcessNetLibErrorCode enumeration + + /// + /// Error codes reported by the DBNETLIB module. + /// + private enum ProcessNetLibErrorCode + { + ZeroBytes = -3, + + Timeout = -2, /* Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. */ + + Unknown = -1, + + InsufficientMemory = 1, + + AccessDenied = 2, + + ConnectionBusy = 3, + + ConnectionBroken = 4, + + ConnectionLimit = 5, + + ServerNotFound = 6, + + NetworkNotFound = 7, + + InsufficientResources = 8, + + NetworkBusy = 9, + + NetworkAccessDenied = 10, + + GeneralError = 11, + + IncorrectMode = 12, + + NameNotFound = 13, + + InvalidConnection = 14, + + ReadWriteError = 15, + + TooManyHandles = 16, + + ServerError = 17, + + SSLError = 18, + + EncryptionError = 19, + + EncryptionNotSupported = 20 + } + + #endregion + + #region ITransientErrorDetectionStrategy implementation + + /// + /// Determines whether the specified exception represents a transient failure that can be compensated by a retry. + /// + /// The exception object to be verified. + /// True if the specified exception is considered as transient, otherwise false. + public bool IsTransient(Exception ex) + { + if (ex != null) + { + SqlException sqlException; + if ((sqlException = ex as SqlException) != null) + { + // Enumerate through all errors found in the exception. + foreach (SqlError err in sqlException.Errors) + { + switch (err.Number) + { + // SQL Error Code: 40501 + // The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded). + case ThrottlingCondition.ThrottlingErrorNumber: + // Decode the reason code from the error message to determine the grounds for throttling. + var condition = ThrottlingCondition.FromError(err); + + // Attach the decoded values as additional attributes to the original SQL exception. + sqlException.Data[condition.ThrottlingMode.GetType().Name] = + condition.ThrottlingMode.ToString(); + sqlException.Data[condition.GetType().Name] = condition; + + return true; + + // SQL Error Code: 40197 + // The service has encountered an error processing your request. Please try again. + case 40197: + // SQL Error Code: 10053 + // A transport-level error has occurred when receiving results from the server. + // An established connection was aborted by the software in your host machine. + case 10053: + // SQL Error Code: 10054 + // A transport-level error has occurred when sending the request to the server. + // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) + case 10054: + // SQL Error Code: 10060 + // A network-related or instance-specific error occurred while establishing a connection to SQL Server. + // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server + // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed + // because the connected party did not properly respond after a period of time, or established connection failed + // because connected host has failed to respond.)"} + case 10060: + // SQL Error Code: 40613 + // Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer + // support, and provide them the session tracing ID of ZZZZZ. + case 40613: + // SQL Error Code: 40143 + // The service has encountered an error processing your request. Please try again. + case 40143: + // SQL Error Code: 233 + // The client was unable to establish a connection because of an error during connection initialization process before login. + // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy + // to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server. + // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) + case 233: + // SQL Error Code: 64 + // A connection was successfully established with the server, but then an error occurred during the login process. + // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) + case 64: + // DBNETLIB Error Code: 20 + // The instance of SQL Server you attempted to connect to does not support encryption. + case (int)ProcessNetLibErrorCode.EncryptionNotSupported: + return true; + } + } + } + else if (ex is TimeoutException) + { + return true; + } + else + { + EntityException entityException; + if ((entityException = ex as EntityException) != null) + { + return this.IsTransient(entityException.InnerException); + } + } + } + + return false; + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/ThrottlingCondition.cs b/src/Umbraco.Core/Persistence/FaultHandling/ThrottlingCondition.cs index 6b623cd5ce..1b3e1c45c9 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/ThrottlingCondition.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/ThrottlingCondition.cs @@ -1,330 +1,330 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -namespace Umbraco.Core.Persistence.FaultHandling -{ - /// - /// Defines the possible throttling modes in SQL Azure. - /// - public enum ThrottlingMode - { - /// - /// Corresponds to "No Throttling" throttling mode whereby all SQL statements can be processed. - /// - NoThrottling = 0, - - /// - /// Corresponds to "Reject Update / Insert" throttling mode whereby SQL statements such as INSERT, UPDATE, CREATE TABLE and CREATE INDEX are rejected. - /// - RejectUpdateInsert = 1, - - /// - /// Corresponds to "Reject All Writes" throttling mode whereby SQL statements such as INSERT, UPDATE, DELETE, CREATE, DROP are rejected. - /// - RejectAllWrites = 2, - - /// - /// Corresponds to "Reject All" throttling mode whereby all SQL statements are rejected. - /// - RejectAll = 3, - - /// - /// Corresponds to an unknown throttling mode whereby throttling mode cannot be determined with certainty. - /// - Unknown = -1 - } - - /// - /// Defines the possible throttling types in SQL Azure. - /// - public enum ThrottlingType - { - /// - /// Indicates that no throttling was applied to a given resource. - /// - None = 0, - - /// - /// Corresponds to a Soft throttling type. Soft throttling is applied when machine resources such as, CPU, IO, storage, and worker threads exceed - /// predefined safety thresholds despite the load balancer’s best efforts. - /// - Soft = 1, - - /// - /// Corresponds to a Hard throttling type. Hard throttling is applied when the machine is out of resources, for example storage space. - /// With hard throttling, no new connections are allowed to the databases hosted on the machine until resources are freed up. - /// - Hard = 2, - - /// - /// Corresponds to an unknown throttling type in the event when the throttling type cannot be determined with certainty. - /// - Unknown = 3 - } - - /// - /// Defines the types of resources in SQL Azure which may be subject to throttling conditions. - /// - public enum ThrottledResourceType - { - /// - /// Corresponds to "Physical Database Space" resource which may be subject to throttling. - /// - PhysicalDatabaseSpace = 0, - - /// - /// Corresponds to "Physical Log File Space" resource which may be subject to throttling. - /// - PhysicalLogSpace = 1, - - /// - /// Corresponds to "Transaction Log Write IO Delay" resource which may be subject to throttling. - /// - LogWriteIoDelay = 2, - - /// - /// Corresponds to "Database Read IO Delay" resource which may be subject to throttling. - /// - DataReadIoDelay = 3, - - /// - /// Corresponds to "CPU" resource which may be subject to throttling. - /// - Cpu = 4, - - /// - /// Corresponds to "Database Size" resource which may be subject to throttling. - /// - DatabaseSize = 5, - - /// - /// Corresponds to "SQL Worker Thread Pool" resource which may be subject to throttling. - /// - WorkerThreads = 7, - - /// - /// Corresponds to an internal resource which may be subject to throttling. - /// - Internal = 6, - - /// - /// Corresponds to an unknown resource type in the event when the actual resource cannot be determined with certainty. - /// - Unknown = -1 - } - - /// - /// Implements an object holding the decoded reason code returned from SQL Azure when encountering throttling conditions. - /// - [Serializable] - public class ThrottlingCondition - { - /// - /// Gets the error number that corresponds to throttling conditions reported by SQL Azure. - /// - public const int ThrottlingErrorNumber = 40501; - - /// - /// Maintains a collection of key-value pairs where a key is resource type and a value is the type of throttling applied to the given resource type. - /// - private readonly IList> throttledResources = new List>(9); - - /// - /// Provides a compiled regular expression used for extracting the reason code from the error message. - /// - private static readonly Regex sqlErrorCodeRegEx = new Regex(@"Code:\s*(\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled); - - /// - /// Gets an unknown throttling condition in the event the actual throttling condition cannot be determined. - /// - public static ThrottlingCondition Unknown - { - get - { - var unknownCondition = new ThrottlingCondition { ThrottlingMode = ThrottlingMode.Unknown }; - unknownCondition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Unknown, ThrottlingType.Unknown)); - - return unknownCondition; - } - } - - /// - /// Gets the value that reflects the throttling mode in SQL Azure. - /// - public ThrottlingMode ThrottlingMode { get; private set; } - - /// - /// Gets a list of resources in SQL Azure that were subject to throttling conditions. - /// - public IEnumerable> ThrottledResources - { - get { return this.throttledResources; } - } - - /// - /// Gets a value indicating whether physical data file space throttling was reported by SQL Azure. - /// - public bool IsThrottledOnDataSpace - { - get { return throttledResources.Any(x => x.Item1 == ThrottledResourceType.PhysicalDatabaseSpace); } - } - - /// - /// Gets a value indicating whether physical log space throttling was reported by SQL Azure. - /// - public bool IsThrottledOnLogSpace - { - get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.PhysicalLogSpace); } - } - - /// - /// Gets a value indicating whether transaction activity throttling was reported by SQL Azure. - /// - public bool IsThrottledOnLogWrite - { - get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.LogWriteIoDelay); } - } - - /// - /// Gets a value indicating whether data read activity throttling was reported by SQL Azure. - /// - public bool IsThrottledOnDataRead - { - get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.DataReadIoDelay); } - } - - /// - /// Gets a value indicating whether CPU throttling was reported by SQL Azure. - /// - public bool IsThrottledOnCpu - { - get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.Cpu); } - } - - /// - /// Gets a value indicating whether database size throttling was reported by SQL Azure. - /// - public bool IsThrottledOnDatabaseSize - { - get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.DatabaseSize); } - } - - /// - /// Gets a value indicating whether concurrent requests throttling was reported by SQL Azure. - /// - public bool IsThrottledOnWorkerThreads - { - get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.WorkerThreads); } - } - - /// - /// Gets a value indicating whether throttling conditions were not determined with certainty. - /// - public bool IsUnknown - { - get { return ThrottlingMode == ThrottlingMode.Unknown; } - } - - /// - /// Determines throttling conditions from the specified SQL exception. - /// - /// The object containing information relevant to an error returned by SQL Server when encountering throttling conditions. - /// An instance of the object holding the decoded reason codes returned from SQL Azure upon encountering throttling conditions. - public static ThrottlingCondition FromException(SqlException ex) - { - if (ex != null) - { - foreach (SqlError error in ex.Errors) - { - if (error.Number == ThrottlingErrorNumber) - { - return FromError(error); - } - } - } - - return Unknown; - } - - /// - /// Determines the throttling conditions from the specified SQL error. - /// - /// The object containing information relevant to a warning or error returned by SQL Server. - /// An instance of the object holding the decoded reason codes returned from SQL Azure when encountering throttling conditions. - public static ThrottlingCondition FromError(SqlError error) - { - if (error != null) - { - var match = sqlErrorCodeRegEx.Match(error.Message); - int reasonCode; - - if (match.Success && int.TryParse(match.Groups[1].Value, out reasonCode)) - { - return FromReasonCode(reasonCode); - } - } - - return Unknown; - } - - /// - /// Determines the throttling conditions from the specified reason code. - /// - /// The reason code returned by SQL Azure which contains the throttling mode and the exceeded resource types. - /// An instance of the object holding the decoded reason codes returned from SQL Azure when encountering throttling conditions. - public static ThrottlingCondition FromReasonCode(int reasonCode) - { - if (reasonCode > 0) - { - // Decode throttling mode from the last 2 bits. - var throttlingMode = (ThrottlingMode)(reasonCode & 3); - - var condition = new ThrottlingCondition { ThrottlingMode = throttlingMode }; - - // Shift 8 bits to truncate throttling mode. - var groupCode = reasonCode >> 8; - - // Determine throttling type for all well-known resources that may be subject to throttling conditions. - condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.PhysicalDatabaseSpace, (ThrottlingType)(groupCode & 3))); - condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.PhysicalLogSpace, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); - condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.LogWriteIoDelay, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); - condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.DataReadIoDelay, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); - condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Cpu, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); - condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.DatabaseSize, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); - condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Internal, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); - condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.WorkerThreads, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); - condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Internal, (ThrottlingType)((groupCode >> 2) & 3))); - - return condition; - } - - return Unknown; - } - - /// - /// Returns a textual representation the current ThrottlingCondition object including the information held with respect to throttled resources. - /// - /// A string that represents the current ThrottlingCondition object. - public override string ToString() - { - var result = new StringBuilder(); - - result.AppendFormat(CultureInfo.CurrentCulture, "Mode: {0} | ", ThrottlingMode); - - var resources = - this.throttledResources - .Where(x => x.Item1 != ThrottledResourceType.Internal) - .Select(x => string.Format(CultureInfo.CurrentCulture, "{0}: {1}", x.Item1, x.Item2)) - .OrderBy(x => x).ToArray(); - - result.Append(string.Join(", ", resources)); - - return result.ToString(); - } - } -} +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace Umbraco.Core.Persistence.FaultHandling +{ + /// + /// Defines the possible throttling modes in SQL Azure. + /// + public enum ThrottlingMode + { + /// + /// Corresponds to "No Throttling" throttling mode whereby all SQL statements can be processed. + /// + NoThrottling = 0, + + /// + /// Corresponds to "Reject Update / Insert" throttling mode whereby SQL statements such as INSERT, UPDATE, CREATE TABLE and CREATE INDEX are rejected. + /// + RejectUpdateInsert = 1, + + /// + /// Corresponds to "Reject All Writes" throttling mode whereby SQL statements such as INSERT, UPDATE, DELETE, CREATE, DROP are rejected. + /// + RejectAllWrites = 2, + + /// + /// Corresponds to "Reject All" throttling mode whereby all SQL statements are rejected. + /// + RejectAll = 3, + + /// + /// Corresponds to an unknown throttling mode whereby throttling mode cannot be determined with certainty. + /// + Unknown = -1 + } + + /// + /// Defines the possible throttling types in SQL Azure. + /// + public enum ThrottlingType + { + /// + /// Indicates that no throttling was applied to a given resource. + /// + None = 0, + + /// + /// Corresponds to a Soft throttling type. Soft throttling is applied when machine resources such as, CPU, IO, storage, and worker threads exceed + /// predefined safety thresholds despite the load balancer’s best efforts. + /// + Soft = 1, + + /// + /// Corresponds to a Hard throttling type. Hard throttling is applied when the machine is out of resources, for example storage space. + /// With hard throttling, no new connections are allowed to the databases hosted on the machine until resources are freed up. + /// + Hard = 2, + + /// + /// Corresponds to an unknown throttling type in the event when the throttling type cannot be determined with certainty. + /// + Unknown = 3 + } + + /// + /// Defines the types of resources in SQL Azure which may be subject to throttling conditions. + /// + public enum ThrottledResourceType + { + /// + /// Corresponds to "Physical Database Space" resource which may be subject to throttling. + /// + PhysicalDatabaseSpace = 0, + + /// + /// Corresponds to "Physical Log File Space" resource which may be subject to throttling. + /// + PhysicalLogSpace = 1, + + /// + /// Corresponds to "Transaction Log Write IO Delay" resource which may be subject to throttling. + /// + LogWriteIoDelay = 2, + + /// + /// Corresponds to "Database Read IO Delay" resource which may be subject to throttling. + /// + DataReadIoDelay = 3, + + /// + /// Corresponds to "CPU" resource which may be subject to throttling. + /// + Cpu = 4, + + /// + /// Corresponds to "Database Size" resource which may be subject to throttling. + /// + DatabaseSize = 5, + + /// + /// Corresponds to "SQL Worker Thread Pool" resource which may be subject to throttling. + /// + WorkerThreads = 7, + + /// + /// Corresponds to an internal resource which may be subject to throttling. + /// + Internal = 6, + + /// + /// Corresponds to an unknown resource type in the event when the actual resource cannot be determined with certainty. + /// + Unknown = -1 + } + + /// + /// Implements an object holding the decoded reason code returned from SQL Azure when encountering throttling conditions. + /// + [Serializable] + public class ThrottlingCondition + { + /// + /// Gets the error number that corresponds to throttling conditions reported by SQL Azure. + /// + public const int ThrottlingErrorNumber = 40501; + + /// + /// Maintains a collection of key-value pairs where a key is resource type and a value is the type of throttling applied to the given resource type. + /// + private readonly IList> throttledResources = new List>(9); + + /// + /// Provides a compiled regular expression used for extracting the reason code from the error message. + /// + private static readonly Regex sqlErrorCodeRegEx = new Regex(@"Code:\s*(\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + /// + /// Gets an unknown throttling condition in the event the actual throttling condition cannot be determined. + /// + public static ThrottlingCondition Unknown + { + get + { + var unknownCondition = new ThrottlingCondition { ThrottlingMode = ThrottlingMode.Unknown }; + unknownCondition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Unknown, ThrottlingType.Unknown)); + + return unknownCondition; + } + } + + /// + /// Gets the value that reflects the throttling mode in SQL Azure. + /// + public ThrottlingMode ThrottlingMode { get; private set; } + + /// + /// Gets a list of resources in SQL Azure that were subject to throttling conditions. + /// + public IEnumerable> ThrottledResources + { + get { return this.throttledResources; } + } + + /// + /// Gets a value indicating whether physical data file space throttling was reported by SQL Azure. + /// + public bool IsThrottledOnDataSpace + { + get { return throttledResources.Any(x => x.Item1 == ThrottledResourceType.PhysicalDatabaseSpace); } + } + + /// + /// Gets a value indicating whether physical log space throttling was reported by SQL Azure. + /// + public bool IsThrottledOnLogSpace + { + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.PhysicalLogSpace); } + } + + /// + /// Gets a value indicating whether transaction activity throttling was reported by SQL Azure. + /// + public bool IsThrottledOnLogWrite + { + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.LogWriteIoDelay); } + } + + /// + /// Gets a value indicating whether data read activity throttling was reported by SQL Azure. + /// + public bool IsThrottledOnDataRead + { + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.DataReadIoDelay); } + } + + /// + /// Gets a value indicating whether CPU throttling was reported by SQL Azure. + /// + public bool IsThrottledOnCpu + { + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.Cpu); } + } + + /// + /// Gets a value indicating whether database size throttling was reported by SQL Azure. + /// + public bool IsThrottledOnDatabaseSize + { + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.DatabaseSize); } + } + + /// + /// Gets a value indicating whether concurrent requests throttling was reported by SQL Azure. + /// + public bool IsThrottledOnWorkerThreads + { + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.WorkerThreads); } + } + + /// + /// Gets a value indicating whether throttling conditions were not determined with certainty. + /// + public bool IsUnknown + { + get { return ThrottlingMode == ThrottlingMode.Unknown; } + } + + /// + /// Determines throttling conditions from the specified SQL exception. + /// + /// The object containing information relevant to an error returned by SQL Server when encountering throttling conditions. + /// An instance of the object holding the decoded reason codes returned from SQL Azure upon encountering throttling conditions. + public static ThrottlingCondition FromException(SqlException ex) + { + if (ex != null) + { + foreach (SqlError error in ex.Errors) + { + if (error.Number == ThrottlingErrorNumber) + { + return FromError(error); + } + } + } + + return Unknown; + } + + /// + /// Determines the throttling conditions from the specified SQL error. + /// + /// The object containing information relevant to a warning or error returned by SQL Server. + /// An instance of the object holding the decoded reason codes returned from SQL Azure when encountering throttling conditions. + public static ThrottlingCondition FromError(SqlError error) + { + if (error != null) + { + var match = sqlErrorCodeRegEx.Match(error.Message); + int reasonCode; + + if (match.Success && int.TryParse(match.Groups[1].Value, out reasonCode)) + { + return FromReasonCode(reasonCode); + } + } + + return Unknown; + } + + /// + /// Determines the throttling conditions from the specified reason code. + /// + /// The reason code returned by SQL Azure which contains the throttling mode and the exceeded resource types. + /// An instance of the object holding the decoded reason codes returned from SQL Azure when encountering throttling conditions. + public static ThrottlingCondition FromReasonCode(int reasonCode) + { + if (reasonCode > 0) + { + // Decode throttling mode from the last 2 bits. + var throttlingMode = (ThrottlingMode)(reasonCode & 3); + + var condition = new ThrottlingCondition { ThrottlingMode = throttlingMode }; + + // Shift 8 bits to truncate throttling mode. + var groupCode = reasonCode >> 8; + + // Determine throttling type for all well-known resources that may be subject to throttling conditions. + condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.PhysicalDatabaseSpace, (ThrottlingType)(groupCode & 3))); + condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.PhysicalLogSpace, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); + condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.LogWriteIoDelay, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); + condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.DataReadIoDelay, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); + condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Cpu, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); + condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.DatabaseSize, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); + condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Internal, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); + condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.WorkerThreads, (ThrottlingType)((groupCode = groupCode >> 2) & 3))); + condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Internal, (ThrottlingType)((groupCode >> 2) & 3))); + + return condition; + } + + return Unknown; + } + + /// + /// Returns a textual representation the current ThrottlingCondition object including the information held with respect to throttled resources. + /// + /// A string that represents the current ThrottlingCondition object. + public override string ToString() + { + var result = new StringBuilder(); + + result.AppendFormat(CultureInfo.CurrentCulture, "Mode: {0} | ", ThrottlingMode); + + var resources = + this.throttledResources + .Where(x => x.Item1 != ThrottledResourceType.Internal) + .Select(x => string.Format(CultureInfo.CurrentCulture, "{0}: {1}", x.Item1, x.Item2)) + .OrderBy(x => x).ToArray(); + + result.Append(string.Join(", ", resources)); + + return result.ToString(); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs index 794daae4ff..22421eca53 100644 --- a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs @@ -1,69 +1,69 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; -using System.Reflection; -using NPoco; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Composing; - -namespace Umbraco.Core.Persistence.Mappers -{ - public abstract class BaseMapper : IDiscoverable - { - protected BaseMapper() - { - Build(); - } - - internal abstract ConcurrentDictionary PropertyInfoCache { get; } - - private void Build() - { - BuildMap(); - } - - protected abstract void BuildMap(); - - internal string Map(ISqlSyntaxProvider sqlSyntax, string propertyName, bool throws = false) - { - if (PropertyInfoCache.TryGetValue(propertyName, out var dtoTypeProperty)) - return GetColumnName(sqlSyntax, dtoTypeProperty.Type, dtoTypeProperty.PropertyInfo); - - if (throws) - throw new InvalidOperationException("Could not get the value with the key " + propertyName + " from the property info cache, keys available: " + string.Join(", ", PropertyInfoCache.Keys)); - - return string.Empty; - } - - internal void CacheMap(Expression> sourceMember, Expression> destinationMember) - { - var property = ResolveMapping(sourceMember, destinationMember); - PropertyInfoCache.AddOrUpdate(property.SourcePropertyName, property, (x, y) => property); - } - - internal DtoMapModel ResolveMapping(Expression> sourceMember, Expression> destinationMember) - { - var source = ExpressionHelper.FindProperty(sourceMember); - var destination = (PropertyInfo) ExpressionHelper.FindProperty(destinationMember).Item1; - - if (destination == null) - { - throw new InvalidOperationException("The 'destination' returned was null, cannot resolve the mapping"); - } - - return new DtoMapModel(typeof(TDestination), destination, source.Item1.Name); - } - - internal virtual string GetColumnName(ISqlSyntaxProvider sqlSyntax, Type dtoType, PropertyInfo dtoProperty) - { - var tableNameAttribute = dtoType.FirstAttribute(); - var tableName = tableNameAttribute.Value; - - var columnAttribute = dtoProperty.FirstAttribute(); - var columnName = columnAttribute.Name; - - var columnMap = sqlSyntax.GetQuotedTableName(tableName) + "." + sqlSyntax.GetQuotedColumnName(columnName); - return columnMap; - } - } -} +using System; +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; +using NPoco; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Persistence.Mappers +{ + public abstract class BaseMapper : IDiscoverable + { + protected BaseMapper() + { + Build(); + } + + internal abstract ConcurrentDictionary PropertyInfoCache { get; } + + private void Build() + { + BuildMap(); + } + + protected abstract void BuildMap(); + + internal string Map(ISqlSyntaxProvider sqlSyntax, string propertyName, bool throws = false) + { + if (PropertyInfoCache.TryGetValue(propertyName, out var dtoTypeProperty)) + return GetColumnName(sqlSyntax, dtoTypeProperty.Type, dtoTypeProperty.PropertyInfo); + + if (throws) + throw new InvalidOperationException("Could not get the value with the key " + propertyName + " from the property info cache, keys available: " + string.Join(", ", PropertyInfoCache.Keys)); + + return string.Empty; + } + + internal void CacheMap(Expression> sourceMember, Expression> destinationMember) + { + var property = ResolveMapping(sourceMember, destinationMember); + PropertyInfoCache.AddOrUpdate(property.SourcePropertyName, property, (x, y) => property); + } + + internal DtoMapModel ResolveMapping(Expression> sourceMember, Expression> destinationMember) + { + var source = ExpressionHelper.FindProperty(sourceMember); + var destination = (PropertyInfo) ExpressionHelper.FindProperty(destinationMember).Item1; + + if (destination == null) + { + throw new InvalidOperationException("The 'destination' returned was null, cannot resolve the mapping"); + } + + return new DtoMapModel(typeof(TDestination), destination, source.Item1.Name); + } + + internal virtual string GetColumnName(ISqlSyntaxProvider sqlSyntax, Type dtoType, PropertyInfo dtoProperty) + { + var tableNameAttribute = dtoType.FirstAttribute(); + var tableName = tableNameAttribute.Value; + + var columnAttribute = dtoProperty.FirstAttribute(); + var columnName = columnAttribute.Name; + + var columnMap = sqlSyntax.GetQuotedTableName(tableName) + "." + sqlSyntax.GetQuotedColumnName(columnName); + return columnMap; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs index 5bbdc07e73..d2e0e7245c 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs @@ -1,49 +1,49 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(Content))] - [MapperFor(typeof(IContent))] - public sealed class ContentMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.Key, dto => dto.UniqueId); - - CacheMap(src => src.VersionId, dto => dto.Id); - CacheMap(src => src.Name, dto => dto.Text); - - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Trashed, dto => dto.Trashed); - - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); - - CacheMap(src => src.UpdateDate, dto => dto.VersionDate); - CacheMap(src => src.ExpireDate, dto => dto.ExpiresDate); - CacheMap(src => src.ReleaseDate, dto => dto.ReleaseDate); - CacheMap(src => src.Published, dto => dto.Published); - - //CacheMap(src => src.Name, dto => dto.Alias); - //CacheMap(src => src, dto => dto.Newest); - //CacheMap(src => src.Template, dto => dto.TemplateId); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(Content))] + [MapperFor(typeof(IContent))] + public sealed class ContentMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + if (PropertyInfoCache.IsEmpty == false) return; + + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.Key, dto => dto.UniqueId); + + CacheMap(src => src.VersionId, dto => dto.Id); + CacheMap(src => src.Name, dto => dto.Text); + + CacheMap(src => src.ParentId, dto => dto.ParentId); + CacheMap(src => src.Level, dto => dto.Level); + CacheMap(src => src.Path, dto => dto.Path); + CacheMap(src => src.SortOrder, dto => dto.SortOrder); + CacheMap(src => src.Trashed, dto => dto.Trashed); + + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.CreatorId, dto => dto.UserId); + CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); + + CacheMap(src => src.UpdateDate, dto => dto.VersionDate); + CacheMap(src => src.ExpireDate, dto => dto.ExpiresDate); + CacheMap(src => src.ReleaseDate, dto => dto.ReleaseDate); + CacheMap(src => src.Published, dto => dto.Published); + + //CacheMap(src => src.Name, dto => dto.Alias); + //CacheMap(src => src, dto => dto.Newest); + //CacheMap(src => src.Template, dto => dto.TemplateId); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs index 3d62c0eab1..c692a75474 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs @@ -1,41 +1,41 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(ContentType))] - [MapperFor(typeof(IContentType))] - public sealed class ContentTypeMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); - CacheMap(src => src.Description, dto => dto.Description); - CacheMap(src => src.Icon, dto => dto.Icon); - CacheMap(src => src.IsContainer, dto => dto.IsContainer); - CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(ContentType))] + [MapperFor(typeof(IContentType))] + public sealed class ContentTypeMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + if (PropertyInfoCache.IsEmpty == false) return; + + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.Level, dto => dto.Level); + CacheMap(src => src.ParentId, dto => dto.ParentId); + CacheMap(src => src.Path, dto => dto.Path); + CacheMap(src => src.SortOrder, dto => dto.SortOrder); + CacheMap(src => src.Name, dto => dto.Text); + CacheMap(src => src.Trashed, dto => dto.Trashed); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.CreatorId, dto => dto.UserId); + CacheMap(src => src.Alias, dto => dto.Alias); + CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); + CacheMap(src => src.Description, dto => dto.Description); + CacheMap(src => src.Icon, dto => dto.Icon); + CacheMap(src => src.IsContainer, dto => dto.IsContainer); + CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs index 62974d7e49..0c2773c2dd 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs @@ -1,27 +1,27 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(DictionaryItem))] - [MapperFor(typeof(IDictionaryItem))] - public sealed class DictionaryMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.PrimaryKey); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.ItemKey, dto => dto.Key); - CacheMap(src => src.ParentId, dto => dto.Parent); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(DictionaryItem))] + [MapperFor(typeof(IDictionaryItem))] + public sealed class DictionaryMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.PrimaryKey); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.ItemKey, dto => dto.Key); + CacheMap(src => src.ParentId, dto => dto.Parent); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs index af2d91e56c..3f8641b959 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs @@ -1,27 +1,27 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(DictionaryTranslation))] - [MapperFor(typeof(IDictionaryTranslation))] - public sealed class DictionaryTranslationMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.PrimaryKey); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.Language, dto => dto.LanguageId); - CacheMap(src => src.Value, dto => dto.Value); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(DictionaryTranslation))] + [MapperFor(typeof(IDictionaryTranslation))] + public sealed class DictionaryTranslationMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.PrimaryKey); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.Language, dto => dto.LanguageId); + CacheMap(src => src.Value, dto => dto.Value); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs b/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs index 51c6520b7f..ebf16a1d36 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs @@ -1,19 +1,19 @@ -using System; -using System.Reflection; - -namespace Umbraco.Core.Persistence.Mappers -{ - internal class DtoMapModel - { - public DtoMapModel(Type type, PropertyInfo propertyInfo, string sourcePropertyName) - { - Type = type; - PropertyInfo = propertyInfo; - SourcePropertyName = sourcePropertyName; - } - - public string SourcePropertyName { get; private set; } - public Type Type { get; private set; } - public PropertyInfo PropertyInfo { get; private set; } - } -} +using System; +using System.Reflection; + +namespace Umbraco.Core.Persistence.Mappers +{ + internal class DtoMapModel + { + public DtoMapModel(Type type, PropertyInfo propertyInfo, string sourcePropertyName) + { + Type = type; + PropertyInfo = propertyInfo; + SourcePropertyName = sourcePropertyName; + } + + public string SourcePropertyName { get; private set; } + public Type Type { get; private set; } + public PropertyInfo PropertyInfo { get; private set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs b/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs index b2d3c87adf..ea7d4c2f09 100644 --- a/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs @@ -1,26 +1,26 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(ILanguage))] - [MapperFor(typeof(Language))] - public sealed class LanguageMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.IsoCode, dto => dto.IsoCode); - CacheMap(src => src.CultureName, dto => dto.CultureName); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(ILanguage))] + [MapperFor(typeof(Language))] + public sealed class LanguageMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.IsoCode, dto => dto.IsoCode); + CacheMap(src => src.CultureName, dto => dto.CultureName); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs index 489bd40ddf..b6da1dc3d8 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs @@ -1,29 +1,29 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - [MapperFor(typeof(Macro))] - [MapperFor(typeof(IMacro))] - internal sealed class MacroMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.CacheByPage, dto => dto.CacheByPage); - CacheMap(src => src.CacheByMember, dto => dto.CachePersonalized); - CacheMap(src => src.MacroType, dto => dto.MacroType); - CacheMap(src => src.DontRender, dto => dto.DontRender); - CacheMap(src => src.Name, dto => dto.Name); - CacheMap(src => src.CacheDuration, dto => dto.RefreshRate); - CacheMap(src => src.MacroSource, dto => dto.MacroSource); - CacheMap(src => src.UseInEditor, dto => dto.UseInEditor); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof(Macro))] + [MapperFor(typeof(IMacro))] + internal sealed class MacroMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.Alias, dto => dto.Alias); + CacheMap(src => src.CacheByPage, dto => dto.CacheByPage); + CacheMap(src => src.CacheByMember, dto => dto.CachePersonalized); + CacheMap(src => src.MacroType, dto => dto.MacroType); + CacheMap(src => src.DontRender, dto => dto.DontRender); + CacheMap(src => src.Name, dto => dto.Name); + CacheMap(src => src.CacheDuration, dto => dto.RefreshRate); + CacheMap(src => src.MacroSource, dto => dto.MacroSource); + CacheMap(src => src.UseInEditor, dto => dto.UseInEditor); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MapperForAttribute.cs b/src/Umbraco.Core/Persistence/Mappers/MapperForAttribute.cs index 63686f2476..146285fc43 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MapperForAttribute.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MapperForAttribute.cs @@ -1,19 +1,19 @@ -using System; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// An attribute used to decorate mappers to be associated with entities - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - internal sealed class MapperForAttribute : Attribute - { - public Type EntityType { get; private set; } - - public MapperForAttribute(Type entityType) - { - EntityType = entityType; - } - } - -} +using System; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// An attribute used to decorate mappers to be associated with entities + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + internal sealed class MapperForAttribute : Attribute + { + public Type EntityType { get; private set; } + + public MapperForAttribute(Type entityType) + { + EntityType = entityType; + } + } + +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs index c379155dd6..776f929e0d 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs @@ -1,40 +1,40 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(IMedia))] - [MapperFor(typeof(Umbraco.Core.Models.Media))] - public sealed class MediaMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.Key, dto => dto.UniqueId); - - CacheMap(src => src.VersionId, dto => dto.Id); - - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); - CacheMap(src => src.UpdateDate, dto => dto.VersionDate); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(IMedia))] + [MapperFor(typeof(Umbraco.Core.Models.Media))] + public sealed class MediaMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + if (PropertyInfoCache.IsEmpty == false) return; + + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.Key, dto => dto.UniqueId); + + CacheMap(src => src.VersionId, dto => dto.Id); + + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.Level, dto => dto.Level); + CacheMap(src => src.ParentId, dto => dto.ParentId); + CacheMap(src => src.Path, dto => dto.Path); + CacheMap(src => src.SortOrder, dto => dto.SortOrder); + CacheMap(src => src.Name, dto => dto.Text); + CacheMap(src => src.Trashed, dto => dto.Trashed); + CacheMap(src => src.CreatorId, dto => dto.UserId); + CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); + CacheMap(src => src.UpdateDate, dto => dto.VersionDate); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs index 5d433d3959..3f5a6e24bc 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs @@ -1,41 +1,41 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(IMediaType))] - [MapperFor(typeof(MediaType))] - public sealed class MediaTypeMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); - CacheMap(src => src.Description, dto => dto.Description); - CacheMap(src => src.Icon, dto => dto.Icon); - CacheMap(src => src.IsContainer, dto => dto.IsContainer); - CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(IMediaType))] + [MapperFor(typeof(MediaType))] + public sealed class MediaTypeMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + if (PropertyInfoCache.IsEmpty == false) return; + + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.Level, dto => dto.Level); + CacheMap(src => src.ParentId, dto => dto.ParentId); + CacheMap(src => src.Path, dto => dto.Path); + CacheMap(src => src.SortOrder, dto => dto.SortOrder); + CacheMap(src => src.Name, dto => dto.Text); + CacheMap(src => src.Trashed, dto => dto.Trashed); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.CreatorId, dto => dto.UserId); + CacheMap(src => src.Alias, dto => dto.Alias); + CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); + CacheMap(src => src.Description, dto => dto.Description); + CacheMap(src => src.Icon, dto => dto.Icon); + CacheMap(src => src.IsContainer, dto => dto.IsContainer); + CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs index 9137df341b..a34d04fb2d 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs @@ -1,59 +1,59 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(IMember))] - [MapperFor(typeof(Member))] - public sealed class MemberMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => ((IUmbracoEntity)src).Level, dto => dto.Level); - CacheMap(src => ((IUmbracoEntity)src).ParentId, dto => dto.ParentId); - CacheMap(src => ((IUmbracoEntity)src).Path, dto => dto.Path); - CacheMap(src => ((IUmbracoEntity)src).SortOrder, dto => dto.SortOrder); - CacheMap(src => ((IUmbracoEntity)src).CreatorId, dto => dto.UserId); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); - CacheMap(src => src.ContentTypeAlias, dto => dto.Alias); - CacheMap(src => src.UpdateDate, dto => dto.VersionDate); - - CacheMap(src => src.Email, dto => dto.Email); - CacheMap(src => src.Username, dto => dto.LoginName); - CacheMap(src => src.RawPasswordValue, dto => dto.Password); - - CacheMap(src => src.IsApproved, dto => dto.IntegerValue); - CacheMap(src => src.IsLockedOut, dto => dto.IntegerValue); - CacheMap(src => src.Comments, dto => dto.TextValue); - CacheMap(src => src.RawPasswordAnswerValue, dto => dto.VarcharValue); - CacheMap(src => src.PasswordQuestion, dto => dto.VarcharValue); - CacheMap(src => src.FailedPasswordAttempts, dto => dto.IntegerValue); - CacheMap(src => src.LastLockoutDate, dto => dto.DateValue); - CacheMap(src => src.LastLoginDate, dto => dto.DateValue); - CacheMap(src => src.LastPasswordChangeDate, dto => dto.DateValue); - - /* Internal experiment */ - CacheMap(src => src.DateTimePropertyValue, dto => dto.DateValue); - CacheMap(src => src.IntegerPropertyValue, dto => dto.IntegerValue); - CacheMap(src => src.BoolPropertyValue, dto => dto.IntegerValue); - CacheMap(src => src.LongStringPropertyValue, dto => dto.TextValue); - CacheMap(src => src.ShortStringPropertyValue, dto => dto.VarcharValue); - CacheMap(src => src.PropertyTypeAlias, dto => dto.Alias); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(IMember))] + [MapperFor(typeof(Member))] + public sealed class MemberMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => ((IUmbracoEntity)src).Level, dto => dto.Level); + CacheMap(src => ((IUmbracoEntity)src).ParentId, dto => dto.ParentId); + CacheMap(src => ((IUmbracoEntity)src).Path, dto => dto.Path); + CacheMap(src => ((IUmbracoEntity)src).SortOrder, dto => dto.SortOrder); + CacheMap(src => ((IUmbracoEntity)src).CreatorId, dto => dto.UserId); + CacheMap(src => src.Name, dto => dto.Text); + CacheMap(src => src.Trashed, dto => dto.Trashed); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); + CacheMap(src => src.ContentTypeAlias, dto => dto.Alias); + CacheMap(src => src.UpdateDate, dto => dto.VersionDate); + + CacheMap(src => src.Email, dto => dto.Email); + CacheMap(src => src.Username, dto => dto.LoginName); + CacheMap(src => src.RawPasswordValue, dto => dto.Password); + + CacheMap(src => src.IsApproved, dto => dto.IntegerValue); + CacheMap(src => src.IsLockedOut, dto => dto.IntegerValue); + CacheMap(src => src.Comments, dto => dto.TextValue); + CacheMap(src => src.RawPasswordAnswerValue, dto => dto.VarcharValue); + CacheMap(src => src.PasswordQuestion, dto => dto.VarcharValue); + CacheMap(src => src.FailedPasswordAttempts, dto => dto.IntegerValue); + CacheMap(src => src.LastLockoutDate, dto => dto.DateValue); + CacheMap(src => src.LastLoginDate, dto => dto.DateValue); + CacheMap(src => src.LastPasswordChangeDate, dto => dto.DateValue); + + /* Internal experiment */ + CacheMap(src => src.DateTimePropertyValue, dto => dto.DateValue); + CacheMap(src => src.IntegerPropertyValue, dto => dto.IntegerValue); + CacheMap(src => src.BoolPropertyValue, dto => dto.IntegerValue); + CacheMap(src => src.LongStringPropertyValue, dto => dto.TextValue); + CacheMap(src => src.ShortStringPropertyValue, dto => dto.VarcharValue); + CacheMap(src => src.PropertyTypeAlias, dto => dto.Alias); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs index b3beb17ef7..28dc19171f 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs @@ -1,41 +1,41 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof (MemberType))] - [MapperFor(typeof (IMemberType))] - public sealed class MemberTypeMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.CreatorId, dto => dto.UserId); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); - CacheMap(src => src.Description, dto => dto.Description); - CacheMap(src => src.Icon, dto => dto.Icon); - CacheMap(src => src.IsContainer, dto => dto.IsContainer); - CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof (MemberType))] + [MapperFor(typeof (IMemberType))] + public sealed class MemberTypeMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + if (PropertyInfoCache.IsEmpty == false) return; + + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.Level, dto => dto.Level); + CacheMap(src => src.ParentId, dto => dto.ParentId); + CacheMap(src => src.Path, dto => dto.Path); + CacheMap(src => src.SortOrder, dto => dto.SortOrder); + CacheMap(src => src.Name, dto => dto.Text); + CacheMap(src => src.Trashed, dto => dto.Trashed); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.CreatorId, dto => dto.UserId); + CacheMap(src => src.Alias, dto => dto.Alias); + CacheMap(src => src.AllowedAsRoot, dto => dto.AllowAtRoot); + CacheMap(src => src.Description, dto => dto.Description); + CacheMap(src => src.Icon, dto => dto.Icon); + CacheMap(src => src.IsContainer, dto => dto.IsContainer); + CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs index 77ed355437..b485a2d420 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs @@ -1,26 +1,26 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(PropertyGroup))] - public sealed class PropertyGroupMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(PropertyGroup))] + public sealed class PropertyGroupMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.SortOrder, dto => dto.SortOrder); + CacheMap(src => src.Name, dto => dto.Text); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs index 0f29f7b951..2df9bf9af5 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs @@ -1,20 +1,20 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - [MapperFor(typeof(Property))] - public sealed class PropertyMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.PropertyTypeId, dto => dto.PropertyTypeId); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof(Property))] + public sealed class PropertyMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.PropertyTypeId, dto => dto.PropertyTypeId); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs index 2355d68c6a..66c1d75165 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs @@ -1,35 +1,35 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(PropertyType))] - public sealed class PropertyTypeMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.DataTypeId, dto => dto.DataTypeId); - CacheMap(src => src.Description, dto => dto.Description); - CacheMap(src => src.Mandatory, dto => dto.Mandatory); - CacheMap(src => src.Name, dto => dto.Name); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.ValidationRegExp, dto => dto.ValidationRegExp); - CacheMap(src => src.PropertyEditorAlias, dto => dto.EditorAlias); - CacheMap(src => src.ValueStorageType, dto => dto.DbType); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(PropertyType))] + public sealed class PropertyTypeMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + if (PropertyInfoCache.IsEmpty == false) return; + + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.Alias, dto => dto.Alias); + CacheMap(src => src.DataTypeId, dto => dto.DataTypeId); + CacheMap(src => src.Description, dto => dto.Description); + CacheMap(src => src.Mandatory, dto => dto.Mandatory); + CacheMap(src => src.Name, dto => dto.Name); + CacheMap(src => src.SortOrder, dto => dto.SortOrder); + CacheMap(src => src.ValidationRegExp, dto => dto.ValidationRegExp); + CacheMap(src => src.PropertyEditorAlias, dto => dto.EditorAlias); + CacheMap(src => src.ValueStorageType, dto => dto.DbType); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs b/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs index abcf198635..83d9eb4220 100644 --- a/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs @@ -1,29 +1,29 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(IRelation))] - [MapperFor(typeof(Relation))] - public sealed class RelationMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.ChildId, dto => dto.ChildId); - CacheMap(src => src.Comment, dto => dto.Comment); - CacheMap(src => src.CreateDate, dto => dto.Datetime); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.RelationTypeId, dto => dto.RelationType); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(IRelation))] + [MapperFor(typeof(Relation))] + public sealed class RelationMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.ChildId, dto => dto.ChildId); + CacheMap(src => src.Comment, dto => dto.Comment); + CacheMap(src => src.CreateDate, dto => dto.Datetime); + CacheMap(src => src.ParentId, dto => dto.ParentId); + CacheMap(src => src.RelationTypeId, dto => dto.RelationType); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs index c8a352a644..b9bd3d0e89 100644 --- a/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs @@ -1,29 +1,29 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(RelationType))] - [MapperFor(typeof(IRelationType))] - public sealed class RelationTypeMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.ChildObjectType, dto => dto.ChildObjectType); - CacheMap(src => src.IsBidirectional, dto => dto.Dual); - CacheMap(src => src.Name, dto => dto.Name); - CacheMap(src => src.ParentObjectType, dto => dto.ParentObjectType); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(RelationType))] + [MapperFor(typeof(IRelationType))] + public sealed class RelationTypeMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.Alias, dto => dto.Alias); + CacheMap(src => src.ChildObjectType, dto => dto.ChildObjectType); + CacheMap(src => src.IsBidirectional, dto => dto.Dual); + CacheMap(src => src.Name, dto => dto.Name); + CacheMap(src => src.ParentObjectType, dto => dto.ParentObjectType); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs index eaa19855ee..0c723fced6 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs @@ -1,26 +1,26 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - [MapperFor(typeof(ServerRegistration))] - [MapperFor(typeof(IServerRegistration))] - internal sealed class ServerRegistrationMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.IsActive, dto => dto.IsActive); - CacheMap(src => src.IsMaster, dto => dto.IsMaster); - CacheMap(src => src.ServerAddress, dto => dto.ServerAddress); - CacheMap(src => src.CreateDate, dto => dto.DateRegistered); - CacheMap(src => src.UpdateDate, dto => dto.DateAccessed); - CacheMap(src => src.ServerIdentity, dto => dto.ServerIdentity); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof(ServerRegistration))] + [MapperFor(typeof(IServerRegistration))] + internal sealed class ServerRegistrationMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.IsActive, dto => dto.IsActive); + CacheMap(src => src.IsMaster, dto => dto.IsMaster); + CacheMap(src => src.ServerAddress, dto => dto.ServerAddress); + CacheMap(src => src.CreateDate, dto => dto.DateRegistered); + CacheMap(src => src.UpdateDate, dto => dto.DateAccessed); + CacheMap(src => src.ServerIdentity, dto => dto.ServerIdentity); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/TagMapper.cs b/src/Umbraco.Core/Persistence/Mappers/TagMapper.cs index 5141e8f79b..8cd2ab27d7 100644 --- a/src/Umbraco.Core/Persistence/Mappers/TagMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/TagMapper.cs @@ -1,28 +1,28 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(Tag))] - [MapperFor(typeof(ITag))] - public sealed class TagMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - if (PropertyInfoCache.IsEmpty == false) return; - - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Text, dto => dto.Text); - CacheMap(src => src.Group, dto => dto.Group); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(Tag))] + [MapperFor(typeof(ITag))] + public sealed class TagMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + if (PropertyInfoCache.IsEmpty == false) return; + + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.Text, dto => dto.Text); + CacheMap(src => src.Group, dto => dto.Group); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs index b5fb374d78..556af9b88a 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs @@ -1,28 +1,28 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - [MapperFor(typeof (IUmbracoEntity))] - public sealed class UmbracoEntityMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.NodeId); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.Level, dto => dto.Level); - CacheMap(src => src.ParentId, dto => dto.ParentId); - CacheMap(src => src.Path, dto => dto.Path); - CacheMap(src => src.SortOrder, dto => dto.SortOrder); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.Trashed, dto => dto.Trashed); - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.CreatorId, dto => dto.UserId); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof (IUmbracoEntity))] + public sealed class UmbracoEntityMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.Level, dto => dto.Level); + CacheMap(src => src.ParentId, dto => dto.ParentId); + CacheMap(src => src.Path, dto => dto.Path); + CacheMap(src => src.SortOrder, dto => dto.SortOrder); + CacheMap(src => src.Name, dto => dto.Text); + CacheMap(src => src.Trashed, dto => dto.Trashed); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.CreatorId, dto => dto.UserId); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs index 9dace89f3a..d32f48910d 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs @@ -1,35 +1,35 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - [MapperFor(typeof(IUser))] - [MapperFor(typeof(User))] - public sealed class UserMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Email, dto => dto.Email); - CacheMap(src => src.Username, dto => dto.Login); - CacheMap(src => src.RawPasswordValue, dto => dto.Password); - CacheMap(src => src.Name, dto => dto.UserName); - //NOTE: This column in the db is *not* used! - //CacheMap(src => src.DefaultPermissions, dto => dto.DefaultPermissions); - CacheMap(src => src.IsApproved, dto => dto.Disabled); - CacheMap(src => src.IsLockedOut, dto => dto.NoConsole); - CacheMap(src => src.Language, dto => dto.UserLanguage); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.UpdateDate, dto => dto.UpdateDate); - CacheMap(src => src.LastLockoutDate, dto => dto.LastLockoutDate); - CacheMap(src => src.LastLoginDate, dto => dto.LastLoginDate); - CacheMap(src => src.LastPasswordChangeDate, dto => dto.LastPasswordChangeDate); - CacheMap(src => src.SecurityStamp, dto => dto.SecurityStampToken); - } - } -} +using System.Collections.Concurrent; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof(IUser))] + [MapperFor(typeof(User))] + public sealed class UserMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.Email, dto => dto.Email); + CacheMap(src => src.Username, dto => dto.Login); + CacheMap(src => src.RawPasswordValue, dto => dto.Password); + CacheMap(src => src.Name, dto => dto.UserName); + //NOTE: This column in the db is *not* used! + //CacheMap(src => src.DefaultPermissions, dto => dto.DefaultPermissions); + CacheMap(src => src.IsApproved, dto => dto.Disabled); + CacheMap(src => src.IsLockedOut, dto => dto.NoConsole); + CacheMap(src => src.Language, dto => dto.UserLanguage); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.UpdateDate, dto => dto.UpdateDate); + CacheMap(src => src.LastLockoutDate, dto => dto.LastLockoutDate); + CacheMap(src => src.LastLoginDate, dto => dto.LastLoginDate); + CacheMap(src => src.LastPasswordChangeDate, dto => dto.LastPasswordChangeDate); + CacheMap(src => src.SecurityStamp, dto => dto.SecurityStampToken); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/UserSectionMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserSectionMapper.cs index af1ccb628c..9600fd1329 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserSectionMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserSectionMapper.cs @@ -1,33 +1,33 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Persistence.Mappers -{ - //[MapperFor(typeof(UserSection))] - //public sealed class UserSectionMapper : BaseMapper - //{ - // private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - // //NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it - // // otherwise that would fail because there is no public constructor. - // public UserSectionMapper() - // { - // BuildMap(); - // } - - // #region Overrides of BaseMapper - - // internal override ConcurrentDictionary PropertyInfoCache - // { - // get { return PropertyInfoCacheInstance; } - // } - - // internal override void BuildMap() - // { - // CacheMap(src => src.UserId, dto => dto.UserId); - // CacheMap(src => src.SectionAlias, dto => dto.AppAlias); - // } - - // #endregion - //} -} +using System.Collections.Concurrent; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Mappers +{ + //[MapperFor(typeof(UserSection))] + //public sealed class UserSectionMapper : BaseMapper + //{ + // private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + // //NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it + // // otherwise that would fail because there is no public constructor. + // public UserSectionMapper() + // { + // BuildMap(); + // } + + // #region Overrides of BaseMapper + + // internal override ConcurrentDictionary PropertyInfoCache + // { + // get { return PropertyInfoCacheInstance; } + // } + + // internal override void BuildMap() + // { + // CacheMap(src => src.UserId, dto => dto.UserId); + // CacheMap(src => src.SectionAlias, dto => dto.AppAlias); + // } + + // #endregion + //} +} diff --git a/src/Umbraco.Core/Persistence/Mappers/UserTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserTypeMapper.cs index ee493a327e..40fce123b7 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserTypeMapper.cs @@ -1,28 +1,28 @@ -//using System.Collections.Concurrent; -//using Umbraco.Core.Models.Membership; -//using Umbraco.Core.Models.Rdbms; - -//namespace Umbraco.Core.Persistence.Mappers -//{ -// /// -// /// Represents a to DTO mapper used to translate the properties of the public api -// /// implementation to that of the database's DTO as sql: [tableName].[columnName]. -// /// -// [MapperFor(typeof(IUserType))] -// [MapperFor(typeof(UserType))] -// public sealed class UserTypeMapper : BaseMapper -// { -// private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - -// internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - -// protected override void BuildMap() -// { -// CacheMap(src => src.Id, dto => dto.Id); -// CacheMap(src => src.Alias, dto => dto.Alias); -// CacheMap(src => src.Name, dto => dto.Name); -// CacheMap(src => src.Permissions, dto => dto.DefaultPermissions); -// } -// } -//} -// fixme remoev this file +//using System.Collections.Concurrent; +//using Umbraco.Core.Models.Membership; +//using Umbraco.Core.Models.Rdbms; + +//namespace Umbraco.Core.Persistence.Mappers +//{ +// /// +// /// Represents a to DTO mapper used to translate the properties of the public api +// /// implementation to that of the database's DTO as sql: [tableName].[columnName]. +// /// +// [MapperFor(typeof(IUserType))] +// [MapperFor(typeof(UserType))] +// public sealed class UserTypeMapper : BaseMapper +// { +// private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + +// internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + +// protected override void BuildMap() +// { +// CacheMap(src => src.Id, dto => dto.Id); +// CacheMap(src => src.Alias, dto => dto.Alias); +// CacheMap(src => src.Name, dto => dto.Name); +// CacheMap(src => src.Permissions, dto => dto.DefaultPermissions); +// } +// } +//} +// fixme remoev this file diff --git a/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs index a43e99177e..76116a8d03 100644 --- a/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs +++ b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs @@ -1,875 +1,875 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Composing; - -namespace Umbraco.Core.Persistence.Querying -{ - // fixme.npoco - are we basically duplicating entire parts of NPoco just because of SqlSyntax ?! - - /// - /// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression. - /// - /// This object is stateful and cannot be re-used to parse an expression. - internal abstract class ExpressionVisitorBase - { - protected ExpressionVisitorBase(ISqlSyntaxProvider sqlSyntax) - { - SqlSyntax = sqlSyntax; - } - - /// - /// Gets or sets a value indicating whether the visited expression has been visited already, - /// in which case visiting will just populate the SQL parameters. - /// - protected bool Visited { get; set; } - - /// - /// Gets or sets the SQL syntax provider for the current database. - /// - protected ISqlSyntaxProvider SqlSyntax { get; private set; } - - /// - /// Gets the list of SQL parameters. - /// - protected readonly List SqlParameters = new List(); - - /// - /// Gets the SQL parameters. - /// - /// - public object[] GetSqlParameters() - { - return SqlParameters.ToArray(); - } - - /// - /// Visits the expression and produces the corresponding SQL statement. - /// - /// The expression - /// The SQL statement corresponding to the expression. - /// Also populates the SQL parameters. - public virtual string Visit(Expression expression) - { - // if the expression is a CachedExpression, - // visit the inner expression if not already visited - var cachedExpression = expression as CachedExpression; - if (cachedExpression != null) - { - Visited = cachedExpression.Visited; - expression = cachedExpression.InnerExpression; - } - - if (expression == null) return string.Empty; - - string result; - - switch (expression.NodeType) - { - case ExpressionType.Lambda: - result = VisitLambda(expression as LambdaExpression); - break; - case ExpressionType.MemberAccess: - result = VisitMemberAccess(expression as MemberExpression); - break; - case ExpressionType.Constant: - result = VisitConstant(expression as ConstantExpression); - break; - case ExpressionType.Add: - case ExpressionType.AddChecked: - case ExpressionType.Subtract: - case ExpressionType.SubtractChecked: - case ExpressionType.Multiply: - case ExpressionType.MultiplyChecked: - case ExpressionType.Divide: - case ExpressionType.Modulo: - case ExpressionType.And: - case ExpressionType.AndAlso: - case ExpressionType.Or: - case ExpressionType.OrElse: - case ExpressionType.LessThan: - case ExpressionType.LessThanOrEqual: - case ExpressionType.GreaterThan: - case ExpressionType.GreaterThanOrEqual: - case ExpressionType.Equal: - case ExpressionType.NotEqual: - case ExpressionType.Coalesce: - case ExpressionType.ArrayIndex: - case ExpressionType.RightShift: - case ExpressionType.LeftShift: - case ExpressionType.ExclusiveOr: - result = VisitBinary(expression as BinaryExpression); - break; - case ExpressionType.Negate: - case ExpressionType.NegateChecked: - case ExpressionType.Not: - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - case ExpressionType.ArrayLength: - case ExpressionType.Quote: - case ExpressionType.TypeAs: - result = VisitUnary(expression as UnaryExpression); - break; - case ExpressionType.Parameter: - result = VisitParameter(expression as ParameterExpression); - break; - case ExpressionType.Call: - result = VisitMethodCall(expression as MethodCallExpression); - break; - case ExpressionType.New: - result = VisitNew(expression as NewExpression); - break; - case ExpressionType.NewArrayInit: - case ExpressionType.NewArrayBounds: - result = VisitNewArray(expression as NewArrayExpression); - break; - default: - result = expression.ToString(); - break; - } - - // if the expression is a CachedExpression, - // and is not already compiled, assign the result - if (cachedExpression != null) - { - if (cachedExpression.Visited == false) - cachedExpression.VisitResult = result; - result = cachedExpression.VisitResult; - } - - return result; - } - - protected abstract string VisitMemberAccess(MemberExpression m); - - protected virtual string VisitLambda(LambdaExpression lambda) - { - if (lambda.Body.NodeType == ExpressionType.MemberAccess) - { - var m = lambda.Body as MemberExpression; - - if (m != null && m.Expression != null) - { - //This deals with members that are boolean (i.e. x => IsTrashed ) - var r = VisitMemberAccess(m); - - SqlParameters.Add(true); - - return Visited ? string.Empty : string.Format("{0} = @{1}", r, SqlParameters.Count - 1); - } - - } - return Visit(lambda.Body); - } - - protected virtual string VisitBinary(BinaryExpression b) - { - var left = string.Empty; - var right = string.Empty; - - var operand = BindOperant(b.NodeType); - if (operand == "AND" || operand == "OR") - { - if (b.Left is MemberExpression mLeft && mLeft.Expression != null) - { - var r = VisitMemberAccess(mLeft); - - SqlParameters.Add(true); - - if (Visited == false) - left = $"{r} = @{SqlParameters.Count - 1}"; - } - else - { - left = Visit(b.Left); - } - if (b.Right is MemberExpression mRight && mRight.Expression != null) - { - var r = VisitMemberAccess(mRight); - - SqlParameters.Add(true); - - if (Visited == false) - right = $"{r} = @{SqlParameters.Count - 1}"; - } - else - { - right = Visit(b.Right); - } - } - else if (operand == "=") - { - // deal with (x == true|false) - most common - if (b.Right is ConstantExpression constRight && constRight.Type == typeof(bool)) - return (bool) constRight.Value ? VisitNotNot(b.Left) : VisitNot(b.Left); - right = Visit(b.Right); - - // deal with (true|false == x) - why not - if (b.Left is ConstantExpression constLeft && constLeft.Type == typeof(bool)) - return (bool) constLeft.Value ? VisitNotNot(b.Right) : VisitNot(b.Right); - left = Visit(b.Left); - } - else if (operand == "<>") - { - // deal with (x != true|false) - most common - if (b.Right is ConstantExpression constRight && constRight.Type == typeof (bool)) - return (bool) constRight.Value ? VisitNot(b.Left) : VisitNotNot(b.Left); - right = Visit(b.Right); - - // deal with (true|false != x) - why not - if (b.Left is ConstantExpression constLeft && constLeft.Type == typeof (bool)) - return (bool) constLeft.Value ? VisitNot(b.Right) : VisitNotNot(b.Right); - left = Visit(b.Left); - } - else - { - left = Visit(b.Left); - right = Visit(b.Right); - } - - if (operand == "=" && right == "null") operand = "is"; - else if (operand == "<>" && right == "null") operand = "is not"; - else if (operand == "=" || operand == "<>") - { - //if (IsTrueExpression(right)) right = GetQuotedTrueValue(); - //else if (IsFalseExpression(right)) right = GetQuotedFalseValue(); - - //if (IsTrueExpression(left)) left = GetQuotedTrueValue(); - //else if (IsFalseExpression(left)) left = GetQuotedFalseValue(); - - } - - switch (operand) - { - case "MOD": - case "COALESCE": - //don't execute if compiled - if (Visited == false) - { - return string.Format("{0}({1},{2})", operand, left, right); - } - //already compiled, return - return string.Empty; - default: - //don't execute if compiled - if (Visited == false) - { - return string.Concat("(", left, " ", operand, " ", right, ")"); - } - //already compiled, return - return string.Empty; - } - } - - protected virtual List VisitExpressionList(ReadOnlyCollection original) - { - var list = new List(); - for (int i = 0, n = original.Count; i < n; i++) - { - if (original[i].NodeType == ExpressionType.NewArrayInit || - original[i].NodeType == ExpressionType.NewArrayBounds) - { - list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression)); - } - else - { - list.Add(Visit(original[i])); - } - } - return list; - } - - protected virtual string VisitNew(NewExpression nex) - { - // TODO : check ! - var member = Expression.Convert(nex, typeof(object)); - var lambda = Expression.Lambda>(member); - try - { - var getter = lambda.Compile(); - var o = getter(); - - SqlParameters.Add(o); - return Visited ? string.Empty : $"@{SqlParameters.Count - 1}"; - } - catch (InvalidOperationException) - { - if (Visited) return string.Empty; - - var exprs = VisitExpressionList(nex.Arguments); - var r = new StringBuilder(); - foreach (var e in exprs) - { - if (r.Length > 0) r.Append(","); - r.Append(e); - } - return r.ToString(); - } - } - - protected virtual string VisitParameter(ParameterExpression p) - { - return p.Name; - } - - protected virtual string VisitConstant(ConstantExpression c) - { - if (c.Value == null) - return "null"; - - SqlParameters.Add(c.Value); - return Visited ? string.Empty : $"@{SqlParameters.Count - 1}"; - } - - protected virtual string VisitUnary(UnaryExpression u) - { - switch (u.NodeType) - { - case ExpressionType.Not: - return VisitNot(u.Operand); - default: - return Visit(u.Operand); - } - } - - private string VisitNot(Expression exp) - { - var o = Visit(exp); - - // use a "NOT (...)" syntax instead of "<>" since we don't know whether "<>" works in all sql servers - // also, x.StartsWith(...) translates to "x LIKE '...%'" which we cannot "<>" and have to "NOT (...") - - switch (exp.NodeType) - { - case ExpressionType.MemberAccess: - // false property , i.e. x => !Trashed - SqlParameters.Add(true); - return Visited ? string.Empty : $"NOT ({o} = @{SqlParameters.Count - 1})"; - default: - // could be anything else, such as: x => !x.Path.StartsWith("-20") - return Visited ? string.Empty : string.Concat("NOT (", o, ")"); - } - } - - private string VisitNotNot(Expression exp) - { - var o = Visit(exp); - - switch (exp.NodeType) - { - case ExpressionType.MemberAccess: - // true property, i.e. x => Trashed - SqlParameters.Add(true); - return Visited ? string.Empty : $"({o} = @{SqlParameters.Count - 1})"; - default: - // could be anything else, such as: x => x.Path.StartsWith("-20") - return Visited ? string.Empty : o; - } - } - - protected virtual string VisitNewArray(NewArrayExpression na) - { - var exprs = VisitExpressionList(na.Expressions); - - //don't execute if compiled - if (Visited == false) - { - var r = new StringBuilder(); - foreach (var e in exprs) - { - r.Append(r.Length > 0 ? "," + e : e); - } - - return r.ToString(); - } - //already compiled, return - return string.Empty; - } - - protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) - { - var exprs = VisitExpressionList(na.Expressions); - return exprs; - } - - protected virtual string BindOperant(ExpressionType e) - { - switch (e) - { - case ExpressionType.Equal: - return "="; - case ExpressionType.NotEqual: - return "<>"; - case ExpressionType.GreaterThan: - return ">"; - case ExpressionType.GreaterThanOrEqual: - return ">="; - case ExpressionType.LessThan: - return "<"; - case ExpressionType.LessThanOrEqual: - return "<="; - case ExpressionType.AndAlso: - return "AND"; - case ExpressionType.OrElse: - return "OR"; - case ExpressionType.Add: - return "+"; - case ExpressionType.Subtract: - return "-"; - case ExpressionType.Multiply: - return "*"; - case ExpressionType.Divide: - return "/"; - case ExpressionType.Modulo: - return "MOD"; - case ExpressionType.Coalesce: - return "COALESCE"; - default: - return e.ToString(); - } - } - - protected virtual string VisitMethodCall(MethodCallExpression m) - { - //Here's what happens with a MethodCallExpression: - // If a method is called that contains a single argument, - // then m.Object is the object on the left hand side of the method call, example: - // x.Path.StartsWith(content.Path) - // m.Object = x.Path - // and m.Arguments.Length == 1, therefor m.Arguments[0] == content.Path - // If a method is called that contains multiple arguments, then m.Object == null and the - // m.Arguments collection contains the left hand side of the method call, example: - // x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar) - // m.Object == null - // m.Arguments.Length == 3, therefor, m.Arguments[0] == x.Path, m.Arguments[1] == content.Path, m.Arguments[2] == TextColumnType.NVarchar - // So, we need to cater for these scenarios. - - var objectForMethod = m.Object ?? m.Arguments[0]; - var visitedObjectForMethod = Visit(objectForMethod); - var methodArgs = m.Object == null - ? m.Arguments.Skip(1).ToArray() - : m.Arguments.ToArray(); - - switch (m.Method.Name) - { - case "ToString": - SqlParameters.Add(objectForMethod.ToString()); - //don't execute if compiled - if (Visited == false) - return string.Format("@{0}", SqlParameters.Count - 1); - //already compiled, return - return string.Empty; - case "ToUpper": - //don't execute if compiled - if (Visited == false) - return string.Format("upper({0})", visitedObjectForMethod); - //already compiled, return - return string.Empty; - case "ToLower": - //don't execute if compiled - if (Visited == false) - return string.Format("lower({0})", visitedObjectForMethod); - //already compiled, return - return string.Empty; - case "SqlWildcard": - case "StartsWith": - case "EndsWith": - case "Contains": - case "Equals": - case "SqlStartsWith": - case "SqlEndsWith": - case "SqlContains": - case "SqlEquals": - case "InvariantStartsWith": - case "InvariantEndsWith": - case "InvariantContains": - case "InvariantEquals": - - //special case, if it is 'Contains' and the argumet that Contains is being called on is - //Enumerable and the methodArgs is the actual member access, then it's an SQL IN claus - if (m.Object == null - && m.Arguments[0].Type != typeof(string) - && m.Arguments.Count == 2 - && methodArgs.Length == 1 - && methodArgs[0].NodeType == ExpressionType.MemberAccess - && TypeHelper.IsTypeAssignableFrom(m.Arguments[0].Type)) - { - goto case "SqlIn"; - } - - string compareValue; - - if (methodArgs[0].NodeType != ExpressionType.Constant) - { - // if it's a field accessor, we could Visit(methodArgs[0]) and get [a].[b] - // but then, what if we want more, eg .StartsWith(node.Path + ',') ? => not - - //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) - // So we'll go get the value: - var member = Expression.Convert(methodArgs[0], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - compareValue = getter().ToString(); - } - else - { - compareValue = methodArgs[0].ToString(); - } - - //default column type - var colType = TextColumnType.NVarchar; - - //then check if the col type argument has been passed to the current method (this will be the case for methods like - // SqlContains and other Sql methods) - if (methodArgs.Length > 1) - { - var colTypeArg = methodArgs.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); - if (colTypeArg != null) - { - colType = (TextColumnType)((ConstantExpression)colTypeArg).Value; - } - } - - return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType); - - case "Replace": - string searchValue; - - if (methodArgs[0].NodeType != ExpressionType.Constant) - { - //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) - // So we'll go get the value: - var member = Expression.Convert(methodArgs[0], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - searchValue = getter().ToString(); - } - else - { - searchValue = methodArgs[0].ToString(); - } - - if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) - { - throw new NotSupportedException("An array Contains method is not supported"); - } - - string replaceValue; - - if (methodArgs[1].NodeType != ExpressionType.Constant) - { - //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) - // So we'll go get the value: - var member = Expression.Convert(methodArgs[1], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - replaceValue = getter().ToString(); - } - else - { - replaceValue = methodArgs[1].ToString(); - } - - if (methodArgs[1].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[1].Type)) - { - throw new NotSupportedException("An array Contains method is not supported"); - } - - SqlParameters.Add(RemoveQuote(searchValue)); - - SqlParameters.Add(RemoveQuote(replaceValue)); - - //don't execute if compiled - if (Visited == false) - return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1); - //already compiled, return - return string.Empty; - - //case "Substring": - // var startIndex = Int32.Parse(args[0].ToString()) + 1; - // if (args.Count == 2) - // { - // var length = Int32.Parse(args[1].ToString()); - // return string.Format("substring({0} from {1} for {2})", - // r, - // startIndex, - // length); - // } - // else - // return string.Format("substring({0} from {1})", - // r, - // startIndex); - //case "Round": - //case "Floor": - //case "Ceiling": - //case "Coalesce": - //case "Abs": - //case "Sum": - // return string.Format("{0}({1}{2})", - // m.Method.Name, - // r, - // args.Count == 1 ? string.Format(",{0}", args[0]) : ""); - //case "Concat": - // var s = new StringBuilder(); - // foreach (Object e in args) - // { - // s.AppendFormat(" || {0}", e); - // } - // return string.Format("{0}{1}", r, s); - - case "SqlIn": - - if (m.Object == null && methodArgs.Length == 1 && methodArgs[0].NodeType == ExpressionType.MemberAccess) - { - var memberAccess = VisitMemberAccess((MemberExpression) methodArgs[0]); - - var member = Expression.Convert(m.Arguments[0], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - - var inArgs = (IEnumerable)getter(); - - var sIn = new StringBuilder(); - foreach (var e in inArgs) - { - SqlParameters.Add(e); - - sIn.AppendFormat("{0}{1}", - sIn.Length > 0 ? "," : "", - string.Format("@{0}", SqlParameters.Count - 1)); - } - - return string.Format("{0} IN ({1})", memberAccess, sIn); - } - - throw new NotSupportedException("SqlIn must contain the member being accessed"); - - //case "Desc": - // return string.Format("{0} DESC", r); - //case "Alias": - //case "As": - // return string.Format("{0} As {1}", r, - // GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); - - case "SqlText": - if (m.Method.DeclaringType != typeof(NPocoSqlExtensions.Statics)) - goto default; - if (m.Arguments.Count == 2) - { - var n1 = Visit(m.Arguments[0]); - var f = m.Arguments[2]; - if (!(f is Expression> fl)) - throw new NotSupportedException("Expression is not a proper lambda."); - var ff = fl.Compile(); - return ff(n1); - } - else if (m.Arguments.Count == 3) - { - var n1 = Visit(m.Arguments[0]); - var n2 = Visit(m.Arguments[1]); - var f = m.Arguments[2]; - if (!(f is Expression> fl)) - throw new NotSupportedException("Expression is not a proper lambda."); - var ff = fl.Compile(); - return ff(n1, n2); - } - else if (m.Arguments.Count == 4) - { - var n1 = Visit(m.Arguments[0]); - var n2 = Visit(m.Arguments[1]); - var n3 = Visit(m.Arguments[3]); - var f = m.Arguments[3]; - if (!(f is Expression> fl)) - throw new NotSupportedException("Expression is not a proper lambda."); - var ff = fl.Compile(); - return ff(n1, n2, n3); - } - else - throw new NotSupportedException("Expression is not a proper lambda."); - - default: - - throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name); - - //var s2 = new StringBuilder(); - //foreach (Object e in args) - //{ - // s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); - //} - //return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); - } - } - - public virtual string GetQuotedTableName(string tableName) - { - return Visited ? tableName : string.Format("\"{0}\"", tableName); - } - - public virtual string GetQuotedColumnName(string columnName) - { - return Visited ? columnName : string.Format("\"{0}\"", columnName); - } - - public virtual string GetQuotedName(string name) - { - return Visited ? name : string.Format("\"{0}\"", name); - } - - protected string HandleStringComparison(string col, string val, string verb, TextColumnType columnType) - { - switch (verb) - { - case "SqlWildcard": - SqlParameters.Add(RemoveQuote(val)); - //don't execute if compiled - if (Visited == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "Equals": - SqlParameters.Add(RemoveQuote(val)); - //don't execute if compiled - if (Visited == false) - return SqlSyntax.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "StartsWith": - SqlParameters.Add(string.Format("{0}{1}", - RemoveQuote(val), - SqlSyntax.GetWildcardPlaceholder())); - //don't execute if compiled - if (Visited == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "EndsWith": - SqlParameters.Add(string.Format("{0}{1}", - SqlSyntax.GetWildcardPlaceholder(), - RemoveQuote(val))); - //don't execute if compiled - if (Visited == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "Contains": - SqlParameters.Add(string.Format("{0}{1}{0}", - SqlSyntax.GetWildcardPlaceholder(), - RemoveQuote(val))); - //don't execute if compiled - if (Visited == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "InvariantEquals": - case "SqlEquals": - //recurse - return HandleStringComparison(col, val, "Equals", columnType); - case "InvariantStartsWith": - case "SqlStartsWith": - //recurse - return HandleStringComparison(col, val, "StartsWith", columnType); - case "InvariantEndsWith": - case "SqlEndsWith": - //recurse - return HandleStringComparison(col, val, "EndsWith", columnType); - case "InvariantContains": - case "SqlContains": - //recurse - return HandleStringComparison(col, val, "Contains", columnType); - default: - throw new ArgumentOutOfRangeException("verb"); - } - } - - //public virtual string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null) - //{ - // if (value == null) return "NULL"; - - // if (escapeCallback == null) - // { - // escapeCallback = EscapeParam; - // } - // if (shouldQuoteCallback == null) - // { - // shouldQuoteCallback = ShouldQuoteValue; - // } - - // if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string)) - // { - // //if (TypeSerializer.CanCreateFromString(fieldType)) - // //{ - // // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'"; - // //} - - // throw new NotSupportedException( - // string.Format("Property of type: {0} is not supported", fieldType.FullName)); - // } - - // if (fieldType == typeof(int)) - // return ((int)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(float)) - // return ((float)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(double)) - // return ((double)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(decimal)) - // return ((decimal)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(DateTime)) - // { - // return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'"; - // } - - // if (fieldType == typeof(bool)) - // return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture); - - // return shouldQuoteCallback(fieldType) - // ? "'" + escapeCallback(value) + "'" - // : value.ToString(); - //} - - public virtual string EscapeParam(object paramValue, ISqlSyntaxProvider sqlSyntax) - { - return paramValue == null - ? string.Empty - : sqlSyntax.EscapeString(paramValue.ToString()); - } - - public virtual bool ShouldQuoteValue(Type fieldType) - { - return true; - } - - protected virtual string RemoveQuote(string exp) - { - if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) - && - (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) - { - exp = exp.Remove(0, 1); - exp = exp.Remove(exp.Length - 1, 1); - } - return exp; - } - - //protected virtual string RemoveQuoteFromAlias(string expression) - //{ - - // if ((expression.StartsWith("\"") || expression.StartsWith("`") || expression.StartsWith("'")) - // && - // (expression.EndsWith("\"") || expression.EndsWith("`") || expression.EndsWith("'"))) - // { - // expression = expression.Remove(0, 1); - // expression = expression.Remove(expression.Length - 1, 1); - // } - // return expression; - //} - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Persistence.Querying +{ + // fixme.npoco - are we basically duplicating entire parts of NPoco just because of SqlSyntax ?! + + /// + /// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression. + /// + /// This object is stateful and cannot be re-used to parse an expression. + internal abstract class ExpressionVisitorBase + { + protected ExpressionVisitorBase(ISqlSyntaxProvider sqlSyntax) + { + SqlSyntax = sqlSyntax; + } + + /// + /// Gets or sets a value indicating whether the visited expression has been visited already, + /// in which case visiting will just populate the SQL parameters. + /// + protected bool Visited { get; set; } + + /// + /// Gets or sets the SQL syntax provider for the current database. + /// + protected ISqlSyntaxProvider SqlSyntax { get; private set; } + + /// + /// Gets the list of SQL parameters. + /// + protected readonly List SqlParameters = new List(); + + /// + /// Gets the SQL parameters. + /// + /// + public object[] GetSqlParameters() + { + return SqlParameters.ToArray(); + } + + /// + /// Visits the expression and produces the corresponding SQL statement. + /// + /// The expression + /// The SQL statement corresponding to the expression. + /// Also populates the SQL parameters. + public virtual string Visit(Expression expression) + { + // if the expression is a CachedExpression, + // visit the inner expression if not already visited + var cachedExpression = expression as CachedExpression; + if (cachedExpression != null) + { + Visited = cachedExpression.Visited; + expression = cachedExpression.InnerExpression; + } + + if (expression == null) return string.Empty; + + string result; + + switch (expression.NodeType) + { + case ExpressionType.Lambda: + result = VisitLambda(expression as LambdaExpression); + break; + case ExpressionType.MemberAccess: + result = VisitMemberAccess(expression as MemberExpression); + break; + case ExpressionType.Constant: + result = VisitConstant(expression as ConstantExpression); + break; + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.And: + case ExpressionType.AndAlso: + case ExpressionType.Or: + case ExpressionType.OrElse: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.Coalesce: + case ExpressionType.ArrayIndex: + case ExpressionType.RightShift: + case ExpressionType.LeftShift: + case ExpressionType.ExclusiveOr: + result = VisitBinary(expression as BinaryExpression); + break; + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.ArrayLength: + case ExpressionType.Quote: + case ExpressionType.TypeAs: + result = VisitUnary(expression as UnaryExpression); + break; + case ExpressionType.Parameter: + result = VisitParameter(expression as ParameterExpression); + break; + case ExpressionType.Call: + result = VisitMethodCall(expression as MethodCallExpression); + break; + case ExpressionType.New: + result = VisitNew(expression as NewExpression); + break; + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + result = VisitNewArray(expression as NewArrayExpression); + break; + default: + result = expression.ToString(); + break; + } + + // if the expression is a CachedExpression, + // and is not already compiled, assign the result + if (cachedExpression != null) + { + if (cachedExpression.Visited == false) + cachedExpression.VisitResult = result; + result = cachedExpression.VisitResult; + } + + return result; + } + + protected abstract string VisitMemberAccess(MemberExpression m); + + protected virtual string VisitLambda(LambdaExpression lambda) + { + if (lambda.Body.NodeType == ExpressionType.MemberAccess) + { + var m = lambda.Body as MemberExpression; + + if (m != null && m.Expression != null) + { + //This deals with members that are boolean (i.e. x => IsTrashed ) + var r = VisitMemberAccess(m); + + SqlParameters.Add(true); + + return Visited ? string.Empty : string.Format("{0} = @{1}", r, SqlParameters.Count - 1); + } + + } + return Visit(lambda.Body); + } + + protected virtual string VisitBinary(BinaryExpression b) + { + var left = string.Empty; + var right = string.Empty; + + var operand = BindOperant(b.NodeType); + if (operand == "AND" || operand == "OR") + { + if (b.Left is MemberExpression mLeft && mLeft.Expression != null) + { + var r = VisitMemberAccess(mLeft); + + SqlParameters.Add(true); + + if (Visited == false) + left = $"{r} = @{SqlParameters.Count - 1}"; + } + else + { + left = Visit(b.Left); + } + if (b.Right is MemberExpression mRight && mRight.Expression != null) + { + var r = VisitMemberAccess(mRight); + + SqlParameters.Add(true); + + if (Visited == false) + right = $"{r} = @{SqlParameters.Count - 1}"; + } + else + { + right = Visit(b.Right); + } + } + else if (operand == "=") + { + // deal with (x == true|false) - most common + if (b.Right is ConstantExpression constRight && constRight.Type == typeof(bool)) + return (bool) constRight.Value ? VisitNotNot(b.Left) : VisitNot(b.Left); + right = Visit(b.Right); + + // deal with (true|false == x) - why not + if (b.Left is ConstantExpression constLeft && constLeft.Type == typeof(bool)) + return (bool) constLeft.Value ? VisitNotNot(b.Right) : VisitNot(b.Right); + left = Visit(b.Left); + } + else if (operand == "<>") + { + // deal with (x != true|false) - most common + if (b.Right is ConstantExpression constRight && constRight.Type == typeof (bool)) + return (bool) constRight.Value ? VisitNot(b.Left) : VisitNotNot(b.Left); + right = Visit(b.Right); + + // deal with (true|false != x) - why not + if (b.Left is ConstantExpression constLeft && constLeft.Type == typeof (bool)) + return (bool) constLeft.Value ? VisitNot(b.Right) : VisitNotNot(b.Right); + left = Visit(b.Left); + } + else + { + left = Visit(b.Left); + right = Visit(b.Right); + } + + if (operand == "=" && right == "null") operand = "is"; + else if (operand == "<>" && right == "null") operand = "is not"; + else if (operand == "=" || operand == "<>") + { + //if (IsTrueExpression(right)) right = GetQuotedTrueValue(); + //else if (IsFalseExpression(right)) right = GetQuotedFalseValue(); + + //if (IsTrueExpression(left)) left = GetQuotedTrueValue(); + //else if (IsFalseExpression(left)) left = GetQuotedFalseValue(); + + } + + switch (operand) + { + case "MOD": + case "COALESCE": + //don't execute if compiled + if (Visited == false) + { + return string.Format("{0}({1},{2})", operand, left, right); + } + //already compiled, return + return string.Empty; + default: + //don't execute if compiled + if (Visited == false) + { + return string.Concat("(", left, " ", operand, " ", right, ")"); + } + //already compiled, return + return string.Empty; + } + } + + protected virtual List VisitExpressionList(ReadOnlyCollection original) + { + var list = new List(); + for (int i = 0, n = original.Count; i < n; i++) + { + if (original[i].NodeType == ExpressionType.NewArrayInit || + original[i].NodeType == ExpressionType.NewArrayBounds) + { + list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression)); + } + else + { + list.Add(Visit(original[i])); + } + } + return list; + } + + protected virtual string VisitNew(NewExpression nex) + { + // TODO : check ! + var member = Expression.Convert(nex, typeof(object)); + var lambda = Expression.Lambda>(member); + try + { + var getter = lambda.Compile(); + var o = getter(); + + SqlParameters.Add(o); + return Visited ? string.Empty : $"@{SqlParameters.Count - 1}"; + } + catch (InvalidOperationException) + { + if (Visited) return string.Empty; + + var exprs = VisitExpressionList(nex.Arguments); + var r = new StringBuilder(); + foreach (var e in exprs) + { + if (r.Length > 0) r.Append(","); + r.Append(e); + } + return r.ToString(); + } + } + + protected virtual string VisitParameter(ParameterExpression p) + { + return p.Name; + } + + protected virtual string VisitConstant(ConstantExpression c) + { + if (c.Value == null) + return "null"; + + SqlParameters.Add(c.Value); + return Visited ? string.Empty : $"@{SqlParameters.Count - 1}"; + } + + protected virtual string VisitUnary(UnaryExpression u) + { + switch (u.NodeType) + { + case ExpressionType.Not: + return VisitNot(u.Operand); + default: + return Visit(u.Operand); + } + } + + private string VisitNot(Expression exp) + { + var o = Visit(exp); + + // use a "NOT (...)" syntax instead of "<>" since we don't know whether "<>" works in all sql servers + // also, x.StartsWith(...) translates to "x LIKE '...%'" which we cannot "<>" and have to "NOT (...") + + switch (exp.NodeType) + { + case ExpressionType.MemberAccess: + // false property , i.e. x => !Trashed + SqlParameters.Add(true); + return Visited ? string.Empty : $"NOT ({o} = @{SqlParameters.Count - 1})"; + default: + // could be anything else, such as: x => !x.Path.StartsWith("-20") + return Visited ? string.Empty : string.Concat("NOT (", o, ")"); + } + } + + private string VisitNotNot(Expression exp) + { + var o = Visit(exp); + + switch (exp.NodeType) + { + case ExpressionType.MemberAccess: + // true property, i.e. x => Trashed + SqlParameters.Add(true); + return Visited ? string.Empty : $"({o} = @{SqlParameters.Count - 1})"; + default: + // could be anything else, such as: x => x.Path.StartsWith("-20") + return Visited ? string.Empty : o; + } + } + + protected virtual string VisitNewArray(NewArrayExpression na) + { + var exprs = VisitExpressionList(na.Expressions); + + //don't execute if compiled + if (Visited == false) + { + var r = new StringBuilder(); + foreach (var e in exprs) + { + r.Append(r.Length > 0 ? "," + e : e); + } + + return r.ToString(); + } + //already compiled, return + return string.Empty; + } + + protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) + { + var exprs = VisitExpressionList(na.Expressions); + return exprs; + } + + protected virtual string BindOperant(ExpressionType e) + { + switch (e) + { + case ExpressionType.Equal: + return "="; + case ExpressionType.NotEqual: + return "<>"; + case ExpressionType.GreaterThan: + return ">"; + case ExpressionType.GreaterThanOrEqual: + return ">="; + case ExpressionType.LessThan: + return "<"; + case ExpressionType.LessThanOrEqual: + return "<="; + case ExpressionType.AndAlso: + return "AND"; + case ExpressionType.OrElse: + return "OR"; + case ExpressionType.Add: + return "+"; + case ExpressionType.Subtract: + return "-"; + case ExpressionType.Multiply: + return "*"; + case ExpressionType.Divide: + return "/"; + case ExpressionType.Modulo: + return "MOD"; + case ExpressionType.Coalesce: + return "COALESCE"; + default: + return e.ToString(); + } + } + + protected virtual string VisitMethodCall(MethodCallExpression m) + { + //Here's what happens with a MethodCallExpression: + // If a method is called that contains a single argument, + // then m.Object is the object on the left hand side of the method call, example: + // x.Path.StartsWith(content.Path) + // m.Object = x.Path + // and m.Arguments.Length == 1, therefor m.Arguments[0] == content.Path + // If a method is called that contains multiple arguments, then m.Object == null and the + // m.Arguments collection contains the left hand side of the method call, example: + // x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar) + // m.Object == null + // m.Arguments.Length == 3, therefor, m.Arguments[0] == x.Path, m.Arguments[1] == content.Path, m.Arguments[2] == TextColumnType.NVarchar + // So, we need to cater for these scenarios. + + var objectForMethod = m.Object ?? m.Arguments[0]; + var visitedObjectForMethod = Visit(objectForMethod); + var methodArgs = m.Object == null + ? m.Arguments.Skip(1).ToArray() + : m.Arguments.ToArray(); + + switch (m.Method.Name) + { + case "ToString": + SqlParameters.Add(objectForMethod.ToString()); + //don't execute if compiled + if (Visited == false) + return string.Format("@{0}", SqlParameters.Count - 1); + //already compiled, return + return string.Empty; + case "ToUpper": + //don't execute if compiled + if (Visited == false) + return string.Format("upper({0})", visitedObjectForMethod); + //already compiled, return + return string.Empty; + case "ToLower": + //don't execute if compiled + if (Visited == false) + return string.Format("lower({0})", visitedObjectForMethod); + //already compiled, return + return string.Empty; + case "SqlWildcard": + case "StartsWith": + case "EndsWith": + case "Contains": + case "Equals": + case "SqlStartsWith": + case "SqlEndsWith": + case "SqlContains": + case "SqlEquals": + case "InvariantStartsWith": + case "InvariantEndsWith": + case "InvariantContains": + case "InvariantEquals": + + //special case, if it is 'Contains' and the argumet that Contains is being called on is + //Enumerable and the methodArgs is the actual member access, then it's an SQL IN claus + if (m.Object == null + && m.Arguments[0].Type != typeof(string) + && m.Arguments.Count == 2 + && methodArgs.Length == 1 + && methodArgs[0].NodeType == ExpressionType.MemberAccess + && TypeHelper.IsTypeAssignableFrom(m.Arguments[0].Type)) + { + goto case "SqlIn"; + } + + string compareValue; + + if (methodArgs[0].NodeType != ExpressionType.Constant) + { + // if it's a field accessor, we could Visit(methodArgs[0]) and get [a].[b] + // but then, what if we want more, eg .StartsWith(node.Path + ',') ? => not + + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + compareValue = getter().ToString(); + } + else + { + compareValue = methodArgs[0].ToString(); + } + + //default column type + var colType = TextColumnType.NVarchar; + + //then check if the col type argument has been passed to the current method (this will be the case for methods like + // SqlContains and other Sql methods) + if (methodArgs.Length > 1) + { + var colTypeArg = methodArgs.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); + if (colTypeArg != null) + { + colType = (TextColumnType)((ConstantExpression)colTypeArg).Value; + } + } + + return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType); + + case "Replace": + string searchValue; + + if (methodArgs[0].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + searchValue = getter().ToString(); + } + else + { + searchValue = methodArgs[0].ToString(); + } + + if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + string replaceValue; + + if (methodArgs[1].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[1], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + replaceValue = getter().ToString(); + } + else + { + replaceValue = methodArgs[1].ToString(); + } + + if (methodArgs[1].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[1].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + SqlParameters.Add(RemoveQuote(searchValue)); + + SqlParameters.Add(RemoveQuote(replaceValue)); + + //don't execute if compiled + if (Visited == false) + return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1); + //already compiled, return + return string.Empty; + + //case "Substring": + // var startIndex = Int32.Parse(args[0].ToString()) + 1; + // if (args.Count == 2) + // { + // var length = Int32.Parse(args[1].ToString()); + // return string.Format("substring({0} from {1} for {2})", + // r, + // startIndex, + // length); + // } + // else + // return string.Format("substring({0} from {1})", + // r, + // startIndex); + //case "Round": + //case "Floor": + //case "Ceiling": + //case "Coalesce": + //case "Abs": + //case "Sum": + // return string.Format("{0}({1}{2})", + // m.Method.Name, + // r, + // args.Count == 1 ? string.Format(",{0}", args[0]) : ""); + //case "Concat": + // var s = new StringBuilder(); + // foreach (Object e in args) + // { + // s.AppendFormat(" || {0}", e); + // } + // return string.Format("{0}{1}", r, s); + + case "SqlIn": + + if (m.Object == null && methodArgs.Length == 1 && methodArgs[0].NodeType == ExpressionType.MemberAccess) + { + var memberAccess = VisitMemberAccess((MemberExpression) methodArgs[0]); + + var member = Expression.Convert(m.Arguments[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + + var inArgs = (IEnumerable)getter(); + + var sIn = new StringBuilder(); + foreach (var e in inArgs) + { + SqlParameters.Add(e); + + sIn.AppendFormat("{0}{1}", + sIn.Length > 0 ? "," : "", + string.Format("@{0}", SqlParameters.Count - 1)); + } + + return string.Format("{0} IN ({1})", memberAccess, sIn); + } + + throw new NotSupportedException("SqlIn must contain the member being accessed"); + + //case "Desc": + // return string.Format("{0} DESC", r); + //case "Alias": + //case "As": + // return string.Format("{0} As {1}", r, + // GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); + + case "SqlText": + if (m.Method.DeclaringType != typeof(NPocoSqlExtensions.Statics)) + goto default; + if (m.Arguments.Count == 2) + { + var n1 = Visit(m.Arguments[0]); + var f = m.Arguments[2]; + if (!(f is Expression> fl)) + throw new NotSupportedException("Expression is not a proper lambda."); + var ff = fl.Compile(); + return ff(n1); + } + else if (m.Arguments.Count == 3) + { + var n1 = Visit(m.Arguments[0]); + var n2 = Visit(m.Arguments[1]); + var f = m.Arguments[2]; + if (!(f is Expression> fl)) + throw new NotSupportedException("Expression is not a proper lambda."); + var ff = fl.Compile(); + return ff(n1, n2); + } + else if (m.Arguments.Count == 4) + { + var n1 = Visit(m.Arguments[0]); + var n2 = Visit(m.Arguments[1]); + var n3 = Visit(m.Arguments[3]); + var f = m.Arguments[3]; + if (!(f is Expression> fl)) + throw new NotSupportedException("Expression is not a proper lambda."); + var ff = fl.Compile(); + return ff(n1, n2, n3); + } + else + throw new NotSupportedException("Expression is not a proper lambda."); + + default: + + throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name); + + //var s2 = new StringBuilder(); + //foreach (Object e in args) + //{ + // s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); + //} + //return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); + } + } + + public virtual string GetQuotedTableName(string tableName) + { + return Visited ? tableName : string.Format("\"{0}\"", tableName); + } + + public virtual string GetQuotedColumnName(string columnName) + { + return Visited ? columnName : string.Format("\"{0}\"", columnName); + } + + public virtual string GetQuotedName(string name) + { + return Visited ? name : string.Format("\"{0}\"", name); + } + + protected string HandleStringComparison(string col, string val, string verb, TextColumnType columnType) + { + switch (verb) + { + case "SqlWildcard": + SqlParameters.Add(RemoveQuote(val)); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "Equals": + SqlParameters.Add(RemoveQuote(val)); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "StartsWith": + SqlParameters.Add(string.Format("{0}{1}", + RemoveQuote(val), + SqlSyntax.GetWildcardPlaceholder())); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "EndsWith": + SqlParameters.Add(string.Format("{0}{1}", + SqlSyntax.GetWildcardPlaceholder(), + RemoveQuote(val))); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "Contains": + SqlParameters.Add(string.Format("{0}{1}{0}", + SqlSyntax.GetWildcardPlaceholder(), + RemoveQuote(val))); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "InvariantEquals": + case "SqlEquals": + //recurse + return HandleStringComparison(col, val, "Equals", columnType); + case "InvariantStartsWith": + case "SqlStartsWith": + //recurse + return HandleStringComparison(col, val, "StartsWith", columnType); + case "InvariantEndsWith": + case "SqlEndsWith": + //recurse + return HandleStringComparison(col, val, "EndsWith", columnType); + case "InvariantContains": + case "SqlContains": + //recurse + return HandleStringComparison(col, val, "Contains", columnType); + default: + throw new ArgumentOutOfRangeException("verb"); + } + } + + //public virtual string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null) + //{ + // if (value == null) return "NULL"; + + // if (escapeCallback == null) + // { + // escapeCallback = EscapeParam; + // } + // if (shouldQuoteCallback == null) + // { + // shouldQuoteCallback = ShouldQuoteValue; + // } + + // if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string)) + // { + // //if (TypeSerializer.CanCreateFromString(fieldType)) + // //{ + // // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'"; + // //} + + // throw new NotSupportedException( + // string.Format("Property of type: {0} is not supported", fieldType.FullName)); + // } + + // if (fieldType == typeof(int)) + // return ((int)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(float)) + // return ((float)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(double)) + // return ((double)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(decimal)) + // return ((decimal)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(DateTime)) + // { + // return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'"; + // } + + // if (fieldType == typeof(bool)) + // return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture); + + // return shouldQuoteCallback(fieldType) + // ? "'" + escapeCallback(value) + "'" + // : value.ToString(); + //} + + public virtual string EscapeParam(object paramValue, ISqlSyntaxProvider sqlSyntax) + { + return paramValue == null + ? string.Empty + : sqlSyntax.EscapeString(paramValue.ToString()); + } + + public virtual bool ShouldQuoteValue(Type fieldType) + { + return true; + } + + protected virtual string RemoveQuote(string exp) + { + if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) + && + (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) + { + exp = exp.Remove(0, 1); + exp = exp.Remove(exp.Length - 1, 1); + } + return exp; + } + + //protected virtual string RemoveQuoteFromAlias(string expression) + //{ + + // if ((expression.StartsWith("\"") || expression.StartsWith("`") || expression.StartsWith("'")) + // && + // (expression.EndsWith("\"") || expression.EndsWith("`") || expression.EndsWith("'"))) + // { + // expression = expression.Remove(0, 1); + // expression = expression.Remove(expression.Length - 1, 1); + // } + // return expression; + //} + } +} diff --git a/src/Umbraco.Core/Persistence/Querying/IQuery.cs b/src/Umbraco.Core/Persistence/Querying/IQuery.cs index d4c1d83f8a..d5723b6032 100644 --- a/src/Umbraco.Core/Persistence/Querying/IQuery.cs +++ b/src/Umbraco.Core/Persistence/Querying/IQuery.cs @@ -1,42 +1,42 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq.Expressions; - -namespace Umbraco.Core.Persistence.Querying -{ - /// - /// Represents a query for building Linq translatable SQL queries - /// - /// - public interface IQuery - { - /// - /// Adds a where clause to the query - /// - /// - /// This instance so calls to this method are chainable - IQuery Where(Expression> predicate); - - /// - /// Returns all translated where clauses and their sql parameters - /// - /// - IEnumerable> GetWhereClauses(); - - /// - /// Adds a where-in clause to the query - /// - /// - /// - /// This instance so calls to this method are chainable - IQuery WhereIn(Expression> fieldSelector, IEnumerable values); - - /// - /// Adds a set of OR-ed where clauses to the query. - /// - /// - /// This instance so calls to this method are chainable. - IQuery WhereAny(IEnumerable>> predicates); - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// Represents a query for building Linq translatable SQL queries + /// + /// + public interface IQuery + { + /// + /// Adds a where clause to the query + /// + /// + /// This instance so calls to this method are chainable + IQuery Where(Expression> predicate); + + /// + /// Returns all translated where clauses and their sql parameters + /// + /// + IEnumerable> GetWhereClauses(); + + /// + /// Adds a where-in clause to the query + /// + /// + /// + /// This instance so calls to this method are chainable + IQuery WhereIn(Expression> fieldSelector, IEnumerable values); + + /// + /// Adds a set of OR-ed where clauses to the query. + /// + /// + /// This instance so calls to this method are chainable. + IQuery WhereAny(IEnumerable>> predicates); + } +} diff --git a/src/Umbraco.Core/Persistence/Querying/Query.cs b/src/Umbraco.Core/Persistence/Querying/Query.cs index 0c5dec7d4e..c7ade3b416 100644 --- a/src/Umbraco.Core/Persistence/Querying/Query.cs +++ b/src/Umbraco.Core/Persistence/Querying/Query.cs @@ -1,99 +1,99 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Text; -using NPoco; - -namespace Umbraco.Core.Persistence.Querying -{ - /// - /// Represents a query builder. - /// - /// A query builder translates Linq queries into Sql queries. - public class Query : IQuery - { - private readonly ISqlContext _sqlContext; - private readonly List> _wheres = new List>(); - - public Query(ISqlContext sqlContext) - { - _sqlContext = sqlContext; - } - - /// - /// Adds a where clause to the query. - /// - public virtual IQuery Where(Expression> predicate) - { - if (predicate == null) return this; - - var expressionHelper = new ModelToSqlExpressionVisitor(_sqlContext.SqlSyntax, _sqlContext.Mappers); - var whereExpression = expressionHelper.Visit(predicate); - _wheres.Add(new Tuple(whereExpression, expressionHelper.GetSqlParameters())); - return this; - } - - /// - /// Adds a where-in clause to the query. - /// - public virtual IQuery WhereIn(Expression> fieldSelector, IEnumerable values) - { - if (fieldSelector == null) return this; - - var expressionHelper = new ModelToSqlExpressionVisitor(_sqlContext.SqlSyntax, _sqlContext.Mappers); - var whereExpression = expressionHelper.Visit(fieldSelector); - _wheres.Add(new Tuple(whereExpression + " IN (@values)", new object[] { new { values } })); - return this; - } - - /// - /// Adds a set of OR-ed where clauses to the query. - /// - public virtual IQuery WhereAny(IEnumerable>> predicates) - { - if (predicates == null) return this; - - StringBuilder sb = null; - List parameters = null; - Sql sql = null; - foreach (var predicate in predicates) - { - // see notes in Where() - var expressionHelper = new ModelToSqlExpressionVisitor(_sqlContext.SqlSyntax, _sqlContext.Mappers); - var whereExpression = expressionHelper.Visit(predicate); - - if (sb == null) - { - sb = new StringBuilder("("); - parameters = new List(); - sql = Sql.BuilderFor(_sqlContext); - } - else - { - sb.Append(" OR "); - sql.Append(" OR "); - } - - sb.Append(whereExpression); - parameters.AddRange(expressionHelper.GetSqlParameters()); - sql.Append(whereExpression, expressionHelper.GetSqlParameters()); - } - - if (sb == null) return this; - - sb.Append(")"); - _wheres.Add(Tuple.Create("(" + sql.SQL + ")", sql.Arguments)); - - return this; - } - - /// - /// Returns all translated where clauses and their sql parameters - /// - public IEnumerable> GetWhereClauses() - { - return _wheres; - } - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; +using NPoco; + +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// Represents a query builder. + /// + /// A query builder translates Linq queries into Sql queries. + public class Query : IQuery + { + private readonly ISqlContext _sqlContext; + private readonly List> _wheres = new List>(); + + public Query(ISqlContext sqlContext) + { + _sqlContext = sqlContext; + } + + /// + /// Adds a where clause to the query. + /// + public virtual IQuery Where(Expression> predicate) + { + if (predicate == null) return this; + + var expressionHelper = new ModelToSqlExpressionVisitor(_sqlContext.SqlSyntax, _sqlContext.Mappers); + var whereExpression = expressionHelper.Visit(predicate); + _wheres.Add(new Tuple(whereExpression, expressionHelper.GetSqlParameters())); + return this; + } + + /// + /// Adds a where-in clause to the query. + /// + public virtual IQuery WhereIn(Expression> fieldSelector, IEnumerable values) + { + if (fieldSelector == null) return this; + + var expressionHelper = new ModelToSqlExpressionVisitor(_sqlContext.SqlSyntax, _sqlContext.Mappers); + var whereExpression = expressionHelper.Visit(fieldSelector); + _wheres.Add(new Tuple(whereExpression + " IN (@values)", new object[] { new { values } })); + return this; + } + + /// + /// Adds a set of OR-ed where clauses to the query. + /// + public virtual IQuery WhereAny(IEnumerable>> predicates) + { + if (predicates == null) return this; + + StringBuilder sb = null; + List parameters = null; + Sql sql = null; + foreach (var predicate in predicates) + { + // see notes in Where() + var expressionHelper = new ModelToSqlExpressionVisitor(_sqlContext.SqlSyntax, _sqlContext.Mappers); + var whereExpression = expressionHelper.Visit(predicate); + + if (sb == null) + { + sb = new StringBuilder("("); + parameters = new List(); + sql = Sql.BuilderFor(_sqlContext); + } + else + { + sb.Append(" OR "); + sql.Append(" OR "); + } + + sb.Append(whereExpression); + parameters.AddRange(expressionHelper.GetSqlParameters()); + sql.Append(whereExpression, expressionHelper.GetSqlParameters()); + } + + if (sb == null) return this; + + sb.Append(")"); + _wheres.Add(Tuple.Create("(" + sql.SQL + ")", sql.Arguments)); + + return this; + } + + /// + /// Returns all translated where clauses and their sql parameters + /// + public IEnumerable> GetWhereClauses() + { + return _wheres; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs index 420b71b4eb..880a95b31f 100644 --- a/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs +++ b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs @@ -1,31 +1,31 @@ -using System; -using NPoco; - -namespace Umbraco.Core.Persistence.Querying -{ - /// - /// Represents the Sql Translator for translating a IQuery object to Sql - /// - /// - internal class SqlTranslator - { - private readonly Sql _sql; - - public SqlTranslator(Sql sql, IQuery query) - { - _sql = sql ?? throw new ArgumentNullException(nameof(sql)); - foreach (var clause in query.GetWhereClauses()) - _sql.Where(clause.Item1, clause.Item2); - } - - public Sql Translate() - { - return _sql; - } - - public override string ToString() - { - return _sql.SQL; - } - } -} +using System; +using NPoco; + +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// Represents the Sql Translator for translating a IQuery object to Sql + /// + /// + internal class SqlTranslator + { + private readonly Sql _sql; + + public SqlTranslator(Sql sql, IQuery query) + { + _sql = sql ?? throw new ArgumentNullException(nameof(sql)); + foreach (var clause in query.GetWhereClauses()) + _sql.Where(clause.Item1, clause.Item2); + } + + public Sql Translate() + { + return _sql; + } + + public override string ToString() + { + return _sql.SQL; + } + } +} diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ColumnInfo.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ColumnInfo.cs index 654be14d34..6ca0805158 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ColumnInfo.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ColumnInfo.cs @@ -1,31 +1,31 @@ -namespace Umbraco.Core.Persistence.SqlSyntax -{ - public class ColumnInfo - { - public ColumnInfo(string tableName, string columnName, int ordinal, string columnDefault, string isNullable, string dataType) - { - TableName = tableName; - ColumnName = columnName; - Ordinal = ordinal; - ColumnDefault = columnDefault; - IsNullable = isNullable.Equals("YES"); - DataType = dataType; - } - - public ColumnInfo(string tableName, string columnName, int ordinal, string isNullable, string dataType) - { - TableName = tableName; - ColumnName = columnName; - Ordinal = ordinal; - IsNullable = isNullable.Equals("YES"); - DataType = dataType; - } - - public string TableName { get; set; } - public string ColumnName { get; set; } - public int Ordinal { get; set; } - public string ColumnDefault { get; set; } - public bool IsNullable { get; set; } - public string DataType { get; set; } - } -} +namespace Umbraco.Core.Persistence.SqlSyntax +{ + public class ColumnInfo + { + public ColumnInfo(string tableName, string columnName, int ordinal, string columnDefault, string isNullable, string dataType) + { + TableName = tableName; + ColumnName = columnName; + Ordinal = ordinal; + ColumnDefault = columnDefault; + IsNullable = isNullable.Equals("YES"); + DataType = dataType; + } + + public ColumnInfo(string tableName, string columnName, int ordinal, string isNullable, string dataType) + { + TableName = tableName; + ColumnName = columnName; + Ordinal = ordinal; + IsNullable = isNullable.Equals("YES"); + DataType = dataType; + } + + public string TableName { get; set; } + public string ColumnName { get; set; } + public int Ordinal { get; set; } + public string ColumnDefault { get; set; } + public bool IsNullable { get; set; } + public string DataType { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/DbTypes.cs b/src/Umbraco.Core/Persistence/SqlSyntax/DbTypes.cs index 63a17fa814..1402c6562b 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/DbTypes.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/DbTypes.cs @@ -1,29 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Data; - -namespace Umbraco.Core.Persistence.SqlSyntax -{ - public class DbTypes - { - public DbType DbType; - public string TextDefinition; - public bool ShouldQuoteValue; - public Dictionary ColumnTypeMap = new Dictionary(); - public Dictionary ColumnDbTypeMap = new Dictionary(); - - public void Set(DbType dbType, string fieldDefinition) - { - DbType = dbType; - TextDefinition = fieldDefinition; - ShouldQuoteValue = fieldDefinition != "INTEGER" - && fieldDefinition != "BIGINT" - && fieldDefinition != "DOUBLE" - && fieldDefinition != "DECIMAL" - && fieldDefinition != "BOOL"; - - ColumnTypeMap[typeof(T)] = fieldDefinition; - ColumnDbTypeMap[typeof(T)] = dbType; - } - } -} +using System; +using System.Collections.Generic; +using System.Data; + +namespace Umbraco.Core.Persistence.SqlSyntax +{ + public class DbTypes + { + public DbType DbType; + public string TextDefinition; + public bool ShouldQuoteValue; + public Dictionary ColumnTypeMap = new Dictionary(); + public Dictionary ColumnDbTypeMap = new Dictionary(); + + public void Set(DbType dbType, string fieldDefinition) + { + DbType = dbType; + TextDefinition = fieldDefinition; + ShouldQuoteValue = fieldDefinition != "INTEGER" + && fieldDefinition != "BIGINT" + && fieldDefinition != "DOUBLE" + && fieldDefinition != "DECIMAL" + && fieldDefinition != "BOOL"; + + ColumnTypeMap[typeof(T)] = fieldDefinition; + ColumnDbTypeMap[typeof(T)] = dbType; + } + } +} diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 167f9dc0b8..46426cb869 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -1,89 +1,89 @@ -using System; -using System.Collections.Generic; -using NPoco; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; - -namespace Umbraco.Core.Persistence.SqlSyntax -{ - /// - /// Defines an SqlSyntaxProvider - /// - public interface ISqlSyntaxProvider - { - string EscapeString(string val); - - string GetWildcardPlaceholder(); - string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType); - string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType); - string GetConcat(params string[] args); - - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType); - - string GetQuotedTableName(string tableName); - string GetQuotedColumnName(string columnName); - string GetQuotedName(string name); - bool DoesTableExist(IDatabase db, string tableName); - string GetIndexType(IndexTypes indexTypes); - string GetSpecialDbType(SpecialDbTypes dbTypes); - string CreateTable { get; } - string DropTable { get; } - string AddColumn { get; } - string DropColumn { get; } - string AlterColumn { get; } - string RenameColumn { get; } - string RenameTable { get; } - string CreateSchema { get; } - string AlterSchema { get; } - string DropSchema { get; } - string CreateIndex { get; } - string DropIndex { get; } - string InsertData { get; } - string UpdateData { get; } - string DeleteData { get; } - string TruncateTable { get; } - string CreateConstraint { get; } - string DeleteConstraint { get; } - string CreateForeignKeyConstraint { get; } - string DeleteDefaultConstraint { get; } - string FormatDateTime(DateTime date, bool includeTime = true); - string Format(TableDefinition table); - string Format(IEnumerable columns); - List Format(IEnumerable indexes); - List Format(IEnumerable foreignKeys); - string FormatPrimaryKey(TableDefinition table); - string GetQuotedValue(string value); - string Format(ColumnDefinition column); - string Format(ColumnDefinition column, string tableName, out IEnumerable sqls); - string Format(IndexDefinition index); - string Format(ForeignKeyDefinition foreignKey); - string FormatColumnRename(string tableName, string oldName, string newName); - string FormatTableRename(string oldName, string newName); - - Sql SelectTop(Sql sql, int top); - - bool SupportsClustered(); - bool SupportsIdentityInsert(); - bool? SupportsCaseInsensitiveQueries(IDatabase db); - - string ConvertIntegerToOrderableString { get; } - string ConvertDateToOrderableString { get; } - string ConvertDecimalToOrderableString { get; } - - IEnumerable GetTablesInSchema(IDatabase db); - IEnumerable GetColumnsInSchema(IDatabase db); - IEnumerable> GetConstraintsPerTable(IDatabase db); - IEnumerable> GetConstraintsPerColumn(IDatabase db); - IEnumerable> GetDefinedIndexes(IDatabase db); - } -} +using System; +using System.Collections.Generic; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Defines an SqlSyntaxProvider + /// + public interface ISqlSyntaxProvider + { + string EscapeString(string val); + + string GetWildcardPlaceholder(); + string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType); + string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType); + string GetConcat(params string[] args); + + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType); + + string GetQuotedTableName(string tableName); + string GetQuotedColumnName(string columnName); + string GetQuotedName(string name); + bool DoesTableExist(IDatabase db, string tableName); + string GetIndexType(IndexTypes indexTypes); + string GetSpecialDbType(SpecialDbTypes dbTypes); + string CreateTable { get; } + string DropTable { get; } + string AddColumn { get; } + string DropColumn { get; } + string AlterColumn { get; } + string RenameColumn { get; } + string RenameTable { get; } + string CreateSchema { get; } + string AlterSchema { get; } + string DropSchema { get; } + string CreateIndex { get; } + string DropIndex { get; } + string InsertData { get; } + string UpdateData { get; } + string DeleteData { get; } + string TruncateTable { get; } + string CreateConstraint { get; } + string DeleteConstraint { get; } + string CreateForeignKeyConstraint { get; } + string DeleteDefaultConstraint { get; } + string FormatDateTime(DateTime date, bool includeTime = true); + string Format(TableDefinition table); + string Format(IEnumerable columns); + List Format(IEnumerable indexes); + List Format(IEnumerable foreignKeys); + string FormatPrimaryKey(TableDefinition table); + string GetQuotedValue(string value); + string Format(ColumnDefinition column); + string Format(ColumnDefinition column, string tableName, out IEnumerable sqls); + string Format(IndexDefinition index); + string Format(ForeignKeyDefinition foreignKey); + string FormatColumnRename(string tableName, string oldName, string newName); + string FormatTableRename(string oldName, string newName); + + Sql SelectTop(Sql sql, int top); + + bool SupportsClustered(); + bool SupportsIdentityInsert(); + bool? SupportsCaseInsensitiveQueries(IDatabase db); + + string ConvertIntegerToOrderableString { get; } + string ConvertDateToOrderableString { get; } + string ConvertDecimalToOrderableString { get; } + + IEnumerable GetTablesInSchema(IDatabase db); + IEnumerable GetColumnsInSchema(IDatabase db); + IEnumerable> GetConstraintsPerTable(IDatabase db); + IEnumerable> GetConstraintsPerColumn(IDatabase db); + IEnumerable> GetDefinedIndexes(IDatabase db); + } +} diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs index 82cb999c37..bb4d8269ce 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs @@ -1,239 +1,239 @@ -using System; -using System.Data; -using System.Linq; -using Umbraco.Core.Persistence.Querying; - -namespace Umbraco.Core.Persistence.SqlSyntax -{ - /// - /// Abstract class for defining MS sql implementations - /// - /// - public abstract class MicrosoftSqlSyntaxProviderBase : SqlSyntaxProviderBase - where TSyntax : ISqlSyntaxProvider - { - protected MicrosoftSqlSyntaxProviderBase() - { - AutoIncrementDefinition = "IDENTITY(1,1)"; - GuidColumnDefinition = "UniqueIdentifier"; - RealColumnDefinition = "FLOAT"; - BoolColumnDefinition = "BIT"; - DecimalColumnDefinition = "DECIMAL(38,6)"; - TimeColumnDefinition = "TIME"; //SQLSERVER 2008+ - BlobColumnDefinition = "VARBINARY(MAX)"; - - InitColumnTypeMap(); - } - - public override string RenameTable => "sp_rename '{0}', '{1}'"; - - public override string AddColumn => "ALTER TABLE {0} ADD {1}"; - - public override string GetQuotedTableName(string tableName) - { - if (tableName.Contains(".") == false) - return $"[{tableName}]"; - - var tableNameParts = tableName.Split(new[] { '.' }, 2); - return $"[{tableNameParts[0]}].[{tableNameParts[1]}]"; - } - - public override string GetQuotedColumnName(string columnName) => $"[{columnName}]"; - - public override string GetQuotedName(string name) => $"[{name}]"; - - public override string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnEqualComparison(column, paramIndex, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for = comparison with NText columns but allows this syntax - return $"{column} LIKE @{paramIndex}"; - default: - throw new ArgumentOutOfRangeException(nameof(columnType)); - } - } - - public override string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnWildcardComparison(column, paramIndex, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return $"{column} LIKE @{paramIndex}"; - default: - throw new ArgumentOutOfRangeException(nameof(columnType)); - } - } - - [Obsolete("Use the overload with the parameter index instead")] - public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnStartsWithComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return $"{column} LIKE '{value}%'"; - default: - throw new ArgumentOutOfRangeException(nameof(columnType)); - } - } - - [Obsolete("Use the overload with the parameter index instead")] - public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnEndsWithComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return $"{column} LIKE '%{value}'"; - default: - throw new ArgumentOutOfRangeException(nameof(columnType)); - } - } - - [Obsolete("Use the overload with the parameter index instead")] - public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnContainsComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return $"{column} LIKE '%{value}%'"; - default: - throw new ArgumentOutOfRangeException(nameof(columnType)); - } - } - - [Obsolete("Use the overload with the parameter index instead")] - public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnContainsComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return $"{column} LIKE '{value}'"; - default: - throw new ArgumentOutOfRangeException(nameof(columnType)); - } - } - - /// - /// This uses a the DbTypeMap created and custom mapping to resolve the SqlDbType - /// - /// - /// - public virtual SqlDbType GetSqlDbType(Type clrType) - { - var dbType = DbTypeMap.ColumnDbTypeMap.First(x => x.Key == clrType).Value; - return GetSqlDbType(dbType); - } - - /// - /// Returns the mapped SqlDbType for the DbType specified - /// - /// - /// - public virtual SqlDbType GetSqlDbType(DbType dbType) - { - SqlDbType sqlDbType; - - //SEE: https://msdn.microsoft.com/en-us/library/cc716729(v=vs.110).aspx - // and https://msdn.microsoft.com/en-us/library/yy6y35y8%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 - switch (dbType) - { - case DbType.AnsiString: - sqlDbType = SqlDbType.VarChar; - break; - case DbType.Binary: - sqlDbType = SqlDbType.VarBinary; - break; - case DbType.Byte: - sqlDbType = SqlDbType.TinyInt; - break; - case DbType.Boolean: - sqlDbType = SqlDbType.Bit; - break; - case DbType.Currency: - sqlDbType = SqlDbType.Money; - break; - case DbType.Date: - sqlDbType = SqlDbType.Date; - break; - case DbType.DateTime: - sqlDbType = SqlDbType.DateTime; - break; - case DbType.Decimal: - sqlDbType = SqlDbType.Decimal; - break; - case DbType.Double: - sqlDbType = SqlDbType.Float; - break; - case DbType.Guid: - sqlDbType = SqlDbType.UniqueIdentifier; - break; - case DbType.Int16: - sqlDbType = SqlDbType.SmallInt; - break; - case DbType.Int32: - sqlDbType = SqlDbType.Int; - break; - case DbType.Int64: - sqlDbType = SqlDbType.BigInt; - break; - case DbType.Object: - sqlDbType = SqlDbType.Variant; - break; - case DbType.SByte: - throw new NotSupportedException("Inferring a SqlDbType from SByte is not supported."); - case DbType.Single: - sqlDbType = SqlDbType.Real; - break; - case DbType.String: - sqlDbType = SqlDbType.NVarChar; - break; - case DbType.Time: - sqlDbType = SqlDbType.Time; - break; - case DbType.UInt16: - throw new NotSupportedException("Inferring a SqlDbType from UInt16 is not supported."); - case DbType.UInt32: - throw new NotSupportedException("Inferring a SqlDbType from UInt32 is not supported."); - case DbType.UInt64: - throw new NotSupportedException("Inferring a SqlDbType from UInt64 is not supported."); - case DbType.VarNumeric: - throw new NotSupportedException("Inferring a VarNumeric from UInt64 is not supported."); - case DbType.AnsiStringFixedLength: - sqlDbType = SqlDbType.Char; - break; - case DbType.StringFixedLength: - sqlDbType = SqlDbType.NChar; - break; - case DbType.Xml: - sqlDbType = SqlDbType.Xml; - break; - case DbType.DateTime2: - sqlDbType = SqlDbType.DateTime2; - break; - case DbType.DateTimeOffset: - sqlDbType = SqlDbType.DateTimeOffset; - break; - default: - throw new ArgumentOutOfRangeException(); - } - return sqlDbType; - } - } -} +using System; +using System.Data; +using System.Linq; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Abstract class for defining MS sql implementations + /// + /// + public abstract class MicrosoftSqlSyntaxProviderBase : SqlSyntaxProviderBase + where TSyntax : ISqlSyntaxProvider + { + protected MicrosoftSqlSyntaxProviderBase() + { + AutoIncrementDefinition = "IDENTITY(1,1)"; + GuidColumnDefinition = "UniqueIdentifier"; + RealColumnDefinition = "FLOAT"; + BoolColumnDefinition = "BIT"; + DecimalColumnDefinition = "DECIMAL(38,6)"; + TimeColumnDefinition = "TIME"; //SQLSERVER 2008+ + BlobColumnDefinition = "VARBINARY(MAX)"; + + InitColumnTypeMap(); + } + + public override string RenameTable => "sp_rename '{0}', '{1}'"; + + public override string AddColumn => "ALTER TABLE {0} ADD {1}"; + + public override string GetQuotedTableName(string tableName) + { + if (tableName.Contains(".") == false) + return $"[{tableName}]"; + + var tableNameParts = tableName.Split(new[] { '.' }, 2); + return $"[{tableNameParts[0]}].[{tableNameParts[1]}]"; + } + + public override string GetQuotedColumnName(string columnName) => $"[{columnName}]"; + + public override string GetQuotedName(string name) => $"[{name}]"; + + public override string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnEqualComparison(column, paramIndex, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for = comparison with NText columns but allows this syntax + return $"{column} LIKE @{paramIndex}"; + default: + throw new ArgumentOutOfRangeException(nameof(columnType)); + } + } + + public override string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnWildcardComparison(column, paramIndex, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return $"{column} LIKE @{paramIndex}"; + default: + throw new ArgumentOutOfRangeException(nameof(columnType)); + } + } + + [Obsolete("Use the overload with the parameter index instead")] + public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnStartsWithComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return $"{column} LIKE '{value}%'"; + default: + throw new ArgumentOutOfRangeException(nameof(columnType)); + } + } + + [Obsolete("Use the overload with the parameter index instead")] + public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnEndsWithComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return $"{column} LIKE '%{value}'"; + default: + throw new ArgumentOutOfRangeException(nameof(columnType)); + } + } + + [Obsolete("Use the overload with the parameter index instead")] + public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnContainsComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return $"{column} LIKE '%{value}%'"; + default: + throw new ArgumentOutOfRangeException(nameof(columnType)); + } + } + + [Obsolete("Use the overload with the parameter index instead")] + public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnContainsComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return $"{column} LIKE '{value}'"; + default: + throw new ArgumentOutOfRangeException(nameof(columnType)); + } + } + + /// + /// This uses a the DbTypeMap created and custom mapping to resolve the SqlDbType + /// + /// + /// + public virtual SqlDbType GetSqlDbType(Type clrType) + { + var dbType = DbTypeMap.ColumnDbTypeMap.First(x => x.Key == clrType).Value; + return GetSqlDbType(dbType); + } + + /// + /// Returns the mapped SqlDbType for the DbType specified + /// + /// + /// + public virtual SqlDbType GetSqlDbType(DbType dbType) + { + SqlDbType sqlDbType; + + //SEE: https://msdn.microsoft.com/en-us/library/cc716729(v=vs.110).aspx + // and https://msdn.microsoft.com/en-us/library/yy6y35y8%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 + switch (dbType) + { + case DbType.AnsiString: + sqlDbType = SqlDbType.VarChar; + break; + case DbType.Binary: + sqlDbType = SqlDbType.VarBinary; + break; + case DbType.Byte: + sqlDbType = SqlDbType.TinyInt; + break; + case DbType.Boolean: + sqlDbType = SqlDbType.Bit; + break; + case DbType.Currency: + sqlDbType = SqlDbType.Money; + break; + case DbType.Date: + sqlDbType = SqlDbType.Date; + break; + case DbType.DateTime: + sqlDbType = SqlDbType.DateTime; + break; + case DbType.Decimal: + sqlDbType = SqlDbType.Decimal; + break; + case DbType.Double: + sqlDbType = SqlDbType.Float; + break; + case DbType.Guid: + sqlDbType = SqlDbType.UniqueIdentifier; + break; + case DbType.Int16: + sqlDbType = SqlDbType.SmallInt; + break; + case DbType.Int32: + sqlDbType = SqlDbType.Int; + break; + case DbType.Int64: + sqlDbType = SqlDbType.BigInt; + break; + case DbType.Object: + sqlDbType = SqlDbType.Variant; + break; + case DbType.SByte: + throw new NotSupportedException("Inferring a SqlDbType from SByte is not supported."); + case DbType.Single: + sqlDbType = SqlDbType.Real; + break; + case DbType.String: + sqlDbType = SqlDbType.NVarChar; + break; + case DbType.Time: + sqlDbType = SqlDbType.Time; + break; + case DbType.UInt16: + throw new NotSupportedException("Inferring a SqlDbType from UInt16 is not supported."); + case DbType.UInt32: + throw new NotSupportedException("Inferring a SqlDbType from UInt32 is not supported."); + case DbType.UInt64: + throw new NotSupportedException("Inferring a SqlDbType from UInt64 is not supported."); + case DbType.VarNumeric: + throw new NotSupportedException("Inferring a VarNumeric from UInt64 is not supported."); + case DbType.AnsiStringFixedLength: + sqlDbType = SqlDbType.Char; + break; + case DbType.StringFixedLength: + sqlDbType = SqlDbType.NChar; + break; + case DbType.Xml: + sqlDbType = SqlDbType.Xml; + break; + case DbType.DateTime2: + sqlDbType = SqlDbType.DateTime2; + break; + case DbType.DateTimeOffset: + sqlDbType = SqlDbType.DateTimeOffset; + break; + default: + throw new ArgumentOutOfRangeException(); + } + return sqlDbType; + } + } +} diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 53da08ae4c..e0eaa46048 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -1,405 +1,405 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NPoco; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Persistence.SqlSyntax -{ - /// - /// Represents an SqlSyntaxProvider for MySql - /// - [SqlSyntaxProvider(Constants.DbProviderNames.MySql)] - public class MySqlSyntaxProvider : SqlSyntaxProviderBase - { - private readonly ILogger _logger; - - public MySqlSyntaxProvider(ILogger logger) - { - _logger = logger; - - AutoIncrementDefinition = "AUTO_INCREMENT"; - IntColumnDefinition = "int(11)"; - BoolColumnDefinition = "tinyint(1)"; - DateTimeColumnDefinition = "TIMESTAMP"; - TimeColumnDefinition = "time"; - DecimalColumnDefinition = "decimal(38,6)"; - GuidColumnDefinition = "char(36)"; - - DefaultValueFormat = "DEFAULT {0}"; - - InitColumnTypeMap(); - } - - public override IEnumerable GetTablesInSchema(IDatabase db) - { - List list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); - - var items = - db.Fetch( - "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = items.Select(x => x.TABLE_NAME).Cast().ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } - - public override IEnumerable GetColumnsInSchema(IDatabase db) - { - List list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); - - var items = - db.Fetch( - "SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = - items.Select( - item => - new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, int.Parse(item.ORDINAL_POSITION.ToString()), item.COLUMN_DEFAULT ?? "", - item.IS_NULLABLE, item.DATA_TYPE)).ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } - - public override IEnumerable> GetConstraintsPerTable(IDatabase db) - { - List> list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); - - //Does not include indexes and constraints are named differently - var items = - db.Fetch( - "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } - - public override IEnumerable> GetConstraintsPerColumn(IDatabase db) - { - List> list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); - - //Does not include indexes and constraints are named differently - var items = - db.Fetch( - "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = - items.Select( - item => - new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)) - .ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } - - public override IEnumerable> GetDefinedIndexes(IDatabase db) - { - List> list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); - - var indexes = - db.Fetch(@"SELECT DISTINCT - TABLE_NAME, INDEX_NAME, COLUMN_NAME, CASE NON_UNIQUE WHEN 1 THEN 0 ELSE 1 END AS `UNIQUE` -FROM INFORMATION_SCHEMA.STATISTICS -WHERE TABLE_SCHEMA = @TableSchema -AND INDEX_NAME <> COLUMN_NAME AND INDEX_NAME <> 'PRIMARY' -ORDER BY TABLE_NAME, INDEX_NAME", - new { TableSchema = db.Connection.Database }); - list = - indexes.Select( - item => - new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE == 1)) - .ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } - - public override bool DoesTableExist(IDatabase db, string tableName) - { - long result; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); - - result = - db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " + - "WHERE TABLE_NAME = @TableName AND " + - "TABLE_SCHEMA = @TableSchema", - new { TableName = tableName, TableSchema = db.Connection.Database }); - - } - finally - { - db.CloseSharedConnection(); - } - - return result > 0; - } - - public override Sql SelectTop(Sql sql, int top) - { - return new Sql(sql.SqlContext, string.Concat(sql.SQL, " LIMIT ", top), sql.Arguments); - } - - public override bool SupportsClustered() - { - return true; - } - - public override bool SupportsIdentityInsert() - { - return false; - } - - /// - /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) - /// - /// - /// - /// - /// - /// MySQL has a DateTime standard that is unambiguous and works on all servers: - /// YYYYMMDDHHMMSS - /// - public override string FormatDateTime(DateTime date, bool includeTime = true) - { - return includeTime ? date.ToString("yyyyMMddHHmmss") : date.ToString("yyyyMMdd"); - } - - public override string GetQuotedTableName(string tableName) - { - return string.Format("`{0}`", tableName); - } - - public override string GetQuotedColumnName(string columnName) - { - return string.Format("`{0}`", columnName); - } - - public override string GetQuotedName(string name) - { - return string.Format("`{0}`", name); - } - - public override string GetSpecialDbType(SpecialDbTypes dbTypes) - { - if (dbTypes == SpecialDbTypes.NCHAR) - { - return "CHAR"; - } - else if (dbTypes == SpecialDbTypes.NTEXT) - return "LONGTEXT"; - - return "NVARCHAR"; - } - - public override string Format(TableDefinition table) - { - string primaryKey = string.Empty; - var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); - if (columnDefinition != null) - { - string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) - ? GetQuotedColumnName(columnDefinition.Name) - : string.Join(", ", columnDefinition.PrimaryKeyColumns - .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(GetQuotedColumnName)); - - primaryKey = string.Format(", \nPRIMARY KEY {0} ({1})", columnDefinition.IsIndexed ? "CLUSTERED" : "NONCLUSTERED", columns); - } - - var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns), primaryKey); - - return statement; - } - - public override string Format(IndexDefinition index) - { - string name = string.IsNullOrEmpty(index.Name) - ? string.Format("IX_{0}_{1}", index.TableName, index.ColumnName) - : index.Name; - - string columns = index.Columns.Any() - ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) - : GetQuotedColumnName(index.ColumnName); - - return string.Format(CreateIndex, - GetQuotedName(name), - GetQuotedTableName(index.TableName), - columns); - } - - public override string Format(ForeignKeyDefinition foreignKey) - { - return string.Format(CreateForeignKeyConstraint, - GetQuotedTableName(foreignKey.ForeignTable), - GetQuotedColumnName(foreignKey.ForeignColumns.First()), - GetQuotedTableName(foreignKey.PrimaryTable), - GetQuotedColumnName(foreignKey.PrimaryColumns.First()), - FormatCascade("DELETE", foreignKey.OnDelete), - FormatCascade("UPDATE", foreignKey.OnUpdate)); - } - - public override string FormatPrimaryKey(TableDefinition table) - { - return string.Empty; - } - - protected override string FormatConstraint(ColumnDefinition column) - { - return string.Empty; - } - - protected override string FormatIdentity(ColumnDefinition column) - { - return column.IsIdentity ? AutoIncrementDefinition : string.Empty; - } - - protected override string FormatDefaultValue(ColumnDefinition column) - { - if (column.DefaultValue == null) - return string.Empty; - - //hack - probably not needed with latest changes - if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) - column.DefaultValue = SystemMethods.CurrentDateTime; - - // see if this is for a system method - if (column.DefaultValue is SystemMethods) - { - string method = FormatSystemMethods((SystemMethods)column.DefaultValue); - if (string.IsNullOrEmpty(method)) - return string.Empty; - - return string.Format(DefaultValueFormat, method); - } - - //needs quote - return string.Format(DefaultValueFormat, string.Format("'{0}'", column.DefaultValue)); - } - - protected override string FormatPrimaryKey(ColumnDefinition column) - { - return string.Empty; - } - - protected override string FormatSystemMethods(SystemMethods systemMethod) - { - switch (systemMethod) - { - case SystemMethods.NewGuid: - return null; // NOT SUPPORTED! - //return "NEWID()"; - case SystemMethods.CurrentDateTime: - return "CURRENT_TIMESTAMP"; - //case SystemMethods.NewSequentialId: - // return "NEWSEQUENTIALID()"; - //case SystemMethods.CurrentUTCDateTime: - // return "GETUTCDATE()"; - } - - return null; - } - - public override string DeleteDefaultConstraint - { - get - { - return "ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT"; - } - } - - public override string AlterColumn { get { return "ALTER TABLE {0} MODIFY COLUMN {1}"; } } - - //CREATE TABLE {0} ({1}) ENGINE = INNODB versus CREATE TABLE {0} ({1}) ENGINE = MYISAM ? - public override string CreateTable { get { return "CREATE TABLE {0} ({1}{2})"; } } - - public override string CreateIndex { get { return "CREATE INDEX {0} ON {1} ({2})"; } } - - public override string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD FOREIGN KEY ({1}) REFERENCES {2} ({3}){4}{5}"; } } - - public override string DeleteConstraint { get { return "ALTER TABLE {0} DROP {1} {2}"; } } - - public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } - - public override string RenameColumn { get { return "ALTER TABLE {0} CHANGE {1} {2}"; } } - public override string ConvertIntegerToOrderableString { get { return "LPAD(FORMAT({0}, 0), 8, '0')"; } } - public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } - public override string ConvertDecimalToOrderableString { get { return "LPAD(FORMAT({0}, 9), 20, '0')"; } } - - public override bool? SupportsCaseInsensitiveQueries(IDatabase db) - { - bool? supportsCaseInsensitiveQueries = null; - - try - { - db.OpenSharedConnection(); - // Need 4 @ signs as it is regarded as a parameter, @@ escapes it once, @@@@ escapes it twice - var lowerCaseTableNames = db.Fetch("SELECT @@@@Global.lower_case_table_names"); - - if (lowerCaseTableNames.Any()) - supportsCaseInsensitiveQueries = lowerCaseTableNames.First() == 1; - } - catch (Exception ex) - { - _logger.Error("Error querying for lower_case support", ex); - } - finally - { - db.CloseSharedConnection(); - } - - // Could return null, which means testing failed, - // add message to check with their hosting provider - return supportsCaseInsensitiveQueries; - } - - public override string EscapeString(string val) - { - return NPocoDatabaseExtensions.EscapeAtSymbols(MySql.Data.MySqlClient.MySqlHelper.EscapeString(val)); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using NPoco; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Represents an SqlSyntaxProvider for MySql + /// + [SqlSyntaxProvider(Constants.DbProviderNames.MySql)] + public class MySqlSyntaxProvider : SqlSyntaxProviderBase + { + private readonly ILogger _logger; + + public MySqlSyntaxProvider(ILogger logger) + { + _logger = logger; + + AutoIncrementDefinition = "AUTO_INCREMENT"; + IntColumnDefinition = "int(11)"; + BoolColumnDefinition = "tinyint(1)"; + DateTimeColumnDefinition = "TIMESTAMP"; + TimeColumnDefinition = "time"; + DecimalColumnDefinition = "decimal(38,6)"; + GuidColumnDefinition = "char(36)"; + + DefaultValueFormat = "DEFAULT {0}"; + + InitColumnTypeMap(); + } + + public override IEnumerable GetTablesInSchema(IDatabase db) + { + List list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); + + var items = + db.Fetch( + "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = items.Select(x => x.TABLE_NAME).Cast().ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } + + public override IEnumerable GetColumnsInSchema(IDatabase db) + { + List list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); + + var items = + db.Fetch( + "SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = + items.Select( + item => + new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, int.Parse(item.ORDINAL_POSITION.ToString()), item.COLUMN_DEFAULT ?? "", + item.IS_NULLABLE, item.DATA_TYPE)).ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } + + public override IEnumerable> GetConstraintsPerTable(IDatabase db) + { + List> list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); + + //Does not include indexes and constraints are named differently + var items = + db.Fetch( + "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } + + public override IEnumerable> GetConstraintsPerColumn(IDatabase db) + { + List> list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); + + //Does not include indexes and constraints are named differently + var items = + db.Fetch( + "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = + items.Select( + item => + new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)) + .ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } + + public override IEnumerable> GetDefinedIndexes(IDatabase db) + { + List> list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); + + var indexes = + db.Fetch(@"SELECT DISTINCT + TABLE_NAME, INDEX_NAME, COLUMN_NAME, CASE NON_UNIQUE WHEN 1 THEN 0 ELSE 1 END AS `UNIQUE` +FROM INFORMATION_SCHEMA.STATISTICS +WHERE TABLE_SCHEMA = @TableSchema +AND INDEX_NAME <> COLUMN_NAME AND INDEX_NAME <> 'PRIMARY' +ORDER BY TABLE_NAME, INDEX_NAME", + new { TableSchema = db.Connection.Database }); + list = + indexes.Select( + item => + new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE == 1)) + .ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } + + public override bool DoesTableExist(IDatabase db, string tableName) + { + long result; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); + + result = + db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " + + "WHERE TABLE_NAME = @TableName AND " + + "TABLE_SCHEMA = @TableSchema", + new { TableName = tableName, TableSchema = db.Connection.Database }); + + } + finally + { + db.CloseSharedConnection(); + } + + return result > 0; + } + + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(sql.SqlContext, string.Concat(sql.SQL, " LIMIT ", top), sql.Arguments); + } + + public override bool SupportsClustered() + { + return true; + } + + public override bool SupportsIdentityInsert() + { + return false; + } + + /// + /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) + /// + /// + /// + /// + /// + /// MySQL has a DateTime standard that is unambiguous and works on all servers: + /// YYYYMMDDHHMMSS + /// + public override string FormatDateTime(DateTime date, bool includeTime = true) + { + return includeTime ? date.ToString("yyyyMMddHHmmss") : date.ToString("yyyyMMdd"); + } + + public override string GetQuotedTableName(string tableName) + { + return string.Format("`{0}`", tableName); + } + + public override string GetQuotedColumnName(string columnName) + { + return string.Format("`{0}`", columnName); + } + + public override string GetQuotedName(string name) + { + return string.Format("`{0}`", name); + } + + public override string GetSpecialDbType(SpecialDbTypes dbTypes) + { + if (dbTypes == SpecialDbTypes.NCHAR) + { + return "CHAR"; + } + else if (dbTypes == SpecialDbTypes.NTEXT) + return "LONGTEXT"; + + return "NVARCHAR"; + } + + public override string Format(TableDefinition table) + { + string primaryKey = string.Empty; + var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); + if (columnDefinition != null) + { + string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) + ? GetQuotedColumnName(columnDefinition.Name) + : string.Join(", ", columnDefinition.PrimaryKeyColumns + .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(GetQuotedColumnName)); + + primaryKey = string.Format(", \nPRIMARY KEY {0} ({1})", columnDefinition.IsIndexed ? "CLUSTERED" : "NONCLUSTERED", columns); + } + + var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns), primaryKey); + + return statement; + } + + public override string Format(IndexDefinition index) + { + string name = string.IsNullOrEmpty(index.Name) + ? string.Format("IX_{0}_{1}", index.TableName, index.ColumnName) + : index.Name; + + string columns = index.Columns.Any() + ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) + : GetQuotedColumnName(index.ColumnName); + + return string.Format(CreateIndex, + GetQuotedName(name), + GetQuotedTableName(index.TableName), + columns); + } + + public override string Format(ForeignKeyDefinition foreignKey) + { + return string.Format(CreateForeignKeyConstraint, + GetQuotedTableName(foreignKey.ForeignTable), + GetQuotedColumnName(foreignKey.ForeignColumns.First()), + GetQuotedTableName(foreignKey.PrimaryTable), + GetQuotedColumnName(foreignKey.PrimaryColumns.First()), + FormatCascade("DELETE", foreignKey.OnDelete), + FormatCascade("UPDATE", foreignKey.OnUpdate)); + } + + public override string FormatPrimaryKey(TableDefinition table) + { + return string.Empty; + } + + protected override string FormatConstraint(ColumnDefinition column) + { + return string.Empty; + } + + protected override string FormatIdentity(ColumnDefinition column) + { + return column.IsIdentity ? AutoIncrementDefinition : string.Empty; + } + + protected override string FormatDefaultValue(ColumnDefinition column) + { + if (column.DefaultValue == null) + return string.Empty; + + //hack - probably not needed with latest changes + if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) + column.DefaultValue = SystemMethods.CurrentDateTime; + + // see if this is for a system method + if (column.DefaultValue is SystemMethods) + { + string method = FormatSystemMethods((SystemMethods)column.DefaultValue); + if (string.IsNullOrEmpty(method)) + return string.Empty; + + return string.Format(DefaultValueFormat, method); + } + + //needs quote + return string.Format(DefaultValueFormat, string.Format("'{0}'", column.DefaultValue)); + } + + protected override string FormatPrimaryKey(ColumnDefinition column) + { + return string.Empty; + } + + protected override string FormatSystemMethods(SystemMethods systemMethod) + { + switch (systemMethod) + { + case SystemMethods.NewGuid: + return null; // NOT SUPPORTED! + //return "NEWID()"; + case SystemMethods.CurrentDateTime: + return "CURRENT_TIMESTAMP"; + //case SystemMethods.NewSequentialId: + // return "NEWSEQUENTIALID()"; + //case SystemMethods.CurrentUTCDateTime: + // return "GETUTCDATE()"; + } + + return null; + } + + public override string DeleteDefaultConstraint + { + get + { + return "ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT"; + } + } + + public override string AlterColumn { get { return "ALTER TABLE {0} MODIFY COLUMN {1}"; } } + + //CREATE TABLE {0} ({1}) ENGINE = INNODB versus CREATE TABLE {0} ({1}) ENGINE = MYISAM ? + public override string CreateTable { get { return "CREATE TABLE {0} ({1}{2})"; } } + + public override string CreateIndex { get { return "CREATE INDEX {0} ON {1} ({2})"; } } + + public override string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD FOREIGN KEY ({1}) REFERENCES {2} ({3}){4}{5}"; } } + + public override string DeleteConstraint { get { return "ALTER TABLE {0} DROP {1} {2}"; } } + + public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } + + public override string RenameColumn { get { return "ALTER TABLE {0} CHANGE {1} {2}"; } } + public override string ConvertIntegerToOrderableString { get { return "LPAD(FORMAT({0}, 0), 8, '0')"; } } + public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } + public override string ConvertDecimalToOrderableString { get { return "LPAD(FORMAT({0}, 9), 20, '0')"; } } + + public override bool? SupportsCaseInsensitiveQueries(IDatabase db) + { + bool? supportsCaseInsensitiveQueries = null; + + try + { + db.OpenSharedConnection(); + // Need 4 @ signs as it is regarded as a parameter, @@ escapes it once, @@@@ escapes it twice + var lowerCaseTableNames = db.Fetch("SELECT @@@@Global.lower_case_table_names"); + + if (lowerCaseTableNames.Any()) + supportsCaseInsensitiveQueries = lowerCaseTableNames.First() == 1; + } + catch (Exception ex) + { + _logger.Error("Error querying for lower_case support", ex); + } + finally + { + db.CloseSharedConnection(); + } + + // Could return null, which means testing failed, + // add message to check with their hosting provider + return supportsCaseInsensitiveQueries; + } + + public override string EscapeString(string val) + { + return NPocoDatabaseExtensions.EscapeAtSymbols(MySql.Data.MySqlClient.MySqlHelper.EscapeString(val)); + } + } +} diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 8759ae2554..5f58c9c53e 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -1,217 +1,217 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NPoco; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; - -namespace Umbraco.Core.Persistence.SqlSyntax -{ - /// - /// Represents an SqlSyntaxProvider for Sql Ce - /// - [SqlSyntaxProvider(Constants.DbProviderNames.SqlCe)] - public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase - { - public override Sql SelectTop(Sql sql, int top) - { - return new Sql(sql.SqlContext, sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); - } - - public override bool SupportsClustered() - { - return false; - } - - /// - /// SqlCe doesn't support the Truncate Table syntax, so we just have to do a DELETE FROM which is slower but we have no choice. - /// - public override string TruncateTable - { - get { return "DELETE FROM {0}"; } - } - - public override string GetIndexType(IndexTypes indexTypes) - { - string indexType; - //NOTE Sql Ce doesn't support clustered indexes - if (indexTypes == IndexTypes.Clustered) - { - indexType = "NONCLUSTERED"; - } - else - { - indexType = indexTypes == IndexTypes.NonClustered - ? "NONCLUSTERED" - : "UNIQUE NONCLUSTERED"; - } - return indexType; - } - - [Obsolete("Use the overload with the parameter index instead")] - public override string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnEqualComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for = comparison with NText columns but allows this syntax - return string.Format("{0} LIKE '{1}'", column, value); - default: - throw new ArgumentOutOfRangeException("columnType"); - } - } - - public override string GetConcat(params string[] args) - { - return "(" + string.Join("+", args) + ")"; - } - - public override string FormatColumnRename(string tableName, string oldName, string newName) - { - //NOTE Sql CE doesn't support renaming a column, so a new column needs to be created, then copy data and finally remove old column - //This assumes that the new column has been created, and that the old column will be deleted after this statement has run. - //http://stackoverflow.com/questions/3967353/microsoft-sql-compact-edition-rename-column - - return string.Format("UPDATE {0} SET {1} = {2}", tableName, newName, oldName); - } - - public override string FormatTableRename(string oldName, string newName) - { - return string.Format(RenameTable, oldName, newName); - } - - public override string FormatPrimaryKey(TableDefinition table) - { - var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); - if (columnDefinition == null) - return string.Empty; - - string constraintName = string.IsNullOrEmpty(columnDefinition.PrimaryKeyName) - ? string.Format("PK_{0}", table.Name) - : columnDefinition.PrimaryKeyName; - - string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) - ? GetQuotedColumnName(columnDefinition.Name) - : string.Join(", ", columnDefinition.PrimaryKeyColumns - .Split(new[]{',', ' '}, StringSplitOptions.RemoveEmptyEntries) - .Select(GetQuotedColumnName)); - - return string.Format(CreateConstraint, - GetQuotedTableName(table.Name), - GetQuotedName(constraintName), - "PRIMARY KEY", - columns); - } - - public override IEnumerable GetTablesInSchema(IDatabase db) - { - var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"); - return items.Select(x => x.TABLE_NAME).Cast().ToList(); - } - - public override IEnumerable GetColumnsInSchema(IDatabase db) - { - var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS"); - return - items.Select( - item => - new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, item.ORDINAL_POSITION, item.COLUMN_DEFAULT, - item.IS_NULLABLE, item.DATA_TYPE)).ToList(); - } - - public override IEnumerable> GetConstraintsPerTable(IDatabase db) - { - var items = db.Fetch("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS"); - var indexItems = db.Fetch("SELECT TABLE_NAME, INDEX_NAME FROM INFORMATION_SCHEMA.INDEXES"); - return - items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)) - .Union( - indexItems.Select( - indexItem => new Tuple(indexItem.TABLE_NAME, indexItem.INDEX_NAME))) - .ToList(); - } - - public override IEnumerable> GetConstraintsPerColumn(IDatabase db) - { - var items = - db.Fetch( - "SELECT CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE"); - var indexItems = db.Fetch("SELECT INDEX_NAME, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.INDEXES"); - return - items.Select( - item => new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)) - .Union( - indexItems.Select( - indexItem => - new Tuple(indexItem.TABLE_NAME, indexItem.COLUMN_NAME, - indexItem.INDEX_NAME))).ToList(); - } - - public override IEnumerable> GetDefinedIndexes(IDatabase db) - { - var items = - db.Fetch( - @"SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME, [UNIQUE] FROM INFORMATION_SCHEMA.INDEXES -WHERE INDEX_NAME NOT LIKE 'PK_%' -ORDER BY TABLE_NAME, INDEX_NAME"); - return - items.Select( - item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE)); - } - - public override bool DoesTableExist(IDatabase db, string tableName) - { - var result = - db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @TableName", - new { TableName = tableName }); - - return result > 0; - } - - protected override string FormatIdentity(ColumnDefinition column) - { - return column.IsIdentity ? GetIdentityString(column) : string.Empty; - } - - private static string GetIdentityString(ColumnDefinition column) - { - if (column.Seeding != default(int)) - return string.Format("IDENTITY({0},1)", column.Seeding); - - return "IDENTITY(1,1)"; - } - - protected override string FormatSystemMethods(SystemMethods systemMethod) - { - switch (systemMethod) - { - case SystemMethods.NewGuid: - return "NEWID()"; - case SystemMethods.CurrentDateTime: - return "GETDATE()"; - //case SystemMethods.NewSequentialId: - // return "NEWSEQUENTIALID()"; - //case SystemMethods.CurrentUTCDateTime: - // return "GETUTCDATE()"; - } - - return null; - } - - public override string DeleteDefaultConstraint - { - get - { - return "ALTER TABLE [{0}] ALTER COLUMN [{1}] DROP DEFAULT"; - } - } - - - - public override string DropIndex { get { return "DROP INDEX {1}.{0}"; } } - - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Represents an SqlSyntaxProvider for Sql Ce + /// + [SqlSyntaxProvider(Constants.DbProviderNames.SqlCe)] + public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase + { + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(sql.SqlContext, sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); + } + + public override bool SupportsClustered() + { + return false; + } + + /// + /// SqlCe doesn't support the Truncate Table syntax, so we just have to do a DELETE FROM which is slower but we have no choice. + /// + public override string TruncateTable + { + get { return "DELETE FROM {0}"; } + } + + public override string GetIndexType(IndexTypes indexTypes) + { + string indexType; + //NOTE Sql Ce doesn't support clustered indexes + if (indexTypes == IndexTypes.Clustered) + { + indexType = "NONCLUSTERED"; + } + else + { + indexType = indexTypes == IndexTypes.NonClustered + ? "NONCLUSTERED" + : "UNIQUE NONCLUSTERED"; + } + return indexType; + } + + [Obsolete("Use the overload with the parameter index instead")] + public override string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnEqualComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for = comparison with NText columns but allows this syntax + return string.Format("{0} LIKE '{1}'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + public override string GetConcat(params string[] args) + { + return "(" + string.Join("+", args) + ")"; + } + + public override string FormatColumnRename(string tableName, string oldName, string newName) + { + //NOTE Sql CE doesn't support renaming a column, so a new column needs to be created, then copy data and finally remove old column + //This assumes that the new column has been created, and that the old column will be deleted after this statement has run. + //http://stackoverflow.com/questions/3967353/microsoft-sql-compact-edition-rename-column + + return string.Format("UPDATE {0} SET {1} = {2}", tableName, newName, oldName); + } + + public override string FormatTableRename(string oldName, string newName) + { + return string.Format(RenameTable, oldName, newName); + } + + public override string FormatPrimaryKey(TableDefinition table) + { + var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); + if (columnDefinition == null) + return string.Empty; + + string constraintName = string.IsNullOrEmpty(columnDefinition.PrimaryKeyName) + ? string.Format("PK_{0}", table.Name) + : columnDefinition.PrimaryKeyName; + + string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) + ? GetQuotedColumnName(columnDefinition.Name) + : string.Join(", ", columnDefinition.PrimaryKeyColumns + .Split(new[]{',', ' '}, StringSplitOptions.RemoveEmptyEntries) + .Select(GetQuotedColumnName)); + + return string.Format(CreateConstraint, + GetQuotedTableName(table.Name), + GetQuotedName(constraintName), + "PRIMARY KEY", + columns); + } + + public override IEnumerable GetTablesInSchema(IDatabase db) + { + var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"); + return items.Select(x => x.TABLE_NAME).Cast().ToList(); + } + + public override IEnumerable GetColumnsInSchema(IDatabase db) + { + var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS"); + return + items.Select( + item => + new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, item.ORDINAL_POSITION, item.COLUMN_DEFAULT, + item.IS_NULLABLE, item.DATA_TYPE)).ToList(); + } + + public override IEnumerable> GetConstraintsPerTable(IDatabase db) + { + var items = db.Fetch("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS"); + var indexItems = db.Fetch("SELECT TABLE_NAME, INDEX_NAME FROM INFORMATION_SCHEMA.INDEXES"); + return + items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)) + .Union( + indexItems.Select( + indexItem => new Tuple(indexItem.TABLE_NAME, indexItem.INDEX_NAME))) + .ToList(); + } + + public override IEnumerable> GetConstraintsPerColumn(IDatabase db) + { + var items = + db.Fetch( + "SELECT CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE"); + var indexItems = db.Fetch("SELECT INDEX_NAME, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.INDEXES"); + return + items.Select( + item => new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)) + .Union( + indexItems.Select( + indexItem => + new Tuple(indexItem.TABLE_NAME, indexItem.COLUMN_NAME, + indexItem.INDEX_NAME))).ToList(); + } + + public override IEnumerable> GetDefinedIndexes(IDatabase db) + { + var items = + db.Fetch( + @"SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME, [UNIQUE] FROM INFORMATION_SCHEMA.INDEXES +WHERE INDEX_NAME NOT LIKE 'PK_%' +ORDER BY TABLE_NAME, INDEX_NAME"); + return + items.Select( + item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE)); + } + + public override bool DoesTableExist(IDatabase db, string tableName) + { + var result = + db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @TableName", + new { TableName = tableName }); + + return result > 0; + } + + protected override string FormatIdentity(ColumnDefinition column) + { + return column.IsIdentity ? GetIdentityString(column) : string.Empty; + } + + private static string GetIdentityString(ColumnDefinition column) + { + if (column.Seeding != default(int)) + return string.Format("IDENTITY({0},1)", column.Seeding); + + return "IDENTITY(1,1)"; + } + + protected override string FormatSystemMethods(SystemMethods systemMethod) + { + switch (systemMethod) + { + case SystemMethods.NewGuid: + return "NEWID()"; + case SystemMethods.CurrentDateTime: + return "GETDATE()"; + //case SystemMethods.NewSequentialId: + // return "NEWSEQUENTIALID()"; + //case SystemMethods.CurrentUTCDateTime: + // return "GETUTCDATE()"; + } + + return null; + } + + public override string DeleteDefaultConstraint + { + get + { + return "ALTER TABLE [{0}] ALTER COLUMN [{1}] DROP DEFAULT"; + } + } + + + + public override string DropIndex { get { return "DROP INDEX {1}.{0}"; } } + + } +} diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 0d22ee5171..d7dadd9f08 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -1,255 +1,255 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NPoco; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.Persistence.SqlSyntax -{ - /// - /// Represents an SqlSyntaxProvider for Sql Server. - /// - [SqlSyntaxProvider(Constants.DbProviderNames.SqlServer)] - public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase - { - // IUmbracoDatabaseFactory to be lazily injected - public SqlServerSyntaxProvider(Lazy lazyScopeProvider) - { - _serverVersion = new Lazy(() => - { - var scopeProvider = lazyScopeProvider.Value; - if (scopeProvider == null) - throw new InvalidOperationException("Failed to determine Sql Server version (no scope provider)."); - using (var scope = scopeProvider.CreateScope()) - { - var version = DetermineVersion(scope.Database); - scope.Complete(); - return version; - } - }); - } - - private readonly Lazy _serverVersion; - - internal ServerVersionInfo ServerVersion => _serverVersion.Value; - - internal enum VersionName - { - Invalid = -1, - Unknown = 0, - V7 = 1, - V2000 = 2, - V2005 = 3, - V2008 = 4, - V2012 = 5, - V2014 = 6, - V2016 = 7, - Other = 99 - } - - internal enum EngineEdition - { - Unknown = 0, - Desktop = 1, - Standard = 2, - Enterprise = 3, - Express = 4, - Azure = 5 - } - - internal class ServerVersionInfo - { - public string Edition { get; set; } - public string InstanceName { get; set; } - public string ProductVersion { get; set; } - public VersionName ProductVersionName { get; private set; } - public EngineEdition EngineEdition { get; set; } - public bool IsAzure => EngineEdition == EngineEdition.Azure; - public string MachineName { get; set; } - public string ProductLevel { get; set; } - - public void Initialize() - { - var firstPart = string.IsNullOrWhiteSpace(ProductVersion) ? "??" : ProductVersion.Split('.')[0]; - switch (firstPart) - { - case "??": - ProductVersionName = VersionName.Invalid; - break; - case "13": - ProductVersionName = VersionName.V2016; - break; - case "12": - ProductVersionName = VersionName.V2014; - break; - case "11": - ProductVersionName = VersionName.V2012; - break; - case "10": - ProductVersionName = VersionName.V2008; - break; - case "9": - ProductVersionName = VersionName.V2005; - break; - case "8": - ProductVersionName = VersionName.V2000; - break; - case "7": - ProductVersionName = VersionName.V7; - break; - default: - ProductVersionName = VersionName.Other; - break; - } - } - } - - private static ServerVersionInfo DetermineVersion(IUmbracoDatabase database) - { - // Edition: "Express Edition", "Windows Azure SQL Database..." - // EngineEdition: 1/Desktop 2/Standard 3/Enterprise 4/Express 5/Azure - // ProductLevel: RTM, SPx, CTP... - - const string sql = @"select - SERVERPROPERTY('Edition') Edition, - SERVERPROPERTY('EditionID') EditionId, - SERVERPROPERTY('InstanceName') InstanceName, - SERVERPROPERTY('ProductVersion') ProductVersion, - SERVERPROPERTY('BuildClrVersion') BuildClrVersion, - SERVERPROPERTY('EngineEdition') EngineEdition, - SERVERPROPERTY('IsClustered') IsClustered, - SERVERPROPERTY('MachineName') MachineName, - SERVERPROPERTY('ResourceLastUpdateDateTime') ResourceLastUpdateDateTime, - SERVERPROPERTY('ProductLevel') ProductLevel;"; - - try - { - var version = database.Fetch(sql).First(); - version.Initialize(); - return version; - } - catch (Exception e) - { - // can't ignore, really - throw new Exception("Failed to determine Sql Server version (see inner exception).", e); - } - } - - /// - /// SQL Server stores default values assigned to columns as constraints, it also stores them with named values, this is the only - /// server type that does this, therefore this method doesn't exist on any other syntax provider - /// - /// - public IEnumerable> GetDefaultConstraintsPerColumn(IDatabase db) - { - var items = db.Fetch("SELECT TableName = t.Name,ColumnName = c.Name,dc.Name,dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id"); - return items.Select(x => new Tuple(x.TableName, x.ColumnName, x.Name, x.Definition)); - } - - public override IEnumerable GetTablesInSchema(IDatabase db) - { - var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"); - return items.Select(x => x.TABLE_NAME).Cast().ToList(); - } - - public override IEnumerable GetColumnsInSchema(IDatabase db) - { - var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS"); - return - items.Select( - item => - new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, item.ORDINAL_POSITION, item.COLUMN_DEFAULT, - item.IS_NULLABLE, item.DATA_TYPE)).ToList(); - } - - public override IEnumerable> GetConstraintsPerTable(IDatabase db) - { - var items = - db.Fetch( - "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE"); - return items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); - } - - public override IEnumerable> GetConstraintsPerColumn(IDatabase db) - { - var items = - db.Fetch( - "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE"); - return items.Select(item => new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)).ToList(); - } - - public override IEnumerable> GetDefinedIndexes(IDatabase db) - { - var items = - db.Fetch( - @"select T.name as TABLE_NAME, I.name as INDEX_NAME, AC.Name as COLUMN_NAME, -CASE WHEN I.is_unique_constraint = 1 OR I.is_unique = 1 THEN 1 ELSE 0 END AS [UNIQUE] -from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id] - inner join sys.index_columns as IC on IC.[object_id] = I.[object_id] and IC.[index_id] = I.[index_id] - inner join sys.all_columns as AC on IC.[object_id] = AC.[object_id] and IC.[column_id] = AC.[column_id] -WHERE I.name NOT LIKE 'PK_%' -order by T.name, I.name"); - return items.Select(item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, - item.UNIQUE == 1)).ToList(); - - } - - public override bool DoesTableExist(IDatabase db, string tableName) - { - var result = - db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @TableName", - new { TableName = tableName }); - - return result > 0; - } - - public override string FormatColumnRename(string tableName, string oldName, string newName) - { - return string.Format(RenameColumn, tableName, oldName, newName); - } - - public override string FormatTableRename(string oldName, string newName) - { - return string.Format(RenameTable, oldName, newName); - } - - protected override string FormatIdentity(ColumnDefinition column) - { - return column.IsIdentity ? GetIdentityString(column) : string.Empty; - } - - public override Sql SelectTop(Sql sql, int top) - { - return new Sql(sql.SqlContext, sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); - } - - private static string GetIdentityString(ColumnDefinition column) - { - return "IDENTITY(1,1)"; - } - - protected override string FormatSystemMethods(SystemMethods systemMethod) - { - switch (systemMethod) - { - case SystemMethods.NewGuid: - return "NEWID()"; - case SystemMethods.CurrentDateTime: - return "GETDATE()"; - //case SystemMethods.NewSequentialId: - // return "NEWSEQUENTIALID()"; - //case SystemMethods.CurrentUTCDateTime: - // return "GETUTCDATE()"; - } - - return null; - } - - public override string DeleteDefaultConstraint => "ALTER TABLE [{0}] DROP CONSTRAINT [DF_{0}_{1}]"; - - public override string DropIndex => "DROP INDEX {0} ON {1}"; - - public override string RenameColumn => "sp_rename '{0}.{1}', '{2}', 'COLUMN'"; - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using NPoco; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Represents an SqlSyntaxProvider for Sql Server. + /// + [SqlSyntaxProvider(Constants.DbProviderNames.SqlServer)] + public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase + { + // IUmbracoDatabaseFactory to be lazily injected + public SqlServerSyntaxProvider(Lazy lazyScopeProvider) + { + _serverVersion = new Lazy(() => + { + var scopeProvider = lazyScopeProvider.Value; + if (scopeProvider == null) + throw new InvalidOperationException("Failed to determine Sql Server version (no scope provider)."); + using (var scope = scopeProvider.CreateScope()) + { + var version = DetermineVersion(scope.Database); + scope.Complete(); + return version; + } + }); + } + + private readonly Lazy _serverVersion; + + internal ServerVersionInfo ServerVersion => _serverVersion.Value; + + internal enum VersionName + { + Invalid = -1, + Unknown = 0, + V7 = 1, + V2000 = 2, + V2005 = 3, + V2008 = 4, + V2012 = 5, + V2014 = 6, + V2016 = 7, + Other = 99 + } + + internal enum EngineEdition + { + Unknown = 0, + Desktop = 1, + Standard = 2, + Enterprise = 3, + Express = 4, + Azure = 5 + } + + internal class ServerVersionInfo + { + public string Edition { get; set; } + public string InstanceName { get; set; } + public string ProductVersion { get; set; } + public VersionName ProductVersionName { get; private set; } + public EngineEdition EngineEdition { get; set; } + public bool IsAzure => EngineEdition == EngineEdition.Azure; + public string MachineName { get; set; } + public string ProductLevel { get; set; } + + public void Initialize() + { + var firstPart = string.IsNullOrWhiteSpace(ProductVersion) ? "??" : ProductVersion.Split('.')[0]; + switch (firstPart) + { + case "??": + ProductVersionName = VersionName.Invalid; + break; + case "13": + ProductVersionName = VersionName.V2016; + break; + case "12": + ProductVersionName = VersionName.V2014; + break; + case "11": + ProductVersionName = VersionName.V2012; + break; + case "10": + ProductVersionName = VersionName.V2008; + break; + case "9": + ProductVersionName = VersionName.V2005; + break; + case "8": + ProductVersionName = VersionName.V2000; + break; + case "7": + ProductVersionName = VersionName.V7; + break; + default: + ProductVersionName = VersionName.Other; + break; + } + } + } + + private static ServerVersionInfo DetermineVersion(IUmbracoDatabase database) + { + // Edition: "Express Edition", "Windows Azure SQL Database..." + // EngineEdition: 1/Desktop 2/Standard 3/Enterprise 4/Express 5/Azure + // ProductLevel: RTM, SPx, CTP... + + const string sql = @"select + SERVERPROPERTY('Edition') Edition, + SERVERPROPERTY('EditionID') EditionId, + SERVERPROPERTY('InstanceName') InstanceName, + SERVERPROPERTY('ProductVersion') ProductVersion, + SERVERPROPERTY('BuildClrVersion') BuildClrVersion, + SERVERPROPERTY('EngineEdition') EngineEdition, + SERVERPROPERTY('IsClustered') IsClustered, + SERVERPROPERTY('MachineName') MachineName, + SERVERPROPERTY('ResourceLastUpdateDateTime') ResourceLastUpdateDateTime, + SERVERPROPERTY('ProductLevel') ProductLevel;"; + + try + { + var version = database.Fetch(sql).First(); + version.Initialize(); + return version; + } + catch (Exception e) + { + // can't ignore, really + throw new Exception("Failed to determine Sql Server version (see inner exception).", e); + } + } + + /// + /// SQL Server stores default values assigned to columns as constraints, it also stores them with named values, this is the only + /// server type that does this, therefore this method doesn't exist on any other syntax provider + /// + /// + public IEnumerable> GetDefaultConstraintsPerColumn(IDatabase db) + { + var items = db.Fetch("SELECT TableName = t.Name,ColumnName = c.Name,dc.Name,dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id"); + return items.Select(x => new Tuple(x.TableName, x.ColumnName, x.Name, x.Definition)); + } + + public override IEnumerable GetTablesInSchema(IDatabase db) + { + var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"); + return items.Select(x => x.TABLE_NAME).Cast().ToList(); + } + + public override IEnumerable GetColumnsInSchema(IDatabase db) + { + var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS"); + return + items.Select( + item => + new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, item.ORDINAL_POSITION, item.COLUMN_DEFAULT, + item.IS_NULLABLE, item.DATA_TYPE)).ToList(); + } + + public override IEnumerable> GetConstraintsPerTable(IDatabase db) + { + var items = + db.Fetch( + "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE"); + return items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); + } + + public override IEnumerable> GetConstraintsPerColumn(IDatabase db) + { + var items = + db.Fetch( + "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE"); + return items.Select(item => new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)).ToList(); + } + + public override IEnumerable> GetDefinedIndexes(IDatabase db) + { + var items = + db.Fetch( + @"select T.name as TABLE_NAME, I.name as INDEX_NAME, AC.Name as COLUMN_NAME, +CASE WHEN I.is_unique_constraint = 1 OR I.is_unique = 1 THEN 1 ELSE 0 END AS [UNIQUE] +from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id] + inner join sys.index_columns as IC on IC.[object_id] = I.[object_id] and IC.[index_id] = I.[index_id] + inner join sys.all_columns as AC on IC.[object_id] = AC.[object_id] and IC.[column_id] = AC.[column_id] +WHERE I.name NOT LIKE 'PK_%' +order by T.name, I.name"); + return items.Select(item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, + item.UNIQUE == 1)).ToList(); + + } + + public override bool DoesTableExist(IDatabase db, string tableName) + { + var result = + db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @TableName", + new { TableName = tableName }); + + return result > 0; + } + + public override string FormatColumnRename(string tableName, string oldName, string newName) + { + return string.Format(RenameColumn, tableName, oldName, newName); + } + + public override string FormatTableRename(string oldName, string newName) + { + return string.Format(RenameTable, oldName, newName); + } + + protected override string FormatIdentity(ColumnDefinition column) + { + return column.IsIdentity ? GetIdentityString(column) : string.Empty; + } + + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(sql.SqlContext, sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); + } + + private static string GetIdentityString(ColumnDefinition column) + { + return "IDENTITY(1,1)"; + } + + protected override string FormatSystemMethods(SystemMethods systemMethod) + { + switch (systemMethod) + { + case SystemMethods.NewGuid: + return "NEWID()"; + case SystemMethods.CurrentDateTime: + return "GETDATE()"; + //case SystemMethods.NewSequentialId: + // return "NEWSEQUENTIALID()"; + //case SystemMethods.CurrentUTCDateTime: + // return "GETUTCDATE()"; + } + + return null; + } + + public override string DeleteDefaultConstraint => "ALTER TABLE [{0}] DROP CONSTRAINT [DF_{0}_{1}]"; + + public override string DropIndex => "DROP INDEX {0} ON {1}"; + + public override string RenameColumn => "sp_rename '{0}.{1}', '{2}', 'COLUMN'"; + } +} diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderAttribute.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderAttribute.cs index 79e564a1f4..191ee86bac 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderAttribute.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderAttribute.cs @@ -1,21 +1,21 @@ -using System; - -namespace Umbraco.Core.Persistence.SqlSyntax -{ - /// - /// Attribute for implementations of an ISqlSyntaxProvider - /// - [AttributeUsage(AttributeTargets.Class)] - public class SqlSyntaxProviderAttribute : Attribute - { - public SqlSyntaxProviderAttribute(string providerName) - { - ProviderName = providerName; - } - - /// - /// Gets or sets the ProviderName that corresponds to the sql syntax in a provider. - /// - public string ProviderName { get; set; } - } -} +using System; + +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Attribute for implementations of an ISqlSyntaxProvider + /// + [AttributeUsage(AttributeTargets.Class)] + public class SqlSyntaxProviderAttribute : Attribute + { + public SqlSyntaxProviderAttribute(string providerName) + { + ProviderName = providerName; + } + + /// + /// Gets or sets the ProviderName that corresponds to the sql syntax in a provider. + /// + public string ProviderName { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 442c2ce2b4..c298955045 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -1,590 +1,590 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Globalization; -using System.Linq; -using System.Text; -using NPoco; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; - -namespace Umbraco.Core.Persistence.SqlSyntax -{ - /// - /// Represents the Base Sql Syntax provider implementation. - /// - /// - /// All Sql Syntax provider implementations should derive from this abstract class. - /// - /// - public abstract class SqlSyntaxProviderBase : ISqlSyntaxProvider - where TSyntax : ISqlSyntaxProvider - { - protected SqlSyntaxProviderBase() - { - ClauseOrder = new List> - { - FormatString, - FormatType, - FormatNullable, - FormatConstraint, - FormatDefaultValue, - FormatPrimaryKey, - FormatIdentity - }; - - //defaults for all providers - StringLengthColumnDefinitionFormat = StringLengthUnicodeColumnDefinitionFormat; - StringColumnDefinition = string.Format(StringLengthColumnDefinitionFormat, DefaultStringLength); - DecimalColumnDefinition = string.Format(DecimalColumnDefinitionFormat, DefaultDecimalPrecision, DefaultDecimalScale); - - InitColumnTypeMap(); - } - - public string GetWildcardPlaceholder() - { - return "%"; - } - - public string StringLengthNonUnicodeColumnDefinitionFormat = "VARCHAR({0})"; - public string StringLengthUnicodeColumnDefinitionFormat = "NVARCHAR({0})"; - public string DecimalColumnDefinitionFormat = "DECIMAL({0},{1})"; - - public string DefaultValueFormat = "DEFAULT ({0})"; - public int DefaultStringLength = 255; - public int DefaultDecimalPrecision = 20; - public int DefaultDecimalScale = 9; - - //Set by Constructor - public string StringColumnDefinition; - public string StringLengthColumnDefinitionFormat; - - public string AutoIncrementDefinition = "AUTOINCREMENT"; - public string IntColumnDefinition = "INTEGER"; - public string LongColumnDefinition = "BIGINT"; - public string GuidColumnDefinition = "GUID"; - public string BoolColumnDefinition = "BOOL"; - public string RealColumnDefinition = "DOUBLE"; - public string DecimalColumnDefinition; - public string BlobColumnDefinition = "BLOB"; - public string DateTimeColumnDefinition = "DATETIME"; - public string TimeColumnDefinition = "DATETIME"; - - protected IList> ClauseOrder { get; } - - protected DbTypes DbTypeMap = new DbTypes(); - protected void InitColumnTypeMap() - { - DbTypeMap.Set(DbType.String, StringColumnDefinition); - DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); - DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); - DbTypeMap.Set(DbType.String, StringColumnDefinition); - DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); - DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); - DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); - DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); - DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); - DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - - DbTypeMap.Set(DbType.Byte, IntColumnDefinition); - DbTypeMap.Set(DbType.Byte, IntColumnDefinition); - DbTypeMap.Set(DbType.SByte, IntColumnDefinition); - DbTypeMap.Set(DbType.SByte, IntColumnDefinition); - DbTypeMap.Set(DbType.Int16, IntColumnDefinition); - DbTypeMap.Set(DbType.Int16, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); - DbTypeMap.Set(DbType.Int32, IntColumnDefinition); - DbTypeMap.Set(DbType.Int32, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); - - DbTypeMap.Set(DbType.Int64, LongColumnDefinition); - DbTypeMap.Set(DbType.Int64, LongColumnDefinition); - DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); - DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); - - DbTypeMap.Set(DbType.Single, RealColumnDefinition); - DbTypeMap.Set(DbType.Single, RealColumnDefinition); - DbTypeMap.Set(DbType.Double, RealColumnDefinition); - DbTypeMap.Set(DbType.Double, RealColumnDefinition); - - DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); - DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); - - DbTypeMap.Set(DbType.Binary, BlobColumnDefinition); - } - - public virtual string EscapeString(string val) - { - return NPocoDatabaseExtensions.EscapeAtSymbols(val.Replace("'", "''")); - } - - public virtual string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return $"upper({column}) = upper(@{paramIndex})"; - } - - public virtual string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return $"upper({column}) LIKE upper(@{paramIndex})"; - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return $"upper({column}) = '{value.ToUpper()}'"; - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return $"upper({column}) LIKE '{value.ToUpper()}%'"; - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return $"upper({column}) LIKE '%{value.ToUpper()}'"; - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return $"upper({column}) LIKE '%{value.ToUpper()}%'"; - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return $"upper({column}) LIKE '{value.ToUpper()}'"; - } - - public virtual string GetConcat(params string[] args) - { - return "concat(" + string.Join(",", args) + ")"; - } - - public virtual string GetQuotedTableName(string tableName) - { - return $"\"{tableName}\""; - } - - public virtual string GetQuotedColumnName(string columnName) - { - return $"\"{columnName}\""; - } - - public virtual string GetQuotedName(string name) - { - return $"\"{name}\""; - } - - public virtual string GetQuotedValue(string value) - { - return $"'{value}'"; - } - - public virtual string GetIndexType(IndexTypes indexTypes) - { - string indexType; - - if (indexTypes == IndexTypes.Clustered) - { - indexType = "CLUSTERED"; - } - else - { - indexType = indexTypes == IndexTypes.NonClustered - ? "NONCLUSTERED" - : "UNIQUE NONCLUSTERED"; - } - return indexType; - } - - public virtual string GetSpecialDbType(SpecialDbTypes dbTypes) - { - if (dbTypes == SpecialDbTypes.NCHAR) - { - return "NCHAR"; - } - else if (dbTypes == SpecialDbTypes.NTEXT) - return "NTEXT"; - - return "NVARCHAR"; - } - - public virtual bool? SupportsCaseInsensitiveQueries(IDatabase db) - { - return true; - } - - public virtual IEnumerable GetTablesInSchema(IDatabase db) - { - return new List(); - } - - public virtual IEnumerable GetColumnsInSchema(IDatabase db) - { - return new List(); - } - - public virtual IEnumerable> GetConstraintsPerTable(IDatabase db) - { - return new List>(); - } - - public virtual IEnumerable> GetConstraintsPerColumn(IDatabase db) - { - return new List>(); - } - - public abstract IEnumerable> GetDefinedIndexes(IDatabase db); - - public virtual bool DoesTableExist(IDatabase db, string tableName) - { - return false; - } - - public virtual bool SupportsClustered() - { - return true; - } - - public virtual bool SupportsIdentityInsert() - { - return true; - } - - /// - /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) - /// - /// - /// - /// - /// - /// MSSQL has a DateTime standard that is unambiguous and works on all servers: - /// YYYYMMDD HH:mm:ss - /// - public virtual string FormatDateTime(DateTime date, bool includeTime = true) - { - // need CultureInfo.InvariantCulture because ":" here is the "time separator" and - // may be converted to something else in different cultures (eg "." in DK). - return date.ToString(includeTime ? "yyyyMMdd HH:mm:ss" : "yyyyMMdd", CultureInfo.InvariantCulture); - } - - public virtual string Format(TableDefinition table) - { - var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns)); - - return statement; - } - - public virtual List Format(IEnumerable indexes) - { - return indexes.Select(Format).ToList(); - } - - public virtual string Format(IndexDefinition index) - { - var name = string.IsNullOrEmpty(index.Name) - ? $"IX_{index.TableName}_{index.ColumnName}" - : index.Name; - - var columns = index.Columns.Any() - ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) - : GetQuotedColumnName(index.ColumnName); - - return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name), - GetQuotedTableName(index.TableName), columns); - } - - public virtual List Format(IEnumerable foreignKeys) - { - return foreignKeys.Select(Format).ToList(); - } - - public virtual string Format(ForeignKeyDefinition foreignKey) - { - var constraintName = string.IsNullOrEmpty(foreignKey.Name) - ? $"FK_{foreignKey.ForeignTable}_{foreignKey.PrimaryTable}_{foreignKey.PrimaryColumns.First()}" - : foreignKey.Name; - - return string.Format(CreateForeignKeyConstraint, - GetQuotedTableName(foreignKey.ForeignTable), - GetQuotedName(constraintName), - GetQuotedColumnName(foreignKey.ForeignColumns.First()), - GetQuotedTableName(foreignKey.PrimaryTable), - GetQuotedColumnName(foreignKey.PrimaryColumns.First()), - FormatCascade("DELETE", foreignKey.OnDelete), - FormatCascade("UPDATE", foreignKey.OnUpdate)); - } - - public virtual string Format(IEnumerable columns) - { - var sb = new StringBuilder(); - foreach (var column in columns) - { - sb.Append(Format(column) + ",\n"); - } - return sb.ToString().TrimEnd(",\n"); - } - - public virtual string Format(ColumnDefinition column) - { - return string.Join(" ", ClauseOrder - .Select(action => action(column)) - .Where(clause => string.IsNullOrEmpty(clause) == false)); - } - - public virtual string Format(ColumnDefinition column, string tableName, out IEnumerable sqls) - { - var sql = new StringBuilder(); - sql.Append(FormatString(column)); - sql.Append(" "); - sql.Append(FormatType(column)); - sql.Append(" "); - sql.Append("NULL"); // always nullable - sql.Append(" "); - sql.Append(FormatConstraint(column)); - sql.Append(" "); - sql.Append(FormatDefaultValue(column)); - sql.Append(" "); - sql.Append(FormatPrimaryKey(column)); - sql.Append(" "); - sql.Append(FormatIdentity(column)); - - var isNullable = column.IsNullable; - - //var constraint = FormatConstraint(column)?.TrimStart("CONSTRAINT "); - //var hasConstraint = !string.IsNullOrWhiteSpace(constraint); - - //var defaultValue = FormatDefaultValue(column); - //var hasDefaultValue = !string.IsNullOrWhiteSpace(defaultValue); - - if (isNullable /*&& !hasConstraint && !hasDefaultValue*/) - { - sqls = Enumerable.Empty(); - return sql.ToString(); - } - - var msql = new List(); - sqls = msql; - - var alterSql = new StringBuilder(); - alterSql.Append(FormatString(column)); - alterSql.Append(" "); - alterSql.Append(FormatType(column)); - alterSql.Append(" "); - alterSql.Append(FormatNullable(column)); - //alterSql.Append(" "); - //alterSql.Append(FormatPrimaryKey(column)); - //alterSql.Append(" "); - //alterSql.Append(FormatIdentity(column)); - msql.Add(string.Format(AlterColumn, tableName, alterSql)); - - //if (hasConstraint) - //{ - // var dropConstraintSql = string.Format(DeleteConstraint, tableName, constraint); - // msql.Add(dropConstraintSql); - // var constraintType = hasDefaultValue ? defaultValue : ""; - // var createConstraintSql = string.Format(CreateConstraint, tableName, constraint, constraintType, FormatString(column)); - // msql.Add(createConstraintSql); - //} - - return sql.ToString(); - } - - public virtual string FormatPrimaryKey(TableDefinition table) - { - var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); - if (columnDefinition == null) - return string.Empty; - - var constraintName = string.IsNullOrEmpty(columnDefinition.PrimaryKeyName) - ? $"PK_{table.Name}" - : columnDefinition.PrimaryKeyName; - - var columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) - ? GetQuotedColumnName(columnDefinition.Name) - : string.Join(", ", columnDefinition.PrimaryKeyColumns - .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(GetQuotedColumnName)); - - var primaryKeyPart = string.Concat("PRIMARY KEY", columnDefinition.IsIndexed ? " CLUSTERED" : " NONCLUSTERED"); - - return string.Format(CreateConstraint, - GetQuotedTableName(table.Name), - GetQuotedName(constraintName), - primaryKeyPart, - columns); - } - - public virtual string FormatColumnRename(string tableName, string oldName, string newName) - { - return string.Format(RenameColumn, - GetQuotedTableName(tableName), - GetQuotedColumnName(oldName), - GetQuotedColumnName(newName)); - } - - public virtual string FormatTableRename(string oldName, string newName) - { - return string.Format(RenameTable, GetQuotedTableName(oldName), GetQuotedTableName(newName)); - } - - protected virtual string FormatCascade(string onWhat, Rule rule) - { - var action = "NO ACTION"; - switch (rule) - { - case Rule.None: - return ""; - case Rule.Cascade: - action = "CASCADE"; - break; - case Rule.SetNull: - action = "SET NULL"; - break; - case Rule.SetDefault: - action = "SET DEFAULT"; - break; - } - - return $" ON {onWhat} {action}"; - } - - protected virtual string FormatString(ColumnDefinition column) - { - return GetQuotedColumnName(column.Name); - } - - protected virtual string FormatType(ColumnDefinition column) - { - if (column.Type.HasValue == false && string.IsNullOrEmpty(column.CustomType) == false) - return column.CustomType; - - if (column.HasSpecialDbType) - { - if (column.Size != default(int)) - { - return $"{GetSpecialDbType(column.DbType)}({column.Size})"; - } - - return GetSpecialDbType(column.DbType); - } - - var type = column.Type.HasValue - ? DbTypeMap.ColumnDbTypeMap.First(x => x.Value == column.Type.Value).Key - : column.PropertyType; - - if (type == typeof(string)) - { - var valueOrDefault = column.Size != default(int) ? column.Size : DefaultStringLength; - return string.Format(StringLengthColumnDefinitionFormat, valueOrDefault); - } - - if (type == typeof(decimal)) - { - var precision = column.Size != default(int) ? column.Size : DefaultDecimalPrecision; - var scale = column.Precision != default(int) ? column.Precision : DefaultDecimalScale; - return string.Format(DecimalColumnDefinitionFormat, precision, scale); - } - - var definition = DbTypeMap.ColumnTypeMap.First(x => x.Key == type).Value; - var dbTypeDefinition = column.Size != default(int) - ? $"{definition}({column.Size})" - : definition; - //NOTE Percision is left out - return dbTypeDefinition; - } - - protected virtual string FormatNullable(ColumnDefinition column) - { - return column.IsNullable ? "NULL" : "NOT NULL"; - } - - protected virtual string FormatConstraint(ColumnDefinition column) - { - if (string.IsNullOrEmpty(column.ConstraintName) && column.DefaultValue == null) - return string.Empty; - - return - $"CONSTRAINT {(string.IsNullOrEmpty(column.ConstraintName) ? GetQuotedName($"DF_{column.TableName}_{column.Name}") : column.ConstraintName)}"; - } - - protected virtual string FormatDefaultValue(ColumnDefinition column) - { - if (column.DefaultValue == null) - return string.Empty; - - //hack - probably not needed with latest changes - if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) - column.DefaultValue = SystemMethods.CurrentDateTime; - - // see if this is for a system method - if (column.DefaultValue is SystemMethods) - { - var method = FormatSystemMethods((SystemMethods)column.DefaultValue); - return string.IsNullOrEmpty(method) ? string.Empty : string.Format(DefaultValueFormat, method); - } - - return string.Format(DefaultValueFormat, GetQuotedValue(column.DefaultValue.ToString())); - } - - protected virtual string FormatPrimaryKey(ColumnDefinition column) - { - return string.Empty; - } - - protected abstract string FormatSystemMethods(SystemMethods systemMethod); - - protected abstract string FormatIdentity(ColumnDefinition column); - - public abstract Sql SelectTop(Sql sql, int top); - - public virtual string DeleteDefaultConstraint => throw new NotSupportedException("Default constraints are not supported"); - - public virtual string CreateTable => "CREATE TABLE {0} ({1})"; - public virtual string DropTable => "DROP TABLE {0}"; - - public virtual string AddColumn => "ALTER TABLE {0} ADD {1}"; - public virtual string DropColumn => "ALTER TABLE {0} DROP COLUMN {1}"; - public virtual string AlterColumn => "ALTER TABLE {0} ALTER COLUMN {1}"; - public virtual string RenameColumn => "ALTER TABLE {0} RENAME COLUMN {1} TO {2}"; - - public virtual string RenameTable => "RENAME TABLE {0} TO {1}"; - - public virtual string CreateSchema => "CREATE SCHEMA {0}"; - public virtual string AlterSchema => "ALTER SCHEMA {0} TRANSFER {1}.{2}"; - public virtual string DropSchema => "DROP SCHEMA {0}"; - - public virtual string CreateIndex => "CREATE {0}{1}INDEX {2} ON {3} ({4})"; - public virtual string DropIndex => "DROP INDEX {0}"; - - public virtual string InsertData => "INSERT INTO {0} ({1}) VALUES ({2})"; - public virtual string UpdateData => "UPDATE {0} SET {1} WHERE {2}"; - public virtual string DeleteData => "DELETE FROM {0} WHERE {1}"; - public virtual string TruncateTable => "TRUNCATE TABLE {0}"; - - public virtual string CreateConstraint => "ALTER TABLE {0} ADD CONSTRAINT {1} {2} ({3})"; - public virtual string DeleteConstraint => "ALTER TABLE {0} DROP CONSTRAINT {1}"; - public virtual string CreateForeignKeyConstraint => "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; - - public virtual string ConvertIntegerToOrderableString => "REPLACE(STR({0}, 8), SPACE(1), '0')"; - public virtual string ConvertDateToOrderableString => "CONVERT(nvarchar, {0}, 102)"; - public virtual string ConvertDecimalToOrderableString => "REPLACE(STR({0}, 20, 9), SPACE(1), '0')"; - } -} +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Text; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Represents the Base Sql Syntax provider implementation. + /// + /// + /// All Sql Syntax provider implementations should derive from this abstract class. + /// + /// + public abstract class SqlSyntaxProviderBase : ISqlSyntaxProvider + where TSyntax : ISqlSyntaxProvider + { + protected SqlSyntaxProviderBase() + { + ClauseOrder = new List> + { + FormatString, + FormatType, + FormatNullable, + FormatConstraint, + FormatDefaultValue, + FormatPrimaryKey, + FormatIdentity + }; + + //defaults for all providers + StringLengthColumnDefinitionFormat = StringLengthUnicodeColumnDefinitionFormat; + StringColumnDefinition = string.Format(StringLengthColumnDefinitionFormat, DefaultStringLength); + DecimalColumnDefinition = string.Format(DecimalColumnDefinitionFormat, DefaultDecimalPrecision, DefaultDecimalScale); + + InitColumnTypeMap(); + } + + public string GetWildcardPlaceholder() + { + return "%"; + } + + public string StringLengthNonUnicodeColumnDefinitionFormat = "VARCHAR({0})"; + public string StringLengthUnicodeColumnDefinitionFormat = "NVARCHAR({0})"; + public string DecimalColumnDefinitionFormat = "DECIMAL({0},{1})"; + + public string DefaultValueFormat = "DEFAULT ({0})"; + public int DefaultStringLength = 255; + public int DefaultDecimalPrecision = 20; + public int DefaultDecimalScale = 9; + + //Set by Constructor + public string StringColumnDefinition; + public string StringLengthColumnDefinitionFormat; + + public string AutoIncrementDefinition = "AUTOINCREMENT"; + public string IntColumnDefinition = "INTEGER"; + public string LongColumnDefinition = "BIGINT"; + public string GuidColumnDefinition = "GUID"; + public string BoolColumnDefinition = "BOOL"; + public string RealColumnDefinition = "DOUBLE"; + public string DecimalColumnDefinition; + public string BlobColumnDefinition = "BLOB"; + public string DateTimeColumnDefinition = "DATETIME"; + public string TimeColumnDefinition = "DATETIME"; + + protected IList> ClauseOrder { get; } + + protected DbTypes DbTypeMap = new DbTypes(); + protected void InitColumnTypeMap() + { + DbTypeMap.Set(DbType.String, StringColumnDefinition); + DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); + DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); + DbTypeMap.Set(DbType.String, StringColumnDefinition); + DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); + DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); + DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); + DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); + DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); + DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + + DbTypeMap.Set(DbType.Byte, IntColumnDefinition); + DbTypeMap.Set(DbType.Byte, IntColumnDefinition); + DbTypeMap.Set(DbType.SByte, IntColumnDefinition); + DbTypeMap.Set(DbType.SByte, IntColumnDefinition); + DbTypeMap.Set(DbType.Int16, IntColumnDefinition); + DbTypeMap.Set(DbType.Int16, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); + DbTypeMap.Set(DbType.Int32, IntColumnDefinition); + DbTypeMap.Set(DbType.Int32, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); + + DbTypeMap.Set(DbType.Int64, LongColumnDefinition); + DbTypeMap.Set(DbType.Int64, LongColumnDefinition); + DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); + DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); + + DbTypeMap.Set(DbType.Single, RealColumnDefinition); + DbTypeMap.Set(DbType.Single, RealColumnDefinition); + DbTypeMap.Set(DbType.Double, RealColumnDefinition); + DbTypeMap.Set(DbType.Double, RealColumnDefinition); + + DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); + DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); + + DbTypeMap.Set(DbType.Binary, BlobColumnDefinition); + } + + public virtual string EscapeString(string val) + { + return NPocoDatabaseExtensions.EscapeAtSymbols(val.Replace("'", "''")); + } + + public virtual string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return $"upper({column}) = upper(@{paramIndex})"; + } + + public virtual string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return $"upper({column}) LIKE upper(@{paramIndex})"; + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return $"upper({column}) = '{value.ToUpper()}'"; + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return $"upper({column}) LIKE '{value.ToUpper()}%'"; + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return $"upper({column}) LIKE '%{value.ToUpper()}'"; + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return $"upper({column}) LIKE '%{value.ToUpper()}%'"; + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return $"upper({column}) LIKE '{value.ToUpper()}'"; + } + + public virtual string GetConcat(params string[] args) + { + return "concat(" + string.Join(",", args) + ")"; + } + + public virtual string GetQuotedTableName(string tableName) + { + return $"\"{tableName}\""; + } + + public virtual string GetQuotedColumnName(string columnName) + { + return $"\"{columnName}\""; + } + + public virtual string GetQuotedName(string name) + { + return $"\"{name}\""; + } + + public virtual string GetQuotedValue(string value) + { + return $"'{value}'"; + } + + public virtual string GetIndexType(IndexTypes indexTypes) + { + string indexType; + + if (indexTypes == IndexTypes.Clustered) + { + indexType = "CLUSTERED"; + } + else + { + indexType = indexTypes == IndexTypes.NonClustered + ? "NONCLUSTERED" + : "UNIQUE NONCLUSTERED"; + } + return indexType; + } + + public virtual string GetSpecialDbType(SpecialDbTypes dbTypes) + { + if (dbTypes == SpecialDbTypes.NCHAR) + { + return "NCHAR"; + } + else if (dbTypes == SpecialDbTypes.NTEXT) + return "NTEXT"; + + return "NVARCHAR"; + } + + public virtual bool? SupportsCaseInsensitiveQueries(IDatabase db) + { + return true; + } + + public virtual IEnumerable GetTablesInSchema(IDatabase db) + { + return new List(); + } + + public virtual IEnumerable GetColumnsInSchema(IDatabase db) + { + return new List(); + } + + public virtual IEnumerable> GetConstraintsPerTable(IDatabase db) + { + return new List>(); + } + + public virtual IEnumerable> GetConstraintsPerColumn(IDatabase db) + { + return new List>(); + } + + public abstract IEnumerable> GetDefinedIndexes(IDatabase db); + + public virtual bool DoesTableExist(IDatabase db, string tableName) + { + return false; + } + + public virtual bool SupportsClustered() + { + return true; + } + + public virtual bool SupportsIdentityInsert() + { + return true; + } + + /// + /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) + /// + /// + /// + /// + /// + /// MSSQL has a DateTime standard that is unambiguous and works on all servers: + /// YYYYMMDD HH:mm:ss + /// + public virtual string FormatDateTime(DateTime date, bool includeTime = true) + { + // need CultureInfo.InvariantCulture because ":" here is the "time separator" and + // may be converted to something else in different cultures (eg "." in DK). + return date.ToString(includeTime ? "yyyyMMdd HH:mm:ss" : "yyyyMMdd", CultureInfo.InvariantCulture); + } + + public virtual string Format(TableDefinition table) + { + var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns)); + + return statement; + } + + public virtual List Format(IEnumerable indexes) + { + return indexes.Select(Format).ToList(); + } + + public virtual string Format(IndexDefinition index) + { + var name = string.IsNullOrEmpty(index.Name) + ? $"IX_{index.TableName}_{index.ColumnName}" + : index.Name; + + var columns = index.Columns.Any() + ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) + : GetQuotedColumnName(index.ColumnName); + + return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name), + GetQuotedTableName(index.TableName), columns); + } + + public virtual List Format(IEnumerable foreignKeys) + { + return foreignKeys.Select(Format).ToList(); + } + + public virtual string Format(ForeignKeyDefinition foreignKey) + { + var constraintName = string.IsNullOrEmpty(foreignKey.Name) + ? $"FK_{foreignKey.ForeignTable}_{foreignKey.PrimaryTable}_{foreignKey.PrimaryColumns.First()}" + : foreignKey.Name; + + return string.Format(CreateForeignKeyConstraint, + GetQuotedTableName(foreignKey.ForeignTable), + GetQuotedName(constraintName), + GetQuotedColumnName(foreignKey.ForeignColumns.First()), + GetQuotedTableName(foreignKey.PrimaryTable), + GetQuotedColumnName(foreignKey.PrimaryColumns.First()), + FormatCascade("DELETE", foreignKey.OnDelete), + FormatCascade("UPDATE", foreignKey.OnUpdate)); + } + + public virtual string Format(IEnumerable columns) + { + var sb = new StringBuilder(); + foreach (var column in columns) + { + sb.Append(Format(column) + ",\n"); + } + return sb.ToString().TrimEnd(",\n"); + } + + public virtual string Format(ColumnDefinition column) + { + return string.Join(" ", ClauseOrder + .Select(action => action(column)) + .Where(clause => string.IsNullOrEmpty(clause) == false)); + } + + public virtual string Format(ColumnDefinition column, string tableName, out IEnumerable sqls) + { + var sql = new StringBuilder(); + sql.Append(FormatString(column)); + sql.Append(" "); + sql.Append(FormatType(column)); + sql.Append(" "); + sql.Append("NULL"); // always nullable + sql.Append(" "); + sql.Append(FormatConstraint(column)); + sql.Append(" "); + sql.Append(FormatDefaultValue(column)); + sql.Append(" "); + sql.Append(FormatPrimaryKey(column)); + sql.Append(" "); + sql.Append(FormatIdentity(column)); + + var isNullable = column.IsNullable; + + //var constraint = FormatConstraint(column)?.TrimStart("CONSTRAINT "); + //var hasConstraint = !string.IsNullOrWhiteSpace(constraint); + + //var defaultValue = FormatDefaultValue(column); + //var hasDefaultValue = !string.IsNullOrWhiteSpace(defaultValue); + + if (isNullable /*&& !hasConstraint && !hasDefaultValue*/) + { + sqls = Enumerable.Empty(); + return sql.ToString(); + } + + var msql = new List(); + sqls = msql; + + var alterSql = new StringBuilder(); + alterSql.Append(FormatString(column)); + alterSql.Append(" "); + alterSql.Append(FormatType(column)); + alterSql.Append(" "); + alterSql.Append(FormatNullable(column)); + //alterSql.Append(" "); + //alterSql.Append(FormatPrimaryKey(column)); + //alterSql.Append(" "); + //alterSql.Append(FormatIdentity(column)); + msql.Add(string.Format(AlterColumn, tableName, alterSql)); + + //if (hasConstraint) + //{ + // var dropConstraintSql = string.Format(DeleteConstraint, tableName, constraint); + // msql.Add(dropConstraintSql); + // var constraintType = hasDefaultValue ? defaultValue : ""; + // var createConstraintSql = string.Format(CreateConstraint, tableName, constraint, constraintType, FormatString(column)); + // msql.Add(createConstraintSql); + //} + + return sql.ToString(); + } + + public virtual string FormatPrimaryKey(TableDefinition table) + { + var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); + if (columnDefinition == null) + return string.Empty; + + var constraintName = string.IsNullOrEmpty(columnDefinition.PrimaryKeyName) + ? $"PK_{table.Name}" + : columnDefinition.PrimaryKeyName; + + var columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) + ? GetQuotedColumnName(columnDefinition.Name) + : string.Join(", ", columnDefinition.PrimaryKeyColumns + .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(GetQuotedColumnName)); + + var primaryKeyPart = string.Concat("PRIMARY KEY", columnDefinition.IsIndexed ? " CLUSTERED" : " NONCLUSTERED"); + + return string.Format(CreateConstraint, + GetQuotedTableName(table.Name), + GetQuotedName(constraintName), + primaryKeyPart, + columns); + } + + public virtual string FormatColumnRename(string tableName, string oldName, string newName) + { + return string.Format(RenameColumn, + GetQuotedTableName(tableName), + GetQuotedColumnName(oldName), + GetQuotedColumnName(newName)); + } + + public virtual string FormatTableRename(string oldName, string newName) + { + return string.Format(RenameTable, GetQuotedTableName(oldName), GetQuotedTableName(newName)); + } + + protected virtual string FormatCascade(string onWhat, Rule rule) + { + var action = "NO ACTION"; + switch (rule) + { + case Rule.None: + return ""; + case Rule.Cascade: + action = "CASCADE"; + break; + case Rule.SetNull: + action = "SET NULL"; + break; + case Rule.SetDefault: + action = "SET DEFAULT"; + break; + } + + return $" ON {onWhat} {action}"; + } + + protected virtual string FormatString(ColumnDefinition column) + { + return GetQuotedColumnName(column.Name); + } + + protected virtual string FormatType(ColumnDefinition column) + { + if (column.Type.HasValue == false && string.IsNullOrEmpty(column.CustomType) == false) + return column.CustomType; + + if (column.HasSpecialDbType) + { + if (column.Size != default(int)) + { + return $"{GetSpecialDbType(column.DbType)}({column.Size})"; + } + + return GetSpecialDbType(column.DbType); + } + + var type = column.Type.HasValue + ? DbTypeMap.ColumnDbTypeMap.First(x => x.Value == column.Type.Value).Key + : column.PropertyType; + + if (type == typeof(string)) + { + var valueOrDefault = column.Size != default(int) ? column.Size : DefaultStringLength; + return string.Format(StringLengthColumnDefinitionFormat, valueOrDefault); + } + + if (type == typeof(decimal)) + { + var precision = column.Size != default(int) ? column.Size : DefaultDecimalPrecision; + var scale = column.Precision != default(int) ? column.Precision : DefaultDecimalScale; + return string.Format(DecimalColumnDefinitionFormat, precision, scale); + } + + var definition = DbTypeMap.ColumnTypeMap.First(x => x.Key == type).Value; + var dbTypeDefinition = column.Size != default(int) + ? $"{definition}({column.Size})" + : definition; + //NOTE Percision is left out + return dbTypeDefinition; + } + + protected virtual string FormatNullable(ColumnDefinition column) + { + return column.IsNullable ? "NULL" : "NOT NULL"; + } + + protected virtual string FormatConstraint(ColumnDefinition column) + { + if (string.IsNullOrEmpty(column.ConstraintName) && column.DefaultValue == null) + return string.Empty; + + return + $"CONSTRAINT {(string.IsNullOrEmpty(column.ConstraintName) ? GetQuotedName($"DF_{column.TableName}_{column.Name}") : column.ConstraintName)}"; + } + + protected virtual string FormatDefaultValue(ColumnDefinition column) + { + if (column.DefaultValue == null) + return string.Empty; + + //hack - probably not needed with latest changes + if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) + column.DefaultValue = SystemMethods.CurrentDateTime; + + // see if this is for a system method + if (column.DefaultValue is SystemMethods) + { + var method = FormatSystemMethods((SystemMethods)column.DefaultValue); + return string.IsNullOrEmpty(method) ? string.Empty : string.Format(DefaultValueFormat, method); + } + + return string.Format(DefaultValueFormat, GetQuotedValue(column.DefaultValue.ToString())); + } + + protected virtual string FormatPrimaryKey(ColumnDefinition column) + { + return string.Empty; + } + + protected abstract string FormatSystemMethods(SystemMethods systemMethod); + + protected abstract string FormatIdentity(ColumnDefinition column); + + public abstract Sql SelectTop(Sql sql, int top); + + public virtual string DeleteDefaultConstraint => throw new NotSupportedException("Default constraints are not supported"); + + public virtual string CreateTable => "CREATE TABLE {0} ({1})"; + public virtual string DropTable => "DROP TABLE {0}"; + + public virtual string AddColumn => "ALTER TABLE {0} ADD {1}"; + public virtual string DropColumn => "ALTER TABLE {0} DROP COLUMN {1}"; + public virtual string AlterColumn => "ALTER TABLE {0} ALTER COLUMN {1}"; + public virtual string RenameColumn => "ALTER TABLE {0} RENAME COLUMN {1} TO {2}"; + + public virtual string RenameTable => "RENAME TABLE {0} TO {1}"; + + public virtual string CreateSchema => "CREATE SCHEMA {0}"; + public virtual string AlterSchema => "ALTER SCHEMA {0} TRANSFER {1}.{2}"; + public virtual string DropSchema => "DROP SCHEMA {0}"; + + public virtual string CreateIndex => "CREATE {0}{1}INDEX {2} ON {3} ({4})"; + public virtual string DropIndex => "DROP INDEX {0}"; + + public virtual string InsertData => "INSERT INTO {0} ({1}) VALUES ({2})"; + public virtual string UpdateData => "UPDATE {0} SET {1} WHERE {2}"; + public virtual string DeleteData => "DELETE FROM {0} WHERE {1}"; + public virtual string TruncateTable => "TRUNCATE TABLE {0}"; + + public virtual string CreateConstraint => "ALTER TABLE {0} ADD CONSTRAINT {1} {2} ({3})"; + public virtual string DeleteConstraint => "ALTER TABLE {0} DROP CONSTRAINT {1}"; + public virtual string CreateForeignKeyConstraint => "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; + + public virtual string ConvertIntegerToOrderableString => "REPLACE(STR({0}, 8), SPACE(1), '0')"; + public virtual string ConvertDateToOrderableString => "CONVERT(nvarchar, {0}, 102)"; + public virtual string ConvertDecimalToOrderableString => "REPLACE(STR({0}, 20, 9), SPACE(1), '0')"; + } +} diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index 703bfa9cb0..0f337595fe 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -1,261 +1,261 @@ -using System; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Text; -using NPoco; -using StackExchange.Profiling; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.FaultHandling; - -namespace Umbraco.Core.Persistence -{ - /// - /// Extends NPoco Database for Umbraco. - /// - /// - /// Is used everywhere in place of the original NPoco Database object, and provides additional features - /// such as profiling, retry policies, logging, etc. - /// Is never created directly but obtained from the . - /// - public class UmbracoDatabase : Database, IUmbracoDatabase - { - // Umbraco's default isolation level is RepeatableRead - private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead; - - private readonly ILogger _logger; - private readonly RetryPolicy _connectionRetryPolicy; - private readonly RetryPolicy _commandRetryPolicy; - private readonly Guid _instanceGuid = Guid.NewGuid(); - - #region Ctor - - /// - /// Initializes a new instance of the class. - /// - /// - /// Used by UmbracoDatabaseFactory to create databases. - /// Also used by DatabaseBuilder for creating databases and installing/upgrading. - /// - public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null) - : base(connectionString, sqlContext.DatabaseType, provider, DefaultIsolationLevel) - { - SqlContext = sqlContext; - - _logger = logger; - _connectionRetryPolicy = connectionRetryPolicy; - _commandRetryPolicy = commandRetryPolicy; - - EnableSqlTrace = EnableSqlTraceDefault; - } - - /// - /// Initializes a new instance of the class. - /// - /// Internal for unit tests only. - internal UmbracoDatabase(DbConnection connection, ISqlContext sqlContext, ILogger logger) - : base(connection, sqlContext.DatabaseType, DefaultIsolationLevel) - { - SqlContext = sqlContext; - _logger = logger; - - EnableSqlTrace = EnableSqlTraceDefault; - } - - #endregion - - /// - public ISqlContext SqlContext { get; } - - #region Testing, Debugging and Troubleshooting - - private bool _enableCount; - -#if DEBUG_DATABASES - private int _spid = -1; - private const bool EnableSqlTraceDefault = true; -#else - private string _instanceId; - private const bool EnableSqlTraceDefault = false; -#endif - - /// - public string InstanceId - { - get - { -#if DEBUG_DATABASES - return _instanceGuid.ToString("N").Substring(0, 8) + ':' + _spid; -#else - return _instanceId ?? (_instanceId = _instanceGuid.ToString("N").Substring(0, 8)); -#endif - } - } - - /// - public bool InTransaction { get; private set; } - - protected override void OnBeginTransaction() - { - base.OnBeginTransaction(); - InTransaction = true; - } - - protected override void OnAbortTransaction() - { - InTransaction = false; - base.OnAbortTransaction(); - } - - protected override void OnCompleteTransaction() - { - InTransaction = false; - base.OnCompleteTransaction(); - } - - /// - /// Gets or sets a value indicating whether to log all executed Sql statements. - /// - internal bool EnableSqlTrace { get; set; } - - /// - /// Gets or sets a value indicating whether to count all executed Sql statements. - /// - internal bool EnableSqlCount - { - get => _enableCount; - set - { - _enableCount = value; - if (_enableCount == false) - SqlCount = 0; - } - } - - /// - /// Gets the count of all executed Sql statements. - /// - internal int SqlCount { get; private set; } - - #endregion - - #region OnSomething - - // fixme.poco - has new interceptors to replace OnSomething? - - protected override DbConnection OnConnectionOpened(DbConnection connection) - { - if (connection == null) throw new ArgumentNullException(nameof(connection)); - -#if DEBUG_DATABASES - // determines the database connection SPID for debugging - if (DatabaseType.IsMySql()) - { - using (var command = connection.CreateCommand()) - { - command.CommandText = "SELECT CONNECTION_ID()"; - _spid = Convert.ToInt32(command.ExecuteScalar()); - } - } - else if (DatabaseType.IsSqlServer()) - { - using (var command = connection.CreateCommand()) - { - command.CommandText = "SELECT @@SPID"; - _spid = Convert.ToInt32(command.ExecuteScalar()); - } - } - else - { - // includes SqlCE - _spid = 0; - } -#endif - - // wrap the connection with a profiling connection that tracks timings - connection = new StackExchange.Profiling.Data.ProfiledDbConnection(connection, MiniProfiler.Current); - - // wrap the connection with a retrying connection - if (_connectionRetryPolicy != null || _commandRetryPolicy != null) - connection = new RetryDbConnection(connection, _connectionRetryPolicy, _commandRetryPolicy); - - return connection; - } - -#if DEBUG_DATABASES - protected override void OnConnectionClosing(DbConnection conn) - { - _spid = -1; - base.OnConnectionClosing(conn); - } -#endif - - protected override void OnException(Exception x) - { - _logger.Error("Exception (" + InstanceId + ").", x); - _logger.Debug(() => "At:\r\n" + Environment.StackTrace); - if (EnableSqlTrace == false) - _logger.Debug(() => "Sql:\r\n" + CommandToString(LastSQL, LastArgs)); - base.OnException(x); - } - - private DbCommand _cmd; - - protected override void OnExecutingCommand(DbCommand cmd) - { - // if no timeout is specified, and the connection has a longer timeout, use it - if (OneTimeCommandTimeout == 0 && CommandTimeout == 0 && cmd.Connection.ConnectionTimeout > 30) - cmd.CommandTimeout = cmd.Connection.ConnectionTimeout; - - if (EnableSqlTrace) - _logger.Debug(() => CommandToString(cmd).Replace("{", "{{").Replace("}", "}}")); // fixme these escapes should be builtin - -#if DEBUG_DATABASES - // detects whether the command is already in use (eg still has an open reader...) - DatabaseDebugHelper.SetCommand(cmd, InstanceId + " [T" + System.Threading.Thread.CurrentThread.ManagedThreadId + "]"); - var refsobj = DatabaseDebugHelper.GetReferencedObjects(cmd.Connection); - if (refsobj != null) _logger.Debug("Oops!" + Environment.NewLine + refsobj); -#endif - - _cmd = cmd; - base.OnExecutingCommand(cmd); - } - - private string CommandToString(DbCommand cmd) - { - return CommandToString(cmd.CommandText, cmd.Parameters.Cast().Select(x => x.Value).ToArray()); - } - - private string CommandToString(string sql, object[] args) - { - var sb = new StringBuilder(); -#if DEBUG_DATABASES - sb.Append(InstanceId); - sb.Append(": "); -#endif - sb.Append(sql); - if (args.Length > 0) - sb.Append(" --"); - var i = 0; - foreach (var arg in args) - { - sb.Append(" @"); - sb.Append(i++); - sb.Append(":"); - sb.Append(arg); - } - - return sb.ToString(); - } - - protected override void OnExecutedCommand(DbCommand cmd) - { - if (_enableCount) - SqlCount++; - - base.OnExecutedCommand(cmd); - } - - #endregion - } -} +using System; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Text; +using NPoco; +using StackExchange.Profiling; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.FaultHandling; + +namespace Umbraco.Core.Persistence +{ + /// + /// Extends NPoco Database for Umbraco. + /// + /// + /// Is used everywhere in place of the original NPoco Database object, and provides additional features + /// such as profiling, retry policies, logging, etc. + /// Is never created directly but obtained from the . + /// + public class UmbracoDatabase : Database, IUmbracoDatabase + { + // Umbraco's default isolation level is RepeatableRead + private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead; + + private readonly ILogger _logger; + private readonly RetryPolicy _connectionRetryPolicy; + private readonly RetryPolicy _commandRetryPolicy; + private readonly Guid _instanceGuid = Guid.NewGuid(); + + #region Ctor + + /// + /// Initializes a new instance of the class. + /// + /// + /// Used by UmbracoDatabaseFactory to create databases. + /// Also used by DatabaseBuilder for creating databases and installing/upgrading. + /// + public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null) + : base(connectionString, sqlContext.DatabaseType, provider, DefaultIsolationLevel) + { + SqlContext = sqlContext; + + _logger = logger; + _connectionRetryPolicy = connectionRetryPolicy; + _commandRetryPolicy = commandRetryPolicy; + + EnableSqlTrace = EnableSqlTraceDefault; + } + + /// + /// Initializes a new instance of the class. + /// + /// Internal for unit tests only. + internal UmbracoDatabase(DbConnection connection, ISqlContext sqlContext, ILogger logger) + : base(connection, sqlContext.DatabaseType, DefaultIsolationLevel) + { + SqlContext = sqlContext; + _logger = logger; + + EnableSqlTrace = EnableSqlTraceDefault; + } + + #endregion + + /// + public ISqlContext SqlContext { get; } + + #region Testing, Debugging and Troubleshooting + + private bool _enableCount; + +#if DEBUG_DATABASES + private int _spid = -1; + private const bool EnableSqlTraceDefault = true; +#else + private string _instanceId; + private const bool EnableSqlTraceDefault = false; +#endif + + /// + public string InstanceId + { + get + { +#if DEBUG_DATABASES + return _instanceGuid.ToString("N").Substring(0, 8) + ':' + _spid; +#else + return _instanceId ?? (_instanceId = _instanceGuid.ToString("N").Substring(0, 8)); +#endif + } + } + + /// + public bool InTransaction { get; private set; } + + protected override void OnBeginTransaction() + { + base.OnBeginTransaction(); + InTransaction = true; + } + + protected override void OnAbortTransaction() + { + InTransaction = false; + base.OnAbortTransaction(); + } + + protected override void OnCompleteTransaction() + { + InTransaction = false; + base.OnCompleteTransaction(); + } + + /// + /// Gets or sets a value indicating whether to log all executed Sql statements. + /// + internal bool EnableSqlTrace { get; set; } + + /// + /// Gets or sets a value indicating whether to count all executed Sql statements. + /// + internal bool EnableSqlCount + { + get => _enableCount; + set + { + _enableCount = value; + if (_enableCount == false) + SqlCount = 0; + } + } + + /// + /// Gets the count of all executed Sql statements. + /// + internal int SqlCount { get; private set; } + + #endregion + + #region OnSomething + + // fixme.poco - has new interceptors to replace OnSomething? + + protected override DbConnection OnConnectionOpened(DbConnection connection) + { + if (connection == null) throw new ArgumentNullException(nameof(connection)); + +#if DEBUG_DATABASES + // determines the database connection SPID for debugging + if (DatabaseType.IsMySql()) + { + using (var command = connection.CreateCommand()) + { + command.CommandText = "SELECT CONNECTION_ID()"; + _spid = Convert.ToInt32(command.ExecuteScalar()); + } + } + else if (DatabaseType.IsSqlServer()) + { + using (var command = connection.CreateCommand()) + { + command.CommandText = "SELECT @@SPID"; + _spid = Convert.ToInt32(command.ExecuteScalar()); + } + } + else + { + // includes SqlCE + _spid = 0; + } +#endif + + // wrap the connection with a profiling connection that tracks timings + connection = new StackExchange.Profiling.Data.ProfiledDbConnection(connection, MiniProfiler.Current); + + // wrap the connection with a retrying connection + if (_connectionRetryPolicy != null || _commandRetryPolicy != null) + connection = new RetryDbConnection(connection, _connectionRetryPolicy, _commandRetryPolicy); + + return connection; + } + +#if DEBUG_DATABASES + protected override void OnConnectionClosing(DbConnection conn) + { + _spid = -1; + base.OnConnectionClosing(conn); + } +#endif + + protected override void OnException(Exception x) + { + _logger.Error("Exception (" + InstanceId + ").", x); + _logger.Debug(() => "At:\r\n" + Environment.StackTrace); + if (EnableSqlTrace == false) + _logger.Debug(() => "Sql:\r\n" + CommandToString(LastSQL, LastArgs)); + base.OnException(x); + } + + private DbCommand _cmd; + + protected override void OnExecutingCommand(DbCommand cmd) + { + // if no timeout is specified, and the connection has a longer timeout, use it + if (OneTimeCommandTimeout == 0 && CommandTimeout == 0 && cmd.Connection.ConnectionTimeout > 30) + cmd.CommandTimeout = cmd.Connection.ConnectionTimeout; + + if (EnableSqlTrace) + _logger.Debug(() => CommandToString(cmd).Replace("{", "{{").Replace("}", "}}")); // fixme these escapes should be builtin + +#if DEBUG_DATABASES + // detects whether the command is already in use (eg still has an open reader...) + DatabaseDebugHelper.SetCommand(cmd, InstanceId + " [T" + System.Threading.Thread.CurrentThread.ManagedThreadId + "]"); + var refsobj = DatabaseDebugHelper.GetReferencedObjects(cmd.Connection); + if (refsobj != null) _logger.Debug("Oops!" + Environment.NewLine + refsobj); +#endif + + _cmd = cmd; + base.OnExecutingCommand(cmd); + } + + private string CommandToString(DbCommand cmd) + { + return CommandToString(cmd.CommandText, cmd.Parameters.Cast().Select(x => x.Value).ToArray()); + } + + private string CommandToString(string sql, object[] args) + { + var sb = new StringBuilder(); +#if DEBUG_DATABASES + sb.Append(InstanceId); + sb.Append(": "); +#endif + sb.Append(sql); + if (args.Length > 0) + sb.Append(" --"); + var i = 0; + foreach (var arg in args) + { + sb.Append(" @"); + sb.Append(i++); + sb.Append(":"); + sb.Append(arg); + } + + return sb.ToString(); + } + + protected override void OnExecutedCommand(DbCommand cmd) + { + if (_enableCount) + SqlCount++; + + base.OnExecutedCommand(cmd); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs index 1391aaa9ed..f2e8feaf29 100644 --- a/src/Umbraco.Core/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs @@ -1,41 +1,41 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("Umbraco.Core")] -[assembly: AssemblyDescription("Umbraco Core")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyProduct("Umbraco CMS")] - -[assembly: ComVisible(false)] -[assembly: Guid("130a6b5c-50e7-4df3-a0dd-e9e7eb0b7c5c")] - -// Umbraco Cms -[assembly: InternalsVisibleTo("Umbraco.Web")] -[assembly: InternalsVisibleTo("Umbraco.Web.UI")] -[assembly: InternalsVisibleTo("Umbraco.Examine")] - -[assembly: InternalsVisibleTo("Umbraco.Tests")] -[assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] - -[assembly: InternalsVisibleTo("Umbraco.Extensions")] // fixme - what is this? - -// Allow this to be mocked in our unit tests -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] - -// Umbraco Deploy -[assembly: InternalsVisibleTo("Umbraco.Deploy")] -[assembly: InternalsVisibleTo("Umbraco.Deploy.UI")] -[assembly: InternalsVisibleTo("Umbraco.Deploy.Cloud")] - -// Umbraco Forms -[assembly: InternalsVisibleTo("Umbraco.Forms.Core")] -[assembly: InternalsVisibleTo("Umbraco.Forms.Core.Providers")] -[assembly: InternalsVisibleTo("Umbraco.Forms.Web")] - -// Umbraco Headless -[assembly: InternalsVisibleTo("Umbraco.Headless")] - -// code analysis -// IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "~_~")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Umbraco.Core")] +[assembly: AssemblyDescription("Umbraco Core")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Umbraco CMS")] + +[assembly: ComVisible(false)] +[assembly: Guid("130a6b5c-50e7-4df3-a0dd-e9e7eb0b7c5c")] + +// Umbraco Cms +[assembly: InternalsVisibleTo("Umbraco.Web")] +[assembly: InternalsVisibleTo("Umbraco.Web.UI")] +[assembly: InternalsVisibleTo("Umbraco.Examine")] + +[assembly: InternalsVisibleTo("Umbraco.Tests")] +[assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] + +[assembly: InternalsVisibleTo("Umbraco.Extensions")] // fixme - what is this? + +// Allow this to be mocked in our unit tests +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] + +// Umbraco Deploy +[assembly: InternalsVisibleTo("Umbraco.Deploy")] +[assembly: InternalsVisibleTo("Umbraco.Deploy.UI")] +[assembly: InternalsVisibleTo("Umbraco.Deploy.Cloud")] + +// Umbraco Forms +[assembly: InternalsVisibleTo("Umbraco.Forms.Core")] +[assembly: InternalsVisibleTo("Umbraco.Forms.Core.Providers")] +[assembly: InternalsVisibleTo("Umbraco.Forms.Web")] + +// Umbraco Headless +[assembly: InternalsVisibleTo("Umbraco.Headless")] + +// code analysis +// IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "~_~")] diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs index a42b26c9a1..6bd36a8f6f 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs @@ -1,100 +1,100 @@ -using System; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Composing; - -namespace Umbraco.Core.PropertyEditors -{ - /// - /// Provides published content properties conversion service. - /// - /// This is not a simple "value converter" because it really works only for properties. - public interface IPropertyValueConverter : IDiscoverable - { - /// - /// Gets a value indicating whether the converter supports a property type. - /// - /// The property type. - /// A value indicating whether the converter supports a property type. - bool IsConverter(PublishedPropertyType propertyType); - - /// - /// Gets the type of values returned by the converter. - /// - /// The property type. - /// The CLR type of values returned by the converter. - /// Some of the CLR types may be generated, therefore this method cannot directly return - /// a Type object (which may not exist yet). In which case it needs to return a ModelType instance. - Type GetPropertyValueType(PublishedPropertyType propertyType); - - /// - /// Gets the property cache level. - /// - /// The property type. - /// The property cache level. - PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType); - - /// - /// Converts a property source value to an intermediate value. - /// - /// The property set owning the property. - /// The property type. - /// The source value. - /// A value indicating whether conversion should take place in preview mode. - /// The result of the conversion. - /// - /// The converter should know how to convert a null source value, meaning that no - /// value has been assigned to the property. The intermediate value can be null. - /// With the XML cache, source values come from the XML cache and therefore are strings. - /// With objects caches, source values would come from the database and therefore be either - /// ints, DateTimes, decimals, or strings. - /// The converter should be prepared to handle both situations. - /// When source values are strings, the converter must handle empty strings, whitespace - /// strings, and xml-whitespace strings appropriately, ie it should know whether to preserve - /// whitespaces. - /// - object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview); - - /// - /// Converts a property intermediate value to an Object value. - /// - /// The property set owning the property. - /// The property type. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether conversion should take place in preview mode. - /// The result of the conversion. - /// - /// The converter should know how to convert a null intermediate value, or any intermediate value - /// indicating that no value has been assigned to the property. It is up to the converter to determine - /// what to return in that case: either null, or the default value... - /// The is passed to the converter so that it can be, in turn, - /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage - /// the cache levels of property values. It is not meant to be used by the converter. - /// - object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); - - /// - /// Converts a property intermediate value to an XPath value. - /// - /// The property set owning the property. - /// The property type. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether conversion should take place in preview mode. - /// The result of the conversion. - /// - /// The converter should know how to convert a null intermediate value, or any intermediate value - /// indicating that no value has been assigned to the property. It is up to the converter to determine - /// what to return in that case: either null, or the default value... - /// If successful, the result should be either null, a string, or an XPathNavigator - /// instance. Whether an xml-whitespace string should be returned as null or litterally, is - /// up to the converter. - /// The converter may want to return an XML fragment that represent a part of the content tree, - /// but should pay attention not to create infinite loops that would kill XPath and XSLT. - /// The is passed to the converter so that it can be, in turn, - /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage - /// the cache levels of property values. It is not meant to be used by the converter. - /// - object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); - } -} +using System; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Provides published content properties conversion service. + /// + /// This is not a simple "value converter" because it really works only for properties. + public interface IPropertyValueConverter : IDiscoverable + { + /// + /// Gets a value indicating whether the converter supports a property type. + /// + /// The property type. + /// A value indicating whether the converter supports a property type. + bool IsConverter(PublishedPropertyType propertyType); + + /// + /// Gets the type of values returned by the converter. + /// + /// The property type. + /// The CLR type of values returned by the converter. + /// Some of the CLR types may be generated, therefore this method cannot directly return + /// a Type object (which may not exist yet). In which case it needs to return a ModelType instance. + Type GetPropertyValueType(PublishedPropertyType propertyType); + + /// + /// Gets the property cache level. + /// + /// The property type. + /// The property cache level. + PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType); + + /// + /// Converts a property source value to an intermediate value. + /// + /// The property set owning the property. + /// The property type. + /// The source value. + /// A value indicating whether conversion should take place in preview mode. + /// The result of the conversion. + /// + /// The converter should know how to convert a null source value, meaning that no + /// value has been assigned to the property. The intermediate value can be null. + /// With the XML cache, source values come from the XML cache and therefore are strings. + /// With objects caches, source values would come from the database and therefore be either + /// ints, DateTimes, decimals, or strings. + /// The converter should be prepared to handle both situations. + /// When source values are strings, the converter must handle empty strings, whitespace + /// strings, and xml-whitespace strings appropriately, ie it should know whether to preserve + /// whitespaces. + /// + object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview); + + /// + /// Converts a property intermediate value to an Object value. + /// + /// The property set owning the property. + /// The property type. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether conversion should take place in preview mode. + /// The result of the conversion. + /// + /// The converter should know how to convert a null intermediate value, or any intermediate value + /// indicating that no value has been assigned to the property. It is up to the converter to determine + /// what to return in that case: either null, or the default value... + /// The is passed to the converter so that it can be, in turn, + /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage + /// the cache levels of property values. It is not meant to be used by the converter. + /// + object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); + + /// + /// Converts a property intermediate value to an XPath value. + /// + /// The property set owning the property. + /// The property type. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether conversion should take place in preview mode. + /// The result of the conversion. + /// + /// The converter should know how to convert a null intermediate value, or any intermediate value + /// indicating that no value has been assigned to the property. It is up to the converter to determine + /// what to return in that case: either null, or the default value... + /// If successful, the result should be either null, a string, or an XPathNavigator + /// instance. Whether an xml-whitespace string should be returned as null or litterally, is + /// up to the converter. + /// The converter may want to return an XML fragment that represent a part of the content tree, + /// but should pay attention not to create infinite loops that would kill XPath and XSLT. + /// The is passed to the converter so that it can be, in turn, + /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage + /// the cache levels of property values. It is not meant to be used by the converter. + /// + object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); + } +} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs b/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs index 22ffa41f52..afecf70cb1 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs @@ -1,39 +1,39 @@ -namespace Umbraco.Core.PropertyEditors -{ - /// - /// Specifies the level of cache for a property value. - /// - public enum PropertyCacheLevel - { - /// - /// Default value. - /// - Unknown = 0, - - /// - /// Indicates that the property value can be cached at the element level, i.e. it can be - /// cached until the element itself is modified. - /// - Element = 1, - - /// - /// Indicates that the property value can be cached at the elements level, i.e. it can - /// be cached until any element is modified. - /// - Elements = 2, - - /// - /// Indicates that the property value can be cached at the snapshot level, i.e. it can be - /// cached for the duration of the current snapshot. - /// - /// In most cases, a snapshot is created per request, and therefore this is - /// equivalent to cache the value for the duration of the request. - Snapshot = 3, - - /// - /// Indicates that the property value cannot be cached and has to be converted each time - /// it is requested. - /// - None = 4 - } -} +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Specifies the level of cache for a property value. + /// + public enum PropertyCacheLevel + { + /// + /// Default value. + /// + Unknown = 0, + + /// + /// Indicates that the property value can be cached at the element level, i.e. it can be + /// cached until the element itself is modified. + /// + Element = 1, + + /// + /// Indicates that the property value can be cached at the elements level, i.e. it can + /// be cached until any element is modified. + /// + Elements = 2, + + /// + /// Indicates that the property value can be cached at the snapshot level, i.e. it can be + /// cached for the duration of the current snapshot. + /// + /// In most cases, a snapshot is created per request, and therefore this is + /// equivalent to cache the value for the duration of the request. + Snapshot = 3, + + /// + /// Indicates that the property value cannot be cached and has to be converted each time + /// it is requested. + /// + None = 4 + } +} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs index 23a5b3f08c..724d7d0b55 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs @@ -1,41 +1,41 @@ -using System; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Core.PropertyEditors -{ - /// - /// Provides a default overridable implementation for that does nothing. - /// - public abstract class PropertyValueConverterBase : IPropertyValueConverter - { - public virtual bool IsConverter(PublishedPropertyType propertyType) - { - return false; - } - - public virtual Type GetPropertyValueType(PublishedPropertyType propertyType) - { - return typeof (object); - } - - public virtual PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) - { - return PropertyCacheLevel.Snapshot; - } - - public virtual object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) - { - return source; - } - - public virtual object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - return inter; - } - - public virtual object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - return inter?.ToString() ?? string.Empty; - } - } -} +using System; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Provides a default overridable implementation for that does nothing. + /// + public abstract class PropertyValueConverterBase : IPropertyValueConverter + { + public virtual bool IsConverter(PublishedPropertyType propertyType) + { + return false; + } + + public virtual Type GetPropertyValueType(PublishedPropertyType propertyType) + { + return typeof (object); + } + + public virtual PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + { + return PropertyCacheLevel.Snapshot; + } + + public virtual object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + return source; + } + + public virtual object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + return inter; + } + + public virtual object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + return inter?.ToString() ?? string.Empty; + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs index fc8b6bf7ee..fde9044108 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs @@ -1,54 +1,54 @@ -using System; -using System.Linq; -using System.Xml; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Core.PropertyEditors.ValueConverters -{ - [DefaultPropertyValueConverter] - public class DatePickerValueConverter : PropertyValueConverterBase - { - private static readonly string[] PropertyEditorAliases = - { - Constants.PropertyEditors.Aliases.DateTime, - Constants.PropertyEditors.Aliases.Date - }; - - public override bool IsConverter(PublishedPropertyType propertyType) - => PropertyEditorAliases.Contains(propertyType.EditorAlias); - - public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (DateTime); - - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) return DateTime.MinValue; - - // in XML a DateTime is: string - format "yyyy-MM-ddTHH:mm:ss" - // Actually, not always sometimes it is formatted in UTC style with 'Z' suffixed on the end but that is due to this bug: - // http://issues.umbraco.org/issue/U4-4145, http://issues.umbraco.org/issue/U4-3894 - // We should just be using TryConvertTo instead. - - if (source is string sourceString) - { - var attempt = sourceString.TryConvertTo(); - return attempt.Success == false ? DateTime.MinValue : attempt.Result; - } - - // in the database a DateTime is: DateTime - // default value is: DateTime.MinValue - return source is DateTime ? source : DateTime.MinValue; - } - - // default ConvertSourceToObject just returns source ie a DateTime value - - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - // source should come from ConvertSource and be a DateTime already - return XmlConvert.ToString((DateTime) inter, XmlDateTimeSerializationMode.Unspecified); - } - } -} +using System; +using System.Linq; +using System.Xml; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors.ValueConverters +{ + [DefaultPropertyValueConverter] + public class DatePickerValueConverter : PropertyValueConverterBase + { + private static readonly string[] PropertyEditorAliases = + { + Constants.PropertyEditors.Aliases.DateTime, + Constants.PropertyEditors.Aliases.Date + }; + + public override bool IsConverter(PublishedPropertyType propertyType) + => PropertyEditorAliases.Contains(propertyType.EditorAlias); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (DateTime); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + if (source == null) return DateTime.MinValue; + + // in XML a DateTime is: string - format "yyyy-MM-ddTHH:mm:ss" + // Actually, not always sometimes it is formatted in UTC style with 'Z' suffixed on the end but that is due to this bug: + // http://issues.umbraco.org/issue/U4-4145, http://issues.umbraco.org/issue/U4-3894 + // We should just be using TryConvertTo instead. + + if (source is string sourceString) + { + var attempt = sourceString.TryConvertTo(); + return attempt.Success == false ? DateTime.MinValue : attempt.Result; + } + + // in the database a DateTime is: DateTime + // default value is: DateTime.MinValue + return source is DateTime ? source : DateTime.MinValue; + } + + // default ConvertSourceToObject just returns source ie a DateTime value + + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + // source should come from ConvertSource and be a DateTime already + return XmlConvert.ToString((DateTime) inter, XmlDateTimeSerializationMode.Unspecified); + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs index 51c902fe1a..e0abf17a7e 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs @@ -1,23 +1,23 @@ -using System; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Core.PropertyEditors.ValueConverters -{ - [DefaultPropertyValueConverter] - public class IntegerValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(PublishedPropertyType propertyType) - => Constants.PropertyEditors.Aliases.Integer.Equals(propertyType.EditorAlias); - - public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (int); - - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) - { - return source.TryConvertTo().Result; - } - } -} +using System; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors.ValueConverters +{ + [DefaultPropertyValueConverter] + public class IntegerValueConverter : PropertyValueConverterBase + { + public override bool IsConverter(PublishedPropertyType propertyType) + => Constants.PropertyEditors.Aliases.Integer.Equals(propertyType.EditorAlias); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (int); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + return source.TryConvertTo().Result; + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs index 243dccaf0f..aeeb1a795f 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs @@ -1,70 +1,70 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Core.PropertyEditors.ValueConverters -{ - /// - /// The default converter for all property editors that expose a JSON value type - /// - /// - /// Since this is a default (umbraco) converter it will be ignored if another converter found conflicts with this one. - /// - [DefaultPropertyValueConverter] - public class JsonValueConverter : PropertyValueConverterBase - { - private readonly PropertyEditorCollection _propertyEditors; - - /// - /// Initializes a new instance of the class. - /// - public JsonValueConverter(PropertyEditorCollection propertyEditors) - { - _propertyEditors = propertyEditors; - } - - /// - /// It is a converter for any value type that is "JSON" - /// - /// - /// - public override bool IsConverter(PublishedPropertyType propertyType) - { - return _propertyEditors.TryGet(propertyType.EditorAlias, out var editor) - && editor.GetValueEditor().ValueType.InvariantEquals(ValueTypes.Json); - } - - public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (JToken); - - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) return null; - var sourceString = source.ToString(); - - if (sourceString.DetectIsJson()) - { - try - { - var obj = JsonConvert.DeserializeObject(sourceString); - return obj; - } - catch (Exception ex) - { - Current.Logger.Error("Could not parse the string " + sourceString + " to a json object", ex); - } - } - - //it's not json, just return the string - return sourceString; - } - - //TODO: Now to convert that to XPath! - } -} +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors.ValueConverters +{ + /// + /// The default converter for all property editors that expose a JSON value type + /// + /// + /// Since this is a default (umbraco) converter it will be ignored if another converter found conflicts with this one. + /// + [DefaultPropertyValueConverter] + public class JsonValueConverter : PropertyValueConverterBase + { + private readonly PropertyEditorCollection _propertyEditors; + + /// + /// Initializes a new instance of the class. + /// + public JsonValueConverter(PropertyEditorCollection propertyEditors) + { + _propertyEditors = propertyEditors; + } + + /// + /// It is a converter for any value type that is "JSON" + /// + /// + /// + public override bool IsConverter(PublishedPropertyType propertyType) + { + return _propertyEditors.TryGet(propertyType.EditorAlias, out var editor) + && editor.GetValueEditor().ValueType.InvariantEquals(ValueTypes.Json); + } + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (JToken); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + if (source == null) return null; + var sourceString = source.ToString(); + + if (sourceString.DetectIsJson()) + { + try + { + var obj = JsonConvert.DeserializeObject(sourceString); + return obj; + } + catch (Exception ex) + { + Current.Logger.Error("Could not parse the string " + sourceString + " to a json object", ex); + } + } + + //it's not json, just return the string + return sourceString; + } + + //TODO: Now to convert that to XPath! + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index b2c53e93e6..aeacf33eef 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -1,40 +1,40 @@ -using System; -using System.Web; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Core.PropertyEditors.ValueConverters -{ - [DefaultPropertyValueConverter] - public class MarkdownEditorValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(PublishedPropertyType propertyType) - => Constants.PropertyEditors.Aliases.MarkdownEditor.Equals(propertyType.EditorAlias); - - public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (IHtmlString); - - // PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) - { - // in xml a string is: string - // in the database a string is: string - // default value is: null - return source; - } - - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return new HtmlString(inter == null ? string.Empty : (string) inter); - } - - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return inter?.ToString() ?? string.Empty; - } - } -} +using System; +using System.Web; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors.ValueConverters +{ + [DefaultPropertyValueConverter] + public class MarkdownEditorValueConverter : PropertyValueConverterBase + { + public override bool IsConverter(PublishedPropertyType propertyType) + => Constants.PropertyEditors.Aliases.MarkdownEditor.Equals(propertyType.EditorAlias); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (IHtmlString); + + // PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + // in xml a string is: string + // in the database a string is: string + // default value is: null + return source; + } + + public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + // source should come from ConvertSource and be a string (or null) already + return new HtmlString(inter == null ? string.Empty : (string) inter); + } + + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + // source should come from ConvertSource and be a string (or null) already + return inter?.ToString() ?? string.Empty; + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs index b91383715a..2b5311da11 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs @@ -1,78 +1,78 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Core.PropertyEditors.ValueConverters -{ - [DefaultPropertyValueConverter] - public class MultipleTextStringValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(PublishedPropertyType propertyType) - => Constants.PropertyEditors.Aliases.MultipleTextstring.Equals(propertyType.EditorAlias); - - public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (IEnumerable); - - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - private static readonly string[] NewLineDelimiters = { "\r\n", "\r", "\n" }; - - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) - { - // data is (both in database and xml): - // - // - // Strong - // Flexible - // Efficient - // - // - - var sourceString = source?.ToString(); - if (string.IsNullOrWhiteSpace(sourceString)) return Enumerable.Empty(); - - //SD: I have no idea why this logic is here, I'm pretty sure we've never saved the multiple txt string - // as xml in the database, it's always been new line delimited. Will ask Stephen about this. - // In the meantime, we'll do this xml check, see if it parses and if not just continue with - // splitting by newline - // - // RS: SD/Stephan Please consider post before deciding to remove - //// https://our.umbraco.org/forum/contributing-to-umbraco-cms/76989-keep-the-xml-values-in-the-multipletextstringvalueconverter - var values = new List(); - var pos = sourceString.IndexOf("", StringComparison.Ordinal); - while (pos >= 0) - { - pos += "".Length; - var npos = sourceString.IndexOf("<", pos, StringComparison.Ordinal); - var value = sourceString.Substring(pos, npos - pos); - values.Add(value); - pos = sourceString.IndexOf("", pos, StringComparison.Ordinal); - } - - // fall back on normal behaviour - return values.Any() == false - ? sourceString.Split(NewLineDelimiters, StringSplitOptions.None) - : values.ToArray(); - } - - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - var d = new XmlDocument(); - var e = d.CreateElement("values"); - d.AppendChild(e); - - var values = (IEnumerable) inter; - foreach (var value in values) - { - var ee = d.CreateElement("value"); - ee.InnerText = value; - e.AppendChild(ee); - } - - return d.CreateNavigator(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors.ValueConverters +{ + [DefaultPropertyValueConverter] + public class MultipleTextStringValueConverter : PropertyValueConverterBase + { + public override bool IsConverter(PublishedPropertyType propertyType) + => Constants.PropertyEditors.Aliases.MultipleTextstring.Equals(propertyType.EditorAlias); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (IEnumerable); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + private static readonly string[] NewLineDelimiters = { "\r\n", "\r", "\n" }; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + // data is (both in database and xml): + // + // + // Strong + // Flexible + // Efficient + // + // + + var sourceString = source?.ToString(); + if (string.IsNullOrWhiteSpace(sourceString)) return Enumerable.Empty(); + + //SD: I have no idea why this logic is here, I'm pretty sure we've never saved the multiple txt string + // as xml in the database, it's always been new line delimited. Will ask Stephen about this. + // In the meantime, we'll do this xml check, see if it parses and if not just continue with + // splitting by newline + // + // RS: SD/Stephan Please consider post before deciding to remove + //// https://our.umbraco.org/forum/contributing-to-umbraco-cms/76989-keep-the-xml-values-in-the-multipletextstringvalueconverter + var values = new List(); + var pos = sourceString.IndexOf("", StringComparison.Ordinal); + while (pos >= 0) + { + pos += "".Length; + var npos = sourceString.IndexOf("<", pos, StringComparison.Ordinal); + var value = sourceString.Substring(pos, npos - pos); + values.Add(value); + pos = sourceString.IndexOf("", pos, StringComparison.Ordinal); + } + + // fall back on normal behaviour + return values.Any() == false + ? sourceString.Split(NewLineDelimiters, StringSplitOptions.None) + : values.ToArray(); + } + + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + var d = new XmlDocument(); + var e = d.CreateElement("values"); + d.AppendChild(e); + + var values = (IEnumerable) inter; + foreach (var value in values) + { + var ee = d.CreateElement("value"); + ee.InnerText = value; + e.AppendChild(ee); + } + + return d.CreateNavigator(); + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs index 854fd4ca2a..2368a1d034 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -1,45 +1,45 @@ -using System; -using System.Linq; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Core.PropertyEditors.ValueConverters -{ - [DefaultPropertyValueConverter] - public class TextStringValueConverter : PropertyValueConverterBase - { - private static readonly string[] PropertyTypeAliases = - { - Constants.PropertyEditors.Aliases.TextBox, - Constants.PropertyEditors.Aliases.TextArea - }; - - public override bool IsConverter(PublishedPropertyType propertyType) - => PropertyTypeAliases.Contains(propertyType.EditorAlias); - - public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (string); - - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) - { - // in xml a string is: string - // in the database a string is: string - // default value is: null - return source; - } - - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return inter ?? string.Empty; - } - - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return inter; - } - } -} +using System; +using System.Linq; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors.ValueConverters +{ + [DefaultPropertyValueConverter] + public class TextStringValueConverter : PropertyValueConverterBase + { + private static readonly string[] PropertyTypeAliases = + { + Constants.PropertyEditors.Aliases.TextBox, + Constants.PropertyEditors.Aliases.TextArea + }; + + public override bool IsConverter(PublishedPropertyType propertyType) + => PropertyTypeAliases.Contains(propertyType.EditorAlias); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (string); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + // in xml a string is: string + // in the database a string is: string + // default value is: null + return source; + } + + public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + // source should come from ConvertSource and be a string (or null) already + return inter ?? string.Empty; + } + + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + // source should come from ConvertSource and be a string (or null) already + return inter; + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs index be7f4b07a2..46f660d829 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs @@ -1,43 +1,43 @@ -using System; -using System.Web; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Core.PropertyEditors.ValueConverters -{ - /// - /// Value converter for the RTE so that it always returns IHtmlString so that Html.Raw doesn't have to be used. - /// - [DefaultPropertyValueConverter] - public class TinyMceValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(PublishedPropertyType propertyType) - => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce; - - public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (IHtmlString); - - // PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) - { - // in xml a string is: string - // in the database a string is: string - // default value is: null - return source; - } - - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return new HtmlString(inter == null ? string.Empty : (string)inter); - } - - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return inter; - } - } -} +using System; +using System.Web; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors.ValueConverters +{ + /// + /// Value converter for the RTE so that it always returns IHtmlString so that Html.Raw doesn't have to be used. + /// + [DefaultPropertyValueConverter] + public class TinyMceValueConverter : PropertyValueConverterBase + { + public override bool IsConverter(PublishedPropertyType propertyType) + => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce; + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (IHtmlString); + + // PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + // in xml a string is: string + // in the database a string is: string + // default value is: null + return source; + } + + public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + // source should come from ConvertSource and be a string (or null) already + return new HtmlString(inter == null ? string.Empty : (string)inter); + } + + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + // source should come from ConvertSource and be a string (or null) already + return inter; + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs index abb66dfa50..09170b746a 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs @@ -1,54 +1,54 @@ -using System; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Core.PropertyEditors.ValueConverters -{ - [DefaultPropertyValueConverter] - public class YesNoValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(PublishedPropertyType propertyType) - => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.Boolean; - - public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (bool); - - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) - { - // in xml a boolean is: string - // in the database a boolean is: string "1" or "0" or empty - // typically the converter does not need to handle anything else ("true"...) - // however there are cases where the value passed to the converter could be a non-string object, e.g. int, bool - - if (source is string s) - { - if (s.Length == 0 || s == "0") - return false; - - if (s == "1") - return true; - - return bool.TryParse(s, out bool result) && result; - } - - if (source is int) - return (int)source == 1; - - if (source is bool) - return (bool)source; - - // default value is: false - return false; - } - - // default ConvertSourceToObject just returns source ie a boolean value - - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - // source should come from ConvertSource and be a boolean already - return (bool)inter ? "1" : "0"; - } - } -} +using System; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors.ValueConverters +{ + [DefaultPropertyValueConverter] + public class YesNoValueConverter : PropertyValueConverterBase + { + public override bool IsConverter(PublishedPropertyType propertyType) + => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.Boolean; + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (bool); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + // in xml a boolean is: string + // in the database a boolean is: string "1" or "0" or empty + // typically the converter does not need to handle anything else ("true"...) + // however there are cases where the value passed to the converter could be a non-string object, e.g. int, bool + + if (source is string s) + { + if (s.Length == 0 || s == "0") + return false; + + if (s == "1") + return true; + + return bool.TryParse(s, out bool result) && result; + } + + if (source is int) + return (int)source == 1; + + if (source is bool) + return (bool)source; + + // default value is: false + return false; + } + + // default ConvertSourceToObject just returns source ie a boolean value + + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + // source should come from ConvertSource and be a boolean already + return (bool)inter ? "1" : "0"; + } + } +} diff --git a/src/Umbraco.Core/ReadLock.cs b/src/Umbraco.Core/ReadLock.cs index 6e6ecf14b8..d575401b10 100644 --- a/src/Umbraco.Core/ReadLock.cs +++ b/src/Umbraco.Core/ReadLock.cs @@ -1,36 +1,36 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; - -namespace Umbraco.Core -{ - /// - /// Provides a convenience methodology for implementing locked access to resources. - /// - /// - /// Intended as an infrastructure class. - /// This is a very unefficient way to lock as it allocates one object each time we lock, - /// so it's OK to use this class for things that happen once, where it is convenient, but not - /// for performance-critical code! - /// - public class ReadLock : IDisposable - { - private readonly ReaderWriterLockSlim _rwLock; - - /// - /// Initializes a new instance of the class. - /// - public ReadLock(ReaderWriterLockSlim rwLock) - { - _rwLock = rwLock; - _rwLock.EnterReadLock(); - } - - void IDisposable.Dispose() - { - _rwLock.ExitReadLock(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Umbraco.Core +{ + /// + /// Provides a convenience methodology for implementing locked access to resources. + /// + /// + /// Intended as an infrastructure class. + /// This is a very unefficient way to lock as it allocates one object each time we lock, + /// so it's OK to use this class for things that happen once, where it is convenient, but not + /// for performance-critical code! + /// + public class ReadLock : IDisposable + { + private readonly ReaderWriterLockSlim _rwLock; + + /// + /// Initializes a new instance of the class. + /// + public ReadLock(ReaderWriterLockSlim rwLock) + { + _rwLock = rwLock; + _rwLock.EnterReadLock(); + } + + void IDisposable.Dispose() + { + _rwLock.ExitReadLock(); + } + } +} diff --git a/src/Umbraco.Core/RenderingEngine.cs b/src/Umbraco.Core/RenderingEngine.cs index 6e55bfdaca..ac1c9dce56 100644 --- a/src/Umbraco.Core/RenderingEngine.cs +++ b/src/Umbraco.Core/RenderingEngine.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Core -{ - public enum RenderingEngine - { - Unknown, - Mvc, - WebForms - } -} +namespace Umbraco.Core +{ + public enum RenderingEngine + { + Unknown, + Mvc, + WebForms + } +} diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 74170a4335..ea9ce4458a 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -1,966 +1,966 @@ -using System; -using System.Collections.Specialized; -using System.ComponentModel.DataAnnotations; -using System.Configuration.Provider; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using System.Web; -using System.Web.Configuration; -using System.Web.Hosting; -using System.Web.Security; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Security -{ - /// - /// A base membership provider class offering much of the underlying functionality for initializing and password encryption/hashing. - /// - public abstract class MembershipProviderBase : MembershipProvider - { - - public string HashPasswordForStorage(string password) - { - string salt; - var hashed = EncryptOrHashNewPassword(password, out salt); - return FormatPasswordForStorage(hashed, salt); - } - - public bool VerifyPassword(string password, string hashedPassword) - { - if (string.IsNullOrWhiteSpace(hashedPassword)) throw new ArgumentException("Value cannot be null or whitespace.", "hashedPassword"); - return CheckPassword(password, hashedPassword); - } - - /// - /// Providers can override this setting, default is 10 - /// - public virtual int DefaultMinPasswordLength - { - get { return 10; } - } - - /// - /// Providers can override this setting, default is 0 - /// - public virtual int DefaultMinNonAlphanumericChars - { - get { return 0; } - } - - /// - /// Providers can override this setting, default is false to use better security - /// - public virtual bool DefaultUseLegacyEncoding - { - get { return false; } - } - - /// - /// Providers can override this setting, by default this is false which means that the provider will - /// authenticate the username + password when ChangePassword is called. This property exists purely for - /// backwards compatibility. - /// - public virtual bool AllowManuallyChangingPassword - { - get { return false; } - } - - /// - /// Returns the raw password value for a given user - /// - /// - /// - /// - /// By default this will return an invalid attempt, inheritors will need to override this to support it - /// - protected virtual Attempt GetRawPassword(string username) - { - return Attempt.Fail(); - } - - private string _applicationName; - private bool _enablePasswordReset; - private bool _enablePasswordRetrieval; - private int _maxInvalidPasswordAttempts; - private int _minRequiredNonAlphanumericCharacters; - private int _minRequiredPasswordLength; - private int _passwordAttemptWindow; - private MembershipPasswordFormat _passwordFormat; - private string _passwordStrengthRegularExpression; - private bool _requiresQuestionAndAnswer; - private bool _requiresUniqueEmail; - private string _customHashAlgorithmType ; - - public bool UseLegacyEncoding { get; private set; } - - #region Properties - - - /// - /// Indicates whether the membership provider is configured to allow users to reset their passwords. - /// - /// - /// true if the membership provider supports password reset; otherwise, false. The default is true. - public override bool EnablePasswordReset - { - get { return _enablePasswordReset; } - } - - /// - /// Indicates whether the membership provider is configured to allow users to retrieve their passwords. - /// - /// - /// true if the membership provider is configured to support password retrieval; otherwise, false. The default is false. - public override bool EnablePasswordRetrieval - { - get { return _enablePasswordRetrieval; } - } - - /// - /// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out. - /// - /// - /// The number of invalid password or password-answer attempts allowed before the membership user is locked out. - public override int MaxInvalidPasswordAttempts - { - get { return _maxInvalidPasswordAttempts; } - } - - /// - /// Gets the minimum number of special characters that must be present in a valid password. - /// - /// - /// The minimum number of special characters that must be present in a valid password. - public override int MinRequiredNonAlphanumericCharacters - { - get { return _minRequiredNonAlphanumericCharacters; } - } - - /// - /// Gets the minimum length required for a password. - /// - /// - /// The minimum length required for a password. - public override int MinRequiredPasswordLength - { - get { return _minRequiredPasswordLength; } - } - - /// - /// Gets the number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. - /// - /// - /// The number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. - public override int PasswordAttemptWindow - { - get { return _passwordAttemptWindow; } - } - - /// - /// Gets a value indicating the format for storing passwords in the membership data store. - /// - /// - /// One of the values indicating the format for storing passwords in the data store. - public override MembershipPasswordFormat PasswordFormat - { - get { return _passwordFormat; } - } - - /// - /// Gets the regular expression used to evaluate a password. - /// - /// - /// A regular expression used to evaluate a password. - public override string PasswordStrengthRegularExpression - { - get { return _passwordStrengthRegularExpression; } - } - - /// - /// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval. - /// - /// - /// true if a password answer is required for password reset and retrieval; otherwise, false. The default is true. - public override bool RequiresQuestionAndAnswer - { - get { return _requiresQuestionAndAnswer; } - } - - /// - /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name. - /// - /// - /// true if the membership provider requires a unique e-mail address; otherwise, false. The default is true. - public override bool RequiresUniqueEmail - { - get { return _requiresUniqueEmail; } - } - - /// - /// The name of the application using the custom membership provider. - /// - /// - /// The name of the application using the custom membership provider. - public override string ApplicationName - { - get - { - return _applicationName; - } - set - { - if (string.IsNullOrEmpty(value)) - throw new ProviderException("ApplicationName cannot be empty."); - - if (value.Length > 0x100) - throw new ProviderException("Provider application name too long."); - - _applicationName = value; - } - } - - #endregion - - /// - /// Initializes the provider. - /// - /// The friendly name of the provider. - /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. - /// The name of the provider is null. - /// An attempt is made to call - /// on a provider after the provider - /// has already been initialized. - /// The name of the provider has a length of zero. - public override void Initialize(string name, NameValueCollection config) - { - // Initialize base provider class - base.Initialize(name, config); - - _enablePasswordRetrieval = config.GetValue("enablePasswordRetrieval", false); - _enablePasswordReset = config.GetValue("enablePasswordReset", true); - _requiresQuestionAndAnswer = config.GetValue("requiresQuestionAndAnswer", false); - _requiresUniqueEmail = config.GetValue("requiresUniqueEmail", true); - _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); - _passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0); - _minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", DefaultMinPasswordLength, true, 0x80); - _minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", DefaultMinNonAlphanumericChars, true, 0x80); - _passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"]; - - _applicationName = config["applicationName"]; - if (string.IsNullOrEmpty(_applicationName)) - _applicationName = GetDefaultAppName(); - - //by default we will continue using the legacy encoding. - UseLegacyEncoding = config.GetValue("useLegacyEncoding", DefaultUseLegacyEncoding); - - // make sure password format is Hashed by default. - string str = config["passwordFormat"] ?? "Hashed"; - - switch (str.ToLower()) - { - case "clear": - _passwordFormat = MembershipPasswordFormat.Clear; - break; - - case "encrypted": - _passwordFormat = MembershipPasswordFormat.Encrypted; - break; - - case "hashed": - _passwordFormat = MembershipPasswordFormat.Hashed; - break; - - default: - throw new ProviderException("Provider bad password format"); - } - - if ((PasswordFormat == MembershipPasswordFormat.Hashed) && EnablePasswordRetrieval) - { - var ex = new ProviderException("Provider can not retrieve a hashed password"); - Current.Logger.Error("Cannot specify a Hashed password format with the enabledPasswordRetrieval option set to true", ex); - throw ex; - } - - _customHashAlgorithmType = config.GetValue("hashAlgorithmType", string.Empty); - } - - /// - /// Override this method to ensure the password is valid before raising the event - /// - /// - protected override void OnValidatingPassword(ValidatePasswordEventArgs e) - { - var attempt = IsPasswordValid(e.Password, MinRequiredNonAlphanumericCharacters, PasswordStrengthRegularExpression, MinRequiredPasswordLength); - if (attempt.Success == false) - { - e.Cancel = true; - return; - } - - base.OnValidatingPassword(e); - } - - protected internal enum PasswordValidityError - { - Ok, - Length, - AlphanumericChars, - Strength - } - - /// - /// Processes a request to update the password for a membership user. - /// - /// The user to update the password for. - /// Required to change a user password if the user is not new and AllowManuallyChangingPassword is false - /// The new password for the specified user. - /// - /// true if the password was updated successfully; otherwise, false. - /// - /// - /// Checks to ensure the AllowManuallyChangingPassword rule is adhered to - /// - public override bool ChangePassword(string username, string oldPassword, string newPassword) - { - string rawPasswordValue = string.Empty; - if (oldPassword.IsNullOrWhiteSpace() && AllowManuallyChangingPassword == false) - { - //we need to lookup the member since this could be a brand new member without a password set - var rawPassword = GetRawPassword(username); - rawPasswordValue = rawPassword.Success ? rawPassword.Result : string.Empty; - if (rawPassword.Success == false || rawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix) == false) - { - //If the old password is empty and AllowManuallyChangingPassword is false, than this provider cannot just arbitrarily change the password - throw new NotSupportedException("This provider does not support manually changing the password"); - } - } - - var args = new ValidatePasswordEventArgs(username, newPassword, false); - OnValidatingPassword(args); - - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Change password canceled due to password validation failure."); - } - - //Special cases to allow changing password without validating existing credentials - // * the member is new and doesn't have a password set - // * during installation to set the admin password - var installing = Current.RuntimeState.Level == RuntimeLevel.Install; - if (AllowManuallyChangingPassword == false - && (rawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix) - || (installing && oldPassword == "default"))) - { - return PerformChangePassword(username, oldPassword, newPassword); - } - - if (AllowManuallyChangingPassword == false) - { - if (ValidateUser(username, oldPassword) == false) return false; - } - - return PerformChangePassword(username, oldPassword, newPassword); - } - - /// - /// Processes a request to update the password for a membership user. - /// - /// The user to update the password for. - /// This property is ignore for this provider - /// The new password for the specified user. - /// - /// true if the password was updated successfully; otherwise, false. - /// - protected abstract bool PerformChangePassword(string username, string oldPassword, string newPassword); - - /// - /// Processes a request to update the password question and answer for a membership user. - /// - /// The user to change the password question and answer for. - /// The password for the specified user. - /// The new password question for the specified user. - /// The new password answer for the specified user. - /// - /// true if the password question and answer are updated successfully; otherwise, false. - /// - /// - /// Performs the basic validation before passing off to PerformChangePasswordQuestionAndAnswer - /// - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) - { - if (RequiresQuestionAndAnswer == false) - { - throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config"); - } - - if (AllowManuallyChangingPassword == false) - { - if (ValidateUser(username, password) == false) - { - return false; - } - } - - return PerformChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); - } - - /// - /// Processes a request to update the password question and answer for a membership user. - /// - /// The user to change the password question and answer for. - /// The password for the specified user. - /// The new password question for the specified user. - /// The new password answer for the specified user. - /// - /// true if the password question and answer are updated successfully; otherwise, false. - /// - protected abstract bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer); - - /// - /// Adds a new membership user to the data source. - /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - /// - /// Ensures the ValidatingPassword event is executed before executing PerformCreateUser and performs basic membership provider validation of values. - /// - public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - var valStatus = ValidateNewUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey); - if (valStatus != MembershipCreateStatus.Success) - { - status = valStatus; - return null; - } - - return PerformCreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Performs the validation of the information for creating a new user - /// - /// - /// - /// - /// - /// - /// - /// - /// - protected MembershipCreateStatus ValidateNewUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey) - { - var args = new ValidatePasswordEventArgs(username, password, true); - OnValidatingPassword(args); - if (args.Cancel) - { - return MembershipCreateStatus.InvalidPassword; - } - - // Validate password - var passwordValidAttempt = IsPasswordValid(password, MinRequiredNonAlphanumericCharacters, PasswordStrengthRegularExpression, MinRequiredPasswordLength); - if (passwordValidAttempt.Success == false) - { - return MembershipCreateStatus.InvalidPassword; - } - - // Validate email - if (IsEmailValid(email) == false) - { - return MembershipCreateStatus.InvalidEmail; - } - - // Make sure username isn't all whitespace - if (string.IsNullOrWhiteSpace(username.Trim())) - { - return MembershipCreateStatus.InvalidUserName; - } - - // Check password question - if (string.IsNullOrWhiteSpace(passwordQuestion) && RequiresQuestionAndAnswer) - { - return MembershipCreateStatus.InvalidQuestion; - } - - // Check password answer - if (string.IsNullOrWhiteSpace(passwordAnswer) && RequiresQuestionAndAnswer) - { - return MembershipCreateStatus.InvalidAnswer; - } - - return MembershipCreateStatus.Success; - } - - /// - /// Adds a new membership user to the data source. - /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - protected abstract MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); - - /// - /// Gets the members password if password retreival is enabled - /// - /// - /// - /// - public override string GetPassword(string username, string answer) - { - if (EnablePasswordRetrieval == false) - throw new ProviderException("Password Retrieval Not Enabled."); - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - throw new ProviderException("Cannot retrieve Hashed passwords."); - - return PerformGetPassword(username, answer); - } - - /// - /// Gets the members password if password retreival is enabled - /// - /// - /// - /// - protected abstract string PerformGetPassword(string username, string answer); - - public override string ResetPassword(string username, string answer) - { - var userService = Current.Services.UserService; - - var canReset = this.CanResetPassword(userService); - - if (canReset == false) - { - throw new NotSupportedException("Password reset is not supported"); - } - - var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); - - var args = new ValidatePasswordEventArgs(username, newPassword, true); - OnValidatingPassword(args); - if (args.Cancel) - { - if (args.FailureInformation != null) - { - throw args.FailureInformation; - } - throw new MembershipPasswordException("Reset password canceled due to password validation failure."); - } - - return PerformResetPassword(username, answer, newPassword); - } - - protected abstract string PerformResetPassword(string username, string answer, string generatedPassword); - - protected internal static Attempt IsPasswordValid(string password, int minRequiredNonAlphanumericChars, string strengthRegex, int minLength) - { - if (minRequiredNonAlphanumericChars > 0) - { - var nonAlphaNumeric = Regex.Replace(password, "[a-zA-Z0-9]", "", RegexOptions.Multiline | RegexOptions.IgnoreCase); - if (nonAlphaNumeric.Length < minRequiredNonAlphanumericChars) - { - return Attempt.Fail(PasswordValidityError.AlphanumericChars); - } - } - - if (string.IsNullOrEmpty(strengthRegex) == false) - { - if (Regex.IsMatch(password, strengthRegex, RegexOptions.Compiled) == false) - { - return Attempt.Fail(PasswordValidityError.Strength); - } - - } - - if (password.Length < minLength) - { - return Attempt.Fail(PasswordValidityError.Length); - } - - return Attempt.Succeed(PasswordValidityError.Ok); - } - - /// - /// Gets the name of the default app. - /// - /// - internal static string GetDefaultAppName() - { - try - { - string applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath; - if (string.IsNullOrEmpty(applicationVirtualPath)) - { - return "/"; - } - return applicationVirtualPath; - } - catch - { - return "/"; - } - } - - internal static int GetIntValue(NameValueCollection config, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed) - { - int num; - string s = config[valueName]; - if (s == null) - { - return defaultValue; - } - if (!int.TryParse(s, out num)) - { - if (zeroAllowed) - { - throw new ProviderException("Value must be non negative integer"); - } - throw new ProviderException("Value must be positive integer"); - } - if (zeroAllowed && (num < 0)) - { - throw new ProviderException("Value must be non negativeinteger"); - } - if (!zeroAllowed && (num <= 0)) - { - throw new ProviderException("Value must be positive integer"); - } - if ((maxValueAllowed > 0) && (num > maxValueAllowed)) - { - throw new ProviderException("Value too big"); - } - return num; - } - - /// - /// If the password format is a hashed keyed algorithm then we will pre-pend the salt used to hash the password - /// to the hashed password itself. - /// - /// - /// - /// - protected internal string FormatPasswordForStorage(string pass, string salt) - { - if (UseLegacyEncoding) - { - return pass; - } - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - { - //the better way, we use salt per member - return salt + pass; - } - return pass; - } - - internal static bool IsEmailValid(string email) - { - return new EmailAddressAttribute().IsValid(email); - } - - protected internal string EncryptOrHashPassword(string pass, string salt) - { - //if we are doing it the old way - - if (UseLegacyEncoding) - { - return LegacyEncodePassword(pass); - } - - //This is the correct way to implement this (as per the sql membership provider) - - if (PasswordFormat == MembershipPasswordFormat.Clear) - return pass; - var bytes = Encoding.Unicode.GetBytes(pass); - var saltBytes = Convert.FromBase64String(salt); - byte[] inArray; - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - { - var hashAlgorithm = GetHashAlgorithm(pass); - var algorithm = hashAlgorithm as KeyedHashAlgorithm; - if (algorithm != null) - { - var keyedHashAlgorithm = algorithm; - if (keyedHashAlgorithm.Key.Length == saltBytes.Length) - { - //if the salt bytes is the required key length for the algorithm, use it as-is - keyedHashAlgorithm.Key = saltBytes; - } - else if (keyedHashAlgorithm.Key.Length < saltBytes.Length) - { - //if the salt bytes is too long for the required key length for the algorithm, reduce it - var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; - Buffer.BlockCopy(saltBytes, 0, numArray2, 0, numArray2.Length); - keyedHashAlgorithm.Key = numArray2; - } - else - { - //if the salt bytes is too long for the required key length for the algorithm, extend it - var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; - var dstOffset = 0; - while (dstOffset < numArray2.Length) - { - var count = Math.Min(saltBytes.Length, numArray2.Length - dstOffset); - Buffer.BlockCopy(saltBytes, 0, numArray2, dstOffset, count); - dstOffset += count; - } - keyedHashAlgorithm.Key = numArray2; - } - inArray = keyedHashAlgorithm.ComputeHash(bytes); - } - else - { - var buffer = new byte[saltBytes.Length + bytes.Length]; - Buffer.BlockCopy(saltBytes, 0, buffer, 0, saltBytes.Length); - Buffer.BlockCopy(bytes, 0, buffer, saltBytes.Length, bytes.Length); - inArray = hashAlgorithm.ComputeHash(buffer); - } - } - else - { - //this code is copied from the sql membership provider - pretty sure this could be nicely re-written to completely - // ignore the salt stuff since we are not salting the password when encrypting. - var password = new byte[saltBytes.Length + bytes.Length]; - Buffer.BlockCopy(saltBytes, 0, password, 0, saltBytes.Length); - Buffer.BlockCopy(bytes, 0, password, saltBytes.Length, bytes.Length); - inArray = EncryptPassword(password, MembershipPasswordCompatibilityMode.Framework40); - } - return Convert.ToBase64String(inArray); - } - - /// - /// Checks the password. - /// - /// The password. - /// The dbPassword. - /// - protected internal bool CheckPassword(string password, string dbPassword) - { - if (string.IsNullOrWhiteSpace(dbPassword)) throw new ArgumentException("Value cannot be null or whitespace.", "dbPassword"); - switch (PasswordFormat) - { - case MembershipPasswordFormat.Encrypted: - var decrypted = DecryptPassword(dbPassword); - return decrypted == password; - case MembershipPasswordFormat.Hashed: - string salt; - var storedHashedPass = StoredPassword(dbPassword, out salt); - var hashed = EncryptOrHashPassword(password, salt); - return storedHashedPass == hashed; - case MembershipPasswordFormat.Clear: - return password == dbPassword; - default: - throw new ArgumentOutOfRangeException(); - } - } - - /// - /// Encrypt/hash a new password with a new salt - /// - /// - /// - /// - protected internal string EncryptOrHashNewPassword(string newPassword, out string salt) - { - salt = GenerateSalt(); - return EncryptOrHashPassword(newPassword, salt); - } - - protected internal string DecryptPassword(string pass) - { - //if we are doing it the old way - - if (UseLegacyEncoding) - { - return LegacyUnEncodePassword(pass); - } - - //This is the correct way to implement this (as per the sql membership provider) - - switch (PasswordFormat) - { - case MembershipPasswordFormat.Clear: - return pass; - case MembershipPasswordFormat.Hashed: - throw new ProviderException("Provider can not decrypt hashed password"); - case MembershipPasswordFormat.Encrypted: - default: - var bytes = DecryptPassword(Convert.FromBase64String(pass)); - return bytes == null ? null : Encoding.Unicode.GetString(bytes, 16, bytes.Length - 16); - } - } - - /// - /// Returns the hashed password without the salt if it is hashed - /// - /// - /// returns the salt - /// - internal string StoredPassword(string storedString, out string salt) - { - if (string.IsNullOrWhiteSpace(storedString)) throw new ArgumentException("Value cannot be null or whitespace.", "storedString"); - if (UseLegacyEncoding) - { - salt = string.Empty; - return storedString; - } - - switch (PasswordFormat) - { - case MembershipPasswordFormat.Hashed: - var saltLen = GenerateSalt(); - salt = storedString.Substring(0, saltLen.Length); - return storedString.Substring(saltLen.Length); - case MembershipPasswordFormat.Clear: - case MembershipPasswordFormat.Encrypted: - default: - salt = string.Empty; - return storedString; - - } - } - - protected internal static string GenerateSalt() - { - var numArray = new byte[16]; - new RNGCryptoServiceProvider().GetBytes(numArray); - return Convert.ToBase64String(numArray); - } - - protected internal HashAlgorithm GetHashAlgorithm(string password) - { - if (UseLegacyEncoding) - { - //before we were never checking for an algorithm type so we were always using HMACSHA1 - // for any SHA specified algorithm :( so we'll need to keep doing that for backwards compat support. - if (Membership.HashAlgorithmType.InvariantContains("SHA")) - { - return new HMACSHA1 - { - //the legacy salt was actually the password :( - Key = Encoding.Unicode.GetBytes(password) - }; - } - } - - //get the algorithm by name - - if (_customHashAlgorithmType.IsNullOrWhiteSpace()) - { - _customHashAlgorithmType = Membership.HashAlgorithmType; - } - - var alg = HashAlgorithm.Create(_customHashAlgorithmType); - if (alg == null) - { - throw new InvalidOperationException("The hash algorithm specified " + Membership.HashAlgorithmType + " cannot be resolved"); - } - - return alg; - } - - /// - /// Encodes the password. - /// - /// The password. - /// The encoded password. - protected string LegacyEncodePassword(string password) - { - string encodedPassword = password; - switch (PasswordFormat) - { - case MembershipPasswordFormat.Clear: - break; - case MembershipPasswordFormat.Encrypted: - encodedPassword = - Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password))); - break; - case MembershipPasswordFormat.Hashed: - var hashAlgorith = GetHashAlgorithm(password); - encodedPassword = Convert.ToBase64String(hashAlgorith.ComputeHash(Encoding.Unicode.GetBytes(password))); - break; - default: - throw new ProviderException("Unsupported password format."); - } - return encodedPassword; - } - - /// - /// Unencode password. - /// - /// The encoded password. - /// The unencoded password. - protected string LegacyUnEncodePassword(string encodedPassword) - { - string password = encodedPassword; - switch (PasswordFormat) - { - case MembershipPasswordFormat.Clear: - break; - case MembershipPasswordFormat.Encrypted: - password = Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password))); - break; - case MembershipPasswordFormat.Hashed: - throw new ProviderException("Cannot unencode a hashed password."); - default: - throw new ProviderException("Unsupported password format."); - } - return password; - } - - public override string ToString() - { - var result = base.ToString(); - var sb = new StringBuilder(result); - sb.AppendLine("Name =" + Name); - sb.AppendLine("_applicationName =" + _applicationName); - sb.AppendLine("_enablePasswordReset=" + _enablePasswordReset); - sb.AppendLine("_enablePasswordRetrieval=" + _enablePasswordRetrieval); - sb.AppendLine("_maxInvalidPasswordAttempts=" + _maxInvalidPasswordAttempts); - sb.AppendLine("_minRequiredNonAlphanumericCharacters=" + _minRequiredNonAlphanumericCharacters); - sb.AppendLine("_minRequiredPasswordLength=" + _minRequiredPasswordLength); - sb.AppendLine("_passwordAttemptWindow=" + _passwordAttemptWindow); - sb.AppendLine("_passwordFormat=" + _passwordFormat); - sb.AppendLine("_passwordStrengthRegularExpression=" + _passwordStrengthRegularExpression); - sb.AppendLine("_requiresQuestionAndAnswer=" + _requiresQuestionAndAnswer); - sb.AppendLine("_requiresUniqueEmail=" + _requiresUniqueEmail); - return sb.ToString(); - } - - /// - /// Returns the current request IP address for logging if there is one - /// - /// - protected string GetCurrentRequestIpAddress() - { - var httpContext = HttpContext.Current == null ? (HttpContextBase) null : new HttpContextWrapper(HttpContext.Current); - return httpContext.GetCurrentRequestIpAddress(); - } - - } -} +using System; +using System.Collections.Specialized; +using System.ComponentModel.DataAnnotations; +using System.Configuration.Provider; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; +using System.Web.Configuration; +using System.Web.Hosting; +using System.Web.Security; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Security +{ + /// + /// A base membership provider class offering much of the underlying functionality for initializing and password encryption/hashing. + /// + public abstract class MembershipProviderBase : MembershipProvider + { + + public string HashPasswordForStorage(string password) + { + string salt; + var hashed = EncryptOrHashNewPassword(password, out salt); + return FormatPasswordForStorage(hashed, salt); + } + + public bool VerifyPassword(string password, string hashedPassword) + { + if (string.IsNullOrWhiteSpace(hashedPassword)) throw new ArgumentException("Value cannot be null or whitespace.", "hashedPassword"); + return CheckPassword(password, hashedPassword); + } + + /// + /// Providers can override this setting, default is 10 + /// + public virtual int DefaultMinPasswordLength + { + get { return 10; } + } + + /// + /// Providers can override this setting, default is 0 + /// + public virtual int DefaultMinNonAlphanumericChars + { + get { return 0; } + } + + /// + /// Providers can override this setting, default is false to use better security + /// + public virtual bool DefaultUseLegacyEncoding + { + get { return false; } + } + + /// + /// Providers can override this setting, by default this is false which means that the provider will + /// authenticate the username + password when ChangePassword is called. This property exists purely for + /// backwards compatibility. + /// + public virtual bool AllowManuallyChangingPassword + { + get { return false; } + } + + /// + /// Returns the raw password value for a given user + /// + /// + /// + /// + /// By default this will return an invalid attempt, inheritors will need to override this to support it + /// + protected virtual Attempt GetRawPassword(string username) + { + return Attempt.Fail(); + } + + private string _applicationName; + private bool _enablePasswordReset; + private bool _enablePasswordRetrieval; + private int _maxInvalidPasswordAttempts; + private int _minRequiredNonAlphanumericCharacters; + private int _minRequiredPasswordLength; + private int _passwordAttemptWindow; + private MembershipPasswordFormat _passwordFormat; + private string _passwordStrengthRegularExpression; + private bool _requiresQuestionAndAnswer; + private bool _requiresUniqueEmail; + private string _customHashAlgorithmType ; + + public bool UseLegacyEncoding { get; private set; } + + #region Properties + + + /// + /// Indicates whether the membership provider is configured to allow users to reset their passwords. + /// + /// + /// true if the membership provider supports password reset; otherwise, false. The default is true. + public override bool EnablePasswordReset + { + get { return _enablePasswordReset; } + } + + /// + /// Indicates whether the membership provider is configured to allow users to retrieve their passwords. + /// + /// + /// true if the membership provider is configured to support password retrieval; otherwise, false. The default is false. + public override bool EnablePasswordRetrieval + { + get { return _enablePasswordRetrieval; } + } + + /// + /// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out. + /// + /// + /// The number of invalid password or password-answer attempts allowed before the membership user is locked out. + public override int MaxInvalidPasswordAttempts + { + get { return _maxInvalidPasswordAttempts; } + } + + /// + /// Gets the minimum number of special characters that must be present in a valid password. + /// + /// + /// The minimum number of special characters that must be present in a valid password. + public override int MinRequiredNonAlphanumericCharacters + { + get { return _minRequiredNonAlphanumericCharacters; } + } + + /// + /// Gets the minimum length required for a password. + /// + /// + /// The minimum length required for a password. + public override int MinRequiredPasswordLength + { + get { return _minRequiredPasswordLength; } + } + + /// + /// Gets the number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. + /// + /// + /// The number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. + public override int PasswordAttemptWindow + { + get { return _passwordAttemptWindow; } + } + + /// + /// Gets a value indicating the format for storing passwords in the membership data store. + /// + /// + /// One of the values indicating the format for storing passwords in the data store. + public override MembershipPasswordFormat PasswordFormat + { + get { return _passwordFormat; } + } + + /// + /// Gets the regular expression used to evaluate a password. + /// + /// + /// A regular expression used to evaluate a password. + public override string PasswordStrengthRegularExpression + { + get { return _passwordStrengthRegularExpression; } + } + + /// + /// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval. + /// + /// + /// true if a password answer is required for password reset and retrieval; otherwise, false. The default is true. + public override bool RequiresQuestionAndAnswer + { + get { return _requiresQuestionAndAnswer; } + } + + /// + /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name. + /// + /// + /// true if the membership provider requires a unique e-mail address; otherwise, false. The default is true. + public override bool RequiresUniqueEmail + { + get { return _requiresUniqueEmail; } + } + + /// + /// The name of the application using the custom membership provider. + /// + /// + /// The name of the application using the custom membership provider. + public override string ApplicationName + { + get + { + return _applicationName; + } + set + { + if (string.IsNullOrEmpty(value)) + throw new ProviderException("ApplicationName cannot be empty."); + + if (value.Length > 0x100) + throw new ProviderException("Provider application name too long."); + + _applicationName = value; + } + } + + #endregion + + /// + /// Initializes the provider. + /// + /// The friendly name of the provider. + /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. + /// The name of the provider is null. + /// An attempt is made to call + /// on a provider after the provider + /// has already been initialized. + /// The name of the provider has a length of zero. + public override void Initialize(string name, NameValueCollection config) + { + // Initialize base provider class + base.Initialize(name, config); + + _enablePasswordRetrieval = config.GetValue("enablePasswordRetrieval", false); + _enablePasswordReset = config.GetValue("enablePasswordReset", true); + _requiresQuestionAndAnswer = config.GetValue("requiresQuestionAndAnswer", false); + _requiresUniqueEmail = config.GetValue("requiresUniqueEmail", true); + _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); + _passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0); + _minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", DefaultMinPasswordLength, true, 0x80); + _minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", DefaultMinNonAlphanumericChars, true, 0x80); + _passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"]; + + _applicationName = config["applicationName"]; + if (string.IsNullOrEmpty(_applicationName)) + _applicationName = GetDefaultAppName(); + + //by default we will continue using the legacy encoding. + UseLegacyEncoding = config.GetValue("useLegacyEncoding", DefaultUseLegacyEncoding); + + // make sure password format is Hashed by default. + string str = config["passwordFormat"] ?? "Hashed"; + + switch (str.ToLower()) + { + case "clear": + _passwordFormat = MembershipPasswordFormat.Clear; + break; + + case "encrypted": + _passwordFormat = MembershipPasswordFormat.Encrypted; + break; + + case "hashed": + _passwordFormat = MembershipPasswordFormat.Hashed; + break; + + default: + throw new ProviderException("Provider bad password format"); + } + + if ((PasswordFormat == MembershipPasswordFormat.Hashed) && EnablePasswordRetrieval) + { + var ex = new ProviderException("Provider can not retrieve a hashed password"); + Current.Logger.Error("Cannot specify a Hashed password format with the enabledPasswordRetrieval option set to true", ex); + throw ex; + } + + _customHashAlgorithmType = config.GetValue("hashAlgorithmType", string.Empty); + } + + /// + /// Override this method to ensure the password is valid before raising the event + /// + /// + protected override void OnValidatingPassword(ValidatePasswordEventArgs e) + { + var attempt = IsPasswordValid(e.Password, MinRequiredNonAlphanumericCharacters, PasswordStrengthRegularExpression, MinRequiredPasswordLength); + if (attempt.Success == false) + { + e.Cancel = true; + return; + } + + base.OnValidatingPassword(e); + } + + protected internal enum PasswordValidityError + { + Ok, + Length, + AlphanumericChars, + Strength + } + + /// + /// Processes a request to update the password for a membership user. + /// + /// The user to update the password for. + /// Required to change a user password if the user is not new and AllowManuallyChangingPassword is false + /// The new password for the specified user. + /// + /// true if the password was updated successfully; otherwise, false. + /// + /// + /// Checks to ensure the AllowManuallyChangingPassword rule is adhered to + /// + public override bool ChangePassword(string username, string oldPassword, string newPassword) + { + string rawPasswordValue = string.Empty; + if (oldPassword.IsNullOrWhiteSpace() && AllowManuallyChangingPassword == false) + { + //we need to lookup the member since this could be a brand new member without a password set + var rawPassword = GetRawPassword(username); + rawPasswordValue = rawPassword.Success ? rawPassword.Result : string.Empty; + if (rawPassword.Success == false || rawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix) == false) + { + //If the old password is empty and AllowManuallyChangingPassword is false, than this provider cannot just arbitrarily change the password + throw new NotSupportedException("This provider does not support manually changing the password"); + } + } + + var args = new ValidatePasswordEventArgs(username, newPassword, false); + OnValidatingPassword(args); + + if (args.Cancel) + { + if (args.FailureInformation != null) + throw args.FailureInformation; + throw new MembershipPasswordException("Change password canceled due to password validation failure."); + } + + //Special cases to allow changing password without validating existing credentials + // * the member is new and doesn't have a password set + // * during installation to set the admin password + var installing = Current.RuntimeState.Level == RuntimeLevel.Install; + if (AllowManuallyChangingPassword == false + && (rawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix) + || (installing && oldPassword == "default"))) + { + return PerformChangePassword(username, oldPassword, newPassword); + } + + if (AllowManuallyChangingPassword == false) + { + if (ValidateUser(username, oldPassword) == false) return false; + } + + return PerformChangePassword(username, oldPassword, newPassword); + } + + /// + /// Processes a request to update the password for a membership user. + /// + /// The user to update the password for. + /// This property is ignore for this provider + /// The new password for the specified user. + /// + /// true if the password was updated successfully; otherwise, false. + /// + protected abstract bool PerformChangePassword(string username, string oldPassword, string newPassword); + + /// + /// Processes a request to update the password question and answer for a membership user. + /// + /// The user to change the password question and answer for. + /// The password for the specified user. + /// The new password question for the specified user. + /// The new password answer for the specified user. + /// + /// true if the password question and answer are updated successfully; otherwise, false. + /// + /// + /// Performs the basic validation before passing off to PerformChangePasswordQuestionAndAnswer + /// + public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + if (RequiresQuestionAndAnswer == false) + { + throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config"); + } + + if (AllowManuallyChangingPassword == false) + { + if (ValidateUser(username, password) == false) + { + return false; + } + } + + return PerformChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); + } + + /// + /// Processes a request to update the password question and answer for a membership user. + /// + /// The user to change the password question and answer for. + /// The password for the specified user. + /// The new password question for the specified user. + /// The new password answer for the specified user. + /// + /// true if the password question and answer are updated successfully; otherwise, false. + /// + protected abstract bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer); + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + /// + /// Ensures the ValidatingPassword event is executed before executing PerformCreateUser and performs basic membership provider validation of values. + /// + public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + var valStatus = ValidateNewUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey); + if (valStatus != MembershipCreateStatus.Success) + { + status = valStatus; + return null; + } + + return PerformCreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Performs the validation of the information for creating a new user + /// + /// + /// + /// + /// + /// + /// + /// + /// + protected MembershipCreateStatus ValidateNewUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey) + { + var args = new ValidatePasswordEventArgs(username, password, true); + OnValidatingPassword(args); + if (args.Cancel) + { + return MembershipCreateStatus.InvalidPassword; + } + + // Validate password + var passwordValidAttempt = IsPasswordValid(password, MinRequiredNonAlphanumericCharacters, PasswordStrengthRegularExpression, MinRequiredPasswordLength); + if (passwordValidAttempt.Success == false) + { + return MembershipCreateStatus.InvalidPassword; + } + + // Validate email + if (IsEmailValid(email) == false) + { + return MembershipCreateStatus.InvalidEmail; + } + + // Make sure username isn't all whitespace + if (string.IsNullOrWhiteSpace(username.Trim())) + { + return MembershipCreateStatus.InvalidUserName; + } + + // Check password question + if (string.IsNullOrWhiteSpace(passwordQuestion) && RequiresQuestionAndAnswer) + { + return MembershipCreateStatus.InvalidQuestion; + } + + // Check password answer + if (string.IsNullOrWhiteSpace(passwordAnswer) && RequiresQuestionAndAnswer) + { + return MembershipCreateStatus.InvalidAnswer; + } + + return MembershipCreateStatus.Success; + } + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected abstract MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); + + /// + /// Gets the members password if password retreival is enabled + /// + /// + /// + /// + public override string GetPassword(string username, string answer) + { + if (EnablePasswordRetrieval == false) + throw new ProviderException("Password Retrieval Not Enabled."); + + if (PasswordFormat == MembershipPasswordFormat.Hashed) + throw new ProviderException("Cannot retrieve Hashed passwords."); + + return PerformGetPassword(username, answer); + } + + /// + /// Gets the members password if password retreival is enabled + /// + /// + /// + /// + protected abstract string PerformGetPassword(string username, string answer); + + public override string ResetPassword(string username, string answer) + { + var userService = Current.Services.UserService; + + var canReset = this.CanResetPassword(userService); + + if (canReset == false) + { + throw new NotSupportedException("Password reset is not supported"); + } + + var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); + + var args = new ValidatePasswordEventArgs(username, newPassword, true); + OnValidatingPassword(args); + if (args.Cancel) + { + if (args.FailureInformation != null) + { + throw args.FailureInformation; + } + throw new MembershipPasswordException("Reset password canceled due to password validation failure."); + } + + return PerformResetPassword(username, answer, newPassword); + } + + protected abstract string PerformResetPassword(string username, string answer, string generatedPassword); + + protected internal static Attempt IsPasswordValid(string password, int minRequiredNonAlphanumericChars, string strengthRegex, int minLength) + { + if (minRequiredNonAlphanumericChars > 0) + { + var nonAlphaNumeric = Regex.Replace(password, "[a-zA-Z0-9]", "", RegexOptions.Multiline | RegexOptions.IgnoreCase); + if (nonAlphaNumeric.Length < minRequiredNonAlphanumericChars) + { + return Attempt.Fail(PasswordValidityError.AlphanumericChars); + } + } + + if (string.IsNullOrEmpty(strengthRegex) == false) + { + if (Regex.IsMatch(password, strengthRegex, RegexOptions.Compiled) == false) + { + return Attempt.Fail(PasswordValidityError.Strength); + } + + } + + if (password.Length < minLength) + { + return Attempt.Fail(PasswordValidityError.Length); + } + + return Attempt.Succeed(PasswordValidityError.Ok); + } + + /// + /// Gets the name of the default app. + /// + /// + internal static string GetDefaultAppName() + { + try + { + string applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath; + if (string.IsNullOrEmpty(applicationVirtualPath)) + { + return "/"; + } + return applicationVirtualPath; + } + catch + { + return "/"; + } + } + + internal static int GetIntValue(NameValueCollection config, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed) + { + int num; + string s = config[valueName]; + if (s == null) + { + return defaultValue; + } + if (!int.TryParse(s, out num)) + { + if (zeroAllowed) + { + throw new ProviderException("Value must be non negative integer"); + } + throw new ProviderException("Value must be positive integer"); + } + if (zeroAllowed && (num < 0)) + { + throw new ProviderException("Value must be non negativeinteger"); + } + if (!zeroAllowed && (num <= 0)) + { + throw new ProviderException("Value must be positive integer"); + } + if ((maxValueAllowed > 0) && (num > maxValueAllowed)) + { + throw new ProviderException("Value too big"); + } + return num; + } + + /// + /// If the password format is a hashed keyed algorithm then we will pre-pend the salt used to hash the password + /// to the hashed password itself. + /// + /// + /// + /// + protected internal string FormatPasswordForStorage(string pass, string salt) + { + if (UseLegacyEncoding) + { + return pass; + } + + if (PasswordFormat == MembershipPasswordFormat.Hashed) + { + //the better way, we use salt per member + return salt + pass; + } + return pass; + } + + internal static bool IsEmailValid(string email) + { + return new EmailAddressAttribute().IsValid(email); + } + + protected internal string EncryptOrHashPassword(string pass, string salt) + { + //if we are doing it the old way + + if (UseLegacyEncoding) + { + return LegacyEncodePassword(pass); + } + + //This is the correct way to implement this (as per the sql membership provider) + + if (PasswordFormat == MembershipPasswordFormat.Clear) + return pass; + var bytes = Encoding.Unicode.GetBytes(pass); + var saltBytes = Convert.FromBase64String(salt); + byte[] inArray; + + if (PasswordFormat == MembershipPasswordFormat.Hashed) + { + var hashAlgorithm = GetHashAlgorithm(pass); + var algorithm = hashAlgorithm as KeyedHashAlgorithm; + if (algorithm != null) + { + var keyedHashAlgorithm = algorithm; + if (keyedHashAlgorithm.Key.Length == saltBytes.Length) + { + //if the salt bytes is the required key length for the algorithm, use it as-is + keyedHashAlgorithm.Key = saltBytes; + } + else if (keyedHashAlgorithm.Key.Length < saltBytes.Length) + { + //if the salt bytes is too long for the required key length for the algorithm, reduce it + var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; + Buffer.BlockCopy(saltBytes, 0, numArray2, 0, numArray2.Length); + keyedHashAlgorithm.Key = numArray2; + } + else + { + //if the salt bytes is too long for the required key length for the algorithm, extend it + var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; + var dstOffset = 0; + while (dstOffset < numArray2.Length) + { + var count = Math.Min(saltBytes.Length, numArray2.Length - dstOffset); + Buffer.BlockCopy(saltBytes, 0, numArray2, dstOffset, count); + dstOffset += count; + } + keyedHashAlgorithm.Key = numArray2; + } + inArray = keyedHashAlgorithm.ComputeHash(bytes); + } + else + { + var buffer = new byte[saltBytes.Length + bytes.Length]; + Buffer.BlockCopy(saltBytes, 0, buffer, 0, saltBytes.Length); + Buffer.BlockCopy(bytes, 0, buffer, saltBytes.Length, bytes.Length); + inArray = hashAlgorithm.ComputeHash(buffer); + } + } + else + { + //this code is copied from the sql membership provider - pretty sure this could be nicely re-written to completely + // ignore the salt stuff since we are not salting the password when encrypting. + var password = new byte[saltBytes.Length + bytes.Length]; + Buffer.BlockCopy(saltBytes, 0, password, 0, saltBytes.Length); + Buffer.BlockCopy(bytes, 0, password, saltBytes.Length, bytes.Length); + inArray = EncryptPassword(password, MembershipPasswordCompatibilityMode.Framework40); + } + return Convert.ToBase64String(inArray); + } + + /// + /// Checks the password. + /// + /// The password. + /// The dbPassword. + /// + protected internal bool CheckPassword(string password, string dbPassword) + { + if (string.IsNullOrWhiteSpace(dbPassword)) throw new ArgumentException("Value cannot be null or whitespace.", "dbPassword"); + switch (PasswordFormat) + { + case MembershipPasswordFormat.Encrypted: + var decrypted = DecryptPassword(dbPassword); + return decrypted == password; + case MembershipPasswordFormat.Hashed: + string salt; + var storedHashedPass = StoredPassword(dbPassword, out salt); + var hashed = EncryptOrHashPassword(password, salt); + return storedHashedPass == hashed; + case MembershipPasswordFormat.Clear: + return password == dbPassword; + default: + throw new ArgumentOutOfRangeException(); + } + } + + /// + /// Encrypt/hash a new password with a new salt + /// + /// + /// + /// + protected internal string EncryptOrHashNewPassword(string newPassword, out string salt) + { + salt = GenerateSalt(); + return EncryptOrHashPassword(newPassword, salt); + } + + protected internal string DecryptPassword(string pass) + { + //if we are doing it the old way + + if (UseLegacyEncoding) + { + return LegacyUnEncodePassword(pass); + } + + //This is the correct way to implement this (as per the sql membership provider) + + switch (PasswordFormat) + { + case MembershipPasswordFormat.Clear: + return pass; + case MembershipPasswordFormat.Hashed: + throw new ProviderException("Provider can not decrypt hashed password"); + case MembershipPasswordFormat.Encrypted: + default: + var bytes = DecryptPassword(Convert.FromBase64String(pass)); + return bytes == null ? null : Encoding.Unicode.GetString(bytes, 16, bytes.Length - 16); + } + } + + /// + /// Returns the hashed password without the salt if it is hashed + /// + /// + /// returns the salt + /// + internal string StoredPassword(string storedString, out string salt) + { + if (string.IsNullOrWhiteSpace(storedString)) throw new ArgumentException("Value cannot be null or whitespace.", "storedString"); + if (UseLegacyEncoding) + { + salt = string.Empty; + return storedString; + } + + switch (PasswordFormat) + { + case MembershipPasswordFormat.Hashed: + var saltLen = GenerateSalt(); + salt = storedString.Substring(0, saltLen.Length); + return storedString.Substring(saltLen.Length); + case MembershipPasswordFormat.Clear: + case MembershipPasswordFormat.Encrypted: + default: + salt = string.Empty; + return storedString; + + } + } + + protected internal static string GenerateSalt() + { + var numArray = new byte[16]; + new RNGCryptoServiceProvider().GetBytes(numArray); + return Convert.ToBase64String(numArray); + } + + protected internal HashAlgorithm GetHashAlgorithm(string password) + { + if (UseLegacyEncoding) + { + //before we were never checking for an algorithm type so we were always using HMACSHA1 + // for any SHA specified algorithm :( so we'll need to keep doing that for backwards compat support. + if (Membership.HashAlgorithmType.InvariantContains("SHA")) + { + return new HMACSHA1 + { + //the legacy salt was actually the password :( + Key = Encoding.Unicode.GetBytes(password) + }; + } + } + + //get the algorithm by name + + if (_customHashAlgorithmType.IsNullOrWhiteSpace()) + { + _customHashAlgorithmType = Membership.HashAlgorithmType; + } + + var alg = HashAlgorithm.Create(_customHashAlgorithmType); + if (alg == null) + { + throw new InvalidOperationException("The hash algorithm specified " + Membership.HashAlgorithmType + " cannot be resolved"); + } + + return alg; + } + + /// + /// Encodes the password. + /// + /// The password. + /// The encoded password. + protected string LegacyEncodePassword(string password) + { + string encodedPassword = password; + switch (PasswordFormat) + { + case MembershipPasswordFormat.Clear: + break; + case MembershipPasswordFormat.Encrypted: + encodedPassword = + Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password))); + break; + case MembershipPasswordFormat.Hashed: + var hashAlgorith = GetHashAlgorithm(password); + encodedPassword = Convert.ToBase64String(hashAlgorith.ComputeHash(Encoding.Unicode.GetBytes(password))); + break; + default: + throw new ProviderException("Unsupported password format."); + } + return encodedPassword; + } + + /// + /// Unencode password. + /// + /// The encoded password. + /// The unencoded password. + protected string LegacyUnEncodePassword(string encodedPassword) + { + string password = encodedPassword; + switch (PasswordFormat) + { + case MembershipPasswordFormat.Clear: + break; + case MembershipPasswordFormat.Encrypted: + password = Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password))); + break; + case MembershipPasswordFormat.Hashed: + throw new ProviderException("Cannot unencode a hashed password."); + default: + throw new ProviderException("Unsupported password format."); + } + return password; + } + + public override string ToString() + { + var result = base.ToString(); + var sb = new StringBuilder(result); + sb.AppendLine("Name =" + Name); + sb.AppendLine("_applicationName =" + _applicationName); + sb.AppendLine("_enablePasswordReset=" + _enablePasswordReset); + sb.AppendLine("_enablePasswordRetrieval=" + _enablePasswordRetrieval); + sb.AppendLine("_maxInvalidPasswordAttempts=" + _maxInvalidPasswordAttempts); + sb.AppendLine("_minRequiredNonAlphanumericCharacters=" + _minRequiredNonAlphanumericCharacters); + sb.AppendLine("_minRequiredPasswordLength=" + _minRequiredPasswordLength); + sb.AppendLine("_passwordAttemptWindow=" + _passwordAttemptWindow); + sb.AppendLine("_passwordFormat=" + _passwordFormat); + sb.AppendLine("_passwordStrengthRegularExpression=" + _passwordStrengthRegularExpression); + sb.AppendLine("_requiresQuestionAndAnswer=" + _requiresQuestionAndAnswer); + sb.AppendLine("_requiresUniqueEmail=" + _requiresUniqueEmail); + return sb.ToString(); + } + + /// + /// Returns the current request IP address for logging if there is one + /// + /// + protected string GetCurrentRequestIpAddress() + { + var httpContext = HttpContext.Current == null ? (HttpContextBase) null : new HttpContextWrapper(HttpContext.Current); + return httpContext.GetCurrentRequestIpAddress(); + } + + } +} diff --git a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs index 6cf5eacc97..589673d601 100644 --- a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs +++ b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs @@ -1,239 +1,239 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Security.Claims; -using System.Security.Principal; -using System.Web; -using System.Web.Security; -using Microsoft.AspNet.Identity; -using Microsoft.Owin.Security; -using Newtonsoft.Json; -using Umbraco.Core.Configuration; - -namespace Umbraco.Core.Security -{ - - /// - /// A custom user identity for the Umbraco backoffice - /// - /// - /// This inherits from FormsIdentity for backwards compatibility reasons since we still support the forms auth cookie, in v8 we can - /// change over to 'pure' asp.net identity and just inherit from ClaimsIdentity. - /// - [Serializable] - public class UmbracoBackOfficeIdentity : ClaimsIdentity - { - public static UmbracoBackOfficeIdentity FromClaimsIdentity(ClaimsIdentity identity) - { - return new UmbracoBackOfficeIdentity(identity); - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Security.Claims; +using System.Security.Principal; +using System.Web; +using System.Web.Security; +using Microsoft.AspNet.Identity; +using Microsoft.Owin.Security; +using Newtonsoft.Json; +using Umbraco.Core.Configuration; - /// - /// Creates a new UmbracoBackOfficeIdentity - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public UmbracoBackOfficeIdentity(int userId, string username, string realName, - IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, - string sessionId, string securityStamp, IEnumerable allowedApps, IEnumerable roles) - : base(Enumerable.Empty(), Constants.Security.BackOfficeAuthenticationType) //this ctor is used to ensure the IsAuthenticated property is true - { - if (allowedApps == null) throw new ArgumentNullException(nameof(allowedApps)); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); - if (string.IsNullOrWhiteSpace(sessionId)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(sessionId)); - if (string.IsNullOrWhiteSpace(securityStamp)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); - AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, sessionId, securityStamp, allowedApps, roles); +namespace Umbraco.Core.Security +{ + + /// + /// A custom user identity for the Umbraco backoffice + /// + /// + /// This inherits from FormsIdentity for backwards compatibility reasons since we still support the forms auth cookie, in v8 we can + /// change over to 'pure' asp.net identity and just inherit from ClaimsIdentity. + /// + [Serializable] + public class UmbracoBackOfficeIdentity : ClaimsIdentity + { + public static UmbracoBackOfficeIdentity FromClaimsIdentity(ClaimsIdentity identity) + { + return new UmbracoBackOfficeIdentity(identity); } - /// - /// Creates a new UmbracoBackOfficeIdentity - /// - /// - /// The original identity created by the ClaimsIdentityFactory - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public UmbracoBackOfficeIdentity(ClaimsIdentity childIdentity, - int userId, string username, string realName, - IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, + /// + /// Creates a new UmbracoBackOfficeIdentity + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public UmbracoBackOfficeIdentity(int userId, string username, string realName, + IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, string sessionId, string securityStamp, IEnumerable allowedApps, IEnumerable roles) - : base(childIdentity.Claims, Constants.Security.BackOfficeAuthenticationType) - { - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); - if (string.IsNullOrWhiteSpace(sessionId)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(sessionId)); - if (string.IsNullOrWhiteSpace(securityStamp)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); - Actor = childIdentity; - AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, sessionId, securityStamp, allowedApps, roles); + : base(Enumerable.Empty(), Constants.Security.BackOfficeAuthenticationType) //this ctor is used to ensure the IsAuthenticated property is true + { + if (allowedApps == null) throw new ArgumentNullException(nameof(allowedApps)); + if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); + if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); + if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); + if (string.IsNullOrWhiteSpace(sessionId)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(sessionId)); + if (string.IsNullOrWhiteSpace(securityStamp)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); + AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, sessionId, securityStamp, allowedApps, roles); } - - /// - /// Create a back office identity based on an existing claims identity - /// - /// - private UmbracoBackOfficeIdentity(ClaimsIdentity identity) - : base(identity.Claims, Constants.Security.BackOfficeAuthenticationType) - { + + /// + /// Creates a new UmbracoBackOfficeIdentity + /// + /// + /// The original identity created by the ClaimsIdentityFactory + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public UmbracoBackOfficeIdentity(ClaimsIdentity childIdentity, + int userId, string username, string realName, + IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, + string sessionId, string securityStamp, IEnumerable allowedApps, IEnumerable roles) + : base(childIdentity.Claims, Constants.Security.BackOfficeAuthenticationType) + { + if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); + if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); + if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); + if (string.IsNullOrWhiteSpace(sessionId)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(sessionId)); + if (string.IsNullOrWhiteSpace(securityStamp)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); + Actor = childIdentity; + AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, sessionId, securityStamp, allowedApps, roles); + } + + /// + /// Create a back office identity based on an existing claims identity + /// + /// + private UmbracoBackOfficeIdentity(ClaimsIdentity identity) + : base(identity.Claims, Constants.Security.BackOfficeAuthenticationType) + { Actor = identity; //validate that all claims exist - foreach (var t in RequiredBackOfficeIdentityClaimTypes) - { - //if the identity doesn't have the claim, or the claim value is null - if (identity.HasClaim(x => x.Type == t) == false || identity.HasClaim(x => x.Type == t && x.Value.IsNullOrWhiteSpace())) - { - throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the required claim " + t + " is missing"); - } - } - } - - public const string Issuer = Constants.Security.BackOfficeAuthenticationType; - - /// - /// Returns the required claim types for a back office identity - /// - /// - /// This does not incude the role claim type or allowed apps type since that is a collection and in theory could be empty - /// - public static IEnumerable RequiredBackOfficeIdentityClaimTypes => new[] - { - ClaimTypes.NameIdentifier, //id - ClaimTypes.Name, //username - ClaimTypes.GivenName, - Constants.Security.StartContentNodeIdClaimType, - Constants.Security.StartMediaNodeIdClaimType, - ClaimTypes.Locality, - Constants.Security.SessionIdClaimType, - Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType - }; - - /// - /// Adds claims based on the ctor data - /// + foreach (var t in RequiredBackOfficeIdentityClaimTypes) + { + //if the identity doesn't have the claim, or the claim value is null + if (identity.HasClaim(x => x.Type == t) == false || identity.HasClaim(x => x.Type == t && x.Value.IsNullOrWhiteSpace())) + { + throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the required claim " + t + " is missing"); + } + } + } + + public const string Issuer = Constants.Security.BackOfficeAuthenticationType; + + /// + /// Returns the required claim types for a back office identity + /// + /// + /// This does not incude the role claim type or allowed apps type since that is a collection and in theory could be empty + /// + public static IEnumerable RequiredBackOfficeIdentityClaimTypes => new[] + { + ClaimTypes.NameIdentifier, //id + ClaimTypes.Name, //username + ClaimTypes.GivenName, + Constants.Security.StartContentNodeIdClaimType, + Constants.Security.StartMediaNodeIdClaimType, + ClaimTypes.Locality, + Constants.Security.SessionIdClaimType, + Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType + }; + + /// + /// Adds claims based on the ctor data + /// private void AddRequiredClaims(int userId, string username, string realName, - IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, - string sessionId, string securityStamp, IEnumerable allowedApps, IEnumerable roles) - { - //This is the id that 'identity' uses to check for the user id - if (HasClaim(x => x.Type == ClaimTypes.NameIdentifier) == false) - AddClaim(new Claim(ClaimTypes.NameIdentifier, userId.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); - - if (HasClaim(x => x.Type == ClaimTypes.Name) == false) - AddClaim(new Claim(ClaimTypes.Name, username, ClaimValueTypes.String, Issuer, Issuer, this)); - - if (HasClaim(x => x.Type == ClaimTypes.GivenName) == false) - AddClaim(new Claim(ClaimTypes.GivenName, realName, ClaimValueTypes.String, Issuer, Issuer, this)); - - if (HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false && startContentNodes != null) - { - foreach (var startContentNode in startContentNodes) - { - AddClaim(new Claim(Constants.Security.StartContentNodeIdClaimType, startContentNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); - } - } - - if (HasClaim(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) == false && startMediaNodes != null) - { - foreach (var startMediaNode in startMediaNodes) - { - AddClaim(new Claim(Constants.Security.StartMediaNodeIdClaimType, startMediaNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); - } - } - - if (HasClaim(x => x.Type == ClaimTypes.Locality) == false) - AddClaim(new Claim(ClaimTypes.Locality, culture, ClaimValueTypes.String, Issuer, Issuer, this)); - - if (HasClaim(x => x.Type == Constants.Security.SessionIdClaimType) == false && SessionId.IsNullOrWhiteSpace() == false) - AddClaim(new Claim(Constants.Security.SessionIdClaimType, sessionId, ClaimValueTypes.String, Issuer, Issuer, this)); - - //The security stamp claim is also required... this is because this claim type is hard coded - // by the SecurityStampValidator, see: https://katanaproject.codeplex.com/workitem/444 - if (HasClaim(x => x.Type == Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType) == false) - AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, securityStamp, ClaimValueTypes.String, Issuer, Issuer, this)); - - //Add each app as a separate claim - if (HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && allowedApps != null) - { - foreach (var application in allowedApps) - { - AddClaim(new Claim(Constants.Security.AllowedApplicationsClaimType, application, ClaimValueTypes.String, Issuer, Issuer, this)); - } - } - - //Claims are added by the ClaimsIdentityFactory because our UserStore supports roles, however this identity might - // not be made with that factory if it was created with a different ticket so perform the check - if (HasClaim(x => x.Type == DefaultRoleClaimType) == false && roles != null) - { - //manually add them - foreach (var roleName in roles) - { - AddClaim(new Claim(RoleClaimType, roleName, ClaimValueTypes.String, Issuer, Issuer, this)); - } - } - - } - - /// - /// - /// Gets the type of authenticated identity. - /// - /// - /// The type of authenticated identity. This property always returns "UmbracoBackOffice". - /// - public override string AuthenticationType => Issuer; - - private int[] _startContentNodes; - public int[] StartContentNodes => _startContentNodes ?? (_startContentNodes = FindAll(x => x.Type == Constants.Security.StartContentNodeIdClaimType).Select(app => int.TryParse(app.Value, out var i) ? i : default).Where(x => x != default).ToArray()); - - private int[] _startMediaNodes; - public int[] StartMediaNodes => _startMediaNodes ?? (_startMediaNodes = FindAll(x => x.Type == Constants.Security.StartMediaNodeIdClaimType).Select(app => int.TryParse(app.Value, out var i) ? i : default).Where(x => x != default).ToArray()); - - private string[] _allowedApplications; - public string[] AllowedApplications => _allowedApplications ?? (_allowedApplications = FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToArray()); - - public int Id => int.Parse(this.FindFirstValue(ClaimTypes.NameIdentifier)); - - public string RealName => this.FindFirstValue(ClaimTypes.GivenName); - - public string Username => this.GetUserName(); - - public string Culture => this.FindFirstValue(ClaimTypes.Locality); - - public string SessionId - { - get => this.FindFirstValue(Constants.Security.SessionIdClaimType); - set - { - var existing = FindFirst(Constants.Security.SessionIdClaimType); - if (existing != null) + IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, + string sessionId, string securityStamp, IEnumerable allowedApps, IEnumerable roles) + { + //This is the id that 'identity' uses to check for the user id + if (HasClaim(x => x.Type == ClaimTypes.NameIdentifier) == false) + AddClaim(new Claim(ClaimTypes.NameIdentifier, userId.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); + + if (HasClaim(x => x.Type == ClaimTypes.Name) == false) + AddClaim(new Claim(ClaimTypes.Name, username, ClaimValueTypes.String, Issuer, Issuer, this)); + + if (HasClaim(x => x.Type == ClaimTypes.GivenName) == false) + AddClaim(new Claim(ClaimTypes.GivenName, realName, ClaimValueTypes.String, Issuer, Issuer, this)); + + if (HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false && startContentNodes != null) + { + foreach (var startContentNode in startContentNodes) + { + AddClaim(new Claim(Constants.Security.StartContentNodeIdClaimType, startContentNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); + } + } + + if (HasClaim(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) == false && startMediaNodes != null) + { + foreach (var startMediaNode in startMediaNodes) + { + AddClaim(new Claim(Constants.Security.StartMediaNodeIdClaimType, startMediaNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); + } + } + + if (HasClaim(x => x.Type == ClaimTypes.Locality) == false) + AddClaim(new Claim(ClaimTypes.Locality, culture, ClaimValueTypes.String, Issuer, Issuer, this)); + + if (HasClaim(x => x.Type == Constants.Security.SessionIdClaimType) == false && SessionId.IsNullOrWhiteSpace() == false) + AddClaim(new Claim(Constants.Security.SessionIdClaimType, sessionId, ClaimValueTypes.String, Issuer, Issuer, this)); + + //The security stamp claim is also required... this is because this claim type is hard coded + // by the SecurityStampValidator, see: https://katanaproject.codeplex.com/workitem/444 + if (HasClaim(x => x.Type == Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType) == false) + AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, securityStamp, ClaimValueTypes.String, Issuer, Issuer, this)); + + //Add each app as a separate claim + if (HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && allowedApps != null) + { + foreach (var application in allowedApps) + { + AddClaim(new Claim(Constants.Security.AllowedApplicationsClaimType, application, ClaimValueTypes.String, Issuer, Issuer, this)); + } + } + + //Claims are added by the ClaimsIdentityFactory because our UserStore supports roles, however this identity might + // not be made with that factory if it was created with a different ticket so perform the check + if (HasClaim(x => x.Type == DefaultRoleClaimType) == false && roles != null) + { + //manually add them + foreach (var roleName in roles) + { + AddClaim(new Claim(RoleClaimType, roleName, ClaimValueTypes.String, Issuer, Issuer, this)); + } + } + + } + + /// + /// + /// Gets the type of authenticated identity. + /// + /// + /// The type of authenticated identity. This property always returns "UmbracoBackOffice". + /// + public override string AuthenticationType => Issuer; + + private int[] _startContentNodes; + public int[] StartContentNodes => _startContentNodes ?? (_startContentNodes = FindAll(x => x.Type == Constants.Security.StartContentNodeIdClaimType).Select(app => int.TryParse(app.Value, out var i) ? i : default).Where(x => x != default).ToArray()); + + private int[] _startMediaNodes; + public int[] StartMediaNodes => _startMediaNodes ?? (_startMediaNodes = FindAll(x => x.Type == Constants.Security.StartMediaNodeIdClaimType).Select(app => int.TryParse(app.Value, out var i) ? i : default).Where(x => x != default).ToArray()); + + private string[] _allowedApplications; + public string[] AllowedApplications => _allowedApplications ?? (_allowedApplications = FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToArray()); + + public int Id => int.Parse(this.FindFirstValue(ClaimTypes.NameIdentifier)); + + public string RealName => this.FindFirstValue(ClaimTypes.GivenName); + + public string Username => this.GetUserName(); + + public string Culture => this.FindFirstValue(ClaimTypes.Locality); + + public string SessionId + { + get => this.FindFirstValue(Constants.Security.SessionIdClaimType); + set + { + var existing = FindFirst(Constants.Security.SessionIdClaimType); + if (existing != null) TryRemoveClaim(existing); - AddClaim(new Claim(Constants.Security.SessionIdClaimType, value, ClaimValueTypes.String, Issuer, Issuer, this)); - } - } - - public string SecurityStamp => this.FindFirstValue(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType); - - public string[] Roles => this.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray(); - - } -} + AddClaim(new Claim(Constants.Security.SessionIdClaimType, value, ClaimValueTypes.String, Issuer, Issuer, this)); + } + } + + public string SecurityStamp => this.FindFirstValue(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType); + + public string[] Roles => this.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray(); + + } +} diff --git a/src/Umbraco.Core/Serialization/AbstractSerializationService.cs b/src/Umbraco.Core/Serialization/AbstractSerializationService.cs index f9e2f122fa..db4afba3e8 100644 --- a/src/Umbraco.Core/Serialization/AbstractSerializationService.cs +++ b/src/Umbraco.Core/Serialization/AbstractSerializationService.cs @@ -1,33 +1,33 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Umbraco.Core.Serialization -{ - public abstract class AbstractSerializationService - { - /// - /// A sequence of registered with this serialization service. - /// - public IEnumerable Formatters { get; set; } - - /// - /// Finds an with a matching , and deserializes the to an object graph. - /// - /// - /// - /// - /// - public abstract object FromStream(Stream input, Type outputType, string intent = null); - - /// - /// Finds an with a matching , and serializes the object graph to an . - /// - /// - /// - /// - public abstract IStreamedResult ToStream(object input, string intent = null); - } -} +using System; +using System.Collections.Generic; +using System.IO; + +namespace Umbraco.Core.Serialization +{ + public abstract class AbstractSerializationService + { + /// + /// A sequence of registered with this serialization service. + /// + public IEnumerable Formatters { get; set; } + + /// + /// Finds an with a matching , and deserializes the to an object graph. + /// + /// + /// + /// + /// + public abstract object FromStream(Stream input, Type outputType, string intent = null); + + /// + /// Finds an with a matching , and serializes the object graph to an . + /// + /// + /// + /// + public abstract IStreamedResult ToStream(object input, string intent = null); + } +} diff --git a/src/Umbraco.Core/Serialization/Formatter.cs b/src/Umbraco.Core/Serialization/Formatter.cs index ad92c65c6e..bfdabbe13f 100644 --- a/src/Umbraco.Core/Serialization/Formatter.cs +++ b/src/Umbraco.Core/Serialization/Formatter.cs @@ -1,21 +1,21 @@ -using System; - -namespace Umbraco.Core.Serialization -{ - public class Formatter : IFormatter - { - #region Implementation of IFormatter - - public string Intent - { - get { throw new NotImplementedException(); } - } - - public ISerializer Serializer - { - get { throw new NotImplementedException(); } - } - - #endregion - } -} +using System; + +namespace Umbraco.Core.Serialization +{ + public class Formatter : IFormatter + { + #region Implementation of IFormatter + + public string Intent + { + get { throw new NotImplementedException(); } + } + + public ISerializer Serializer + { + get { throw new NotImplementedException(); } + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Serialization/IFormatter.cs b/src/Umbraco.Core/Serialization/IFormatter.cs index 974c140f9e..5c34819a18 100644 --- a/src/Umbraco.Core/Serialization/IFormatter.cs +++ b/src/Umbraco.Core/Serialization/IFormatter.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Core.Serialization -{ - public interface IFormatter - { - string Intent { get; } - - ISerializer Serializer { get; } - } -} +namespace Umbraco.Core.Serialization +{ + public interface IFormatter + { + string Intent { get; } + + ISerializer Serializer { get; } + } +} diff --git a/src/Umbraco.Core/Serialization/ISerializer.cs b/src/Umbraco.Core/Serialization/ISerializer.cs index e8239d424d..329d7a9109 100644 --- a/src/Umbraco.Core/Serialization/ISerializer.cs +++ b/src/Umbraco.Core/Serialization/ISerializer.cs @@ -1,12 +1,12 @@ -using System; -using System.IO; - -namespace Umbraco.Core.Serialization -{ - public interface ISerializer - { - object FromStream(Stream input, Type outputType); - - IStreamedResult ToStream(object input); - } -} +using System; +using System.IO; + +namespace Umbraco.Core.Serialization +{ + public interface ISerializer + { + object FromStream(Stream input, Type outputType); + + IStreamedResult ToStream(object input); + } +} diff --git a/src/Umbraco.Core/Serialization/IStreamedResult.cs b/src/Umbraco.Core/Serialization/IStreamedResult.cs index b49dac6650..0c6e6078cb 100644 --- a/src/Umbraco.Core/Serialization/IStreamedResult.cs +++ b/src/Umbraco.Core/Serialization/IStreamedResult.cs @@ -1,10 +1,10 @@ -using System.IO; - -namespace Umbraco.Core.Serialization -{ - public interface IStreamedResult - { - Stream ResultStream { get; } - bool Success { get; } - } -} +using System.IO; + +namespace Umbraco.Core.Serialization +{ + public interface IStreamedResult + { + Stream ResultStream { get; } + bool Success { get; } + } +} diff --git a/src/Umbraco.Core/Serialization/JsonNetSerializer.cs b/src/Umbraco.Core/Serialization/JsonNetSerializer.cs index 14dea715df..1b5a9c6caf 100644 --- a/src/Umbraco.Core/Serialization/JsonNetSerializer.cs +++ b/src/Umbraco.Core/Serialization/JsonNetSerializer.cs @@ -1,72 +1,72 @@ -using System; -using System.IO; -using System.Reflection; -using System.Text; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace Umbraco.Core.Serialization -{ - internal class JsonNetSerializer : ISerializer - { - private readonly JsonSerializerSettings _settings; - - public JsonNetSerializer() - { - _settings = new JsonSerializerSettings(); - - //var customResolver = new CustomIgnoreResolver - // { - // DefaultMembersSearchFlags = BindingFlags.Instance | BindingFlags.Public - // }; - //_settings.ContractResolver = customResolver; - - var javaScriptDateTimeConverter = new JavaScriptDateTimeConverter(); - - _settings.Converters.Add(javaScriptDateTimeConverter); - _settings.Converters.Add(new EntityKeyMemberConverter()); - _settings.Converters.Add(new KeyValuePairConverter()); - _settings.Converters.Add(new ExpandoObjectConverter()); - _settings.Converters.Add(new XmlNodeConverter()); - - _settings.NullValueHandling = NullValueHandling.Include; - _settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; - _settings.TypeNameHandling = TypeNameHandling.Objects; - _settings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor; - } - - #region Implementation of ISerializer - - /// - /// Deserialize input stream to object - /// - /// - /// - /// - public object FromStream(Stream input, Type outputType) - { - byte[] bytes = new byte[input.Length]; - input.Position = 0; - input.Read(bytes, 0, (int)input.Length); - string s = Encoding.UTF8.GetString(bytes); - - return JsonConvert.DeserializeObject(s, outputType, _settings); - } - - /// - /// Serialize object to streamed result - /// - /// - /// - public IStreamedResult ToStream(object input) - { - string s = JsonConvert.SerializeObject(input, Formatting.Indented, _settings); - byte[] bytes = Encoding.UTF8.GetBytes(s); - MemoryStream ms = new MemoryStream(bytes); - - return new StreamedResult(ms, true); - } - - #endregion - } -} +using System; +using System.IO; +using System.Reflection; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Umbraco.Core.Serialization +{ + internal class JsonNetSerializer : ISerializer + { + private readonly JsonSerializerSettings _settings; + + public JsonNetSerializer() + { + _settings = new JsonSerializerSettings(); + + //var customResolver = new CustomIgnoreResolver + // { + // DefaultMembersSearchFlags = BindingFlags.Instance | BindingFlags.Public + // }; + //_settings.ContractResolver = customResolver; + + var javaScriptDateTimeConverter = new JavaScriptDateTimeConverter(); + + _settings.Converters.Add(javaScriptDateTimeConverter); + _settings.Converters.Add(new EntityKeyMemberConverter()); + _settings.Converters.Add(new KeyValuePairConverter()); + _settings.Converters.Add(new ExpandoObjectConverter()); + _settings.Converters.Add(new XmlNodeConverter()); + + _settings.NullValueHandling = NullValueHandling.Include; + _settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + _settings.TypeNameHandling = TypeNameHandling.Objects; + _settings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor; + } + + #region Implementation of ISerializer + + /// + /// Deserialize input stream to object + /// + /// + /// + /// + public object FromStream(Stream input, Type outputType) + { + byte[] bytes = new byte[input.Length]; + input.Position = 0; + input.Read(bytes, 0, (int)input.Length); + string s = Encoding.UTF8.GetString(bytes); + + return JsonConvert.DeserializeObject(s, outputType, _settings); + } + + /// + /// Serialize object to streamed result + /// + /// + /// + public IStreamedResult ToStream(object input) + { + string s = JsonConvert.SerializeObject(input, Formatting.Indented, _settings); + byte[] bytes = Encoding.UTF8.GetBytes(s); + MemoryStream ms = new MemoryStream(bytes); + + return new StreamedResult(ms, true); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Serialization/JsonToStringConverter.cs b/src/Umbraco.Core/Serialization/JsonToStringConverter.cs index 59d5e90c4e..08c9a44d00 100644 --- a/src/Umbraco.Core/Serialization/JsonToStringConverter.cs +++ b/src/Umbraco.Core/Serialization/JsonToStringConverter.cs @@ -1,33 +1,33 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Umbraco.Core.Serialization -{ - /// - /// This is used in order to deserialize a json object on a property into a json string since the property's type is 'string' - /// - internal class JsonToStringConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - if (reader.ValueType == typeof(string)) - { - return reader.Value; - } - // Load JObject from stream - JObject jObject = JObject.Load(reader); - return jObject.ToString(); - } - - public override bool CanConvert(Type objectType) - { - return typeof(string).IsAssignableFrom(objectType); - } - } -} +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Umbraco.Core.Serialization +{ + /// + /// This is used in order to deserialize a json object on a property into a json string since the property's type is 'string' + /// + internal class JsonToStringConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.ValueType == typeof(string)) + { + return reader.Value; + } + // Load JObject from stream + JObject jObject = JObject.Load(reader); + return jObject.ToString(); + } + + public override bool CanConvert(Type objectType) + { + return typeof(string).IsAssignableFrom(objectType); + } + } +} diff --git a/src/Umbraco.Core/Serialization/SerializationExtensions.cs b/src/Umbraco.Core/Serialization/SerializationExtensions.cs index eff6f29bf5..c88641a337 100644 --- a/src/Umbraco.Core/Serialization/SerializationExtensions.cs +++ b/src/Umbraco.Core/Serialization/SerializationExtensions.cs @@ -1,40 +1,40 @@ -using System; -using System.IO; -using System.Text; - -namespace Umbraco.Core.Serialization -{ - public static class SerializationExtensions - { - public static T FromJson(this AbstractSerializationService service, string json, string intent = null) - { - if (string.IsNullOrWhiteSpace(json)) return default(T); - return (T)service.FromJson(json, typeof(T), intent); - } - - public static T FromJson(this ISerializer serializer, string json, string intent = null) - { - if (string.IsNullOrWhiteSpace(json)) return default(T); - return (T)serializer.FromJson(json, typeof(T)); - } - - public static object FromJson(this ISerializer serializer, string json, Type outputType) - { - if (string.IsNullOrWhiteSpace(json)) return outputType.GetDefaultValue(); - var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - return serializer.FromStream(stream, outputType); - } - - public static object FromJson(this AbstractSerializationService service, string json, Type outputType, string intent = null) - { - if (string.IsNullOrWhiteSpace(json)) return outputType.GetDefaultValue(); - var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - return service.FromStream(stream, outputType, intent); - } - - public static string ToJson(this AbstractSerializationService service, object input, string intent = null) - { - return StreamResultExtensions.ToJsonString(service.ToStream(input, intent).ResultStream); - } - } -} +using System; +using System.IO; +using System.Text; + +namespace Umbraco.Core.Serialization +{ + public static class SerializationExtensions + { + public static T FromJson(this AbstractSerializationService service, string json, string intent = null) + { + if (string.IsNullOrWhiteSpace(json)) return default(T); + return (T)service.FromJson(json, typeof(T), intent); + } + + public static T FromJson(this ISerializer serializer, string json, string intent = null) + { + if (string.IsNullOrWhiteSpace(json)) return default(T); + return (T)serializer.FromJson(json, typeof(T)); + } + + public static object FromJson(this ISerializer serializer, string json, Type outputType) + { + if (string.IsNullOrWhiteSpace(json)) return outputType.GetDefaultValue(); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + return serializer.FromStream(stream, outputType); + } + + public static object FromJson(this AbstractSerializationService service, string json, Type outputType, string intent = null) + { + if (string.IsNullOrWhiteSpace(json)) return outputType.GetDefaultValue(); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + return service.FromStream(stream, outputType, intent); + } + + public static string ToJson(this AbstractSerializationService service, object input, string intent = null) + { + return StreamResultExtensions.ToJsonString(service.ToStream(input, intent).ResultStream); + } + } +} diff --git a/src/Umbraco.Core/Serialization/SerializationService.cs b/src/Umbraco.Core/Serialization/SerializationService.cs index 2d280b1cab..dee9712552 100644 --- a/src/Umbraco.Core/Serialization/SerializationService.cs +++ b/src/Umbraco.Core/Serialization/SerializationService.cs @@ -1,45 +1,45 @@ -using System; -using System.IO; - -namespace Umbraco.Core.Serialization -{ - public class SerializationService : AbstractSerializationService - { - private readonly ISerializer _serializer; - - public SerializationService(ISerializer serializer) - { - _serializer = serializer; - } - - #region Overrides of AbstractSerializationService - - /// - /// Finds an with a matching , and deserializes the to an object graph. - /// - /// - /// - /// - /// - public override object FromStream(Stream input, Type outputType, string intent = null) - { - if (input.CanSeek && input.Position > 0) input.Seek(0, SeekOrigin.Begin); - return _serializer.FromStream(input, outputType); - } - - /// - /// Finds an with a matching , and serializes the object graph to an . - /// - /// - /// - /// - public override IStreamedResult ToStream(object input, string intent = null) - { - return _serializer.ToStream(input); - } - - #endregion - } -} +using System; +using System.IO; + +namespace Umbraco.Core.Serialization +{ + public class SerializationService : AbstractSerializationService + { + private readonly ISerializer _serializer; + + public SerializationService(ISerializer serializer) + { + _serializer = serializer; + } + + #region Overrides of AbstractSerializationService + + /// + /// Finds an with a matching , and deserializes the to an object graph. + /// + /// + /// + /// + /// + public override object FromStream(Stream input, Type outputType, string intent = null) + { + if (input.CanSeek && input.Position > 0) input.Seek(0, SeekOrigin.Begin); + return _serializer.FromStream(input, outputType); + } + + /// + /// Finds an with a matching , and serializes the object graph to an . + /// + /// + /// + /// + public override IStreamedResult ToStream(object input, string intent = null) + { + return _serializer.ToStream(input); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Serialization/StreamedResult.cs b/src/Umbraco.Core/Serialization/StreamedResult.cs index c7d5a33248..6bb192cf8a 100644 --- a/src/Umbraco.Core/Serialization/StreamedResult.cs +++ b/src/Umbraco.Core/Serialization/StreamedResult.cs @@ -1,21 +1,21 @@ -using System.IO; - -namespace Umbraco.Core.Serialization -{ - public class StreamedResult : IStreamedResult - { - internal StreamedResult(Stream stream, bool success) - { - ResultStream = stream; - Success = success; - } - - #region Implementation of IStreamedResult - - public Stream ResultStream { get; protected set; } - - public bool Success { get; protected set; } - - #endregion - } -} +using System.IO; + +namespace Umbraco.Core.Serialization +{ + public class StreamedResult : IStreamedResult + { + internal StreamedResult(Stream stream, bool success) + { + ResultStream = stream; + Success = success; + } + + #region Implementation of IStreamedResult + + public Stream ResultStream { get; protected set; } + + public bool Success { get; protected set; } + + #endregion + } +} diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index 2f6e043c88..03a790d4b8 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -1,76 +1,76 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; - -namespace Umbraco.Core.Services -{ - /// - /// Content service extension methods - /// - public static class ContentServiceExtensions - { - public static IEnumerable GetByIds(this IContentService contentService, IEnumerable ids) - { - var guids = new List(); - foreach (var udi in ids) - { - var guidUdi = udi as GuidUdi; - if (guidUdi == null) - throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content"); - guids.Add(guidUdi); - } - - return contentService.GetByIds(guids.Select(x => x.Guid)); - } - - /// - /// Method to create an IContent object based on the Udi of a parent - /// - /// - /// - /// - /// - /// - /// - public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string mediaTypeAlias, int userId = 0) - { - var guidUdi = parentId as GuidUdi; - if (guidUdi == null) - throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content"); - var parent = contentService.GetById(guidUdi.Guid); - return contentService.Create(name, parent, mediaTypeAlias, userId); - } - - /// - /// Remove all permissions for this user for all nodes - /// - /// - /// - public static void RemoveContentPermissions(this IContentService contentService, int contentId) - { - contentService.SetPermissions(new EntityPermissionSet(contentId, new EntityPermissionCollection())); - } - - /// - /// Returns true if there is any content in the recycle bin - /// - /// - /// - public static bool RecycleBinSmells(this IContentService contentService) - { - return contentService.CountChildren(Constants.System.RecycleBinContent) > 0; - } - - /// - /// Returns true if there is any media in the recycle bin - /// - /// - /// - public static bool RecycleBinSmells(this IMediaService mediaService) - { - return mediaService.CountChildren(Constants.System.RecycleBinMedia) > 0; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Services +{ + /// + /// Content service extension methods + /// + public static class ContentServiceExtensions + { + public static IEnumerable GetByIds(this IContentService contentService, IEnumerable ids) + { + var guids = new List(); + foreach (var udi in ids) + { + var guidUdi = udi as GuidUdi; + if (guidUdi == null) + throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content"); + guids.Add(guidUdi); + } + + return contentService.GetByIds(guids.Select(x => x.Guid)); + } + + /// + /// Method to create an IContent object based on the Udi of a parent + /// + /// + /// + /// + /// + /// + /// + public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string mediaTypeAlias, int userId = 0) + { + var guidUdi = parentId as GuidUdi; + if (guidUdi == null) + throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content"); + var parent = contentService.GetById(guidUdi.Guid); + return contentService.Create(name, parent, mediaTypeAlias, userId); + } + + /// + /// Remove all permissions for this user for all nodes + /// + /// + /// + public static void RemoveContentPermissions(this IContentService contentService, int contentId) + { + contentService.SetPermissions(new EntityPermissionSet(contentId, new EntityPermissionCollection())); + } + + /// + /// Returns true if there is any content in the recycle bin + /// + /// + /// + public static bool RecycleBinSmells(this IContentService contentService) + { + return contentService.CountChildren(Constants.System.RecycleBinContent) > 0; + } + + /// + /// Returns true if there is any media in the recycle bin + /// + /// + /// + public static bool RecycleBinSmells(this IMediaService mediaService) + { + return mediaService.CountChildren(Constants.System.RecycleBinMedia) > 0; + } + } +} diff --git a/src/Umbraco.Core/Services/IApplicationTreeService.cs b/src/Umbraco.Core/Services/IApplicationTreeService.cs index b8ac272439..5b6976c021 100644 --- a/src/Umbraco.Core/Services/IApplicationTreeService.cs +++ b/src/Umbraco.Core/Services/IApplicationTreeService.cs @@ -1,137 +1,137 @@ -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Services -{ - public interface IApplicationTreeService - { - /// - /// Creates a new application tree. - /// - /// if set to true [initialize]. - /// The sort order. - /// The application alias. - /// The alias. - /// The title. - /// The icon closed. - /// The icon opened. - /// The type. - void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type); - - /// - /// Saves this instance. - /// - void SaveTree(ApplicationTree tree); - - /// - /// Deletes this instance. - /// - void DeleteTree(ApplicationTree tree); - - /// - /// Gets an ApplicationTree by it's tree alias. - /// - /// The tree alias. - /// An ApplicationTree instance - ApplicationTree GetByAlias(string treeAlias); - - /// - /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. - /// - /// Returns a ApplicationTree Array - IEnumerable GetAll(); - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// Returns a ApplicationTree Array - IEnumerable GetApplicationTrees(string applicationAlias); - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// - /// Returns a ApplicationTree Array - IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized); - } - - /// - /// Purely used to allow a service context to create the default services - /// - internal class EmptyApplicationTreeService : IApplicationTreeService - { - /// - /// Creates a new application tree. - /// - /// if set to true [initialize]. - /// The sort order. - /// The application alias. - /// The alias. - /// The title. - /// The icon closed. - /// The icon opened. - /// The type. - public void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) - { - throw new System.NotImplementedException(); - } - - /// - /// Saves this instance. - /// - public void SaveTree(ApplicationTree tree) - { - throw new System.NotImplementedException(); - } - - /// - /// Deletes this instance. - /// - public void DeleteTree(ApplicationTree tree) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets an ApplicationTree by it's tree alias. - /// - /// The tree alias. - /// An ApplicationTree instance - public ApplicationTree GetByAlias(string treeAlias) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. - /// - /// Returns a ApplicationTree Array - public IEnumerable GetAll() - { - throw new System.NotImplementedException(); - } - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// Returns a ApplicationTree Array - public IEnumerable GetApplicationTrees(string applicationAlias) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// - /// Returns a ApplicationTree Array - public IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized) - { - throw new System.NotImplementedException(); - } - } -} +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public interface IApplicationTreeService + { + /// + /// Creates a new application tree. + /// + /// if set to true [initialize]. + /// The sort order. + /// The application alias. + /// The alias. + /// The title. + /// The icon closed. + /// The icon opened. + /// The type. + void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type); + + /// + /// Saves this instance. + /// + void SaveTree(ApplicationTree tree); + + /// + /// Deletes this instance. + /// + void DeleteTree(ApplicationTree tree); + + /// + /// Gets an ApplicationTree by it's tree alias. + /// + /// The tree alias. + /// An ApplicationTree instance + ApplicationTree GetByAlias(string treeAlias); + + /// + /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. + /// + /// Returns a ApplicationTree Array + IEnumerable GetAll(); + + /// + /// Gets the application tree for the applcation with the specified alias + /// + /// The application alias. + /// Returns a ApplicationTree Array + IEnumerable GetApplicationTrees(string applicationAlias); + + /// + /// Gets the application tree for the applcation with the specified alias + /// + /// The application alias. + /// + /// Returns a ApplicationTree Array + IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized); + } + + /// + /// Purely used to allow a service context to create the default services + /// + internal class EmptyApplicationTreeService : IApplicationTreeService + { + /// + /// Creates a new application tree. + /// + /// if set to true [initialize]. + /// The sort order. + /// The application alias. + /// The alias. + /// The title. + /// The icon closed. + /// The icon opened. + /// The type. + public void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) + { + throw new System.NotImplementedException(); + } + + /// + /// Saves this instance. + /// + public void SaveTree(ApplicationTree tree) + { + throw new System.NotImplementedException(); + } + + /// + /// Deletes this instance. + /// + public void DeleteTree(ApplicationTree tree) + { + throw new System.NotImplementedException(); + } + + /// + /// Gets an ApplicationTree by it's tree alias. + /// + /// The tree alias. + /// An ApplicationTree instance + public ApplicationTree GetByAlias(string treeAlias) + { + throw new System.NotImplementedException(); + } + + /// + /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. + /// + /// Returns a ApplicationTree Array + public IEnumerable GetAll() + { + throw new System.NotImplementedException(); + } + + /// + /// Gets the application tree for the applcation with the specified alias + /// + /// The application alias. + /// Returns a ApplicationTree Array + public IEnumerable GetApplicationTrees(string applicationAlias) + { + throw new System.NotImplementedException(); + } + + /// + /// Gets the application tree for the applcation with the specified alias + /// + /// The application alias. + /// + /// Returns a ApplicationTree Array + public IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index a08f2291b4..edc10353c9 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -1,443 +1,443 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; - -namespace Umbraco.Core.Services -{ - /// - /// Defines the ContentService, which is an easy access to operations involving - /// - public interface IContentService : IContentServiceBase - { - #region Blueprints - - /// - /// Gets a blueprint. - /// - IContent GetBlueprintById(int id); - - /// - /// Gets a blueprint. - /// - IContent GetBlueprintById(Guid id); - - /// - /// Gets blueprints for a content type. - /// - IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeId); - - /// - /// Saves a blueprint. - /// - void SaveBlueprint(IContent content, int userId = 0); - - /// - /// Deletes a blueprint. - /// - void DeleteBlueprint(IContent content, int userId = 0); - - /// - /// Creates a new content item from a blueprint. - /// - IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0); - - /// - /// Deletes blueprints for a content type. - /// - void DeleteBlueprintsOfType(int contentTypeId, int userId = 0); - - /// - /// Deletes blueprints for content types. - /// - void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0); - - #endregion - - #region Get, Count Documents - - /// - /// Gets a document. - /// - IContent GetById(int id); - - /// - /// Gets a document. - /// - IContent GetById(Guid key); - - /// - /// Gets documents. - /// - IEnumerable GetByIds(IEnumerable ids); - - /// - /// Gets documents. - /// - IEnumerable GetByIds(IEnumerable ids); - - /// - /// Gets documents of a given document type. - /// - IEnumerable GetByType(int documentTypeId); - - /// - /// Gets documents at a given level. - /// - IEnumerable GetByLevel(int level); - - /// - /// Gets child documents of a given parent. - /// - IEnumerable GetChildren(int parentId); - - /// - /// Gets child documents of a document, (partially) matching a name. - /// - IEnumerable GetChildren(int parentId, string name); - - /// - /// Gets the parent of a document. - /// - IContent GetParent(int id); - - /// - /// Gets the parent of a document. - /// - IContent GetParent(IContent content); - - /// - /// Gets ancestor documents of a document. - /// - IEnumerable GetAncestors(int id); - - /// - /// Gets ancestor documents of a document. - /// - IEnumerable GetAncestors(IContent content); - - /// - /// Gets descendant documents of a document. - /// - IEnumerable GetDescendants(int id); - - /// - /// Gets descendant documents of a document. - /// - IEnumerable GetDescendants(IContent content); - - /// - /// Gets all versions of a document. - /// - /// Versions are ordered with current first, then most recent first. - IEnumerable GetVersions(int id); - - /// - /// Gets top versions of a document. - /// - /// Versions are ordered with current first, then most recent first. - IEnumerable GetVersionIds(int id, int topRows); - - /// - /// Gets a version of a document. - /// - IContent GetVersion(int versionId); - - /// - /// Gets root-level documents. - /// - IEnumerable GetRootContent(); - - /// - /// Gets documents with an expiration date greater then today. - /// - IEnumerable GetContentForExpiration(); - - /// - /// Gets documents with a release date greater then today. - /// - IEnumerable GetContentForRelease(); - - /// - /// Gets documents in the recycle bin. - /// - IEnumerable GetContentInRecycleBin(); - - /// - /// Gets child documents of a given parent. - /// - /// The parent identifier. - /// The page number. - /// The page size. - /// Total number of documents. - /// A field to order by. - /// The ordering direction. - /// Search text filter. - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets child documents of a given parent. - /// - /// The parent identifier. - /// The page number. - /// The page size. - /// Total number of documents. - /// A field to order by. - /// The ordering direction. - /// A flag indicating whether the ordering field is a system field. - /// Query filter. - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); - - /// - /// Gets descendant documents of a given parent. - /// - /// The parent identifier. - /// The page number. - /// The page size. - /// Total number of documents. - /// A field to order by. - /// The ordering direction. - /// Search text filter. - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets descendant documents of a given parent. - /// - /// The parent identifier. - /// The page number. - /// The page size. - /// Total number of documents. - /// A field to order by. - /// The ordering direction. - /// A flag indicating whether the ordering field is a system field. - /// Query filter. - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); - - /// - /// Counts documents of a given document type. - /// - int Count(string documentTypeAlias = null); - - /// - /// Counts published documents of a given document type. - /// - int CountPublished(string documentTypeAlias = null); - - /// - /// Counts child documents of a given parent, of a given document type. - /// - int CountChildren(int parentId, string documentTypeAlias = null); - - /// - /// Counts descendant documents of a given parent, of a given document type. - /// - int CountDescendants(int parentId, string documentTypeAlias = null); - - /// - /// Gets a value indicating whether a document has children. - /// - bool HasChildren(int id); - - #endregion - - #region Save, Delete Document - - /// - /// Saves a document. - /// - OperationResult Save(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// Saves documents. - /// - // fixme why only 1 result not 1 per content?! - OperationResult Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); - - /// - /// Deletes a document. - /// - /// - /// This method will also delete associated media files, child content and possibly associated domains. - /// This method entirely clears the content from the database. - /// - OperationResult Delete(IContent content, int userId = 0); - - /// - /// Deletes all documents of a given document type. - /// - /// - /// All non-deleted descendants of the deleted documents are moved to the recycle bin. - /// This operation is potentially dangerous and expensive. - /// - void DeleteOfType(int documentTypeId, int userId = 0); - - /// - /// Deletes all documents of given document types. - /// - /// - /// All non-deleted descendants of the deleted documents are moved to the recycle bin. - /// This operation is potentially dangerous and expensive. - /// - void DeleteOfTypes(IEnumerable contentTypeIds, int userId = 0); - - /// - /// Deletes versions of a document prior to a given date. - /// - void DeleteVersions(int id, DateTime date, int userId = 0); - - /// - /// Deletes a version of a document. - /// - void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = 0); - - #endregion - - #region Move, Copy, Sort Document - - /// - /// Moves a document under a new parent. - /// - void Move(IContent content, int parentId, int userId = 0); - - /// - /// Copies a document. - /// - /// - /// Recursively copies all children. - /// - IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0); - - /// - /// Copies a document. - /// - /// - /// Optionaly recursively copies all children. - /// - IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0); - - /// - /// Moves a document to the recycle bin. - /// - OperationResult MoveToRecycleBin(IContent content, int userId = 0); - - /// - /// Empties the recycle bin. - /// - OperationResult EmptyRecycleBin(); - - /// - /// Sorts documents. - /// - bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); - - /// - /// Sorts documents. - /// - bool Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true); - - #endregion - - #region Publish Document - - /// - /// Saves and publishes a document. - /// - /// Property values must first be published at document level. - PublishResult SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// Saves and publishes a document branch. - /// - IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = null, string segment = null, int userId = 0); - - /// - /// Saves and publishes a document branch. - /// - IEnumerable SaveAndPublishBranch(IContent content, bool force, Func editing, Func publishValues, int userId = 0); - - /// - /// Unpublishes a document or optionally unpublishes a culture and/or segment for the document. - /// - UnpublishResult Unpublish(IContent content, string culture = null, string segment = null, int userId = 0); - - /// - /// Gets a value indicating whether a document is path-publishable. - /// - /// A document is path-publishable when all its ancestors are published. - bool IsPathPublishable(IContent content); - - /// - /// Gets a value indicating whether a document is path-published. - /// - /// A document is path-published when all its ancestors, and the document itself, are published. - bool IsPathPublished(IContent content); - - /// - /// Saves a document and raises the "sent to publication" events. - /// - bool SendToPublication(IContent content, int userId = 0); - - /// - /// Publishes and unpublishes scheduled documents. - /// - IEnumerable PerformScheduledPublish(); - - #endregion - - #region Permissions - - /// - /// Gets permissions assigned to a document. - /// - EntityPermissionCollection GetPermissions(IContent content); - - /// - /// Sets the permission of a document. - /// - /// Replaces all permissions with the new set of permissions. - void SetPermissions(EntityPermissionSet permissionSet); - - /// - /// Assigns a permission to a document. - /// - /// Adds the permission to existing permissions. - void SetPermission(IContent entity, char permission, IEnumerable groupIds); - - #endregion - - #region Create - - /// - /// Creates a document. - /// - IContent Create(string name, Guid parentId, string documentTypeAlias, int userId = 0); - - /// - /// Creates a document. - /// - IContent Create(string name, int parentId, string documentTypeAlias, int userId = 0); - - /// - /// Creates a document. - /// - IContent Create(string name, IContent parent, string documentTypeAlias, int userId = 0); - - /// - /// Creates and saves a document. - /// - IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = 0); - - /// - /// Creates and saves a document. - /// - IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = 0); - - #endregion - } -} +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Services +{ + /// + /// Defines the ContentService, which is an easy access to operations involving + /// + public interface IContentService : IContentServiceBase + { + #region Blueprints + + /// + /// Gets a blueprint. + /// + IContent GetBlueprintById(int id); + + /// + /// Gets a blueprint. + /// + IContent GetBlueprintById(Guid id); + + /// + /// Gets blueprints for a content type. + /// + IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeId); + + /// + /// Saves a blueprint. + /// + void SaveBlueprint(IContent content, int userId = 0); + + /// + /// Deletes a blueprint. + /// + void DeleteBlueprint(IContent content, int userId = 0); + + /// + /// Creates a new content item from a blueprint. + /// + IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0); + + /// + /// Deletes blueprints for a content type. + /// + void DeleteBlueprintsOfType(int contentTypeId, int userId = 0); + + /// + /// Deletes blueprints for content types. + /// + void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0); + + #endregion + + #region Get, Count Documents + + /// + /// Gets a document. + /// + IContent GetById(int id); + + /// + /// Gets a document. + /// + IContent GetById(Guid key); + + /// + /// Gets documents. + /// + IEnumerable GetByIds(IEnumerable ids); + + /// + /// Gets documents. + /// + IEnumerable GetByIds(IEnumerable ids); + + /// + /// Gets documents of a given document type. + /// + IEnumerable GetByType(int documentTypeId); + + /// + /// Gets documents at a given level. + /// + IEnumerable GetByLevel(int level); + + /// + /// Gets child documents of a given parent. + /// + IEnumerable GetChildren(int parentId); + + /// + /// Gets child documents of a document, (partially) matching a name. + /// + IEnumerable GetChildren(int parentId, string name); + + /// + /// Gets the parent of a document. + /// + IContent GetParent(int id); + + /// + /// Gets the parent of a document. + /// + IContent GetParent(IContent content); + + /// + /// Gets ancestor documents of a document. + /// + IEnumerable GetAncestors(int id); + + /// + /// Gets ancestor documents of a document. + /// + IEnumerable GetAncestors(IContent content); + + /// + /// Gets descendant documents of a document. + /// + IEnumerable GetDescendants(int id); + + /// + /// Gets descendant documents of a document. + /// + IEnumerable GetDescendants(IContent content); + + /// + /// Gets all versions of a document. + /// + /// Versions are ordered with current first, then most recent first. + IEnumerable GetVersions(int id); + + /// + /// Gets top versions of a document. + /// + /// Versions are ordered with current first, then most recent first. + IEnumerable GetVersionIds(int id, int topRows); + + /// + /// Gets a version of a document. + /// + IContent GetVersion(int versionId); + + /// + /// Gets root-level documents. + /// + IEnumerable GetRootContent(); + + /// + /// Gets documents with an expiration date greater then today. + /// + IEnumerable GetContentForExpiration(); + + /// + /// Gets documents with a release date greater then today. + /// + IEnumerable GetContentForRelease(); + + /// + /// Gets documents in the recycle bin. + /// + IEnumerable GetContentInRecycleBin(); + + /// + /// Gets child documents of a given parent. + /// + /// The parent identifier. + /// The page number. + /// The page size. + /// Total number of documents. + /// A field to order by. + /// The ordering direction. + /// Search text filter. + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Gets child documents of a given parent. + /// + /// The parent identifier. + /// The page number. + /// The page size. + /// Total number of documents. + /// A field to order by. + /// The ordering direction. + /// A flag indicating whether the ordering field is a system field. + /// Query filter. + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); + + /// + /// Gets descendant documents of a given parent. + /// + /// The parent identifier. + /// The page number. + /// The page size. + /// Total number of documents. + /// A field to order by. + /// The ordering direction. + /// Search text filter. + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Gets descendant documents of a given parent. + /// + /// The parent identifier. + /// The page number. + /// The page size. + /// Total number of documents. + /// A field to order by. + /// The ordering direction. + /// A flag indicating whether the ordering field is a system field. + /// Query filter. + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); + + /// + /// Counts documents of a given document type. + /// + int Count(string documentTypeAlias = null); + + /// + /// Counts published documents of a given document type. + /// + int CountPublished(string documentTypeAlias = null); + + /// + /// Counts child documents of a given parent, of a given document type. + /// + int CountChildren(int parentId, string documentTypeAlias = null); + + /// + /// Counts descendant documents of a given parent, of a given document type. + /// + int CountDescendants(int parentId, string documentTypeAlias = null); + + /// + /// Gets a value indicating whether a document has children. + /// + bool HasChildren(int id); + + #endregion + + #region Save, Delete Document + + /// + /// Saves a document. + /// + OperationResult Save(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// Saves documents. + /// + // fixme why only 1 result not 1 per content?! + OperationResult Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); + + /// + /// Deletes a document. + /// + /// + /// This method will also delete associated media files, child content and possibly associated domains. + /// This method entirely clears the content from the database. + /// + OperationResult Delete(IContent content, int userId = 0); + + /// + /// Deletes all documents of a given document type. + /// + /// + /// All non-deleted descendants of the deleted documents are moved to the recycle bin. + /// This operation is potentially dangerous and expensive. + /// + void DeleteOfType(int documentTypeId, int userId = 0); + + /// + /// Deletes all documents of given document types. + /// + /// + /// All non-deleted descendants of the deleted documents are moved to the recycle bin. + /// This operation is potentially dangerous and expensive. + /// + void DeleteOfTypes(IEnumerable contentTypeIds, int userId = 0); + + /// + /// Deletes versions of a document prior to a given date. + /// + void DeleteVersions(int id, DateTime date, int userId = 0); + + /// + /// Deletes a version of a document. + /// + void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = 0); + + #endregion + + #region Move, Copy, Sort Document + + /// + /// Moves a document under a new parent. + /// + void Move(IContent content, int parentId, int userId = 0); + + /// + /// Copies a document. + /// + /// + /// Recursively copies all children. + /// + IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0); + + /// + /// Copies a document. + /// + /// + /// Optionaly recursively copies all children. + /// + IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0); + + /// + /// Moves a document to the recycle bin. + /// + OperationResult MoveToRecycleBin(IContent content, int userId = 0); + + /// + /// Empties the recycle bin. + /// + OperationResult EmptyRecycleBin(); + + /// + /// Sorts documents. + /// + bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); + + /// + /// Sorts documents. + /// + bool Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true); + + #endregion + + #region Publish Document + + /// + /// Saves and publishes a document. + /// + /// Property values must first be published at document level. + PublishResult SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// Saves and publishes a document branch. + /// + IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = null, string segment = null, int userId = 0); + + /// + /// Saves and publishes a document branch. + /// + IEnumerable SaveAndPublishBranch(IContent content, bool force, Func editing, Func publishValues, int userId = 0); + + /// + /// Unpublishes a document or optionally unpublishes a culture and/or segment for the document. + /// + UnpublishResult Unpublish(IContent content, string culture = null, string segment = null, int userId = 0); + + /// + /// Gets a value indicating whether a document is path-publishable. + /// + /// A document is path-publishable when all its ancestors are published. + bool IsPathPublishable(IContent content); + + /// + /// Gets a value indicating whether a document is path-published. + /// + /// A document is path-published when all its ancestors, and the document itself, are published. + bool IsPathPublished(IContent content); + + /// + /// Saves a document and raises the "sent to publication" events. + /// + bool SendToPublication(IContent content, int userId = 0); + + /// + /// Publishes and unpublishes scheduled documents. + /// + IEnumerable PerformScheduledPublish(); + + #endregion + + #region Permissions + + /// + /// Gets permissions assigned to a document. + /// + EntityPermissionCollection GetPermissions(IContent content); + + /// + /// Sets the permission of a document. + /// + /// Replaces all permissions with the new set of permissions. + void SetPermissions(EntityPermissionSet permissionSet); + + /// + /// Assigns a permission to a document. + /// + /// Adds the permission to existing permissions. + void SetPermission(IContent entity, char permission, IEnumerable groupIds); + + #endregion + + #region Create + + /// + /// Creates a document. + /// + IContent Create(string name, Guid parentId, string documentTypeAlias, int userId = 0); + + /// + /// Creates a document. + /// + IContent Create(string name, int parentId, string documentTypeAlias, int userId = 0); + + /// + /// Creates a document. + /// + IContent Create(string name, IContent parent, string documentTypeAlias, int userId = 0); + + /// + /// Creates and saves a document. + /// + IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = 0); + + /// + /// Creates and saves a document. + /// + IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = 0); + + #endregion + } +} diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 3474efa509..bddd276e58 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -1,35 +1,35 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Services -{ - /// - /// Manages objects. - /// - public interface IContentTypeService : IContentTypeServiceBase - { - /// - /// Gets all property type aliases. - /// - /// - IEnumerable GetAllPropertyTypeAliases(); - - /// - /// Gets all content type aliases - /// - /// - /// If this list is empty, it will return all content type aliases for media, members and content, otherwise - /// it will only return content type aliases for the object types specified - /// - /// - IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes); - - /// - /// Returns all content type Ids for the aliases given - /// - /// - /// - IEnumerable GetAllContentTypeIds(string[] aliases); - } -} +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + /// + /// Manages objects. + /// + public interface IContentTypeService : IContentTypeServiceBase + { + /// + /// Gets all property type aliases. + /// + /// + IEnumerable GetAllPropertyTypeAliases(); + + /// + /// Gets all content type aliases + /// + /// + /// If this list is empty, it will return all content type aliases for media, members and content, otherwise + /// it will only return content type aliases for the object types specified + /// + /// + IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes); + + /// + /// Returns all content type Ids for the aliases given + /// + /// + /// + IEnumerable GetAllContentTypeIds(string[] aliases); + } +} diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index fef7065571..06aef3f1c7 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -1,92 +1,92 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Services -{ - /// - /// Defines the DataType Service, which is an easy access to operations involving - /// - public interface IDataTypeService : IService - { - Attempt> CreateContainer(int parentId, string name, int userId = 0); - Attempt SaveContainer(EntityContainer container, int userId = 0); - EntityContainer GetContainer(int containerId); - EntityContainer GetContainer(Guid containerId); - IEnumerable GetContainers(string folderName, int level); - IEnumerable GetContainers(IDataType dataType); - IEnumerable GetContainers(int[] containerIds); - Attempt DeleteContainer(int containerId, int userId = 0); - Attempt> RenameContainer(int id, string name, int userId = 0); - - /// - /// Gets a by its Name - /// - /// Name of the - /// - IDataType GetDataType(string name); - - /// - /// Gets a by its Id - /// - /// Id of the - /// - IDataType GetDataType(int id); - - /// - /// Gets a by its unique guid Id - /// - /// Unique guid Id of the DataType - /// - IDataType GetDataType(Guid id); - - /// - /// Gets all objects or those with the ids passed in - /// - /// Optional array of Ids - /// An enumerable list of objects - IEnumerable GetAll(params int[] ids); - - /// - /// Saves an - /// - /// to save - /// Id of the user issueing the save - void Save(IDataType dataType, int userId = 0); - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - void Save(IEnumerable dataTypeDefinitions, int userId = 0); - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - /// Boolean indicating whether or not to raise events - void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents); - - /// - /// Deletes an - /// - /// - /// Please note that deleting a will remove - /// all the data that references this . - /// - /// to delete - /// Id of the user issueing the deletion - void Delete(IDataType dataType, int userId = 0); - - /// - /// Gets a by its control Id - /// - /// Alias of the property editor - /// Collection of objects with a matching contorl id - IEnumerable GetByEditorAlias(string propertyEditorAlias); - - Attempt> Move(IDataType toMove, int parentId); - } -} +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + /// + /// Defines the DataType Service, which is an easy access to operations involving + /// + public interface IDataTypeService : IService + { + Attempt> CreateContainer(int parentId, string name, int userId = 0); + Attempt SaveContainer(EntityContainer container, int userId = 0); + EntityContainer GetContainer(int containerId); + EntityContainer GetContainer(Guid containerId); + IEnumerable GetContainers(string folderName, int level); + IEnumerable GetContainers(IDataType dataType); + IEnumerable GetContainers(int[] containerIds); + Attempt DeleteContainer(int containerId, int userId = 0); + Attempt> RenameContainer(int id, string name, int userId = 0); + + /// + /// Gets a by its Name + /// + /// Name of the + /// + IDataType GetDataType(string name); + + /// + /// Gets a by its Id + /// + /// Id of the + /// + IDataType GetDataType(int id); + + /// + /// Gets a by its unique guid Id + /// + /// Unique guid Id of the DataType + /// + IDataType GetDataType(Guid id); + + /// + /// Gets all objects or those with the ids passed in + /// + /// Optional array of Ids + /// An enumerable list of objects + IEnumerable GetAll(params int[] ids); + + /// + /// Saves an + /// + /// to save + /// Id of the user issueing the save + void Save(IDataType dataType, int userId = 0); + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + void Save(IEnumerable dataTypeDefinitions, int userId = 0); + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + /// Boolean indicating whether or not to raise events + void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents); + + /// + /// Deletes an + /// + /// + /// Please note that deleting a will remove + /// all the data that references this . + /// + /// to delete + /// Id of the user issueing the deletion + void Delete(IDataType dataType, int userId = 0); + + /// + /// Gets a by its control Id + /// + /// Alias of the property editor + /// Collection of objects with a matching contorl id + IEnumerable GetByEditorAlias(string propertyEditorAlias); + + Attempt> Move(IDataType toMove, int parentId); + } +} diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index c28202ba8b..926afcf0a9 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -1,303 +1,303 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Services -{ - public interface IEntityService - { - /// - /// Gets an entity. - /// - /// The identifier of the entity. - IEntitySlim Get(int id); - - /// - /// Gets an entity. - /// - /// The identifier of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(int id, bool full); - - /// - /// Gets an entity. - /// - /// The unique key of the entity. - IEntitySlim Get(Guid key); - - /// - /// Gets an entity. - /// - /// The unique key of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(Guid key, bool full); - - /// - /// Gets an entity. - /// - /// The identifier of the entity. - /// The object type of the entity. - IEntitySlim Get(int id, UmbracoObjectTypes objectType); - - /// - /// Gets an entity. - /// - /// The identifier of the entity. - /// The object type of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(int id, UmbracoObjectTypes objectType, bool full); - - /// - /// Gets an entity. - /// - /// The unique key of the entity. - /// The object type of the entity. - IEntitySlim Get(Guid key, UmbracoObjectTypes objectType); - - /// - /// Gets an entity. - /// - /// The unique key of the entity. - /// The object type of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(Guid key, UmbracoObjectTypes objectType, bool full); - - /// - /// Gets an entity. - /// - /// The type used to determine the object type of the entity. - /// The identifier of the entity. - IEntitySlim Get(int id) where T : IUmbracoEntity; - - /// - /// Gets an entity. - /// - /// The type used to determine the object type of the entity. - /// The identifier of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(int id, bool full) where T : IUmbracoEntity; - - /// - /// Gets an entity. - /// - /// The type used to determine the object type of the entity. - /// The unique key of the entity. - IEntitySlim Get(Guid key) where T : IUmbracoEntity; - - /// - /// Gets an entity. - /// - /// The type used to determine the object type of the entity. - /// The unique key of the entity. - /// A value indicating whether to load a light entity, or the full entity. - /// Returns either a , or an actual entity, depending on . - IUmbracoEntity Get(Guid key, bool full) where T : IUmbracoEntity; - - /// - /// Determines whether an entity exists. - /// - /// The identifier of the entity. - bool Exists(int id); - - /// - /// Determines whether an entity exists. - /// - /// The unique key of the entity. - bool Exists(Guid key); - - /// - /// Gets entities of a given object type. - /// - /// The type used to determine the object type of the entities. - IEnumerable GetAll() where T : IUmbracoEntity; - - /// - /// Gets entities of a given object type. - /// - /// The type used to determine the object type of the entities. - /// The identifiers of the entities. - /// If is empty, returns all entities. - IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity; - - /// - /// Gets entities of a given object type. - /// - /// The object type of the entities. - IEnumerable GetAll(UmbracoObjectTypes objectType); - - /// - /// Gets entities of a given object type. - /// - /// The object type of the entities. - /// The identifiers of the entities. - /// If is empty, returns all entities. - IEnumerable GetAll(UmbracoObjectTypes objectType, params int[] ids); - - /// - /// Gets entities of a given object type. - /// - /// The object type of the entities. - IEnumerable GetAll(Guid objectType); - - /// - /// Gets entities of a given object type. - /// - /// The object type of the entities. - /// The identifiers of the entities. - /// If is empty, returns all entities. - IEnumerable GetAll(Guid objectType, params int[] ids); - - /// - /// Gets entities of a given object type. - /// - /// The type used to determine the object type of the entities. - /// The unique identifiers of the entities. - /// If is empty, returns all entities. - IEnumerable GetAll(params Guid[] keys) where T : IUmbracoEntity; - - /// - /// Gets entities of a given object type. - /// - /// The object type of the entities. - /// The unique identifiers of the entities. - /// If is empty, returns all entities. - IEnumerable GetAll(UmbracoObjectTypes objectType, Guid[] keys); - - /// - /// Gets entities of a given object type. - /// - /// The object type of the entities. - /// The unique identifiers of the entities. - /// If is empty, returns all entities. - IEnumerable GetAll(Guid objectType, params Guid[] keys); - - /// - /// Gets entities at root. - /// - /// The object type of the entities. - IEnumerable GetRootEntities(UmbracoObjectTypes objectType); - - /// - /// Gets the parent of an entity. - /// - /// The identifier of the entity. - IEntitySlim GetParent(int id); - - /// - /// Gets the parent of an entity. - /// - /// The identifier of the entity. - /// The object type of the parent. - IEntitySlim GetParent(int id, UmbracoObjectTypes objectType); - - /// - /// Gets the children of an entity. - /// - /// The identifier of the entity. - IEnumerable GetChildren(int id); - - /// - /// Gets the children of an entity. - /// - /// The identifier of the entity. - /// The object type of the children. - IEnumerable GetChildren(int id, UmbracoObjectTypes objectType); - - /// - /// Gets the descendants of an entity. - /// - /// The identifier of the entity. - IEnumerable GetDescendants(int id); - - /// - /// Gets the descendants of an entity. - /// - /// The identifier of the entity. - /// The object type of the descendants. - IEnumerable GetDescendants(int id, UmbracoObjectTypes objectType); - - /// - /// Gets children of an entity. - /// - IEnumerable GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets descendants of an entity. - /// - IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets descendants of entities. - /// - IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets descendants of root. - /// - IEnumerable GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true); - - /// - /// Gets the object type of an entity. - /// - UmbracoObjectTypes GetObjectType(int id); - - /// - /// Gets the object type of an entity. - /// - UmbracoObjectTypes GetObjectType(Guid key); - - /// - /// Gets the object type of an entity. - /// - UmbracoObjectTypes GetObjectType(IUmbracoEntity entity); - - /// - /// Gets the Clr type of an entity. - /// - Type GetEntityType(int id); - - /// - /// Gets the integer identifier corresponding to a unique Guid identifier. - /// - Attempt GetId(Guid key, UmbracoObjectTypes objectType); - - /// - /// Gets the integer identifier corresponding to a Udi. - /// - Attempt GetId(Udi udi); - - /// - /// Gets the unique Guid identifier corresponding to an integer identifier. - /// - Attempt GetKey(int id, UmbracoObjectTypes umbracoObjectType); - - /// - /// Gets paths for entities. - /// - IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params int[] ids); - - /// - /// Gets paths for entities. - /// - IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params Guid[] keys); - - /// - /// Reserves an identifier for a key. - /// - /// They key. - /// The identifier. - /// When a new content or a media is saved with the key, it will have the reserved identifier. - int ReserveId(Guid key); - } -} +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Services +{ + public interface IEntityService + { + /// + /// Gets an entity. + /// + /// The identifier of the entity. + IEntitySlim Get(int id); + + /// + /// Gets an entity. + /// + /// The identifier of the entity. + /// A value indicating whether to load a light entity, or the full entity. + /// Returns either a , or an actual entity, depending on . + IUmbracoEntity Get(int id, bool full); + + /// + /// Gets an entity. + /// + /// The unique key of the entity. + IEntitySlim Get(Guid key); + + /// + /// Gets an entity. + /// + /// The unique key of the entity. + /// A value indicating whether to load a light entity, or the full entity. + /// Returns either a , or an actual entity, depending on . + IUmbracoEntity Get(Guid key, bool full); + + /// + /// Gets an entity. + /// + /// The identifier of the entity. + /// The object type of the entity. + IEntitySlim Get(int id, UmbracoObjectTypes objectType); + + /// + /// Gets an entity. + /// + /// The identifier of the entity. + /// The object type of the entity. + /// A value indicating whether to load a light entity, or the full entity. + /// Returns either a , or an actual entity, depending on . + IUmbracoEntity Get(int id, UmbracoObjectTypes objectType, bool full); + + /// + /// Gets an entity. + /// + /// The unique key of the entity. + /// The object type of the entity. + IEntitySlim Get(Guid key, UmbracoObjectTypes objectType); + + /// + /// Gets an entity. + /// + /// The unique key of the entity. + /// The object type of the entity. + /// A value indicating whether to load a light entity, or the full entity. + /// Returns either a , or an actual entity, depending on . + IUmbracoEntity Get(Guid key, UmbracoObjectTypes objectType, bool full); + + /// + /// Gets an entity. + /// + /// The type used to determine the object type of the entity. + /// The identifier of the entity. + IEntitySlim Get(int id) where T : IUmbracoEntity; + + /// + /// Gets an entity. + /// + /// The type used to determine the object type of the entity. + /// The identifier of the entity. + /// A value indicating whether to load a light entity, or the full entity. + /// Returns either a , or an actual entity, depending on . + IUmbracoEntity Get(int id, bool full) where T : IUmbracoEntity; + + /// + /// Gets an entity. + /// + /// The type used to determine the object type of the entity. + /// The unique key of the entity. + IEntitySlim Get(Guid key) where T : IUmbracoEntity; + + /// + /// Gets an entity. + /// + /// The type used to determine the object type of the entity. + /// The unique key of the entity. + /// A value indicating whether to load a light entity, or the full entity. + /// Returns either a , or an actual entity, depending on . + IUmbracoEntity Get(Guid key, bool full) where T : IUmbracoEntity; + + /// + /// Determines whether an entity exists. + /// + /// The identifier of the entity. + bool Exists(int id); + + /// + /// Determines whether an entity exists. + /// + /// The unique key of the entity. + bool Exists(Guid key); + + /// + /// Gets entities of a given object type. + /// + /// The type used to determine the object type of the entities. + IEnumerable GetAll() where T : IUmbracoEntity; + + /// + /// Gets entities of a given object type. + /// + /// The type used to determine the object type of the entities. + /// The identifiers of the entities. + /// If is empty, returns all entities. + IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity; + + /// + /// Gets entities of a given object type. + /// + /// The object type of the entities. + IEnumerable GetAll(UmbracoObjectTypes objectType); + + /// + /// Gets entities of a given object type. + /// + /// The object type of the entities. + /// The identifiers of the entities. + /// If is empty, returns all entities. + IEnumerable GetAll(UmbracoObjectTypes objectType, params int[] ids); + + /// + /// Gets entities of a given object type. + /// + /// The object type of the entities. + IEnumerable GetAll(Guid objectType); + + /// + /// Gets entities of a given object type. + /// + /// The object type of the entities. + /// The identifiers of the entities. + /// If is empty, returns all entities. + IEnumerable GetAll(Guid objectType, params int[] ids); + + /// + /// Gets entities of a given object type. + /// + /// The type used to determine the object type of the entities. + /// The unique identifiers of the entities. + /// If is empty, returns all entities. + IEnumerable GetAll(params Guid[] keys) where T : IUmbracoEntity; + + /// + /// Gets entities of a given object type. + /// + /// The object type of the entities. + /// The unique identifiers of the entities. + /// If is empty, returns all entities. + IEnumerable GetAll(UmbracoObjectTypes objectType, Guid[] keys); + + /// + /// Gets entities of a given object type. + /// + /// The object type of the entities. + /// The unique identifiers of the entities. + /// If is empty, returns all entities. + IEnumerable GetAll(Guid objectType, params Guid[] keys); + + /// + /// Gets entities at root. + /// + /// The object type of the entities. + IEnumerable GetRootEntities(UmbracoObjectTypes objectType); + + /// + /// Gets the parent of an entity. + /// + /// The identifier of the entity. + IEntitySlim GetParent(int id); + + /// + /// Gets the parent of an entity. + /// + /// The identifier of the entity. + /// The object type of the parent. + IEntitySlim GetParent(int id, UmbracoObjectTypes objectType); + + /// + /// Gets the children of an entity. + /// + /// The identifier of the entity. + IEnumerable GetChildren(int id); + + /// + /// Gets the children of an entity. + /// + /// The identifier of the entity. + /// The object type of the children. + IEnumerable GetChildren(int id, UmbracoObjectTypes objectType); + + /// + /// Gets the descendants of an entity. + /// + /// The identifier of the entity. + IEnumerable GetDescendants(int id); + + /// + /// Gets the descendants of an entity. + /// + /// The identifier of the entity. + /// The object type of the descendants. + IEnumerable GetDescendants(int id, UmbracoObjectTypes objectType); + + /// + /// Gets children of an entity. + /// + IEnumerable GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Gets descendants of an entity. + /// + IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Gets descendants of entities. + /// + IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Gets descendants of root. + /// + IEnumerable GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true); + + /// + /// Gets the object type of an entity. + /// + UmbracoObjectTypes GetObjectType(int id); + + /// + /// Gets the object type of an entity. + /// + UmbracoObjectTypes GetObjectType(Guid key); + + /// + /// Gets the object type of an entity. + /// + UmbracoObjectTypes GetObjectType(IUmbracoEntity entity); + + /// + /// Gets the Clr type of an entity. + /// + Type GetEntityType(int id); + + /// + /// Gets the integer identifier corresponding to a unique Guid identifier. + /// + Attempt GetId(Guid key, UmbracoObjectTypes objectType); + + /// + /// Gets the integer identifier corresponding to a Udi. + /// + Attempt GetId(Udi udi); + + /// + /// Gets the unique Guid identifier corresponding to an integer identifier. + /// + Attempt GetKey(int id, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets paths for entities. + /// + IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params int[] ids); + + /// + /// Gets paths for entities. + /// + IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params Guid[] keys); + + /// + /// Reserves an identifier for a key. + /// + /// They key. + /// The identifier. + /// When a new content or a media is saved with the key, it will have the reserved identifier. + int ReserveId(Guid key); + } +} diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index 1f938c8e27..a61faa10c7 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -1,362 +1,362 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Services -{ - /// - /// Defines the File Service, which is an easy access to operations involving objects like Scripts, Stylesheets and Templates - /// - public interface IFileService : IService - { - IEnumerable GetPartialViewSnippetNames(params string[] filterNames); - void CreatePartialViewFolder(string folderPath); - void CreatePartialViewMacroFolder(string folderPath); - void DeletePartialViewFolder(string folderPath); - void DeletePartialViewMacroFolder(string folderPath); - IPartialView GetPartialView(string path); - IPartialView GetPartialViewMacro(string path); - IEnumerable GetPartialViewMacros(params string[] names); - Attempt CreatePartialView(IPartialView partialView, string snippetName = null, int userId = 0); - Attempt CreatePartialViewMacro(IPartialView partialView, string snippetName = null, int userId = 0); - bool DeletePartialView(string path, int userId = 0); - bool DeletePartialViewMacro(string path, int userId = 0); - Attempt SavePartialView(IPartialView partialView, int userId = 0); - Attempt SavePartialViewMacro(IPartialView partialView, int userId = 0); - bool ValidatePartialView(PartialView partialView); - bool ValidatePartialViewMacro(PartialView partialView); - - /// - /// Gets a list of all objects - /// - /// An enumerable list of objects - IEnumerable GetStylesheets(params string[] names); - - /// - /// Gets a object by its name - /// - /// Name of the stylesheet incl. extension - /// A object - Stylesheet GetStylesheetByName(string name); - - /// - /// Saves a - /// - /// to save - /// Optional id of the user saving the stylesheet - void SaveStylesheet(Stylesheet stylesheet, int userId = 0); - - /// - /// Deletes a stylesheet by its name - /// - /// Name incl. extension of the Stylesheet to delete - /// Optional id of the user deleting the stylesheet - void DeleteStylesheet(string path, int userId = 0); - - /// - /// Validates a - /// - /// to validate - /// True if Stylesheet is valid, otherwise false - bool ValidateStylesheet(Stylesheet stylesheet); - - /// - /// Gets a list of all objects - /// - /// An enumerable list of objects - IEnumerable - - - - -]]> - - - - - - admin - - - - - - - - Header 2 - h2 - - - - - Header 3 - h3 - - - - - Float Right - .fr - - - - - - - base-min - base-min - - - - - - default - default - - - - - - - - Map - Map - - - - - - - True - 0 - Map.cshtml - - - - - - - - - - /usercontrols/StandardWebsiteInstall.ascx + + + + + Map.cshtml + /macroScripts + Map.cshtml + + + AccountController.cs + /App_Code + AccountController.cs + + + ContactController.cs + /App_Code + ContactController.cs + + + LuceneHighlightHelper.cs + /App_Code + LuceneHighlightHelper.cs + + + default.js + /scripts + default.js + + + jquery.timers.js + /scripts + jquery.timers.js + + + Creative_Founds_Square.jpg + /images + Creative_Founds_Square.jpg + + + header_bg.png + /images + header_bg.png + + + li_bg.gif + /images + li_bg.gif + + + li_white_bg.gif + /images + li_white_bg.gif + + + logo.png + /images + logo.png + + + nav_li_bg.png + /images + nav_li_bg.png + + + search_bg.png + /images + search_bg.png + + + search_btn_bg.png + /images + search_btn_bg.png + + + slider_bg.png + /images + slider_bg.png + + + twitter_square.png + /images + twitter_square.png + + + umbraco_Square.jpg + /images + umbraco_Square.jpg + + + web_applications.jpg + /images + web_applications.jpg + + + Lucene.Net.Contrib.Highlighter.dll + /bin + Lucene.Net.Contrib.Highlighter.dll + + + StandardWebsiteInstall.ascx.cs + /usercontrols + StandardWebsiteInstall.ascx.cs + + + Affiliations.cshtml + /Views/Partials + Affiliations.cshtml + + + ContactForm.cshtml + /Views/Partials + ContactForm.cshtml + + + ContentPanels.cshtml + /Views/Partials + ContentPanels.cshtml + + + LeftNavigation.cshtml + /Views/Partials + LeftNavigation.cshtml + + + LoginForm.cshtml + /Views/Partials + LoginForm.cshtml + + + dice.png + /media/1824 + dice.png + + + dice_thumb.jpg + /media/1824 + dice_thumb.jpg + + + cap.png + /media/1813 + cap.png + + + cap_thumb.jpg + /media/1813 + cap_thumb.jpg + + + chat.jpg + /media/2075 + chat.jpg + + + chat_thumb.jpg + /media/2075 + chat_thumb.jpg + + + umbraco_logo.png + /media/1477 + umbraco_logo.png + + + umbraco_logo_thumb.jpg + /media/1477 + umbraco_logo_thumb.jpg + + + StandardWebsiteInstall.ascx + /usercontrols + StandardWebsiteInstall.ascx + + + + + StandardWebsiteMVC + 2.0 + MIT license + http://our.umbraco.org/projects/starter-kits/standard-website-mvc + + 3 + 0 + 0 + + + + Chris Koiak + http://www.creativefounds.co.uk + + + + + + + + + + + Built by Creative Founds +

Web ApplicationsCreative Founds design and build first class software solutions that deliver big results. We provide ASP.NET web and mobile applications, Umbraco development service & technical consultancy.

+

www.creativefounds.co.uk

]]> +
+ + Umbraco Development +

UmbracoUmbraco the the leading ASP.NET open source CMS, under pinning over 150,000 websites. Our Certified Developers are experts in developing high performance and feature rich websites.

]]> +
+ + Contact Us +

Contact Us on TwitterWe'd love to hear how this package has helped you and how it can be improved. Get in touch on the project website or via twitter

]]> +
+ +
Standard Website MVC, Company Address, Glasgow, Postcode
+ Copyright &copy; 2012 Your Company + http://www.umbraco.org + /media/1477/umbraco_logo.png + + + + + + Standard Site for Umbraco by Koiak + + + 0 + + + About Us +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur dictum, nisi non gravida blandit, odio nulla ultrices orci, quis blandit tortor libero vitae massa. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nulla at velit lacus.

+

Vivamus dictum, lorem vitae bold text, libero link text elit, vitae tincidunt ante nibh vitae lectus. Praesent molestie justo non mi dapibus et venenatis ante facilisis. Morbi gravida molestie cursus.

+

Sub Section

+

Aliquam at est dui. Pellentesque tortor risus, congue eget pretium ut, elementum eu velit. Phasellus tempus leo sed elit tempus vestibulum. Integer mollis arcu porta leo vulputate dignissim.

+
    +
  • List Item 1
  • +
  • List Item 2
  • +
  • List Item 3
  • +
+
    +
  1. Numbered Item 1
  2. +
  3. Numbered Item 2
  4. +
  5. Numbered Item 3
  6. +
+

In suscipit lectus vitae nibh faucibus vel lobortis est ullamcorper. Morbi risus nisl, sodales egestas placerat nec, cursus vel tellus. Vivamus aliquet sagittis pellentesque. Nulla rutrum neque nec metus mattis volutpat.

+

Vivamus egestas enim sed augue eleifend id tristique magna tempus. Nunc ullamcorper scelerisque ante quis consectetur.

+

Curabitur vel dui a enim adipiscing malesuada non quis velit.

]]> +
+ + + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + <Standard id="1074" parentID="1073" level="3" creatorID="0" sortOrder="0" createDate="2013-02-17T09:04:41" updateDate="2013-02-17T09:04:46" nodeName="Sub Navigation 1" urlName="sub-navigation-1" path="-1,1072,1073,1074" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1053"> + <bodyText><![CDATA[]]></bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + <Standard id="1075" parentID="1074" level="4" creatorID="0" sortOrder="0" createDate="2013-02-17T09:04:41" updateDate="2013-02-17T09:04:46" nodeName="3rd level nav 2" urlName="3rd-level-nav-2" path="-1,1072,1073,1074,1075" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1053"> + <bodyText><![CDATA[]]></bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + </Standard> + </Standard> + <Standard id="1076" parentID="1073" level="3" creatorID="0" sortOrder="1" createDate="2013-02-17T09:04:41" updateDate="2013-02-17T09:04:46" nodeName="Sub Navigation 2" urlName="sub-navigation-2" path="-1,1072,1073,1076" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1053"> + <bodyText><![CDATA[]]></bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + </Standard> + </Standard> + <Standard id="1077" parentID="1072" level="2" creatorID="0" sortOrder="1" createDate="2013-02-17T09:04:41" updateDate="2013-02-17T09:10:47" nodeName="News" urlName="news" path="-1,1072,1077" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1046"> + <bodyText><![CDATA[<h2>News</h2>]]></bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + <NewsArticle id="1078" parentID="1077" level="3" creatorID="0" sortOrder="0" createDate="2013-02-17T09:04:42" updateDate="2013-02-17T09:11:22" nodeName="Article 1" urlName="article-1" path="-1,1072,1077,1078" isDoc="" nodeType="1063" creatorName="admin" writerName="admin" writerID="0" template="1053"> + <articleSummary><![CDATA[Here is an article summary, you can add as much of a description as you require.]]></articleSummary> + <articleDate>2012-11-08T00:00:00</articleDate> + <bodyText> + <![CDATA[<h2>Your first Article</h2> +<p><span><strong>Aliquam erat volutpat. Sed laoreet leo id nisi convallis sollicitudin sodales orci adipiscing. Sed a dolor ipsum. Quisque ut quam eu arcu placerat rhoncus vel et mi. Pellentesque sed est nisl.</strong> </span></p> +<p><span>Aliquam at orci justo, id pharetra augue. Aenean ut nunc ut nibh interdum scelerisque ut ac mi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vehicula dictum lectus in interdum. Fusce pellentesque elit nec metus convallis id porttitor felis laoreet. Nunc vitae enim quam.</span></p>]]> + </bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + </NewsArticle> + </Standard> + <Standard id="1079" parentID="1072" level="2" creatorID="0" sortOrder="2" createDate="2013-02-17T09:04:42" updateDate="2013-02-17T09:10:55" nodeName="Clients" urlName="clients" path="-1,1072,1079" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1053"> + <bodyText> + <![CDATA[<h2>Clients</h2> +<p><strong>This is a standard content page.</strong></p> +<p><span>Vestibulum malesuada aliquet ante, vitae ullamcorper felis faucibus vel. Vestibulum condimentum faucibus tellus porta ultrices. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. </span></p> +<p><span>Cras at auctor orci. Praesent facilisis erat nec odio consequat at posuere ligula pretium. Nulla eget felis id nisl volutpat pellentesque. Ut id augue id ligula placerat rutrum a nec purus. Maecenas sed lectus ac mi pellentesque luctus quis sit amet turpis. Vestibulum adipiscing convallis vestibulum. </span></p> +<p><span>Duis condimentum lectus at orci placerat vitae imperdiet lorem cursus. Duis hendrerit porta lorem, non suscipit quam consectetur vitae. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean elit augue, tincidunt nec tincidunt id, elementum vel est.</span></p>]]> + </bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + </Standard> + <ContactForm id="1080" parentID="1072" level="2" creatorID="0" sortOrder="3" createDate="2013-02-17T09:04:42" updateDate="2013-02-17T09:04:46" nodeName="Contact Us" urlName="contact-us" path="-1,1072,1080" isDoc="" nodeType="1059" creatorName="admin" writerName="admin" writerID="0" template="1048"> + <recipientEmailAddress>chriskoiak@gmail.com</recipientEmailAddress> + <emailSubject>Standard Website Contact Form</emailSubject> + <thankYouPage>1124</thankYouPage> + <senderEmailAddress>chriskoiak@gmail.com</senderEmailAddress> + <bodyText> + <![CDATA[<h2>Contact Us</h2> +<p>Please get in touch!</p> +<?UMBRACO_MACRO macroAlias="Map" />]]> + </bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + <Standard id="1081" parentID="1080" level="3" creatorID="0" sortOrder="0" createDate="2013-02-17T09:04:42" updateDate="2013-02-17T09:04:46" nodeName="Thank You" urlName="thank-you" path="-1,1072,1080,1081" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1053"> + <bodyText><![CDATA[<p><strong>Email sent successfully</strong></p>]]></bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>1</umbracoNaviHide> + </Standard> + </ContactForm> + <Standard id="1082" parentID="1072" level="2" creatorID="0" sortOrder="4" createDate="2013-02-17T09:04:42" updateDate="2013-02-17T09:10:47" nodeName="Search" urlName="search" path="-1,1072,1082" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1051"> + <bodyText><![CDATA[<h2>Search</h2>]]></bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + </Standard> + <Standard id="1083" parentID="1072" level="2" creatorID="0" sortOrder="5" createDate="2013-02-17T09:04:43" updateDate="2013-02-17T09:10:47" nodeName="Sitemap" urlName="sitemap" path="-1,1072,1083" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1052"> + <bodyText> + <![CDATA[<h2>Sitemap</h2> +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur dictum, nisi non gravida blandit, odio nulla ultrices orci, quis blandit tortor libero vitae massa.<a href="/contact-us.aspx"><br /></a></p>]]> + </bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + </Standard> + <ClientArea id="1084" parentID="1072" level="2" creatorID="0" sortOrder="6" createDate="2013-02-17T09:04:43" updateDate="2013-02-17T09:04:46" nodeName="Client Area" urlName="client-area" path="-1,1072,1084" isDoc="" nodeType="1056" creatorName="admin" writerName="admin" writerID="0" template="1047"> + <umbracoNaviHide>0</umbracoNaviHide> + <Standard id="1085" parentID="1084" level="3" creatorID="0" sortOrder="0" createDate="2013-02-17T09:04:43" updateDate="2013-02-17T09:04:46" nodeName="Client Area 1" urlName="client-area-1" path="-1,1072,1084,1085" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1053"> + <bodyText><![CDATA[]]></bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + <Standard id="1086" parentID="1085" level="4" creatorID="0" sortOrder="0" createDate="2013-02-17T09:04:43" updateDate="2013-02-17T09:04:46" nodeName="Page 1" urlName="page-1" path="-1,1072,1084,1085,1086" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1053"> + <bodyText><![CDATA[]]></bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + </Standard> + </Standard> + <Standard id="1087" parentID="1084" level="3" creatorID="0" sortOrder="1" createDate="2013-02-17T09:04:43" updateDate="2013-02-17T09:04:46" nodeName="Client Area 2" urlName="client-area-2" path="-1,1072,1084,1087" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1053"> + <bodyText><![CDATA[]]></bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>0</umbracoNaviHide> + </Standard> + </ClientArea> + <Standard id="1088" parentID="1072" level="2" creatorID="0" sortOrder="7" createDate="2013-02-17T09:04:44" updateDate="2013-02-17T09:04:46" nodeName="Insufficent Access" urlName="insufficent-access" path="-1,1072,1088" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1053"> + <bodyText> + <![CDATA[<h2>Insufficent Access</h2> +<p>You have tried to access a page you do not have access to.</p>]]> + </bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>1</umbracoNaviHide> + </Standard> + <Standard id="1089" parentID="1072" level="2" creatorID="0" sortOrder="8" createDate="2013-02-17T09:04:44" updateDate="2013-02-17T09:10:47" nodeName="Login" urlName="login" path="-1,1072,1089" isDoc="" nodeType="1058" creatorName="admin" writerName="admin" writerID="0" template="1050"> + <bodyText><![CDATA[<h2>Login</h2>]]></bodyText> + <contentPanels><![CDATA[]]></contentPanels> + <title /> + <description><![CDATA[]]></description> + <keywords><![CDATA[]]></keywords> + <umbracoNaviHide>1</umbracoNaviHide> + </Standard> + <Slideshow id="1090" parentID="1072" level="2" creatorID="0" sortOrder="9" createDate="2013-02-17T09:04:44" updateDate="2013-02-17T09:04:46" nodeName="Slideshow" urlName="slideshow" path="-1,1072,1090" isDoc="" nodeType="1065" creatorName="admin" writerName="admin" writerID="0" template="0"> + <umbracoNaviHide>0</umbracoNaviHide> + <Slide id="1091" parentID="1090" level="3" creatorID="0" sortOrder="0" createDate="2013-02-17T09:04:44" updateDate="2013-02-17T09:04:46" nodeName="Purple Hat" urlName="purple-hat" path="-1,1072,1090,1091" isDoc="" nodeType="1064" creatorName="admin" writerName="admin" writerID="0" template="0"> + <mainImage>/media/1813/cap.png</mainImage> + <bodyText> + <![CDATA[<h3>Standard Website MVC</h3> +<p>Well hello! This website package demonstrates all the standard functionality of Umbraco. It's a great starting point for <span>starting point for further development or as a prototype.</span></p> +<h3>Creative Founds</h3> +<p>This package was developed by <a href="http://www.twitter.com/chriskoiak" target="_blank">Chris Koiak</a> & <a href="http://www.creativefounds.co.uk" target="_blank">Creative Founds</a>. </p>]]> + </bodyText> + <umbracoNaviHide>1</umbracoNaviHide> + </Slide> + <Slide id="1092" parentID="1090" level="3" creatorID="0" sortOrder="1" createDate="2013-02-17T09:04:44" updateDate="2013-02-17T09:04:46" nodeName="Red Dice" urlName="red-dice" path="-1,1072,1090,1092" isDoc="" nodeType="1064" creatorName="admin" writerName="admin" writerID="0" template="0"> + <mainImage>/media/1824/dice.png</mainImage> + <bodyText> + <![CDATA[<h3>Secure Client Areas</h3> +<p>Make sure you check out the secure client areas and MVC login form,</p> +<h3>Multiple Areas</h3> +<p>You can create different areas for different clients or just the functionality to suit your project.</p>]]> + </bodyText> + <umbracoNaviHide>1</umbracoNaviHide> + </Slide> + <Slide id="1093" parentID="1090" level="3" creatorID="0" sortOrder="2" createDate="2013-02-17T09:04:44" updateDate="2013-02-17T09:04:46" nodeName="Umbraco Speechbubble" urlName="umbraco-speechbubble" path="-1,1072,1090,1093" isDoc="" nodeType="1064" creatorName="admin" writerName="admin" writerID="0" template="0"> + <mainImage>/media/2075/chat.jpg</mainImage> + <bodyText> + <![CDATA[<h3>News List</h3> +<p>The news page is an example of a list of content. Use this as a starting point for building any list of any type</p> +<h3>Contact Form</h3> +<p>The contact form is 100% MVC with strongly type view models.</p>]]> + </bodyText> + <umbracoNaviHide>1</umbracoNaviHide> + </Slide> + </Slideshow> + <ContentPanels id="1094" parentID="1072" level="2" creatorID="0" sortOrder="10" createDate="2013-02-17T09:04:45" updateDate="2013-02-17T09:04:46" nodeName="Content Panels" urlName="content-panels" path="-1,1072,1094" isDoc="" nodeType="1061" creatorName="admin" writerName="admin" writerID="0" template="0"> + <umbracoNaviHide>1</umbracoNaviHide> + <ContentPanel id="1095" parentID="1094" level="3" creatorID="0" sortOrder="0" createDate="2013-02-17T09:04:45" updateDate="2013-02-17T09:04:46" nodeName="Introductory Offers" urlName="introductory-offers" path="-1,1072,1094,1095" isDoc="" nodeType="1060" creatorName="admin" writerName="admin" writerID="0" template="0"> + <bodyText> + <![CDATA[ +<h3>Introductory Offers</h3> + +<p>asdjbasd askdja asio]ywer asduyiuy</p> + +<ul> +<li><a href="/rooms.aspx">related link 1</a></li> + +<li><a href="/rooms.aspx">related link 1</a></li> +</ul> +]]> + </bodyText> + <umbracoNaviHide>1</umbracoNaviHide> + </ContentPanel> + <ContentPanel id="1096" parentID="1094" level="3" creatorID="0" sortOrder="1" createDate="2013-02-17T09:04:45" updateDate="2013-02-17T09:04:46" nodeName="Sample Panel" urlName="sample-panel" path="-1,1072,1094,1096" isDoc="" nodeType="1060" creatorName="admin" writerName="admin" writerID="0" template="0"> + <bodyText> + <![CDATA[<p><strong>Sample Panel</strong></p> +<p>Don't you just love MVC?</p> +<ul> +<li><a href="/rooms.aspx">related link 1</a></li> +<li><a href="/rooms.aspx">related link 2</a></li> +</ul>]]> + </bodyText> + <umbracoNaviHide>0</umbracoNaviHide> + </ContentPanel> + </ContentPanels> + </Homepage> + </DocumentSet> + </Documents> + <DocumentTypes> + <DocumentType> + <Info> + <Name>Base</Name> + <Alias>Base</Alias> + <Icon>folder.gif</Icon> + <Thumbnail>folder.png</Thumbnail> + <Description> + </Description> + <AllowAtRoot>False</AllowAtRoot> + <AllowedTemplates /> + <DefaultTemplate> + </DefaultTemplate> + </Info> + <Structure /> + <GenericProperties> + <GenericProperty> + <Name>Hide From Navigation</Name> + <Alias>umbracoNaviHide</Alias> + <Type>38b352c1-e9f8-4fd8-9324-9a2eab06d97a</Type> + <Definition>92897bc6-a5f3-4ffe-ae27-f2e7e33dda49</Definition> + <Tab> + </Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + </GenericProperties> + <Tabs /> + </DocumentType> + <DocumentType> + <Info> + <Name>Client Area Folder</Name> + <Alias>ClientArea</Alias> + <Icon>.sprTreeFolder</Icon> + <Thumbnail>folder.png</Thumbnail> + <Description> + </Description> + <AllowAtRoot>False</AllowAtRoot> + <Master>Base</Master> + <AllowedTemplates> + <Template>ClientAreas</Template> + </AllowedTemplates> + <DefaultTemplate>ClientAreas</DefaultTemplate> + </Info> + <Structure> + <DocumentType>ClientArea</DocumentType> + <DocumentType>Standard</DocumentType> + </Structure> + <GenericProperties /> + <Tabs /> + </DocumentType> + <DocumentType> + <Info> + <Name>Content Master</Name> + <Alias>ContentMaster</Alias> + <Icon>folder.gif</Icon> + <Thumbnail>folder.png</Thumbnail> + <Description> + </Description> + <AllowAtRoot>False</AllowAtRoot> + <Master>Base</Master> + <AllowedTemplates /> + <DefaultTemplate> + </DefaultTemplate> + </Info> + <Structure /> + <GenericProperties> + <GenericProperty> + <Name>Title</Name> + <Alias>title</Alias> + <Type>ec15c1e5-9d90-422a-aa52-4f7622c63bea</Type> + <Definition>0cc0eba1-9960-42c9-bf9b-60e150b429ae</Definition> + <Tab>SEO</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Description</Name> + <Alias>description</Alias> + <Type>67db8357-ef57-493e-91ac-936d305e0f2a</Type> + <Definition>c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3</Definition> + <Tab>SEO</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Keywords</Name> + <Alias>keywords</Alias> + <Type>67db8357-ef57-493e-91ac-936d305e0f2a</Type> + <Definition>c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3</Definition> + <Tab>SEO</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + </GenericProperties> + <Tabs> + <Tab> + <Id>6</Id> + <Caption>SEO</Caption> + </Tab> + </Tabs> + </DocumentType> + <DocumentType> + <Info> + <Name>Standard</Name> + <Alias>Standard</Alias> + <Icon>.sprTreeDoc</Icon> + <Thumbnail>doc.png</Thumbnail> + <Description> + </Description> + <AllowAtRoot>False</AllowAtRoot> + <Master>ContentMaster</Master> + <AllowedTemplates> + <Template>Articles</Template> + <Template>Login</Template> + <Template>Search</Template> + <Template>Sitemap</Template> + <Template>StandardPage</Template> + </AllowedTemplates> + <DefaultTemplate>StandardPage</DefaultTemplate> + </Info> + <Structure> + <DocumentType>Standard</DocumentType> + <DocumentType>NewsArticle</DocumentType> + </Structure> + <GenericProperties> + <GenericProperty> + <Name>Main Content</Name> + <Alias>bodyText</Alias> + <Type>5e9b75ae-face-41c8-b47e-5f4b0fd82f83</Type> + <Definition>ca90c950-0aff-4e72-b976-a30b1ac57dad</Definition> + <Tab>Content</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Content Panels</Name> + <Alias>contentPanels</Alias> + <Type>7e062c13-7c41-4ad9-b389-41d88aeef87c</Type> + <Definition>d719387b-8261-48ac-b94b-00654896d114</Definition> + <Tab>Panels</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[Select the panels to appear in the right column]]></Description> + </GenericProperty> + </GenericProperties> + <Tabs> + <Tab> + <Id>7</Id> + <Caption>Content</Caption> + </Tab> + <Tab> + <Id>8</Id> + <Caption>Panels</Caption> + </Tab> + </Tabs> + </DocumentType> + <DocumentType> + <Info> + <Name>ContactForm</Name> + <Alias>ContactForm</Alias> + <Icon>newsletter.gif</Icon> + <Thumbnail>doc.png</Thumbnail> + <Description> + </Description> + <AllowAtRoot>False</AllowAtRoot> + <Master>Standard</Master> + <AllowedTemplates> + <Template>Contact</Template> + </AllowedTemplates> + <DefaultTemplate>Contact</DefaultTemplate> + </Info> + <Structure> + <DocumentType>Standard</DocumentType> + </Structure> + <GenericProperties> + <GenericProperty> + <Name>Recipient Email Address</Name> + <Alias>recipientEmailAddress</Alias> + <Type>ec15c1e5-9d90-422a-aa52-4f7622c63bea</Type> + <Definition>0cc0eba1-9960-42c9-bf9b-60e150b429ae</Definition> + <Tab>Form</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Email Subject</Name> + <Alias>emailSubject</Alias> + <Type>ec15c1e5-9d90-422a-aa52-4f7622c63bea</Type> + <Definition>0cc0eba1-9960-42c9-bf9b-60e150b429ae</Definition> + <Tab>Form</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Thank You Page</Name> + <Alias>thankYouPage</Alias> + <Type>158aa029-24ed-4948-939e-c3da209e5fba</Type> + <Definition>a6857c73-d6e9-480c-b6e6-f15f6ad11125</Definition> + <Tab>Form</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Sender Email Address</Name> + <Alias>senderEmailAddress</Alias> + <Type>ec15c1e5-9d90-422a-aa52-4f7622c63bea</Type> + <Definition>0cc0eba1-9960-42c9-bf9b-60e150b429ae</Definition> + <Tab>Form</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + </GenericProperties> + <Tabs> + <Tab> + <Id>9</Id> + <Caption>Form</Caption> + </Tab> + </Tabs> + </DocumentType> + <DocumentType> + <Info> + <Name>ContentPanel</Name> + <Alias>ContentPanel</Alias> + <Icon>.sprTreeDoc</Icon> + <Thumbnail>doc.png</Thumbnail> + <Description> + </Description> + <AllowAtRoot>False</AllowAtRoot> + <Master>Base</Master> + <AllowedTemplates /> + <DefaultTemplate> + </DefaultTemplate> + </Info> + <Structure /> + <GenericProperties> + <GenericProperty> + <Name>Main Content</Name> + <Alias>bodyText</Alias> + <Type>5e9b75ae-face-41c8-b47e-5f4b0fd82f83</Type> + <Definition>ca90c950-0aff-4e72-b976-a30b1ac57dad</Definition> + <Tab>Content</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + </GenericProperties> + <Tabs> + <Tab> + <Id>10</Id> + <Caption>Content</Caption> + </Tab> + </Tabs> + </DocumentType> + <DocumentType> + <Info> + <Name>ContentPanels</Name> + <Alias>ContentPanels</Alias> + <Icon>.sprTreeFolder</Icon> + <Thumbnail>folder.png</Thumbnail> + <Description> + </Description> + <AllowAtRoot>False</AllowAtRoot> + <Master>Base</Master> + <AllowedTemplates /> + <DefaultTemplate> + </DefaultTemplate> + </Info> + <Structure> + <DocumentType>ContentPanel</DocumentType> + </Structure> + <GenericProperties /> + <Tabs /> + </DocumentType> + <DocumentType> + <Info> + <Name>Homepage</Name> + <Alias>Homepage</Alias> + <Icon>.sprTreeSettingDomain</Icon> + <Thumbnail>folder.png</Thumbnail> + <Description> + </Description> + <AllowAtRoot>False</AllowAtRoot> + <Master>ContentMaster</Master> + <AllowedTemplates> + <Template>Home</Template> + </AllowedTemplates> + <DefaultTemplate>Home</DefaultTemplate> + </Info> + <Structure> + <DocumentType>ClientArea</DocumentType> + <DocumentType>Standard</DocumentType> + <DocumentType>ContactForm</DocumentType> + <DocumentType>ContentPanels</DocumentType> + <DocumentType>Slideshow</DocumentType> + </Structure> + <GenericProperties> + <GenericProperty> + <Name>Primary Navigation</Name> + <Alias>primaryNavigation</Alias> + <Type>7e062c13-7c41-4ad9-b389-41d88aeef87c</Type> + <Definition>d719387b-8261-48ac-b94b-00654896d114</Definition> + <Tab>Navigation</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Panel Content 1</Name> + <Alias>panelContent1</Alias> + <Type>5e9b75ae-face-41c8-b47e-5f4b0fd82f83</Type> + <Definition>ca90c950-0aff-4e72-b976-a30b1ac57dad</Definition> + <Tab>Panel 1</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Panel Content 2</Name> + <Alias>panelContent2</Alias> + <Type>5e9b75ae-face-41c8-b47e-5f4b0fd82f83</Type> + <Definition>ca90c950-0aff-4e72-b976-a30b1ac57dad</Definition> + <Tab>Panel 2</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Panel Content 3</Name> + <Alias>panelContent3</Alias> + <Type>5e9b75ae-face-41c8-b47e-5f4b0fd82f83</Type> + <Definition>ca90c950-0aff-4e72-b976-a30b1ac57dad</Definition> + <Tab>Panel 3</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Slideshow</Name> + <Alias>slideshow</Alias> + <Type>7e062c13-7c41-4ad9-b389-41d88aeef87c</Type> + <Definition>d719387b-8261-48ac-b94b-00654896d114</Definition> + <Tab>Slideshow</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Header Navigation</Name> + <Alias>headerNavigation</Alias> + <Type>7e062c13-7c41-4ad9-b389-41d88aeef87c</Type> + <Definition>d719387b-8261-48ac-b94b-00654896d114</Definition> + <Tab>Header</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Address</Name> + <Alias>address</Alias> + <Type>ec15c1e5-9d90-422a-aa52-4f7622c63bea</Type> + <Definition>0cc0eba1-9960-42c9-bf9b-60e150b429ae</Definition> + <Tab>Footer</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[The address appears in the footer of all pages]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Copyright</Name> + <Alias>copyright</Alias> + <Type>ec15c1e5-9d90-422a-aa52-4f7622c63bea</Type> + <Definition>0cc0eba1-9960-42c9-bf9b-60e150b429ae</Definition> + <Tab>Footer</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Affiliation Link 1</Name> + <Alias>affiliationLink1</Alias> + <Type>ec15c1e5-9d90-422a-aa52-4f7622c63bea</Type> + <Definition>0cc0eba1-9960-42c9-bf9b-60e150b429ae</Definition> + <Tab>Footer</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Affiliation Image 1</Name> + <Alias>affiliationImage1</Alias> + <Type>5032a6e6-69e3-491d-bb28-cd31cd11086c</Type> + <Definition>84c6b441-31df-4ffe-b67e-67d5bc3ae65a</Definition> + <Tab>Footer</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Affiliation Link 2</Name> + <Alias>affiliationLink2</Alias> + <Type>ec15c1e5-9d90-422a-aa52-4f7622c63bea</Type> + <Definition>0cc0eba1-9960-42c9-bf9b-60e150b429ae</Definition> + <Tab>Footer</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Affiliation Image 2</Name> + <Alias>affiliationImage2</Alias> + <Type>5032a6e6-69e3-491d-bb28-cd31cd11086c</Type> + <Definition>84c6b441-31df-4ffe-b67e-67d5bc3ae65a</Definition> + <Tab>Footer</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Affiliation Link 3</Name> + <Alias>affiliationLink3</Alias> + <Type>ec15c1e5-9d90-422a-aa52-4f7622c63bea</Type> + <Definition>0cc0eba1-9960-42c9-bf9b-60e150b429ae</Definition> + <Tab>Footer</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Affiliation Image 3</Name> + <Alias>affiliationImage3</Alias> + <Type>5032a6e6-69e3-491d-bb28-cd31cd11086c</Type> + <Definition>84c6b441-31df-4ffe-b67e-67d5bc3ae65a</Definition> + <Tab>Footer</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + </GenericProperties> + <Tabs> + <Tab> + <Id>11</Id> + <Caption>Slideshow</Caption> + </Tab> + <Tab> + <Id>12</Id> + <Caption>Panel 1</Caption> + </Tab> + <Tab> + <Id>13</Id> + <Caption>Panel 2</Caption> + </Tab> + <Tab> + <Id>14</Id> + <Caption>Panel 3</Caption> + </Tab> + <Tab> + <Id>15</Id> + <Caption>Navigation</Caption> + </Tab> + <Tab> + <Id>16</Id> + <Caption>Footer</Caption> + </Tab> + <Tab> + <Id>17</Id> + <Caption>Header</Caption> + </Tab> + </Tabs> + </DocumentType> + <DocumentType> + <Info> + <Name>NewsArticle</Name> + <Alias>NewsArticle</Alias> + <Icon>doc.gif</Icon> + <Thumbnail>folder.png</Thumbnail> + <Description> + </Description> + <AllowAtRoot>False</AllowAtRoot> + <Master>Standard</Master> + <AllowedTemplates> + <Template>StandardPage</Template> + </AllowedTemplates> + <DefaultTemplate>StandardPage</DefaultTemplate> + </Info> + <Structure /> + <GenericProperties> + <GenericProperty> + <Name>Article Summary</Name> + <Alias>articleSummary</Alias> + <Type>67db8357-ef57-493e-91ac-936d305e0f2a</Type> + <Definition>c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3</Definition> + <Tab>Article</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Article Date</Name> + <Alias>articleDate</Alias> + <Type>23e93522-3200-44e2-9f29-e61a6fcbb79a</Type> + <Definition>5046194e-4237-453c-a547-15db3a07c4e1</Definition> + <Tab>Article</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + </GenericProperties> + <Tabs> + <Tab> + <Id>18</Id> + <Caption>Article</Caption> + </Tab> + </Tabs> + </DocumentType> + <DocumentType> + <Info> + <Name>Slide</Name> + <Alias>Slide</Alias> + <Icon>.sprTreeMediaPhoto</Icon> + <Thumbnail>docWithImage.png</Thumbnail> + <Description> + </Description> + <AllowAtRoot>False</AllowAtRoot> + <Master>Base</Master> + <AllowedTemplates /> + <DefaultTemplate> + </DefaultTemplate> + </Info> + <Structure /> + <GenericProperties> + <GenericProperty> + <Name>Main Image</Name> + <Alias>mainImage</Alias> + <Type>5032a6e6-69e3-491d-bb28-cd31cd11086c</Type> + <Definition>84c6b441-31df-4ffe-b67e-67d5bc3ae65a</Definition> + <Tab>Content</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[500px x 260px]]></Description> + </GenericProperty> + <GenericProperty> + <Name>Main Content</Name> + <Alias>bodyText</Alias> + <Type>5e9b75ae-face-41c8-b47e-5f4b0fd82f83</Type> + <Definition>ca90c950-0aff-4e72-b976-a30b1ac57dad</Definition> + <Tab>Content</Tab> + <Mandatory>False</Mandatory> + <Validation> + </Validation> + <Description><![CDATA[]]></Description> + </GenericProperty> + </GenericProperties> + <Tabs> + <Tab> + <Id>19</Id> + <Caption>Content</Caption> + </Tab> + </Tabs> + </DocumentType> + <DocumentType> + <Info> + <Name>Slideshow</Name> + <Alias>Slideshow</Alias> + <Icon>.sprTreeFolder</Icon> + <Thumbnail>folder.png</Thumbnail> + <Description> + </Description> + <AllowAtRoot>False</AllowAtRoot> + <Master>Base</Master> + <AllowedTemplates /> + <DefaultTemplate> + </DefaultTemplate> + </Info> + <Structure> + <DocumentType>Slide</DocumentType> + </Structure> + <GenericProperties /> + <Tabs /> + </DocumentType> + </DocumentTypes> + <Templates> + <Template> + <Name>Articles</Name> + <Alias>Articles</Alias> + <Master>SW_Master</Master> + <Design> + <![CDATA[@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = "SW_Master.cshtml"; + int pageSize = 2; // How many items per page + int page; // The page we are viewing + + /* Set up parameters */ + + if (!int.TryParse(Request.QueryString["page"], out page)) + { + page = 1; + } + + /* This is your basic query to select the nodes you want */ + + var nodes = Model.Content.Children.Where(x => x.DocumentTypeAlias == "NewsArticle").OrderBy(x=>x.CreateDate); + + int totalNodes = nodes.Count(); + int totalPages = (int)Math.Ceiling((double)totalNodes / (double)pageSize); + + /* Bounds checking */ + + if (page > totalPages) + { + page = totalPages; + } + else if (page < 1) + { + page = 1; + } + + + + + + + + + + + + + + +} + + + + <div id="mainContent" class="fc"> + <div class="navigation"> + @Html.Partial("LeftNavigation",@Model.Content) +   + </div> + + <div id="content"> + + @Html.Raw(Model.Content.GetPropertyValue<string>("bodyText")) + + + <ul id="newsList"> + @foreach (var item in nodes.Skip((page - 1) * pageSize).Take(pageSize)) + { + <li> + <h3> + <a href="@item.NiceUrl()"> + @item.Name + </a> + </h3> + <p class="articleDate"> + @Convert.ToDateTime(item.GetPropertyValue("articleDate")).ToString("dd MMMM yyyy") + </p> + <p> + @item.GetPropertyValue("articleSummary") + + </p> + </li> + } +</ul> + +@if(totalPages > 1) +{ + + <p id="pager"> + @for (int p = 1; p < totalPages + 1; p++) + { + //string selected = (p == page) ? "selected" : String.Empty; + //<li class="@selected"><a href="?page=@p" title="Go to page @p of results">@p</a></li> + <a href="?page=@p" title="Go to page @p of results">@p</a> + if (p < totalPages) + { + <text>| </text> + } + } +</p> +} + + + </div> + + @Html.Partial("ContentPanels",@Model.Content) + </div>]]> + </Design> + </Template> + <Template> + <Name>ClientAreas</Name> + <Alias>ClientAreas</Alias> + <Master>SW_Master</Master> + <Design> + <![CDATA[@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = "SW_Master.cshtml"; + var pages = Model.Content.Children.Where(x => x.IsVisible() && x.TemplateId > 0 && Umbraco.MemberHasAccess(x.Id, x.Path)); +} + <div id="mainContent" class="fc"> + + <div class="navigation"> + @Html.Partial("LeftNavigation",@Model.Content) +   + </div> + + <div id="content"> + @Html.Raw(Model.Content.GetPropertyValue<string>("bodyText")) + + @foreach (var page in pages) + { + <h3><a href="@page.NiceUrl()">@page.Name</a></h3> + } + + </div> + + + </div>]]> + </Design> + </Template> + <Template> + <Name>Contact</Name> + <Alias>Contact</Alias> + <Master>SW_Master</Master> + <Design> + <![CDATA[@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = "SW_Master.cshtml"; +} + <div id="mainContent" class="fc"> + + <div class="navigation"> + @Html.Partial("LeftNavigation",@Model.Content) +   + </div> + + <div id="content"> + @Html.Raw(Model.Content.GetPropertyValue<string>("bodyText")) + + @Html.Partial("ContactForm",new Koiak.StandardWebsite.ContactFormModel()) + </div> +</div>]]> + </Design> + </Template> + <Template> + <Name>Home</Name> + <Alias>Home</Alias> + <Master>SW_Master</Master> + <Design> + <![CDATA[@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = "SW_Master.cshtml"; +} + + <div id="slideshow"> + <ul> + @{ + var nodeIds = Model.Content.GetPropertyValue("slideshow").ToString().Split(','); + List<IPublishedContent> slides = new List<IPublishedContent>(); + + foreach (var nodeId in nodeIds) + { + if (!String.IsNullOrEmpty(nodeId)) + { + var publishedContent = Umbraco.NiceUrl(Convert.ToInt32(nodeId)); + if (!String.IsNullOrEmpty(publishedContent) && publishedContent != "#") + { + slides.Add(Umbraco.TypedContent(nodeId)); + } + } + } + } + + @foreach (var slide in slides) + { + if(slide != null) + { + string styleString = !slide.IsFirst() ? "display:none;" : ""; + <li class="rotating-panel fc" style="@styleString"> + <img class="fl" alt="@slide.Name" src="@slide.GetPropertyValue("mainImage")"/> + <div class=""> + @Html.Raw(slide.GetPropertyValue("bodyText")) + </div> + </li> + } + } + </ul> + <ul id="slidePager"> + @foreach (var slide in slides) + { + string classString = slide.IsFirst() ? "selected" : ""; + <li> + <a href="?position={position()}" class="@classString"> + @slide.Position() + </a> + </li> + } + </ul> + + </div> + + <div class="fc"> + <div class="feature fl"> + @Html.Raw(Model.Content.GetPropertyValue("panelContent1").ToString()) + </div> + + <div class="feature fl"> + @Html.Raw(Model.Content.GetPropertyValue("panelContent2").ToString()) + </div> + + <div class="feature fr"> + @Html.Raw(Model.Content.GetPropertyValue("panelContent3").ToString()) + </div> + </div>]]> + </Design> + </Template> + <Template> + <Name>Login</Name> + <Alias>Login</Alias> + <Master>SW_Master</Master> + <Design> + <![CDATA[ @inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = "SW_Master.cshtml"; + + if(!String.IsNullOrEmpty(Request.QueryString["signout"])) + { + FormsAuthentication.SignOut(); + Context.Response.Redirect(Model.Content.NiceUrl(), true); + } +} + <div id="mainContent" class="fc"> + + <div class="navigation"> + @Html.Partial("LeftNavigation",@Model.Content) +   + </div> + + <div id="content"> + @Html.Raw(Model.Content.GetPropertyValue<string>("bodyText")) + + @Html.Partial("LoginForm",new Koiak.StandardWebsite.LoginModel()) + </div> +</div>]]> + </Design> + </Template> + <Template> + <Name>Search</Name> + <Alias>Search</Alias> + <Master>SW_Master</Master> + <Design> + <![CDATA[@inherits Umbraco.Web.Mvc.UmbracoViewPage +@using Lucene.Net; +@using Examine; +@using Examine.LuceneEngine.SearchCriteria; + +@{ + Layout = "SW_Master.cshtml"; + + + int pageSize = 10; // How many items per page + int page; // The page we are viewing + string searchTerm = Context.Request.QueryString["search"]; + + /* Set up parameters */ + + if (!int.TryParse(Request.QueryString["page"], out page)) + { + page = 1; + } + + /* This is your basic query to select the nodes you want */ + + Examine.Providers.BaseSearchProvider baseSearchProvider = ExamineManager.Instance.DefaultSearchProvider; + IEnumerable<SearchResult> nodes = null; + //ISearchResults nodes = null; + Lucene.Net.Search.Searcher luceneSearcher = null; + int totalNodes = 0; + int totalPages = 0; + + if (!String.IsNullOrEmpty(searchTerm)) + { + //nodes = baseSearchProvider.Search(searchTerm, true); + var searchCriteria = Examine.ExamineManager.Instance.CreateSearchCriteria(Examine.SearchCriteria.BooleanOperation.And); + + var query = searchCriteria.GroupedOr( + new string[] { "nodeName", "bodyText", "panelContent1", "panelContent2", "panelContent3", "articleSummary"},searchTerm.Fuzzy(0.7f)) + .Compile(); + + var results = baseSearchProvider.Search(query); //.OrderByDescending(x => x.Score) + luceneSearcher = ((Examine.LuceneEngine.SearchResults)results).LuceneSearcher; + nodes = results.OrderByDescending(x => x.Score); + + totalNodes = nodes.Count(); + totalPages = (int)Math.Ceiling((double)totalNodes / (double)pageSize); + + /* Bounds checking */ + + if (page > totalPages) + { + page = totalPages; + } + else if (page < 1) + { + page = 1; + } + } + +} +<div id="mainContent" class="fc"> + <div class="navigation"> + @Html.Partial("LeftNavigation", @Model.Content) +   + + </div> + + <div id="content"> + @Html.Raw(Model.Content.GetPropertyValue<string>("bodyText")) + + @if (totalNodes == 0) + { + <p>No results match your search</p> + } + else + { + <ul id="newsList"> + @foreach (var item in nodes.Skip((page - 1) * pageSize).Take(pageSize)) + { + <li> + <h3> + <a href="@Umbraco.NiceUrl(Convert.ToInt32(@item.Fields["id"]))"> + @item.Fields["nodeName"] + </a> + </h3> + @{ + string fieldName = "bodyText"; + string searchHiglight = ""; + + if (item.Fields.ContainsKey(fieldName)) + { + string fieldValue = item.Fields[fieldName]; + searchHiglight = LuceneHighlightHelper.Instance.GetHighlight(fieldValue, fieldName, luceneSearcher, searchTerm); + + if (String.IsNullOrEmpty(searchHiglight)) + { + searchHiglight = Umbraco.Truncate(fieldValue, 200).ToString(); + } + else + { + searchHiglight = System.Text.RegularExpressions.Regex.Replace(searchHiglight, searchTerm, "<strong>" + searchTerm + "</strong>", System.Text.RegularExpressions.RegexOptions.IgnoreCase); + } + } + } + + <p>@Html.Raw(searchHiglight)</p> + + <!--<dl> + + @foreach (var field in item.Fields) + { + <dt>@field.Key</dt> + <dd>@field.Value</dd> + } + </dl>--> + </li> + } + </ul> + } + + @if (totalPages > 1) + { + + <p id="pager"> + @for (int p = 1; p < totalPages + 1; p++) + { + //string selected = (p == page) ? "selected" : String.Empty; + //<li class="@selected"><a href="?page=@p" title="Go to page @p of results">@p</a></li> + <a href="?search=@searchTerm&page=@p" title="Go to page @p of results">@p</a> + if (p < totalPages) + { + <text>| </text> + } + } + </p> + } + </div> + + @Html.Partial("ContentPanels", @Model.Content) +</div>]]> + </Design> + </Template> + <Template> + <Name>Sitemap</Name> + <Alias>Sitemap</Alias> + <Master>SW_Master</Master> + <Design> + <![CDATA[@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = "SW_Master.cshtml"; +} + <div id="mainContent" class="fc"> + + <div class="navigation"> + @Html.Partial("LeftNavigation",@Model.Content) +   + </div> + + <div id="content"> + @Html.Raw(Model.Content.GetPropertyValue<string>("bodyText")) + + <div id="sitemap"> + @traverse(Model.Content.AncestorOrSelf(1)) + </div> + + </div> + + @Html.Partial("ContentPanels",@Model.Content) + </div> + +@helper traverse(IPublishedContent node) +{ + var cc = node.Children.Where(x=>x.IsVisible() && x.TemplateId > 0); + if (cc.Count()>0) + { + <ul> + @foreach (var c in cc) + { + <li> + <a href="@c.NiceUrl()">@c.Name</a> + @traverse(c) + </li> + } + </ul> + } +}]]> + </Design> + </Template> + <Template> + <Name>Standard Page</Name> + <Alias>StandardPage</Alias> + <Master>SW_Master</Master> + <Design> + <![CDATA[@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = "SW_Master.cshtml"; +} + <div id="mainContent" class="fc"> + + <div class="navigation"> + @Html.Partial("LeftNavigation",@Model.Content) +   + </div> + + <div id="content"> + @Html.Raw(Model.Content.GetPropertyValue<string>("bodyText")) + </div> + + @Html.Partial("ContentPanels",@Model.Content) + </div>]]> + </Design> + </Template> + <Template> + <Name>SW_Master</Name> + <Alias>SW_Master</Alias> + <Design> + <![CDATA[@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = null; + var homepage = Model.Content.AncestorOrSelf(1); +} +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>@Model.Content.GetPropertyValue("title") + + + + + + + + + + + + + + + + + + + +
+ @RenderBody() +
+ + + + + + + + +]]> + + + + + + admin + + + + + + + + Header 2 + h2 + + + + + Header 3 + h3 + + + + + Float Right + .fr + + + + + + + base-min + base-min + + + + + + default + default + + + + + + + + Map + Map + + + + + + + True + 0 + Map.cshtml + + + + + + + + + + /usercontrols/StandardWebsiteInstall.ascx
diff --git a/src/Umbraco.Tests/Services/Importing/TemplateOnly-Package.xml b/src/Umbraco.Tests/Services/Importing/TemplateOnly-Package.xml index 1f29ffb806..a16e953a76 100644 --- a/src/Umbraco.Tests/Services/Importing/TemplateOnly-Package.xml +++ b/src/Umbraco.Tests/Services/Importing/TemplateOnly-Package.xml @@ -1,90 +1,90 @@ - - - - - - Template-Update - 0.1 - MIT license - http://our.umbraco.org/projects - - 3 - 0 - 0 - - - - Morten Christensen - http://blog.sitereactor.dk - - - - - - - - - - - - - + + + + + + Template-Update + 0.1 + MIT license + http://our.umbraco.org/projects + + 3 + 0 + 0 + + + + Morten Christensen + http://blog.sitereactor.dk + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/Importing/TemplateOnly-Updated-Package.xml b/src/Umbraco.Tests/Services/Importing/TemplateOnly-Updated-Package.xml index 1225b36fa2..7d24a06cbd 100644 --- a/src/Umbraco.Tests/Services/Importing/TemplateOnly-Updated-Package.xml +++ b/src/Umbraco.Tests/Services/Importing/TemplateOnly-Updated-Package.xml @@ -1,87 +1,87 @@ - - - - - - Template-Update - 0.1 - MIT license - http://our.umbraco.org/projects - - 3 - 0 - 0 - - - - Morten Christensen - http://blog.sitereactor.dk - - - - - - - - - - - - - + + + + + + Template-Update + 0.1 + MIT license + http://our.umbraco.org/projects + + 3 + 0 + 0 + + + + Morten Christensen + http://blog.sitereactor.dk + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/Importing/XsltSearch-Package.xml b/src/Umbraco.Tests/Services/Importing/XsltSearch-Package.xml index 4839f3c0db..fdcbfba25e 100644 --- a/src/Umbraco.Tests/Services/Importing/XsltSearch-Package.xml +++ b/src/Umbraco.Tests/Services/Importing/XsltSearch-Package.xml @@ -1,252 +1,252 @@ - - - - - XSLTsearch.xslt - /xslt - XSLTsearch.xslt - - - XSLTsearch.cs - /App_Code - XSLTsearch.cs - - - - - XSLTsearch - 3.0.4 - MIT license - http://www.percipientstudios.com - - 3 - 0 - 0 - - - - Percipient Studios - http://www.percipientstudios.com - - - - - - - - - 0 - - - - - - - XSLTsearch - XSLTsearch - .sprTreeDoc2 - doc.png - - XSLTsearch page. - (adjust settings via the macro in the XSLTsearch template) - - - - - XSLTsearch - - - - - Hide in navigation - umbracoNaviHide - 38b352c1-e9f8-4fd8-9324-9a2eab06d97a - 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 - - - False - - - - - - - - - - - - - - XSLTsearch - - - - - - - - - - XSLTsearch - XSLTsearch - Unknown - - XSLTsearch.xslt - False - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - - - + + + + + XSLTsearch.xslt + /xslt + XSLTsearch.xslt + + + XSLTsearch.cs + /App_Code + XSLTsearch.cs + + + + + XSLTsearch + 3.0.4 + MIT license + http://www.percipientstudios.com + + 3 + 0 + 0 + + + + Percipient Studios + http://www.percipientstudios.com + + + + + + + + + 0 + + + + + + + XSLTsearch + XSLTsearch + .sprTreeDoc2 + doc.png + + XSLTsearch page. + (adjust settings via the macro in the XSLTsearch template) + + + + + XSLTsearch + + + + + Hide in navigation + umbracoNaviHide + 38b352c1-e9f8-4fd8-9324-9a2eab06d97a + 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 + + + False + + + + + + + + + + + + + + XSLTsearch + + + + + + + + + + XSLTsearch + XSLTsearch + Unknown + + XSLTsearch.xslt + False + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + diff --git a/src/Umbraco.Tests/Services/Importing/uBlogsy-Package.xml b/src/Umbraco.Tests/Services/Importing/uBlogsy-Package.xml index e1af13bb5e..5d579079eb 100644 --- a/src/Umbraco.Tests/Services/Importing/uBlogsy-Package.xml +++ b/src/Umbraco.Tests/Services/Importing/uBlogsy-Package.xml @@ -1,2161 +1,2161 @@ - - - - - uBlogsy.BusinessLogic.dll - /bin - uBlogsy.BusinessLogic.dll - - - uBlogsy.BusinessLogic.pdb - /bin - uBlogsy.BusinessLogic.pdb - - - uBlogsy.Common.dll - /bin - uBlogsy.Common.dll - - - uBlogsy.Common.pdb - /bin - uBlogsy.Common.pdb - - - uBlogsy.Mvc.Parts.dll - /bin - uBlogsy.Mvc.Parts.dll - - - uBlogsy.Mvc.Parts.pdb - /bin - uBlogsy.Mvc.Parts.pdb - - - uBlogsy.Web.dll - /bin - uBlogsy.Web.dll - - - uBlogsy.Web.pdb - /bin - uBlogsy.Web.pdb - - - uDateFoldersy.dll - /bin - uDateFoldersy.dll - - - uDateFoldersy.pdb - /bin - uDateFoldersy.pdb - - - uLoremsy.dll - /bin - uLoremsy.dll - - - uLoremsy.pdb - /bin - uLoremsy.pdb - - - uHelpsy.dll - /bin - uHelpsy.dll - - - uHelpsy.pdb - /bin - uHelpsy.pdb - - - uTagsy.Web.dll - /bin - uTagsy.Web.dll - - - uTagsy.Web.pdb - /bin - uTagsy.Web.pdb - - - CreatePost.ascx - /usercontrols/uBlogsy/dashboard - CreatePost.ascx - - - RSSImport.ascx - /usercontrols/uBlogsy/dashboard - RSSImport.ascx - - - uTagsy.ascx - /usercontrols/uTagsy - uTagsy.ascx - - - uDateFoldersy.config - /config - uDateFoldersy.config - - - uTagsy.config - /config - uTagsy.config - - - uLoremsy.config - /config - uLoremsy.config - - - uBlogsy.css - /css - uBlogsy.css - - - uBlogsy_feed-icon-14x14.png - /Images - uBlogsy_feed-icon-14x14.png - - - calendar_view_day.png - /umbraco/images/umbraco - calendar_view_day.png - - - date.png - /umbraco/images/umbraco - date.png - - - feed.png - /umbraco/images/umbraco - feed.png - - - folder_table.png - /umbraco/images/umbraco - folder_table.png - - - folder_user.png - /umbraco/images/umbraco - folder_user.png - - - house.png - /umbraco/images/umbraco - house.png - - - page_green.png - /umbraco/images/umbraco - page_green.png - - - page_white_cup.png - /umbraco/images/umbraco - page_white_cup.png - - - rss.png - /umbraco/images/umbraco - rss.png - - - tag.png - /umbraco/images/umbraco - tag.png - - - tag_blue.png - /Umbraco/Images/Umbraco - tag_blue.png - - - tag_blue_add.png - /Umbraco/Images/Umbraco - tag_blue_add.png - - - user.png - /umbraco/images/umbraco - user.png - - - _uBlogsyBase.cshtml - /Views - _uBlogsyBase.cshtml - - - _uBlogsyBaseSite.cshtml - /Views - _uBlogsyBaseSite.cshtml - - - _uBlogsyBaseBlog.cshtml - /Views - _uBlogsyBaseBlog.cshtml - - - uBlogsyMacroShowSomeLove.cshtml - /Views/MacroPartials/uBlogsy - uBlogsyMacroShowSomeLove.cshtml - - - uBlogsyGlobalBrowserTitle.cshtml - /Views/Partials/uBlogsy/Global - uBlogsyGlobalBrowserTitle.cshtml - - - uBlogsyGlobalFooter.cshtml - /Views/Partials/uBlogsy/Global - uBlogsyGlobalFooter.cshtml - - - uBlogsyGlobalHeader.cshtml - /Views/Partials/uBlogsy/Global - uBlogsyGlobalHeader.cshtml - - - uBlogsyGlobalNavigation.cshtml - /Views/Partials/uBlogsy/Global - uBlogsyGlobalNavigation.cshtml - - - uBlogsyGlobalSeoMeta.cshtml - /Views/Partials/uBlogsy/Global - uBlogsyGlobalSeoMeta.cshtml - - - uBlogsyLandingListPosts.cshtml - /Views/Partials/uBlogsy/Landing - uBlogsyLandingListPosts.cshtml - - - uBlogsyLandingShowPost.cshtml - /Views/Partials/uBlogsy/Landing - uBlogsyLandingShowPost.cshtml - - - uBlogsyPostListAuthors.cshtml - /Views/Partials/uBlogsy/Post - uBlogsyPostListAuthors.cshtml - - - uBlogsyPostListLabels.cshtml - /Views/Partials/uBlogsy/Post - uBlogsyPostListLabels.cshtml - - - uBlogsyPostListRelatedPosts.cshtml - /Views/Partials/uBlogsy/Post - uBlogsyPostListRelatedPosts.cshtml - - - uBlogsyPostListTags.cshtml - /Views/Partials/uBlogsy/Post - uBlogsyPostListTags.cshtml - - - uBlogsyPostShowPost.cshtml - /Views/Partials/uBlogsy/Post - uBlogsyPostShowPost.cshtml - - - uBlogsyShowImage.cshtml - /Views/Partials/uBlogsy/Shared - uBlogsyShowImage.cshtml - - - uBlogsyWidgetListAuthors.cshtml - /Views/Partials/uBlogsy/Widgets - uBlogsyWidgetListAuthors.cshtml - - - uBlogsyWidgetListBlogRoll.cshtml - /Views/Partials/uBlogsy/Widgets - uBlogsyWidgetListBlogRoll.cshtml - - - uBlogsyWidgetListLabels.cshtml - /Views/Partials/uBlogsy/Widgets - uBlogsyWidgetListLabels.cshtml - - - uBlogsyWidgetListPostArchive.cshtml - /Views/Partials/uBlogsy/Widgets - uBlogsyWidgetListPostArchive.cshtml - - - uBlogsyWidgetListPosts.cshtml - /Views/Partials/uBlogsy/Widgets - uBlogsyWidgetListPosts.cshtml - - - uBlogsyWidgetListPostsForHome.cshtml - /Views/Partials/uBlogsy/Widgets - uBlogsyWidgetListPostsForHome.cshtml - - - uBlogsyWidgetListTags.cshtml - /Views/Partials/uBlogsy/Widgets - uBlogsyWidgetListTags.cshtml - - - uBlogsyWidgetSearch.cshtml - /Views/Partials/uBlogsy/Widgets - uBlogsyWidgetSearch.cshtml - - - uBlogsyWidgetShowRSSLink.cshtml - /Views/Partials/uBlogsy/Widgets - uBlogsyWidgetShowRSSLink.cshtml - - - uDateFoldersyFolderRedirect.cshtml - /Views/Partials/uDateFoldersy - uDateFoldersyFolderRedirect.cshtml - - - Installer.ascx - /usercontrols/uBlogsy/dashboard - Installer.ascx - - - - - uBlogsy - 3.0 - MIT license - http://our.umbraco.org/projects/starter-kits/ublogsy - - 3 - 0 - 0 - - - - Anthony Dang - http://anthonydotnet.blogspot.com - - - - - - - - - My Site - - - This is an example home page. 

-

From uBlogsy 3.0, the package is now a site (with basic pages) which has a blog (not a blog which has pages).

-

Below on this page you will see a widget (/Views/uBlogsy/Widgets/uBlogsyWidgetListPostsForHome.cshtml) which lists latest news. The purpose of the widget is to list news for the home page. Your home page will typically be the parent of the uBlogsyLanding node, or a sibling.

-

Take a look at /Views/uBlogsySiteHome.cshtml

-

 

-

 

]]> -
- - - 0 - - - - - - - - - - 1 - 1 - 1 - - - - Blog - - - Show some love -

Did you know that uBlogsy (a completely free open-source package) has taken many 100's of hours to create and mainatain?

-

If you want to show some love, and support why not click the button below.

-]]> -
- Keywords - - 0 - - - - - Blog - - 0 - - - - - - Author 1 - 0 - - - - - - - - - - Tag 1 - - - - 0 - - - - - - Category 1 - 0 - - - - - - - - 0 - - - - - My Blog - - Copyright @AnthonyDotNet - -
- - About - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut nibh massa, ornare ac tempor in, elementum ut sem. Fusce nisl urna, auctor eu varius vel, mattis eu sapien. Sed venenatis facilisis ligula, vitae adipiscing nulla scelerisque eget. Nulla facilisi. Aliquam rutrum sollicitudin erat, non consectetur nunc rutrum sit amet. Proin sit amet elit eget metus fermentum laoreet eu non ipsum. Vivamus non est nibh, non congue turpis. Pellentesque tempus vehicula diam ut tincidunt. Fusce sem diam, vestibulum quis faucibus sit amet, tincidunt id mi. Donec scelerisque blandit suscipit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

-

Duis eget elementum libero. Donec in erat erat, vel convallis diam. Nulla facilisi. Vivamus adipiscing pellentesque facilisis. Nullam convallis nunc ac metus facilisis dapibus. Suspendisse potenti. Phasellus dictum arcu ac velit hendrerit non facilisis erat dignissim. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed vitae ante non magna mollis varius at at nisi. Suspendisse sagittis dignissim mi vel adipiscing. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed porttitor viverra congue. Ut sollicitudin enim quis orci scelerisque quis fringilla lorem fringilla. Integer urna leo, aliquam vel sollicitudin eget, facilisis id orci. Ut mollis libero nunc. Integer tortor nisi, aliquet non fermentum ac, bibendum in eros. Mauris aliquet arcu quis enim tincidunt venenatis. Vestibulum tincidunt ullamcorper nulla id blandit. Nam vitae quam id nisi aliquet malesuada malesuada sed eros. Aenean faucibus tempus enim sed pretium.

-

Cras malesuada magna ut elit condimentum mattis. Vestibulum eu risus ante, quis preti

]]> -
- - - 0 - - - - - - About -
- - Contact - - - Get your free embedable form from one of these:

-

 

-

http://www.formsite.com/landing/embed.html

-

 

-

http://www.jotformeu.com/

-

 

-

http://www.wufoo.com/

]]> -
- - - 0 - - - - - - Contact -
-
-
-
- - - - [uBlogsy] [Base] - uBlogsyBaseDocType - folder.gif - folder.png - This document type exists to allow sorting and categorisation of sub-document types. Do not create content nodes using this document type! - False - - - - - - - - Hide from navigation - umbracoNaviHide - 38b352c1-e9f8-4fd8-9324-9a2eab06d97a - 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 - - - False - - - - - - 302 Redirect - umbracoRedirect - 158aa029-24ed-4948-939e-c3da209e5fba - a6857c73-d6e9-480c-b6e6-f15f6ad11125 - - - False - - - - - - Invisible Redirect - umbracoInternalRedirectId - 158aa029-24ed-4948-939e-c3da209e5fba - a6857c73-d6e9-480c-b6e6-f15f6ad11125 - - - False - - - - - - Url Name Change - umbracoUrlName - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - - - False - - - - - - Url Alias - umbracoUrlAlias - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - - - False - - - - - - - - - - [uBlogsy] [Base] Container - uBlogsyBaseContainer - folder.gif - folder.png - - - False - uBlogsyBaseDocType - - - - - - - - - - - [uBlogsy] [Base] Page - uBlogsyBasePage - folder.gif - folder.png - This document type exists to allow sorting and categorisation of sub-document types. Do not create content nodes using this document type! - False - uBlogsyBaseDocType - - - - - - - - Title - uBlogsyContentTitle - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - Content - False - - - - - - Summary - uBlogsyContentSummary - 67db8357-ef57-493e-91ac-936d305e0f2a - c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3 - Content - False - - - - - - Body - uBlogsyContentBody - 5e9b75ae-face-41c8-b47e-5f4b0fd82f83 - ca90c950-0aff-4e72-b976-a30b1ac57dad - Content - False - - - - - - Seo Title - uBlogsySeoTitle - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - SEO - False - - - - - - Navigation Title - uBlogsyNavigationTitle - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - Navigation - False - - - - - - Keywords - uBlogsySeoKeywords - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - SEO - False - - - - - - Description - uBlogsySeoDescription - 67db8357-ef57-493e-91ac-936d305e0f2a - c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3 - SEO - False - - - - - - - - 6 - Content - - - 7 - SEO - - - 30 - Navigation - - - - - - [uBlogsy] Author - uBlogsyAuthor - user.png - folder.png - - - False - uBlogsyBaseDocType - - - - - - - - Author Name - uBlogsyAuthorName - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - Author - False - - - - - - Gravatar Email - uBlogsyAuthorGravatarEmail - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - Author - False - - - - - - - - 20 - Author - - - - - - [uBlogsy] Container - Author - uBlogsyContainerAuthor - folder_user.png - folder.png - - - False - uBlogsyBaseContainer - - - - - - uBlogsyAuthor - - - - - - - [uBlogsy] Container - Email Template - uBlogsyContainerEmailTemplate - folder_page_white.png - folder.png - - - False - uBlogsyBaseContainer - - - - - - - - - - - [uBlogsy] Container - Label - uBlogsyContainerLabel - folder_table.png - folder.png - - - False - uBlogsyBaseContainer - - - - - - uBlogsyLabel - - - - - - - [uBlogsy] Label - uBlogsyLabel - tag.png - folder.png - - - False - uBlogsyBaseDocType - - - - - - - - Label Name - uBlogsyLabelName - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - Category - False - - - - - - - - 22 - Category - - - - - - [uBlogsy] Landing - uBlogsyLanding - feed.png - folder.png - This is the root of your blog. It is required. There can be multiple of these. But they cannot be nested under eachother. - False - uBlogsyBasePage - - - - uBlogsyLanding - - - uBlogsyPost - uBlogsyRSS - uTagsyTagContainer - uBlogsyContainerAuthor - uBlogsyContainerLabel - uBlogsyContainerEmailTemplate - uDateFoldersyFolderYear - - - - Blog Roll Links - uBlogsyBlogLinks - 67db8357-ef57-493e-91ac-936d305e0f2a - c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3 - Blog Roll - False - - - - - - - - Default Author - uBlogsyGeneralDefaultAuthor - 7e062c13-7c41-4ad9-b389-41d88aeef87c - ec090322-7f08-4c1a-92cd-682bff7dbdcf - General Settings - False - - - - - - Use Summary On Landing - uBlogsyGeneralUseSummary - 38b352c1-e9f8-4fd8-9324-9a2eab06d97a - 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 - General Settings - False - - - - - - - - Show Social Media Links - uBlogsyGeneralShowSocialMedia - 38b352c1-e9f8-4fd8-9324-9a2eab06d97a - 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 - General Settings - False - - - - - - Use Title As Node Name - uBlogsyGeneralUseTitleAsNodeName - 38b352c1-e9f8-4fd8-9324-9a2eab06d97a - 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 - General Settings - False - - - - - - - - - - 14 - General Settings - - - 15 - Blog Roll - - - - - - [uBlogsy] Page - uBlogsyPage - page_white_cup.png - folder.png - This document type is for simple pages. - False - uBlogsyBasePage - - - - uBlogsyPage - - - - - - - - [uBlogsy] Post - uBlogsyPost - page_green.png - folder.png - This document type is for your blog posts. Put these posts directly under the year folder or under a month folder. - False - uBlogsyBasePage - - - - uBlogsyPost - - - - - Main Image - uBlogsyPostImage - ead69342-f06d-4253-83ac-28000225583b - 93929b9a-93a2-4e2a-b239-d99334440a59 - Post Info - False - - - - - - Author - uBlogsyPostAuthor - 7e062c13-7c41-4ad9-b389-41d88aeef87c - ec090322-7f08-4c1a-92cd-682bff7dbdcf - Post Info - False - - - - - - Post Date - uBlogsyPostDate - b6fb1622-afa5-4bbf-a3cc-d9672a442222 - e4d66c0f-b935-4200-81f0-025f7256b89a - Post Info - True - - - - - - Tags - uBlogsyPostTags - d15e1281-e456-4b24-aa86-1dda3e4299d5 - 1dcab42b-3ade-46d0-b13a-83e2f2944d20 - Post Info - False - - - - - - Categories - uBlogsyPostLabels - 7e062c13-7c41-4ad9-b389-41d88aeef87c - e9bc6389-9a8a-4f7b-b752-96e8b0e9fd51 - Post Info - False - - - - - - Disable Comments - uBlogsyPostDisableComments - 38b352c1-e9f8-4fd8-9324-9a2eab06d97a - 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 - Post Info - False - - - - - - - - 16 - Post Info - - - - - - [uBlogsy] RSS - uBlogsyRSS - rss.png - folder.png - This document type exists only for an RSS feed. There should only be one of these. - False - uBlogsyBaseDocType - - - - uBlogsyRss - - - - - Title - uBlogsyRssTitle - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - RSS Feed - False - - - - - - Description - uBlogsyRssDescription - 67db8357-ef57-493e-91ac-936d305e0f2a - c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3 - RSS Feed - False - - - - - - Copyright - uBlogsyRssCopyright - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - RSS Feed - False - - - - - - - - - - 29 - RSS Feed - - - - - - [uBlogsy] Site Root - uBlogsySiteRoot - house.png - folder.png - - - True - uBlogsyBasePage - - - - uBlogsySiteHome - - - uBlogsyLanding - uBlogsyPage - - - - - - - [uDateFoldersy] [Base] - uDateFoldersyBase - folder.gif - folder.png - - - False - - - - - - - - - - - [uDateFoldersy] Folder - Day - uDateFoldersyFolderDay - calendar_view_day.png - folder.png - - - False - uDateFoldersyBase - - - - uDateFoldersyFolderRedirect - - - uBlogsyPost - - - - - - - [uDateFoldersy] Folder - Month - uDateFoldersyFolderMonth - date.png - folder.png - - - False - uDateFoldersyBase - - - - uDateFoldersyFolderRedirect - - - uBlogsyPost - - - - - - - [uDateFoldersy] Folder - Year - uDateFoldersyFolderYear - folder_table.png - folder.png - - - False - uDateFoldersyBase - - - - uDateFoldersyFolderRedirect - - - uBlogsyPost - - - - - - - [uTagsy] [Base] - uTagsyBaseDocType - folder.gif - folder.png - - - False - - - - - - - - - - - [uTagsy] Tag - uTagsyTag - tag_blue.png - folder.png - - - False - uTagsyBaseDocType - - - - - - - - Tag Name - uTagsyTagName - ec15c1e5-9d90-422a-aa52-4f7622c63bea - 0cc0eba1-9960-42c9-bf9b-60e150b429ae - Tag - False - - - - - - - - 21 - Tag - - - - - - [uTagsy] Tag Container - uTagsyTagContainer - tag_blue_add.png - folder.png - - - False - uTagsyBaseDocType - - - - - - uTagsyTag - - - - - - - - - - - - - - - - - - - uBlogsy - uBlogsy - - div { padding-bottom:20px;} - -/* right col - lists */ -#uBlogsy_right_col li{ clear: both; line-height:20px;} -#uBlogsy_right_col ul { list-style-type:none; margin:5px 0; padding-left:0px; overflow:hidden; } - -/* right col - post archive */ -#uBlogsy_post_archive ul { list-style-type:none; } -#uBlogsy_post_archive ul ul { padding-left:15px; } - -#uBlogsy_post_archive .uBlogsy_post_items {display:none; margin-top:0;} -#uBlogsy_post_archive .uBlogsy_months{display:none;} - -#uBlogsy_post_archive .uBlogsy_current{ font-weight: bold;} - - -/* right col - post archive - alternate layout */ -#uBlogsy_right_col .uBlogsy_post_archive_alt .uBlogsy_year_first .uBlogsy_year_name{display:none; } -#uBlogsy_right_col .uBlogsy_post_archive_alt .uBlogsy_year_first .uBlogsy_months{ padding-left:0px; margin-top: 0;} - - -/* right col - rss logo */ -#uBlogsy_right_col .uBlogsy_feed { margin-left: 3px; padding: 0 0 0 19px; background: url("../images/uBlogsy_feed-icon-14x14.png") no-repeat 0 50%; height: 15px; display: block; float: left; overflow: hidden;} - - -#uBlogsy_right_col .uBlogsy_post_list_image{ display: inline-block;float: left; padding: 0;margin: 0 5px 1px 0;overflow: hidden;height: 25px;width: 25px;border: 1px solid #555555;} - - -/********************************************** - pagination -***********************************************/ -/* pagination - landing */ -#uBlogsy_pagination { display:block; float:right; overflow:hidden;} -#uBlogsy_pagination li{ list-style-type:none; display:block; float:left; height: 20px; padding: 0 4px 0 0;} -#uBlogsy_pagination li a { display:block; padding :3px 0 0 6px; } -#uBlogsy_pagination li.uBlogsy_page_prev span, #uBlogsy_pagination li.uBlogsy_page_next span { display:block; padding :2px 0 0 5px; color:#CCC} -#uBlogsy_pagination .uBlogsy_page_next, #uBlogsy_pagination .uBlogsy_page_prev{ width:40px;} -#uBlogsy_pagination li.uBlogsy_current a{font-weight:bold;} -#uBlogsy_pagination li.uBlogsy_current span{padding:2px 0 0 6px; display:block;} - -/* pagination - post */ -.uBlogsy_next_prev{padding-top:20px;overflow: hidden;clear: both;} -#uBlogsy .uBlogsy_next_prev a { color:#505050; } -.uBlogsy_prev {float:left;} -.uBlogsy_next {float:right;} - - - - -/********************************************** - forms -***********************************************/ -/* forms */ -.uBlogsy_search input[type=submit] { border: 1px solid #cccccc; } - -.uBlogsy_row{ overflow:hidden; position: relative; } - -.uBlogsy_row.uBlogsy_Subscribe{padding-bottom:10px;} -.uBlogsy_row.uBlogsy_Subscribe input[type=checkbox] { float:left;} -.uBlogsy_row .field-validation-error{ position: absolute; top: 0px;} -.uBlogsy_row .field-validation-valid{ display: none;} - -/* forms - search */ -.uBlogsy_search input[type=text] { border:1px solid #ccc; height:20px; margin-bottom:10px; width:200px;} -#uBlogsyBtnSearch:hover{ color:#234B7B;} - - - - - -/********************************************** - misc -***********************************************/ -.uBlogsy_bottom_border { border-bottom-color: #CCC; border-bottom-style: dotted; border-bottom-width: 1px; } - -/*add this*/ -#uBlogsy .addthis_toolbox { float:left;clear: both;margin-top: 30px;height: 30px;} -#uBlogsy .addthis_toolbox a { display:block; float:right;} - - - - - - -/********************************************** - tag cloud -***********************************************/ -.uBlogsy_tag_cloud li { display:inline-block; padding-right:10px; } -.uBlogsy_tag_cloud li span {color:#505050} -.uBlogsy_tag_cloud1 { font-size: 10px;} -.uBlogsy_tag_cloud2 { font-size: 10px;} -.uBlogsy_tag_cloud3 { font-size: 11px;} -.uBlogsy_tag_cloud4 { font-size: 11px;} -.uBlogsy_tag_cloud5 { font-size: 12px;} -.uBlogsy_tag_cloud6 { font-size: 12px;} -.uBlogsy_tag_cloud7 { font-size: 13px;} -.uBlogsy_tag_cloud8 { font-size: 13px;} -.uBlogsy_tag_cloud9 { font-size: 14px;} -.uBlogsy_tag_cloud10 { font-size: 14px;} -.uBlogsy_tag_cloud11 { font-size: 15px;} -.uBlogsy_tag_cloud12 { font-size: 15px;} -.uBlogsy_tag_cloud13 { font-size: 16px;} -.uBlogsy_tag_cloud14 { font-size: 16px;} -.uBlogsy_tag_cloud15 { font-size: 17px;} -.uBlogsy_tag_cloud16 { font-size: 17px;} -.uBlogsy_tag_cloud17 { font-size: 18px;} -.uBlogsy_tag_cloud18 { font-size: 18px;} -.uBlogsy_tag_cloud19 { font-size: 19px;} -.uBlogsy_tag_cloud20 { font-size: 19px;} -.uBlogsy_tag_cloud21 { font-size: 20px;} -.uBlogsy_tag_cloud22 { font-size: 20px;} -.uBlogsy_tag_cloud23 { font-size: 21px;} -.uBlogsy_tag_cloud24 { font-size: 21px;} -.uBlogsy_tag_cloud25 { font-size: 22px;} -.uBlogsy_tag_cloud26 { font-size: 22px;} -.uBlogsy_tag_cloud27 { font-size: 23px;} -.uBlogsy_tag_cloud28 { font-size: 23px;} -.uBlogsy_tag_cloud29 { font-size: 24px;} -.uBlogsy_tag_cloud30 { font-size: 24px;} -.uBlogsy_tag_cloud31 { font-size: 25px;} -.uBlogsy_tag_cloud32 { font-size: 25px;} -.uBlogsy_tag_cloud33 { font-size: 26px;} -.uBlogsy_tag_cloud34 { font-size: 26px;} -.uBlogsy_tag_cloud35 { font-size: 27px;} -.uBlogsy_tag_cloud36 { font-size: 27px;} -.uBlogsy_tag_cloud37 { font-size: 28px;} -.uBlogsy_tag_cloud38 { font-size: 28px;} -.uBlogsy_tag_cloud39 { font-size: 29px;} -.uBlogsy_tag_cloud40 { font-size: 29px;} -.uBlogsy_tag_cloud40 { font-size: 30px;} -.uBlogsy_tag_cloud41 { font-size: 31px;} -.uBlogsy_tag_cloud42 { font-size: 31px;} -.uBlogsy_tag_cloud43 { font-size: 32px;} -.uBlogsy_tag_cloud44 { font-size: 32px;} -.uBlogsy_tag_cloud45 { font-size: 33px;} -.uBlogsy_tag_cloud46 { font-size: 33px;} -.uBlogsy_tag_cloud47 { font-size: 34px;} -.uBlogsy_tag_cloud48 { font-size: 34px;} -.uBlogsy_tag_cloud49 { font-size: 35px;} -.uBlogsy_tag_cloud50 { font-size: 35px;} -.uBlogsy_tag_cloud50 { font-size: 36px;} -.uBlogsy_tag_cloud51 { font-size: 36px;} -.uBlogsy_tag_cloud52 { font-size: 37px;} -.uBlogsy_tag_cloud53 { font-size: 37px;} -.uBlogsy_tag_cloud54 { font-size: 38px;} -.uBlogsy_tag_cloud55 { font-size: 38px;} -.uBlogsy_tag_cloud56 { font-size: 39px;} -.uBlogsy_tag_cloud57 { font-size: 39px;} -.uBlogsy_tag_cloud58 { font-size: 40px;} -.uBlogsy_tag_cloud59 { font-size: 40px;} -.uBlogsy_tag_cloud60 { font-size: 41px;} -.uBlogsy_tag_cloud71 { font-size: 41px;} -.uBlogsy_tag_cloud72 { font-size: 42px;} -.uBlogsy_tag_cloud73 { font-size: 42px;} -.uBlogsy_tag_cloud74 { font-size: 43px;} -.uBlogsy_tag_cloud75 { font-size: 43px;} -.uBlogsy_tag_cloud76 { font-size: 44px;} -.uBlogsy_tag_cloud77 { font-size: 44px;} -.uBlogsy_tag_cloud78 { font-size: 45px;} -.uBlogsy_tag_cloud79 { font-size: 45px;} -.uBlogsy_tag_cloud80 { font-size: 46px;} -.uBlogsy_tag_cloud81 { font-size: 46px;} -.uBlogsy_tag_cloud82 { font-size: 47px;} -.uBlogsy_tag_cloud83 { font-size: 47px;} -.uBlogsy_tag_cloud84 { font-size: 48px;} -.uBlogsy_tag_cloud85 { font-size: 48px;} -.uBlogsy_tag_cloud86 { font-size: 49px;} -.uBlogsy_tag_cloud87 { font-size: 49px;} -.uBlogsy_tag_cloud88 { font-size: 50px;} -.uBlogsy_tag_cloud89 { font-size: 50px;} -.uBlogsy_tag_cloud90 { font-size: 51px;} -.uBlogsy_tag_cloud91 { font-size: 51px;} -.uBlogsy_tag_cloud92 { font-size: 52px;} -.uBlogsy_tag_cloud93 { font-size: 52px;} -.uBlogsy_tag_cloud94 { font-size: 53px;} -.uBlogsy_tag_cloud95 { font-size: 53px;} -.uBlogsy_tag_cloud96 { font-size: 54px;} -.uBlogsy_tag_cloud97 { font-size: 54px;} -.uBlogsy_tag_cloud98 { font-size: 55px;} -.uBlogsy_tag_cloud99 { font-size: 55px;} -.uBlogsy_tag_cloud100 { font-size: 56px;} - - - -]]> - - - - - - [uBlogsy] Show Some Love - uBlogsyShowSomeLove - Unknown - - - - True - 0 - ~/Views/MacroPartials/uBlogsy/uBlogsyMacroShowSomeLove.cshtml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /usercontrols/uBlogsy/dashboard/Installer.ascx - - -
- - default - content - - - /usercontrols/uBlogsy/dashboard/CreatePost.ascx - - - /usercontrols/uBlogsy/dashboard/RSSImport.ascx - -
-
-
+ + + + + uBlogsy.BusinessLogic.dll + /bin + uBlogsy.BusinessLogic.dll + + + uBlogsy.BusinessLogic.pdb + /bin + uBlogsy.BusinessLogic.pdb + + + uBlogsy.Common.dll + /bin + uBlogsy.Common.dll + + + uBlogsy.Common.pdb + /bin + uBlogsy.Common.pdb + + + uBlogsy.Mvc.Parts.dll + /bin + uBlogsy.Mvc.Parts.dll + + + uBlogsy.Mvc.Parts.pdb + /bin + uBlogsy.Mvc.Parts.pdb + + + uBlogsy.Web.dll + /bin + uBlogsy.Web.dll + + + uBlogsy.Web.pdb + /bin + uBlogsy.Web.pdb + + + uDateFoldersy.dll + /bin + uDateFoldersy.dll + + + uDateFoldersy.pdb + /bin + uDateFoldersy.pdb + + + uLoremsy.dll + /bin + uLoremsy.dll + + + uLoremsy.pdb + /bin + uLoremsy.pdb + + + uHelpsy.dll + /bin + uHelpsy.dll + + + uHelpsy.pdb + /bin + uHelpsy.pdb + + + uTagsy.Web.dll + /bin + uTagsy.Web.dll + + + uTagsy.Web.pdb + /bin + uTagsy.Web.pdb + + + CreatePost.ascx + /usercontrols/uBlogsy/dashboard + CreatePost.ascx + + + RSSImport.ascx + /usercontrols/uBlogsy/dashboard + RSSImport.ascx + + + uTagsy.ascx + /usercontrols/uTagsy + uTagsy.ascx + + + uDateFoldersy.config + /config + uDateFoldersy.config + + + uTagsy.config + /config + uTagsy.config + + + uLoremsy.config + /config + uLoremsy.config + + + uBlogsy.css + /css + uBlogsy.css + + + uBlogsy_feed-icon-14x14.png + /Images + uBlogsy_feed-icon-14x14.png + + + calendar_view_day.png + /umbraco/images/umbraco + calendar_view_day.png + + + date.png + /umbraco/images/umbraco + date.png + + + feed.png + /umbraco/images/umbraco + feed.png + + + folder_table.png + /umbraco/images/umbraco + folder_table.png + + + folder_user.png + /umbraco/images/umbraco + folder_user.png + + + house.png + /umbraco/images/umbraco + house.png + + + page_green.png + /umbraco/images/umbraco + page_green.png + + + page_white_cup.png + /umbraco/images/umbraco + page_white_cup.png + + + rss.png + /umbraco/images/umbraco + rss.png + + + tag.png + /umbraco/images/umbraco + tag.png + + + tag_blue.png + /Umbraco/Images/Umbraco + tag_blue.png + + + tag_blue_add.png + /Umbraco/Images/Umbraco + tag_blue_add.png + + + user.png + /umbraco/images/umbraco + user.png + + + _uBlogsyBase.cshtml + /Views + _uBlogsyBase.cshtml + + + _uBlogsyBaseSite.cshtml + /Views + _uBlogsyBaseSite.cshtml + + + _uBlogsyBaseBlog.cshtml + /Views + _uBlogsyBaseBlog.cshtml + + + uBlogsyMacroShowSomeLove.cshtml + /Views/MacroPartials/uBlogsy + uBlogsyMacroShowSomeLove.cshtml + + + uBlogsyGlobalBrowserTitle.cshtml + /Views/Partials/uBlogsy/Global + uBlogsyGlobalBrowserTitle.cshtml + + + uBlogsyGlobalFooter.cshtml + /Views/Partials/uBlogsy/Global + uBlogsyGlobalFooter.cshtml + + + uBlogsyGlobalHeader.cshtml + /Views/Partials/uBlogsy/Global + uBlogsyGlobalHeader.cshtml + + + uBlogsyGlobalNavigation.cshtml + /Views/Partials/uBlogsy/Global + uBlogsyGlobalNavigation.cshtml + + + uBlogsyGlobalSeoMeta.cshtml + /Views/Partials/uBlogsy/Global + uBlogsyGlobalSeoMeta.cshtml + + + uBlogsyLandingListPosts.cshtml + /Views/Partials/uBlogsy/Landing + uBlogsyLandingListPosts.cshtml + + + uBlogsyLandingShowPost.cshtml + /Views/Partials/uBlogsy/Landing + uBlogsyLandingShowPost.cshtml + + + uBlogsyPostListAuthors.cshtml + /Views/Partials/uBlogsy/Post + uBlogsyPostListAuthors.cshtml + + + uBlogsyPostListLabels.cshtml + /Views/Partials/uBlogsy/Post + uBlogsyPostListLabels.cshtml + + + uBlogsyPostListRelatedPosts.cshtml + /Views/Partials/uBlogsy/Post + uBlogsyPostListRelatedPosts.cshtml + + + uBlogsyPostListTags.cshtml + /Views/Partials/uBlogsy/Post + uBlogsyPostListTags.cshtml + + + uBlogsyPostShowPost.cshtml + /Views/Partials/uBlogsy/Post + uBlogsyPostShowPost.cshtml + + + uBlogsyShowImage.cshtml + /Views/Partials/uBlogsy/Shared + uBlogsyShowImage.cshtml + + + uBlogsyWidgetListAuthors.cshtml + /Views/Partials/uBlogsy/Widgets + uBlogsyWidgetListAuthors.cshtml + + + uBlogsyWidgetListBlogRoll.cshtml + /Views/Partials/uBlogsy/Widgets + uBlogsyWidgetListBlogRoll.cshtml + + + uBlogsyWidgetListLabels.cshtml + /Views/Partials/uBlogsy/Widgets + uBlogsyWidgetListLabels.cshtml + + + uBlogsyWidgetListPostArchive.cshtml + /Views/Partials/uBlogsy/Widgets + uBlogsyWidgetListPostArchive.cshtml + + + uBlogsyWidgetListPosts.cshtml + /Views/Partials/uBlogsy/Widgets + uBlogsyWidgetListPosts.cshtml + + + uBlogsyWidgetListPostsForHome.cshtml + /Views/Partials/uBlogsy/Widgets + uBlogsyWidgetListPostsForHome.cshtml + + + uBlogsyWidgetListTags.cshtml + /Views/Partials/uBlogsy/Widgets + uBlogsyWidgetListTags.cshtml + + + uBlogsyWidgetSearch.cshtml + /Views/Partials/uBlogsy/Widgets + uBlogsyWidgetSearch.cshtml + + + uBlogsyWidgetShowRSSLink.cshtml + /Views/Partials/uBlogsy/Widgets + uBlogsyWidgetShowRSSLink.cshtml + + + uDateFoldersyFolderRedirect.cshtml + /Views/Partials/uDateFoldersy + uDateFoldersyFolderRedirect.cshtml + + + Installer.ascx + /usercontrols/uBlogsy/dashboard + Installer.ascx + + + + + uBlogsy + 3.0 + MIT license + http://our.umbraco.org/projects/starter-kits/ublogsy + + 3 + 0 + 0 + + + + Anthony Dang + http://anthonydotnet.blogspot.com + + + + + + + + + My Site + + + This is an example home page. 

+

From uBlogsy 3.0, the package is now a site (with basic pages) which has a blog (not a blog which has pages).

+

Below on this page you will see a widget (/Views/uBlogsy/Widgets/uBlogsyWidgetListPostsForHome.cshtml) which lists latest news. The purpose of the widget is to list news for the home page. Your home page will typically be the parent of the uBlogsyLanding node, or a sibling.

+

Take a look at /Views/uBlogsySiteHome.cshtml

+

 

+

 

]]> +
+ + + 0 + + + + + + + + + + 1 + 1 + 1 + + + + Blog + + + Show some love +

Did you know that uBlogsy (a completely free open-source package) has taken many 100's of hours to create and mainatain?

+

If you want to show some love, and support why not click the button below.

+]]> +
+ Keywords + + 0 + + + + + Blog + + 0 + + + + + + Author 1 + 0 + + + + + + + + + + Tag 1 + + + + 0 + + + + + + Category 1 + 0 + + + + + + + + 0 + + + + + My Blog + + Copyright @AnthonyDotNet + +
+ + About + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut nibh massa, ornare ac tempor in, elementum ut sem. Fusce nisl urna, auctor eu varius vel, mattis eu sapien. Sed venenatis facilisis ligula, vitae adipiscing nulla scelerisque eget. Nulla facilisi. Aliquam rutrum sollicitudin erat, non consectetur nunc rutrum sit amet. Proin sit amet elit eget metus fermentum laoreet eu non ipsum. Vivamus non est nibh, non congue turpis. Pellentesque tempus vehicula diam ut tincidunt. Fusce sem diam, vestibulum quis faucibus sit amet, tincidunt id mi. Donec scelerisque blandit suscipit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

+

Duis eget elementum libero. Donec in erat erat, vel convallis diam. Nulla facilisi. Vivamus adipiscing pellentesque facilisis. Nullam convallis nunc ac metus facilisis dapibus. Suspendisse potenti. Phasellus dictum arcu ac velit hendrerit non facilisis erat dignissim. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed vitae ante non magna mollis varius at at nisi. Suspendisse sagittis dignissim mi vel adipiscing. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed porttitor viverra congue. Ut sollicitudin enim quis orci scelerisque quis fringilla lorem fringilla. Integer urna leo, aliquam vel sollicitudin eget, facilisis id orci. Ut mollis libero nunc. Integer tortor nisi, aliquet non fermentum ac, bibendum in eros. Mauris aliquet arcu quis enim tincidunt venenatis. Vestibulum tincidunt ullamcorper nulla id blandit. Nam vitae quam id nisi aliquet malesuada malesuada sed eros. Aenean faucibus tempus enim sed pretium.

+

Cras malesuada magna ut elit condimentum mattis. Vestibulum eu risus ante, quis preti

]]> +
+ + + 0 + + + + + + About +
+ + Contact + + + Get your free embedable form from one of these:

+

 

+

http://www.formsite.com/landing/embed.html

+

 

+

http://www.jotformeu.com/

+

 

+

http://www.wufoo.com/

]]> +
+ + + 0 + + + + + + Contact +
+
+
+
+ + + + [uBlogsy] [Base] + uBlogsyBaseDocType + folder.gif + folder.png + This document type exists to allow sorting and categorisation of sub-document types. Do not create content nodes using this document type! + False + + + + + + + + Hide from navigation + umbracoNaviHide + 38b352c1-e9f8-4fd8-9324-9a2eab06d97a + 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 + + + False + + + + + + 302 Redirect + umbracoRedirect + 158aa029-24ed-4948-939e-c3da209e5fba + a6857c73-d6e9-480c-b6e6-f15f6ad11125 + + + False + + + + + + Invisible Redirect + umbracoInternalRedirectId + 158aa029-24ed-4948-939e-c3da209e5fba + a6857c73-d6e9-480c-b6e6-f15f6ad11125 + + + False + + + + + + Url Name Change + umbracoUrlName + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + + + False + + + + + + Url Alias + umbracoUrlAlias + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + + + False + + + + + + + + + + [uBlogsy] [Base] Container + uBlogsyBaseContainer + folder.gif + folder.png + + + False + uBlogsyBaseDocType + + + + + + + + + + + [uBlogsy] [Base] Page + uBlogsyBasePage + folder.gif + folder.png + This document type exists to allow sorting and categorisation of sub-document types. Do not create content nodes using this document type! + False + uBlogsyBaseDocType + + + + + + + + Title + uBlogsyContentTitle + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Content + False + + + + + + Summary + uBlogsyContentSummary + 67db8357-ef57-493e-91ac-936d305e0f2a + c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3 + Content + False + + + + + + Body + uBlogsyContentBody + 5e9b75ae-face-41c8-b47e-5f4b0fd82f83 + ca90c950-0aff-4e72-b976-a30b1ac57dad + Content + False + + + + + + Seo Title + uBlogsySeoTitle + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + SEO + False + + + + + + Navigation Title + uBlogsyNavigationTitle + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Navigation + False + + + + + + Keywords + uBlogsySeoKeywords + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + SEO + False + + + + + + Description + uBlogsySeoDescription + 67db8357-ef57-493e-91ac-936d305e0f2a + c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3 + SEO + False + + + + + + + + 6 + Content + + + 7 + SEO + + + 30 + Navigation + + + + + + [uBlogsy] Author + uBlogsyAuthor + user.png + folder.png + + + False + uBlogsyBaseDocType + + + + + + + + Author Name + uBlogsyAuthorName + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Author + False + + + + + + Gravatar Email + uBlogsyAuthorGravatarEmail + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Author + False + + + + + + + + 20 + Author + + + + + + [uBlogsy] Container - Author + uBlogsyContainerAuthor + folder_user.png + folder.png + + + False + uBlogsyBaseContainer + + + + + + uBlogsyAuthor + + + + + + + [uBlogsy] Container - Email Template + uBlogsyContainerEmailTemplate + folder_page_white.png + folder.png + + + False + uBlogsyBaseContainer + + + + + + + + + + + [uBlogsy] Container - Label + uBlogsyContainerLabel + folder_table.png + folder.png + + + False + uBlogsyBaseContainer + + + + + + uBlogsyLabel + + + + + + + [uBlogsy] Label + uBlogsyLabel + tag.png + folder.png + + + False + uBlogsyBaseDocType + + + + + + + + Label Name + uBlogsyLabelName + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Category + False + + + + + + + + 22 + Category + + + + + + [uBlogsy] Landing + uBlogsyLanding + feed.png + folder.png + This is the root of your blog. It is required. There can be multiple of these. But they cannot be nested under eachother. + False + uBlogsyBasePage + + + + uBlogsyLanding + + + uBlogsyPost + uBlogsyRSS + uTagsyTagContainer + uBlogsyContainerAuthor + uBlogsyContainerLabel + uBlogsyContainerEmailTemplate + uDateFoldersyFolderYear + + + + Blog Roll Links + uBlogsyBlogLinks + 67db8357-ef57-493e-91ac-936d305e0f2a + c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3 + Blog Roll + False + + + + + + + + Default Author + uBlogsyGeneralDefaultAuthor + 7e062c13-7c41-4ad9-b389-41d88aeef87c + ec090322-7f08-4c1a-92cd-682bff7dbdcf + General Settings + False + + + + + + Use Summary On Landing + uBlogsyGeneralUseSummary + 38b352c1-e9f8-4fd8-9324-9a2eab06d97a + 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 + General Settings + False + + + + + + + + Show Social Media Links + uBlogsyGeneralShowSocialMedia + 38b352c1-e9f8-4fd8-9324-9a2eab06d97a + 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 + General Settings + False + + + + + + Use Title As Node Name + uBlogsyGeneralUseTitleAsNodeName + 38b352c1-e9f8-4fd8-9324-9a2eab06d97a + 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 + General Settings + False + + + + + + + + + + 14 + General Settings + + + 15 + Blog Roll + + + + + + [uBlogsy] Page + uBlogsyPage + page_white_cup.png + folder.png + This document type is for simple pages. + False + uBlogsyBasePage + + + + uBlogsyPage + + + + + + + + [uBlogsy] Post + uBlogsyPost + page_green.png + folder.png + This document type is for your blog posts. Put these posts directly under the year folder or under a month folder. + False + uBlogsyBasePage + + + + uBlogsyPost + + + + + Main Image + uBlogsyPostImage + ead69342-f06d-4253-83ac-28000225583b + 93929b9a-93a2-4e2a-b239-d99334440a59 + Post Info + False + + + + + + Author + uBlogsyPostAuthor + 7e062c13-7c41-4ad9-b389-41d88aeef87c + ec090322-7f08-4c1a-92cd-682bff7dbdcf + Post Info + False + + + + + + Post Date + uBlogsyPostDate + b6fb1622-afa5-4bbf-a3cc-d9672a442222 + e4d66c0f-b935-4200-81f0-025f7256b89a + Post Info + True + + + + + + Tags + uBlogsyPostTags + d15e1281-e456-4b24-aa86-1dda3e4299d5 + 1dcab42b-3ade-46d0-b13a-83e2f2944d20 + Post Info + False + + + + + + Categories + uBlogsyPostLabels + 7e062c13-7c41-4ad9-b389-41d88aeef87c + e9bc6389-9a8a-4f7b-b752-96e8b0e9fd51 + Post Info + False + + + + + + Disable Comments + uBlogsyPostDisableComments + 38b352c1-e9f8-4fd8-9324-9a2eab06d97a + 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 + Post Info + False + + + + + + + + 16 + Post Info + + + + + + [uBlogsy] RSS + uBlogsyRSS + rss.png + folder.png + This document type exists only for an RSS feed. There should only be one of these. + False + uBlogsyBaseDocType + + + + uBlogsyRss + + + + + Title + uBlogsyRssTitle + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + RSS Feed + False + + + + + + Description + uBlogsyRssDescription + 67db8357-ef57-493e-91ac-936d305e0f2a + c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3 + RSS Feed + False + + + + + + Copyright + uBlogsyRssCopyright + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + RSS Feed + False + + + + + + + + + + 29 + RSS Feed + + + + + + [uBlogsy] Site Root + uBlogsySiteRoot + house.png + folder.png + + + True + uBlogsyBasePage + + + + uBlogsySiteHome + + + uBlogsyLanding + uBlogsyPage + + + + + + + [uDateFoldersy] [Base] + uDateFoldersyBase + folder.gif + folder.png + + + False + + + + + + + + + + + [uDateFoldersy] Folder - Day + uDateFoldersyFolderDay + calendar_view_day.png + folder.png + + + False + uDateFoldersyBase + + + + uDateFoldersyFolderRedirect + + + uBlogsyPost + + + + + + + [uDateFoldersy] Folder - Month + uDateFoldersyFolderMonth + date.png + folder.png + + + False + uDateFoldersyBase + + + + uDateFoldersyFolderRedirect + + + uBlogsyPost + + + + + + + [uDateFoldersy] Folder - Year + uDateFoldersyFolderYear + folder_table.png + folder.png + + + False + uDateFoldersyBase + + + + uDateFoldersyFolderRedirect + + + uBlogsyPost + + + + + + + [uTagsy] [Base] + uTagsyBaseDocType + folder.gif + folder.png + + + False + + + + + + + + + + + [uTagsy] Tag + uTagsyTag + tag_blue.png + folder.png + + + False + uTagsyBaseDocType + + + + + + + + Tag Name + uTagsyTagName + ec15c1e5-9d90-422a-aa52-4f7622c63bea + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Tag + False + + + + + + + + 21 + Tag + + + + + + [uTagsy] Tag Container + uTagsyTagContainer + tag_blue_add.png + folder.png + + + False + uTagsyBaseDocType + + + + + + uTagsyTag + + + + + + + + + + + + + + + + + + + uBlogsy + uBlogsy + + div { padding-bottom:20px;} + +/* right col - lists */ +#uBlogsy_right_col li{ clear: both; line-height:20px;} +#uBlogsy_right_col ul { list-style-type:none; margin:5px 0; padding-left:0px; overflow:hidden; } + +/* right col - post archive */ +#uBlogsy_post_archive ul { list-style-type:none; } +#uBlogsy_post_archive ul ul { padding-left:15px; } + +#uBlogsy_post_archive .uBlogsy_post_items {display:none; margin-top:0;} +#uBlogsy_post_archive .uBlogsy_months{display:none;} + +#uBlogsy_post_archive .uBlogsy_current{ font-weight: bold;} + + +/* right col - post archive - alternate layout */ +#uBlogsy_right_col .uBlogsy_post_archive_alt .uBlogsy_year_first .uBlogsy_year_name{display:none; } +#uBlogsy_right_col .uBlogsy_post_archive_alt .uBlogsy_year_first .uBlogsy_months{ padding-left:0px; margin-top: 0;} + + +/* right col - rss logo */ +#uBlogsy_right_col .uBlogsy_feed { margin-left: 3px; padding: 0 0 0 19px; background: url("../images/uBlogsy_feed-icon-14x14.png") no-repeat 0 50%; height: 15px; display: block; float: left; overflow: hidden;} + + +#uBlogsy_right_col .uBlogsy_post_list_image{ display: inline-block;float: left; padding: 0;margin: 0 5px 1px 0;overflow: hidden;height: 25px;width: 25px;border: 1px solid #555555;} + + +/********************************************** + pagination +***********************************************/ +/* pagination - landing */ +#uBlogsy_pagination { display:block; float:right; overflow:hidden;} +#uBlogsy_pagination li{ list-style-type:none; display:block; float:left; height: 20px; padding: 0 4px 0 0;} +#uBlogsy_pagination li a { display:block; padding :3px 0 0 6px; } +#uBlogsy_pagination li.uBlogsy_page_prev span, #uBlogsy_pagination li.uBlogsy_page_next span { display:block; padding :2px 0 0 5px; color:#CCC} +#uBlogsy_pagination .uBlogsy_page_next, #uBlogsy_pagination .uBlogsy_page_prev{ width:40px;} +#uBlogsy_pagination li.uBlogsy_current a{font-weight:bold;} +#uBlogsy_pagination li.uBlogsy_current span{padding:2px 0 0 6px; display:block;} + +/* pagination - post */ +.uBlogsy_next_prev{padding-top:20px;overflow: hidden;clear: both;} +#uBlogsy .uBlogsy_next_prev a { color:#505050; } +.uBlogsy_prev {float:left;} +.uBlogsy_next {float:right;} + + + + +/********************************************** + forms +***********************************************/ +/* forms */ +.uBlogsy_search input[type=submit] { border: 1px solid #cccccc; } + +.uBlogsy_row{ overflow:hidden; position: relative; } + +.uBlogsy_row.uBlogsy_Subscribe{padding-bottom:10px;} +.uBlogsy_row.uBlogsy_Subscribe input[type=checkbox] { float:left;} +.uBlogsy_row .field-validation-error{ position: absolute; top: 0px;} +.uBlogsy_row .field-validation-valid{ display: none;} + +/* forms - search */ +.uBlogsy_search input[type=text] { border:1px solid #ccc; height:20px; margin-bottom:10px; width:200px;} +#uBlogsyBtnSearch:hover{ color:#234B7B;} + + + + + +/********************************************** + misc +***********************************************/ +.uBlogsy_bottom_border { border-bottom-color: #CCC; border-bottom-style: dotted; border-bottom-width: 1px; } + +/*add this*/ +#uBlogsy .addthis_toolbox { float:left;clear: both;margin-top: 30px;height: 30px;} +#uBlogsy .addthis_toolbox a { display:block; float:right;} + + + + + + +/********************************************** + tag cloud +***********************************************/ +.uBlogsy_tag_cloud li { display:inline-block; padding-right:10px; } +.uBlogsy_tag_cloud li span {color:#505050} +.uBlogsy_tag_cloud1 { font-size: 10px;} +.uBlogsy_tag_cloud2 { font-size: 10px;} +.uBlogsy_tag_cloud3 { font-size: 11px;} +.uBlogsy_tag_cloud4 { font-size: 11px;} +.uBlogsy_tag_cloud5 { font-size: 12px;} +.uBlogsy_tag_cloud6 { font-size: 12px;} +.uBlogsy_tag_cloud7 { font-size: 13px;} +.uBlogsy_tag_cloud8 { font-size: 13px;} +.uBlogsy_tag_cloud9 { font-size: 14px;} +.uBlogsy_tag_cloud10 { font-size: 14px;} +.uBlogsy_tag_cloud11 { font-size: 15px;} +.uBlogsy_tag_cloud12 { font-size: 15px;} +.uBlogsy_tag_cloud13 { font-size: 16px;} +.uBlogsy_tag_cloud14 { font-size: 16px;} +.uBlogsy_tag_cloud15 { font-size: 17px;} +.uBlogsy_tag_cloud16 { font-size: 17px;} +.uBlogsy_tag_cloud17 { font-size: 18px;} +.uBlogsy_tag_cloud18 { font-size: 18px;} +.uBlogsy_tag_cloud19 { font-size: 19px;} +.uBlogsy_tag_cloud20 { font-size: 19px;} +.uBlogsy_tag_cloud21 { font-size: 20px;} +.uBlogsy_tag_cloud22 { font-size: 20px;} +.uBlogsy_tag_cloud23 { font-size: 21px;} +.uBlogsy_tag_cloud24 { font-size: 21px;} +.uBlogsy_tag_cloud25 { font-size: 22px;} +.uBlogsy_tag_cloud26 { font-size: 22px;} +.uBlogsy_tag_cloud27 { font-size: 23px;} +.uBlogsy_tag_cloud28 { font-size: 23px;} +.uBlogsy_tag_cloud29 { font-size: 24px;} +.uBlogsy_tag_cloud30 { font-size: 24px;} +.uBlogsy_tag_cloud31 { font-size: 25px;} +.uBlogsy_tag_cloud32 { font-size: 25px;} +.uBlogsy_tag_cloud33 { font-size: 26px;} +.uBlogsy_tag_cloud34 { font-size: 26px;} +.uBlogsy_tag_cloud35 { font-size: 27px;} +.uBlogsy_tag_cloud36 { font-size: 27px;} +.uBlogsy_tag_cloud37 { font-size: 28px;} +.uBlogsy_tag_cloud38 { font-size: 28px;} +.uBlogsy_tag_cloud39 { font-size: 29px;} +.uBlogsy_tag_cloud40 { font-size: 29px;} +.uBlogsy_tag_cloud40 { font-size: 30px;} +.uBlogsy_tag_cloud41 { font-size: 31px;} +.uBlogsy_tag_cloud42 { font-size: 31px;} +.uBlogsy_tag_cloud43 { font-size: 32px;} +.uBlogsy_tag_cloud44 { font-size: 32px;} +.uBlogsy_tag_cloud45 { font-size: 33px;} +.uBlogsy_tag_cloud46 { font-size: 33px;} +.uBlogsy_tag_cloud47 { font-size: 34px;} +.uBlogsy_tag_cloud48 { font-size: 34px;} +.uBlogsy_tag_cloud49 { font-size: 35px;} +.uBlogsy_tag_cloud50 { font-size: 35px;} +.uBlogsy_tag_cloud50 { font-size: 36px;} +.uBlogsy_tag_cloud51 { font-size: 36px;} +.uBlogsy_tag_cloud52 { font-size: 37px;} +.uBlogsy_tag_cloud53 { font-size: 37px;} +.uBlogsy_tag_cloud54 { font-size: 38px;} +.uBlogsy_tag_cloud55 { font-size: 38px;} +.uBlogsy_tag_cloud56 { font-size: 39px;} +.uBlogsy_tag_cloud57 { font-size: 39px;} +.uBlogsy_tag_cloud58 { font-size: 40px;} +.uBlogsy_tag_cloud59 { font-size: 40px;} +.uBlogsy_tag_cloud60 { font-size: 41px;} +.uBlogsy_tag_cloud71 { font-size: 41px;} +.uBlogsy_tag_cloud72 { font-size: 42px;} +.uBlogsy_tag_cloud73 { font-size: 42px;} +.uBlogsy_tag_cloud74 { font-size: 43px;} +.uBlogsy_tag_cloud75 { font-size: 43px;} +.uBlogsy_tag_cloud76 { font-size: 44px;} +.uBlogsy_tag_cloud77 { font-size: 44px;} +.uBlogsy_tag_cloud78 { font-size: 45px;} +.uBlogsy_tag_cloud79 { font-size: 45px;} +.uBlogsy_tag_cloud80 { font-size: 46px;} +.uBlogsy_tag_cloud81 { font-size: 46px;} +.uBlogsy_tag_cloud82 { font-size: 47px;} +.uBlogsy_tag_cloud83 { font-size: 47px;} +.uBlogsy_tag_cloud84 { font-size: 48px;} +.uBlogsy_tag_cloud85 { font-size: 48px;} +.uBlogsy_tag_cloud86 { font-size: 49px;} +.uBlogsy_tag_cloud87 { font-size: 49px;} +.uBlogsy_tag_cloud88 { font-size: 50px;} +.uBlogsy_tag_cloud89 { font-size: 50px;} +.uBlogsy_tag_cloud90 { font-size: 51px;} +.uBlogsy_tag_cloud91 { font-size: 51px;} +.uBlogsy_tag_cloud92 { font-size: 52px;} +.uBlogsy_tag_cloud93 { font-size: 52px;} +.uBlogsy_tag_cloud94 { font-size: 53px;} +.uBlogsy_tag_cloud95 { font-size: 53px;} +.uBlogsy_tag_cloud96 { font-size: 54px;} +.uBlogsy_tag_cloud97 { font-size: 54px;} +.uBlogsy_tag_cloud98 { font-size: 55px;} +.uBlogsy_tag_cloud99 { font-size: 55px;} +.uBlogsy_tag_cloud100 { font-size: 56px;} + + + +]]> + + + + + + [uBlogsy] Show Some Love + uBlogsyShowSomeLove + Unknown + + + + True + 0 + ~/Views/MacroPartials/uBlogsy/uBlogsyMacroShowSomeLove.cshtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /usercontrols/uBlogsy/dashboard/Installer.ascx + + +
+ + default + content + + + /usercontrols/uBlogsy/dashboard/CreatePost.ascx + + + /usercontrols/uBlogsy/dashboard/RSSImport.ascx + +
+
+
diff --git a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs index c62f14a4b0..033fa08d4a 100644 --- a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs +++ b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs @@ -1,433 +1,433 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; -using Umbraco.Core.Persistence; - -namespace Umbraco.Tests.Services -{ - /// - /// Tests covering all methods in the LocalizationService class. - /// This is more of an integration test as it involves multiple layers - /// as well as configuration. - /// - [TestFixture] - [Apartment(ApartmentState.STA)] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class LocalizationServiceTests : TestWithSomeContentBase - { - private Guid _parentItemGuidId; - private int _parentItemIntId; - private Guid _childItemGuidId; - private int _childItemIntId; - private int _danishLangId; - private int _englishLangId; - - [Test] - public void Can_Get_Root_Dictionary_Items() - { - var rootItems = ServiceContext.LocalizationService.GetRootDictionaryItems(); - - Assert.NotNull(rootItems); - Assert.IsTrue(rootItems.Any()); - } - - [Test] - public void Can_Determint_If_DictionaryItem_Exists() - { - var exists = ServiceContext.LocalizationService.DictionaryItemExists("Parent"); - Assert.IsTrue(exists); - } - - [Test] - public void Can_Get_All_Languages() - { - var languages = ServiceContext.LocalizationService.GetAllLanguages(); - Assert.NotNull(languages); - Assert.IsTrue(languages.Any()); - Assert.That(languages.Count(), Is.EqualTo(3)); - } - - [Test] - public void Can_Get_Dictionary_Item_By_Int_Id() - { - var parentItem = ServiceContext.LocalizationService.GetDictionaryItemById(_parentItemIntId); - Assert.NotNull(parentItem); - - var childItem = ServiceContext.LocalizationService.GetDictionaryItemById(_childItemIntId); - Assert.NotNull(childItem); - } - - [Test] - public void Can_Get_Dictionary_Item_By_Guid_Id() - { - var parentItem = ServiceContext.LocalizationService.GetDictionaryItemById(_parentItemGuidId); - Assert.NotNull(parentItem); - - var childItem = ServiceContext.LocalizationService.GetDictionaryItemById(_childItemGuidId); - Assert.NotNull(childItem); - } - - [Test] - public void Can_Get_Dictionary_Item_By_Key() - { - var parentItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Parent"); - Assert.NotNull(parentItem); - - var childItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); - Assert.NotNull(childItem); - } - - [Test] - public void Can_Get_Dictionary_Item_Children() - { - var item = ServiceContext.LocalizationService.GetDictionaryItemChildren(_parentItemGuidId); - Assert.NotNull(item); - Assert.That(item.Count(), Is.EqualTo(1)); - - foreach (var dictionaryItem in item) - { - Assert.AreEqual(_parentItemGuidId, dictionaryItem.ParentId); - Assert.IsFalse(string.IsNullOrEmpty(dictionaryItem.ItemKey)); - } - } - - [Test] - public void Can_Get_Dictionary_Item_Descendants() - { - using (var scope = ScopeProvider.CreateScope()) - { - var en = ServiceContext.LocalizationService.GetLanguageById(_englishLangId); - var dk = ServiceContext.LocalizationService.GetLanguageById(_danishLangId); - - var currParentId = _childItemGuidId; - for (int i = 0; i < 25; i++) - { - //Create 2 per level - var desc1 = new DictionaryItem(currParentId, "D1" + i) - { - Translations = new List - { - new DictionaryTranslation(en, "ChildValue1 " + i), - new DictionaryTranslation(dk, "BørnVærdi1 " + i) - } - }; - var desc2 = new DictionaryItem(currParentId, "D2" + i) - { - Translations = new List - { - new DictionaryTranslation(en, "ChildValue2 " + i), - new DictionaryTranslation(dk, "BørnVærdi2 " + i) - } - }; - ServiceContext.LocalizationService.Save(desc1); - ServiceContext.LocalizationService.Save(desc2); - - currParentId = desc1.Key; - } - - scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - scope.Database.AsUmbracoDatabase().EnableSqlCount = true; - - var items = ServiceContext.LocalizationService.GetDictionaryItemDescendants(_parentItemGuidId) - .ToArray(); - - Debug.WriteLine("SQL CALLS: " + scope.Database.AsUmbracoDatabase().SqlCount); - - Assert.AreEqual(51, items.Length); - //there's a call or two to get languages, so apart from that there should only be one call per level - Assert.Less(scope.Database.AsUmbracoDatabase().SqlCount, 30); - } +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing; +using Umbraco.Core.Persistence; + +namespace Umbraco.Tests.Services +{ + /// + /// Tests covering all methods in the LocalizationService class. + /// This is more of an integration test as it involves multiple layers + /// as well as configuration. + /// + [TestFixture] + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class LocalizationServiceTests : TestWithSomeContentBase + { + private Guid _parentItemGuidId; + private int _parentItemIntId; + private Guid _childItemGuidId; + private int _childItemIntId; + private int _danishLangId; + private int _englishLangId; + + [Test] + public void Can_Get_Root_Dictionary_Items() + { + var rootItems = ServiceContext.LocalizationService.GetRootDictionaryItems(); + + Assert.NotNull(rootItems); + Assert.IsTrue(rootItems.Any()); } - - [Test] - public void Can_GetLanguageById() - { - var danish = ServiceContext.LocalizationService.GetLanguageById(_danishLangId); - var english = ServiceContext.LocalizationService.GetLanguageById(_englishLangId); - Assert.NotNull(danish); - Assert.NotNull(english); - } - - [Test] - public void Can_GetLanguageByIsoCode() - { - var danish = ServiceContext.LocalizationService.GetLanguageByIsoCode("da-DK"); - var english = ServiceContext.LocalizationService.GetLanguageByIsoCode("en-GB"); - Assert.NotNull(danish); - Assert.NotNull(english); - } - - [Test] - public void Does_Not_Fail_When_Language_Doesnt_Exist() - { - var language = ServiceContext.LocalizationService.GetLanguageByIsoCode("sv-SE"); - Assert.Null(language); - } - - [Test] - public void Does_Not_Fail_When_DictionaryItem_Doesnt_Exist() - { - var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("RandomKey"); - Assert.Null(item); - } - - [Test] - public void Can_Delete_Language() - { - var norwegian = new Language("nb-NO") { CultureName = "Norwegian" }; - ServiceContext.LocalizationService.Save(norwegian, 0); - Assert.That(norwegian.HasIdentity, Is.True); - var languageId = norwegian.Id; - - ServiceContext.LocalizationService.Delete(norwegian); - - var language = ServiceContext.LocalizationService.GetLanguageById(languageId); - Assert.Null(language); - } - - [Test] - public void Can_Create_DictionaryItem_At_Root() - { - var english = ServiceContext.LocalizationService.GetLanguageByIsoCode("en-US"); - - var item = (IDictionaryItem)new DictionaryItem("Testing123") - { - Translations = new List - { - new DictionaryTranslation(english, "Hello world") - } - }; - ServiceContext.LocalizationService.Save(item); - - //re-get - item = ServiceContext.LocalizationService.GetDictionaryItemById(item.Id); - - Assert.Greater(item.Id, 0); - Assert.IsTrue(item.HasIdentity); - Assert.IsFalse(item.ParentId.HasValue); - Assert.AreEqual("Testing123", item.ItemKey); - Assert.AreEqual(1, item.Translations.Count()); - } - - [Test] - public void Can_Create_DictionaryItem_At_Root_With_Identity() - { - - var item = ServiceContext.LocalizationService.CreateDictionaryItemWithIdentity( - "Testing12345", null, "Hellooooo"); - - //re-get - item = ServiceContext.LocalizationService.GetDictionaryItemById(item.Id); - - Assert.IsNotNull(item); - Assert.Greater(item.Id, 0); - Assert.IsTrue(item.HasIdentity); - Assert.IsFalse(item.ParentId.HasValue); - Assert.AreEqual("Testing12345", item.ItemKey); - var allLangs = ServiceContext.LocalizationService.GetAllLanguages(); - Assert.Greater(allLangs.Count(), 0); - foreach (var language in allLangs) - { - Assert.AreEqual("Hellooooo", item.Translations.Single(x => x.Language.CultureName == language.CultureName).Value); - } - - } - - [Test] - public void Can_Add_Translation_To_Existing_Dictionary_Item() - { - var english = ServiceContext.LocalizationService.GetLanguageByIsoCode("en-US"); - - var item = (IDictionaryItem) new DictionaryItem("Testing123"); - ServiceContext.LocalizationService.Save(item); - - //re-get - item = ServiceContext.LocalizationService.GetDictionaryItemById(item.Id); - - item.Translations = new List - { - new DictionaryTranslation(english, "Hello world") - }; - - ServiceContext.LocalizationService.Save(item); - - Assert.AreEqual(1, item.Translations.Count()); - foreach (var translation in item.Translations) - { - Assert.AreEqual("Hello world", translation.Value); - } - - item.Translations = new List(item.Translations) - { - new DictionaryTranslation( - ServiceContext.LocalizationService.GetLanguageByIsoCode("en-GB"), - "My new value") - }; - - ServiceContext.LocalizationService.Save(item); - - //re-get - item = ServiceContext.LocalizationService.GetDictionaryItemById(item.Id); - - Assert.AreEqual(2, item.Translations.Count()); - Assert.AreEqual("Hello world", item.Translations.First().Value); - Assert.AreEqual("My new value", item.Translations.Last().Value); - } - - [Test] - public void Can_Delete_DictionaryItem() - { - var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); - Assert.NotNull(item); - - ServiceContext.LocalizationService.Delete(item); - - var deletedItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); - Assert.Null(deletedItem); - } - - [Test] - public void Can_Update_Existing_DictionaryItem() - { - var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); - foreach (var translation in item.Translations) - { - translation.Value = translation.Value + "UPDATED"; - } - - ServiceContext.LocalizationService.Save(item); - - var updatedItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); - Assert.NotNull(updatedItem); - - foreach (var translation in updatedItem.Translations) - { - Assert.That(translation.Value.EndsWith("UPDATED"), Is.True); - } - } - - [Test] - public void Find_BaseData_Language() - { - // Arrange - var localizationService = ServiceContext.LocalizationService; - - // Act - var languages = localizationService.GetAllLanguages(); - - // Assert - Assert.That(3, Is.EqualTo(languages.Count())); - } - - [Test] - public void Save_Language_And_GetLanguageByIsoCode() - { - // Arrange - var localizationService = ServiceContext.LocalizationService; - var isoCode = "en-AU"; - var language = new Core.Models.Language(isoCode); - - // Act - localizationService.Save(language); - var result = localizationService.GetLanguageByIsoCode(isoCode); - - // Assert - Assert.NotNull(result); - } - - [Test] - public void Save_Language_And_GetLanguageById() - { - var localizationService = ServiceContext.LocalizationService; - var isoCode = "en-AU"; - var language = new Core.Models.Language(isoCode); - - // Act - localizationService.Save(language); - var result = localizationService.GetLanguageById(language.Id); - - // Assert - Assert.NotNull(result); - } - - [Test] - public void Set_Default_Language() - { - var localizationService = ServiceContext.LocalizationService; - var language = new Core.Models.Language("en-AU"); - language.IsDefaultVariantLanguage = true; - localizationService.Save(language); - var result = localizationService.GetLanguageById(language.Id); - - Assert.IsTrue(result.IsDefaultVariantLanguage); - - var language2 = new Core.Models.Language("en-NZ"); - language2.IsDefaultVariantLanguage = true; - localizationService.Save(language2); - var result2 = localizationService.GetLanguageById(language2.Id); - //re-get - result = localizationService.GetLanguageById(language.Id); - - Assert.IsTrue(result2.IsDefaultVariantLanguage); - Assert.IsFalse(result.IsDefaultVariantLanguage); - } - - [Test] - public void Deleted_Language_Should_Not_Exist() - { - var localizationService = ServiceContext.LocalizationService; - var isoCode = "en-AU"; - var language = new Core.Models.Language(isoCode); - localizationService.Save(language); - - // Act - localizationService.Delete(language); - var result = localizationService.GetLanguageByIsoCode(isoCode); - - // Assert - Assert.Null(result); - } - - public override void CreateTestData() - { - var danish = new Language("da-DK") { CultureName = "Danish" }; - var english = new Language("en-GB") { CultureName = "English" }; - ServiceContext.LocalizationService.Save(danish, 0); - ServiceContext.LocalizationService.Save(english, 0); - _danishLangId = danish.Id; - _englishLangId = english.Id; - - var parentItem = new DictionaryItem("Parent") - { - Translations = new List - { - new DictionaryTranslation(english, "ParentValue"), - new DictionaryTranslation(danish, "ForældreVærdi") - } - }; - ServiceContext.LocalizationService.Save(parentItem); - _parentItemGuidId = parentItem.Key; - _parentItemIntId = parentItem.Id; - - var childItem = new DictionaryItem(parentItem.Key, "Child") - { - Translations = new List - { - new DictionaryTranslation(english, "ChildValue"), - new DictionaryTranslation(danish, "BørnVærdi") - } - }; - ServiceContext.LocalizationService.Save(childItem); - _childItemGuidId = childItem.Key; - _childItemIntId = childItem.Id; - } - - } -} + + [Test] + public void Can_Determint_If_DictionaryItem_Exists() + { + var exists = ServiceContext.LocalizationService.DictionaryItemExists("Parent"); + Assert.IsTrue(exists); + } + + [Test] + public void Can_Get_All_Languages() + { + var languages = ServiceContext.LocalizationService.GetAllLanguages(); + Assert.NotNull(languages); + Assert.IsTrue(languages.Any()); + Assert.That(languages.Count(), Is.EqualTo(3)); + } + + [Test] + public void Can_Get_Dictionary_Item_By_Int_Id() + { + var parentItem = ServiceContext.LocalizationService.GetDictionaryItemById(_parentItemIntId); + Assert.NotNull(parentItem); + + var childItem = ServiceContext.LocalizationService.GetDictionaryItemById(_childItemIntId); + Assert.NotNull(childItem); + } + + [Test] + public void Can_Get_Dictionary_Item_By_Guid_Id() + { + var parentItem = ServiceContext.LocalizationService.GetDictionaryItemById(_parentItemGuidId); + Assert.NotNull(parentItem); + + var childItem = ServiceContext.LocalizationService.GetDictionaryItemById(_childItemGuidId); + Assert.NotNull(childItem); + } + + [Test] + public void Can_Get_Dictionary_Item_By_Key() + { + var parentItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Parent"); + Assert.NotNull(parentItem); + + var childItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); + Assert.NotNull(childItem); + } + + [Test] + public void Can_Get_Dictionary_Item_Children() + { + var item = ServiceContext.LocalizationService.GetDictionaryItemChildren(_parentItemGuidId); + Assert.NotNull(item); + Assert.That(item.Count(), Is.EqualTo(1)); + + foreach (var dictionaryItem in item) + { + Assert.AreEqual(_parentItemGuidId, dictionaryItem.ParentId); + Assert.IsFalse(string.IsNullOrEmpty(dictionaryItem.ItemKey)); + } + } + + [Test] + public void Can_Get_Dictionary_Item_Descendants() + { + using (var scope = ScopeProvider.CreateScope()) + { + var en = ServiceContext.LocalizationService.GetLanguageById(_englishLangId); + var dk = ServiceContext.LocalizationService.GetLanguageById(_danishLangId); + + var currParentId = _childItemGuidId; + for (int i = 0; i < 25; i++) + { + //Create 2 per level + var desc1 = new DictionaryItem(currParentId, "D1" + i) + { + Translations = new List + { + new DictionaryTranslation(en, "ChildValue1 " + i), + new DictionaryTranslation(dk, "BørnVærdi1 " + i) + } + }; + var desc2 = new DictionaryItem(currParentId, "D2" + i) + { + Translations = new List + { + new DictionaryTranslation(en, "ChildValue2 " + i), + new DictionaryTranslation(dk, "BørnVærdi2 " + i) + } + }; + ServiceContext.LocalizationService.Save(desc1); + ServiceContext.LocalizationService.Save(desc2); + + currParentId = desc1.Key; + } + + scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + + var items = ServiceContext.LocalizationService.GetDictionaryItemDescendants(_parentItemGuidId) + .ToArray(); + + Debug.WriteLine("SQL CALLS: " + scope.Database.AsUmbracoDatabase().SqlCount); + + Assert.AreEqual(51, items.Length); + //there's a call or two to get languages, so apart from that there should only be one call per level + Assert.Less(scope.Database.AsUmbracoDatabase().SqlCount, 30); + } + } + + [Test] + public void Can_GetLanguageById() + { + var danish = ServiceContext.LocalizationService.GetLanguageById(_danishLangId); + var english = ServiceContext.LocalizationService.GetLanguageById(_englishLangId); + Assert.NotNull(danish); + Assert.NotNull(english); + } + + [Test] + public void Can_GetLanguageByIsoCode() + { + var danish = ServiceContext.LocalizationService.GetLanguageByIsoCode("da-DK"); + var english = ServiceContext.LocalizationService.GetLanguageByIsoCode("en-GB"); + Assert.NotNull(danish); + Assert.NotNull(english); + } + + [Test] + public void Does_Not_Fail_When_Language_Doesnt_Exist() + { + var language = ServiceContext.LocalizationService.GetLanguageByIsoCode("sv-SE"); + Assert.Null(language); + } + + [Test] + public void Does_Not_Fail_When_DictionaryItem_Doesnt_Exist() + { + var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("RandomKey"); + Assert.Null(item); + } + + [Test] + public void Can_Delete_Language() + { + var norwegian = new Language("nb-NO") { CultureName = "Norwegian" }; + ServiceContext.LocalizationService.Save(norwegian, 0); + Assert.That(norwegian.HasIdentity, Is.True); + var languageId = norwegian.Id; + + ServiceContext.LocalizationService.Delete(norwegian); + + var language = ServiceContext.LocalizationService.GetLanguageById(languageId); + Assert.Null(language); + } + + [Test] + public void Can_Create_DictionaryItem_At_Root() + { + var english = ServiceContext.LocalizationService.GetLanguageByIsoCode("en-US"); + + var item = (IDictionaryItem)new DictionaryItem("Testing123") + { + Translations = new List + { + new DictionaryTranslation(english, "Hello world") + } + }; + ServiceContext.LocalizationService.Save(item); + + //re-get + item = ServiceContext.LocalizationService.GetDictionaryItemById(item.Id); + + Assert.Greater(item.Id, 0); + Assert.IsTrue(item.HasIdentity); + Assert.IsFalse(item.ParentId.HasValue); + Assert.AreEqual("Testing123", item.ItemKey); + Assert.AreEqual(1, item.Translations.Count()); + } + + [Test] + public void Can_Create_DictionaryItem_At_Root_With_Identity() + { + + var item = ServiceContext.LocalizationService.CreateDictionaryItemWithIdentity( + "Testing12345", null, "Hellooooo"); + + //re-get + item = ServiceContext.LocalizationService.GetDictionaryItemById(item.Id); + + Assert.IsNotNull(item); + Assert.Greater(item.Id, 0); + Assert.IsTrue(item.HasIdentity); + Assert.IsFalse(item.ParentId.HasValue); + Assert.AreEqual("Testing12345", item.ItemKey); + var allLangs = ServiceContext.LocalizationService.GetAllLanguages(); + Assert.Greater(allLangs.Count(), 0); + foreach (var language in allLangs) + { + Assert.AreEqual("Hellooooo", item.Translations.Single(x => x.Language.CultureName == language.CultureName).Value); + } + + } + + [Test] + public void Can_Add_Translation_To_Existing_Dictionary_Item() + { + var english = ServiceContext.LocalizationService.GetLanguageByIsoCode("en-US"); + + var item = (IDictionaryItem) new DictionaryItem("Testing123"); + ServiceContext.LocalizationService.Save(item); + + //re-get + item = ServiceContext.LocalizationService.GetDictionaryItemById(item.Id); + + item.Translations = new List + { + new DictionaryTranslation(english, "Hello world") + }; + + ServiceContext.LocalizationService.Save(item); + + Assert.AreEqual(1, item.Translations.Count()); + foreach (var translation in item.Translations) + { + Assert.AreEqual("Hello world", translation.Value); + } + + item.Translations = new List(item.Translations) + { + new DictionaryTranslation( + ServiceContext.LocalizationService.GetLanguageByIsoCode("en-GB"), + "My new value") + }; + + ServiceContext.LocalizationService.Save(item); + + //re-get + item = ServiceContext.LocalizationService.GetDictionaryItemById(item.Id); + + Assert.AreEqual(2, item.Translations.Count()); + Assert.AreEqual("Hello world", item.Translations.First().Value); + Assert.AreEqual("My new value", item.Translations.Last().Value); + } + + [Test] + public void Can_Delete_DictionaryItem() + { + var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); + Assert.NotNull(item); + + ServiceContext.LocalizationService.Delete(item); + + var deletedItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); + Assert.Null(deletedItem); + } + + [Test] + public void Can_Update_Existing_DictionaryItem() + { + var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); + foreach (var translation in item.Translations) + { + translation.Value = translation.Value + "UPDATED"; + } + + ServiceContext.LocalizationService.Save(item); + + var updatedItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); + Assert.NotNull(updatedItem); + + foreach (var translation in updatedItem.Translations) + { + Assert.That(translation.Value.EndsWith("UPDATED"), Is.True); + } + } + + [Test] + public void Find_BaseData_Language() + { + // Arrange + var localizationService = ServiceContext.LocalizationService; + + // Act + var languages = localizationService.GetAllLanguages(); + + // Assert + Assert.That(3, Is.EqualTo(languages.Count())); + } + + [Test] + public void Save_Language_And_GetLanguageByIsoCode() + { + // Arrange + var localizationService = ServiceContext.LocalizationService; + var isoCode = "en-AU"; + var language = new Core.Models.Language(isoCode); + + // Act + localizationService.Save(language); + var result = localizationService.GetLanguageByIsoCode(isoCode); + + // Assert + Assert.NotNull(result); + } + + [Test] + public void Save_Language_And_GetLanguageById() + { + var localizationService = ServiceContext.LocalizationService; + var isoCode = "en-AU"; + var language = new Core.Models.Language(isoCode); + + // Act + localizationService.Save(language); + var result = localizationService.GetLanguageById(language.Id); + + // Assert + Assert.NotNull(result); + } + + [Test] + public void Set_Default_Language() + { + var localizationService = ServiceContext.LocalizationService; + var language = new Core.Models.Language("en-AU"); + language.IsDefaultVariantLanguage = true; + localizationService.Save(language); + var result = localizationService.GetLanguageById(language.Id); + + Assert.IsTrue(result.IsDefaultVariantLanguage); + + var language2 = new Core.Models.Language("en-NZ"); + language2.IsDefaultVariantLanguage = true; + localizationService.Save(language2); + var result2 = localizationService.GetLanguageById(language2.Id); + //re-get + result = localizationService.GetLanguageById(language.Id); + + Assert.IsTrue(result2.IsDefaultVariantLanguage); + Assert.IsFalse(result.IsDefaultVariantLanguage); + } + + [Test] + public void Deleted_Language_Should_Not_Exist() + { + var localizationService = ServiceContext.LocalizationService; + var isoCode = "en-AU"; + var language = new Core.Models.Language(isoCode); + localizationService.Save(language); + + // Act + localizationService.Delete(language); + var result = localizationService.GetLanguageByIsoCode(isoCode); + + // Assert + Assert.Null(result); + } + + public override void CreateTestData() + { + var danish = new Language("da-DK") { CultureName = "Danish" }; + var english = new Language("en-GB") { CultureName = "English" }; + ServiceContext.LocalizationService.Save(danish, 0); + ServiceContext.LocalizationService.Save(english, 0); + _danishLangId = danish.Id; + _englishLangId = english.Id; + + var parentItem = new DictionaryItem("Parent") + { + Translations = new List + { + new DictionaryTranslation(english, "ParentValue"), + new DictionaryTranslation(danish, "ForældreVærdi") + } + }; + ServiceContext.LocalizationService.Save(parentItem); + _parentItemGuidId = parentItem.Key; + _parentItemIntId = parentItem.Id; + + var childItem = new DictionaryItem(parentItem.Key, "Child") + { + Translations = new List + { + new DictionaryTranslation(english, "ChildValue"), + new DictionaryTranslation(danish, "BørnVærdi") + } + }; + ServiceContext.LocalizationService.Save(childItem); + _childItemGuidId = childItem.Key; + _childItemIntId = childItem.Id; + } + + } +} diff --git a/src/Umbraco.Tests/Services/MacroServiceTests.cs b/src/Umbraco.Tests/Services/MacroServiceTests.cs index ab8d18b249..fa86f4baab 100644 --- a/src/Umbraco.Tests/Services/MacroServiceTests.cs +++ b/src/Umbraco.Tests/Services/MacroServiceTests.cs @@ -1,276 +1,276 @@ -using System; -using System.Linq; -using System.Threading; -using Moq; -using NUnit.Framework; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; - -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; -using Umbraco.Core.Scoping; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Services -{ - [TestFixture] - [Apartment(ApartmentState.STA)] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class MacroServiceTests : TestWithSomeContentBase - { - public override void CreateTestData() - { - base.CreateTestData(); - - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) - { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); - - repository.Save(new Macro("test1", "Test1", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView)); - repository.Save(new Macro("test2", "Test2", "~/views/macropartials/test2.cshtml", MacroTypes.PartialView)); - repository.Save(new Macro("test3", "Tet3", "~/views/macropartials/test3.cshtml", MacroTypes.PartialView)); - scope.Complete(); - } - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } - - [Test] - public void Can_Get_By_Alias() - { - // Arrange - var macroService = ServiceContext.MacroService; - - // Act - var macro = macroService.GetByAlias("test1"); - - //assert - Assert.IsNotNull(macro); - Assert.AreEqual("Test1", macro.Name); - } - - [Test] - public void Can_Get_All() - { - // Arrange - var macroService = ServiceContext.MacroService; - - // Act - var result = macroService.GetAll(); - - //assert - Assert.AreEqual(3, result.Count()); - } - - [Test] - public void Can_Create() - { - // Arrange - var macroService = ServiceContext.MacroService; - - // Act - var macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); - macroService.Save(macro); - - //assert - Assert.IsTrue(macro.HasIdentity); - Assert.Greater(macro.Id, 0); - Assert.AreNotEqual(Guid.Empty, macro.Key); - var result = macroService.GetById(macro.Id); - Assert.AreEqual("test", result.Alias); - Assert.AreEqual("Test", result.Name); - Assert.AreEqual("~/Views/MacroPartials/Test.cshtml", result.MacroSource); - Assert.AreEqual(1234, result.CacheDuration); - - result = macroService.GetById(macro.Key); - Assert.AreEqual("test", result.Alias); - Assert.AreEqual("Test", result.Name); - Assert.AreEqual("~/Views/MacroPartials/Test.cshtml", result.MacroSource); - Assert.AreEqual(1234, result.CacheDuration); - } - - [Test] - public void Can_Delete() - { - // Arrange - var macroService = ServiceContext.MacroService; - var macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); - macroService.Save(macro); - - // Act - macroService.Delete(macro); - - //assert - var result = macroService.GetById(macro.Id); - Assert.IsNull(result); - - result = macroService.GetById(macro.Key); - Assert.IsNull(result); - } - - [Test] - public void Can_Update() - { - // Arrange - var macroService = ServiceContext.MacroService; - IMacro macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); - macroService.Save(macro); - - // Act - var currKey = macro.Key; - macro.Name = "New name"; - macro.Alias = "NewAlias"; - macroService.Save(macro); - - - macro = macroService.GetById(macro.Id); - - //assert - Assert.AreEqual("New name", macro.Name); - Assert.AreEqual("NewAlias", macro.Alias); - Assert.AreEqual(currKey, macro.Key); - - } - - [Test] - public void Can_Update_Property() - { - // Arrange - var macroService = ServiceContext.MacroService; - IMacro macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); - macro.Properties.Add(new MacroProperty("blah", "Blah", 0, "blah")); - macroService.Save(macro); - - Assert.AreNotEqual(Guid.Empty, macro.Properties[0].Key); - - // Act - var currPropKey = macro.Properties[0].Key; - macro.Properties[0].Alias = "new Alias"; - macro.Properties[0].Name = "new Name"; - macro.Properties[0].SortOrder = 1; - macro.Properties[0].EditorAlias = "new"; - macroService.Save(macro); - - macro = macroService.GetById(macro.Id); - - //assert - Assert.AreEqual(1, macro.Properties.Count); - Assert.AreEqual(currPropKey, macro.Properties[0].Key); - Assert.AreEqual("new Alias", macro.Properties[0].Alias); - Assert.AreEqual("new Name", macro.Properties[0].Name); - Assert.AreEqual(1, macro.Properties[0].SortOrder); - Assert.AreEqual("new", macro.Properties[0].EditorAlias); - Assert.AreEqual(currPropKey, macro.Properties[0].Key); - } - - [Test] - public void Can_Update_Remove_Property() - { - // Arrange - var macroService = ServiceContext.MacroService; - IMacro macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); - macro.Properties.Add(new MacroProperty("blah1", "Blah1", 0, "blah1")); - macro.Properties.Add(new MacroProperty("blah2", "Blah2", 1, "blah2")); - macro.Properties.Add(new MacroProperty("blah3", "Blah3", 2, "blah3")); - macroService.Save(macro); - - var lastKey = macro.Properties[0].Key; - for (var i = 1; i < macro.Properties.Count; i++) - { - Assert.AreNotEqual(Guid.Empty, macro.Properties[i].Key); - Assert.AreNotEqual(lastKey, macro.Properties[i].Key); - lastKey = macro.Properties[i].Key; - } - - // Act - macro.Properties["blah1"].Alias = "newAlias"; - macro.Properties["blah1"].Name = "new Name"; - macro.Properties["blah1"].SortOrder = 1; - macro.Properties["blah1"].EditorAlias = "new"; - macro.Properties.Remove("blah3"); - - var allPropKeys = macro.Properties.Select(x => new { x.Alias, x.Key }).ToArray(); - - macroService.Save(macro); - - macro = macroService.GetById(macro.Id); - - //assert - Assert.AreEqual(2, macro.Properties.Count); - Assert.AreEqual("newAlias", macro.Properties["newAlias"].Alias); - Assert.AreEqual("new Name", macro.Properties["newAlias"].Name); - Assert.AreEqual(1, macro.Properties["newAlias"].SortOrder); - Assert.AreEqual("new", macro.Properties["newAlias"].EditorAlias); - foreach (var propKey in allPropKeys) - { - Assert.AreEqual(propKey.Key, macro.Properties[propKey.Alias].Key); - } - - } - - [Test] - public void Can_Add_And_Remove_Properties() - { - var macroService = ServiceContext.MacroService; - var macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); - - //adds some properties - macro.Properties.Add(new MacroProperty("blah1", "Blah1", 0, "blah1")); - macro.Properties.Add(new MacroProperty("blah2", "Blah2", 0, "blah2")); - macro.Properties.Add(new MacroProperty("blah3", "Blah3", 0, "blah3")); - macro.Properties.Add(new MacroProperty("blah4", "Blah4", 0, "blah4")); - macroService.Save(macro); - - var result1 = macroService.GetById(macro.Id); - Assert.AreEqual(4, result1.Properties.Count()); - - //simulate clearing the sections - foreach (var s in result1.Properties.ToArray()) - { - result1.Properties.Remove(s.Alias); - } - //now just re-add a couple - result1.Properties.Add(new MacroProperty("blah3", "Blah3", 0, "blah3")); - result1.Properties.Add(new MacroProperty("blah4", "Blah4", 0, "blah4")); - macroService.Save(result1); - - //assert - - //re-get - result1 = macroService.GetById(result1.Id); - Assert.AreEqual(2, result1.Properties.Count()); - - } - - [Test] - public void Cannot_Save_Macro_With_Empty_Name() - { - // Arrange - var macroService = ServiceContext.MacroService; - var macro = new Macro("test", string.Empty, "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); - - // Act & Assert - Assert.Throws(() => macroService.Save(macro)); - } - - //[Test] - //public void Can_Get_Many_By_Alias() - //{ - // // Arrange - // var macroService = ServiceContext.MacroService; - - // // Act - // var result = macroService.GetAll("test1", "test2"); - - // //assert - // Assert.AreEqual(2, result.Count()); - //} - - } -} +using System; +using System.Linq; +using System.Threading; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; + +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Scoping; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class MacroServiceTests : TestWithSomeContentBase + { + public override void CreateTestData() + { + base.CreateTestData(); + + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + + repository.Save(new Macro("test1", "Test1", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView)); + repository.Save(new Macro("test2", "Test2", "~/views/macropartials/test2.cshtml", MacroTypes.PartialView)); + repository.Save(new Macro("test3", "Tet3", "~/views/macropartials/test3.cshtml", MacroTypes.PartialView)); + scope.Complete(); + } + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + public void Can_Get_By_Alias() + { + // Arrange + var macroService = ServiceContext.MacroService; + + // Act + var macro = macroService.GetByAlias("test1"); + + //assert + Assert.IsNotNull(macro); + Assert.AreEqual("Test1", macro.Name); + } + + [Test] + public void Can_Get_All() + { + // Arrange + var macroService = ServiceContext.MacroService; + + // Act + var result = macroService.GetAll(); + + //assert + Assert.AreEqual(3, result.Count()); + } + + [Test] + public void Can_Create() + { + // Arrange + var macroService = ServiceContext.MacroService; + + // Act + var macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); + macroService.Save(macro); + + //assert + Assert.IsTrue(macro.HasIdentity); + Assert.Greater(macro.Id, 0); + Assert.AreNotEqual(Guid.Empty, macro.Key); + var result = macroService.GetById(macro.Id); + Assert.AreEqual("test", result.Alias); + Assert.AreEqual("Test", result.Name); + Assert.AreEqual("~/Views/MacroPartials/Test.cshtml", result.MacroSource); + Assert.AreEqual(1234, result.CacheDuration); + + result = macroService.GetById(macro.Key); + Assert.AreEqual("test", result.Alias); + Assert.AreEqual("Test", result.Name); + Assert.AreEqual("~/Views/MacroPartials/Test.cshtml", result.MacroSource); + Assert.AreEqual(1234, result.CacheDuration); + } + + [Test] + public void Can_Delete() + { + // Arrange + var macroService = ServiceContext.MacroService; + var macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); + macroService.Save(macro); + + // Act + macroService.Delete(macro); + + //assert + var result = macroService.GetById(macro.Id); + Assert.IsNull(result); + + result = macroService.GetById(macro.Key); + Assert.IsNull(result); + } + + [Test] + public void Can_Update() + { + // Arrange + var macroService = ServiceContext.MacroService; + IMacro macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); + macroService.Save(macro); + + // Act + var currKey = macro.Key; + macro.Name = "New name"; + macro.Alias = "NewAlias"; + macroService.Save(macro); + + + macro = macroService.GetById(macro.Id); + + //assert + Assert.AreEqual("New name", macro.Name); + Assert.AreEqual("NewAlias", macro.Alias); + Assert.AreEqual(currKey, macro.Key); + + } + + [Test] + public void Can_Update_Property() + { + // Arrange + var macroService = ServiceContext.MacroService; + IMacro macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); + macro.Properties.Add(new MacroProperty("blah", "Blah", 0, "blah")); + macroService.Save(macro); + + Assert.AreNotEqual(Guid.Empty, macro.Properties[0].Key); + + // Act + var currPropKey = macro.Properties[0].Key; + macro.Properties[0].Alias = "new Alias"; + macro.Properties[0].Name = "new Name"; + macro.Properties[0].SortOrder = 1; + macro.Properties[0].EditorAlias = "new"; + macroService.Save(macro); + + macro = macroService.GetById(macro.Id); + + //assert + Assert.AreEqual(1, macro.Properties.Count); + Assert.AreEqual(currPropKey, macro.Properties[0].Key); + Assert.AreEqual("new Alias", macro.Properties[0].Alias); + Assert.AreEqual("new Name", macro.Properties[0].Name); + Assert.AreEqual(1, macro.Properties[0].SortOrder); + Assert.AreEqual("new", macro.Properties[0].EditorAlias); + Assert.AreEqual(currPropKey, macro.Properties[0].Key); + } + + [Test] + public void Can_Update_Remove_Property() + { + // Arrange + var macroService = ServiceContext.MacroService; + IMacro macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); + macro.Properties.Add(new MacroProperty("blah1", "Blah1", 0, "blah1")); + macro.Properties.Add(new MacroProperty("blah2", "Blah2", 1, "blah2")); + macro.Properties.Add(new MacroProperty("blah3", "Blah3", 2, "blah3")); + macroService.Save(macro); + + var lastKey = macro.Properties[0].Key; + for (var i = 1; i < macro.Properties.Count; i++) + { + Assert.AreNotEqual(Guid.Empty, macro.Properties[i].Key); + Assert.AreNotEqual(lastKey, macro.Properties[i].Key); + lastKey = macro.Properties[i].Key; + } + + // Act + macro.Properties["blah1"].Alias = "newAlias"; + macro.Properties["blah1"].Name = "new Name"; + macro.Properties["blah1"].SortOrder = 1; + macro.Properties["blah1"].EditorAlias = "new"; + macro.Properties.Remove("blah3"); + + var allPropKeys = macro.Properties.Select(x => new { x.Alias, x.Key }).ToArray(); + + macroService.Save(macro); + + macro = macroService.GetById(macro.Id); + + //assert + Assert.AreEqual(2, macro.Properties.Count); + Assert.AreEqual("newAlias", macro.Properties["newAlias"].Alias); + Assert.AreEqual("new Name", macro.Properties["newAlias"].Name); + Assert.AreEqual(1, macro.Properties["newAlias"].SortOrder); + Assert.AreEqual("new", macro.Properties["newAlias"].EditorAlias); + foreach (var propKey in allPropKeys) + { + Assert.AreEqual(propKey.Key, macro.Properties[propKey.Alias].Key); + } + + } + + [Test] + public void Can_Add_And_Remove_Properties() + { + var macroService = ServiceContext.MacroService; + var macro = new Macro("test", "Test", "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); + + //adds some properties + macro.Properties.Add(new MacroProperty("blah1", "Blah1", 0, "blah1")); + macro.Properties.Add(new MacroProperty("blah2", "Blah2", 0, "blah2")); + macro.Properties.Add(new MacroProperty("blah3", "Blah3", 0, "blah3")); + macro.Properties.Add(new MacroProperty("blah4", "Blah4", 0, "blah4")); + macroService.Save(macro); + + var result1 = macroService.GetById(macro.Id); + Assert.AreEqual(4, result1.Properties.Count()); + + //simulate clearing the sections + foreach (var s in result1.Properties.ToArray()) + { + result1.Properties.Remove(s.Alias); + } + //now just re-add a couple + result1.Properties.Add(new MacroProperty("blah3", "Blah3", 0, "blah3")); + result1.Properties.Add(new MacroProperty("blah4", "Blah4", 0, "blah4")); + macroService.Save(result1); + + //assert + + //re-get + result1 = macroService.GetById(result1.Id); + Assert.AreEqual(2, result1.Properties.Count()); + + } + + [Test] + public void Cannot_Save_Macro_With_Empty_Name() + { + // Arrange + var macroService = ServiceContext.MacroService; + var macro = new Macro("test", string.Empty, "~/Views/MacroPartials/Test.cshtml", MacroTypes.PartialView, cacheDuration: 1234); + + // Act & Assert + Assert.Throws(() => macroService.Save(macro)); + } + + //[Test] + //public void Can_Get_Many_By_Alias() + //{ + // // Arrange + // var macroService = ServiceContext.MacroService; + + // // Act + // var result = macroService.GetAll("test1", "test2"); + + // //assert + // Assert.AreEqual(2, result.Count()); + //} + + } +} diff --git a/src/Umbraco.Tests/Services/PackagingServiceTests.cs b/src/Umbraco.Tests/Services/PackagingServiceTests.cs index 5a7d7320e1..829365ae7f 100644 --- a/src/Umbraco.Tests/Services/PackagingServiceTests.cs +++ b/src/Umbraco.Tests/Services/PackagingServiceTests.cs @@ -1,152 +1,152 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using NUnit.Framework; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Packaging; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; -using Umbraco.Tests.Services.Importing; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Services -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class PackagingServiceTests : TestWithSomeContentBase - { - [Test] - public void PackagingService_Can_Export_Macro() - { - // Arrange - var macro = new Macro("test1", "Test", "~/views/macropartials/test.cshtml", MacroTypes.PartialView); - ServiceContext.MacroService.Save(macro); - - // Act - var element = ServiceContext.PackagingService.Export(macro); - - // Assert - Assert.That(element, Is.Not.Null); - Assert.That(element.Element("name").Value, Is.EqualTo("Test")); - Assert.That(element.Element("alias").Value, Is.EqualTo("test1")); - Debug.Print(element.ToString()); - } - - [Test] - public void PackagingService_Can_Export_DictionaryItems() - { - // Arrange - CreateDictionaryData(); - var dictionaryItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Parent"); - - var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); - var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); - - // Act - var xml = ServiceContext.PackagingService.Export(new []{dictionaryItem}); - - // Assert - Assert.That(xml.ToString(), Is.EqualTo(dictionaryItemsElement.ToString())); - } - - [Test] - public void PackagingService_Can_Export_Languages() - { - // Arrange - var languageNbNo = new Language("nb-NO") { CultureName = "Norwegian" }; - ServiceContext.LocalizationService.Save(languageNbNo); - - var languageEnGb = new Language("en-GB") { CultureName = "English (United Kingdom)" }; - ServiceContext.LocalizationService.Save(languageEnGb); - - var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); - var languageItemsElement = newPackageXml.Elements("Languages").First(); - - // Act - var xml = ServiceContext.PackagingService.Export(new[] { languageNbNo, languageEnGb }); - - // Assert - Assert.That(xml.ToString(), Is.EqualTo(languageItemsElement.ToString())); - } - - private static string GetTestPackagePath(string packageName) - { - const string testPackagesDirName = "Packaging\\Packages"; - string path = Path.Combine(Core.Configuration.GlobalSettings.FullPathToRoot, testPackagesDirName, packageName); - return path; - } - - - [Test] - public void PackagingService_Can_ImportPackage() - { - var packagingService = (PackagingService)ServiceContext.PackagingService; - - const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; - - string testPackagePath = GetTestPackagePath(documentTypePickerUmb); - - InstallationSummary installationSummary = packagingService.InstallPackage(testPackagePath); - - Assert.IsNotNull(installationSummary); - } - - - [Test] - public void PackagingService_Can_GetPackageMetaData() - { - var packagingService = (PackagingService)ServiceContext.PackagingService; - - const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; - - string testPackagePath = GetTestPackagePath(documentTypePickerUmb); - - MetaData packageMetaData = packagingService.GetPackageMetaData(testPackagePath); - Assert.IsNotNull(packageMetaData); - } - - [Test] - public void PackagingService_Can_GetPackageWarnings() - { - var packagingService = (PackagingService)ServiceContext.PackagingService; - - const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; - - string testPackagePath = GetTestPackagePath(documentTypePickerUmb); - - PreInstallWarnings preInstallWarnings = packagingService.GetPackageWarnings(testPackagePath); - Assert.IsNotNull(preInstallWarnings); - } - - private void CreateDictionaryData() - { - var languageNbNo = new Language("nb-NO") { CultureName = "nb-NO" }; - ServiceContext.LocalizationService.Save(languageNbNo); - - var languageEnGb = new Language("en-GB") { CultureName = "en-GB" }; - ServiceContext.LocalizationService.Save(languageEnGb); - - var parentItem = new DictionaryItem("Parent"); - var parentTranslations = new List - { - new DictionaryTranslation(languageNbNo, "ForelderVerdi"), - new DictionaryTranslation(languageEnGb, "ParentValue") - }; - parentItem.Translations = parentTranslations; - ServiceContext.LocalizationService.Save(parentItem); - - var childItem = new DictionaryItem(parentItem.Key, "Child"); - var childTranslations = new List - { - new DictionaryTranslation(languageNbNo, "BarnVerdi"), - new DictionaryTranslation(languageEnGb, "ChildValue") - }; - childItem.Translations = childTranslations; - ServiceContext.LocalizationService.Save(childItem); - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Packaging; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Tests.Services.Importing; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class PackagingServiceTests : TestWithSomeContentBase + { + [Test] + public void PackagingService_Can_Export_Macro() + { + // Arrange + var macro = new Macro("test1", "Test", "~/views/macropartials/test.cshtml", MacroTypes.PartialView); + ServiceContext.MacroService.Save(macro); + + // Act + var element = ServiceContext.PackagingService.Export(macro); + + // Assert + Assert.That(element, Is.Not.Null); + Assert.That(element.Element("name").Value, Is.EqualTo("Test")); + Assert.That(element.Element("alias").Value, Is.EqualTo("test1")); + Debug.Print(element.ToString()); + } + + [Test] + public void PackagingService_Can_Export_DictionaryItems() + { + // Arrange + CreateDictionaryData(); + var dictionaryItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Parent"); + + var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); + var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); + + // Act + var xml = ServiceContext.PackagingService.Export(new []{dictionaryItem}); + + // Assert + Assert.That(xml.ToString(), Is.EqualTo(dictionaryItemsElement.ToString())); + } + + [Test] + public void PackagingService_Can_Export_Languages() + { + // Arrange + var languageNbNo = new Language("nb-NO") { CultureName = "Norwegian" }; + ServiceContext.LocalizationService.Save(languageNbNo); + + var languageEnGb = new Language("en-GB") { CultureName = "English (United Kingdom)" }; + ServiceContext.LocalizationService.Save(languageEnGb); + + var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); + var languageItemsElement = newPackageXml.Elements("Languages").First(); + + // Act + var xml = ServiceContext.PackagingService.Export(new[] { languageNbNo, languageEnGb }); + + // Assert + Assert.That(xml.ToString(), Is.EqualTo(languageItemsElement.ToString())); + } + + private static string GetTestPackagePath(string packageName) + { + const string testPackagesDirName = "Packaging\\Packages"; + string path = Path.Combine(Core.Configuration.GlobalSettings.FullPathToRoot, testPackagesDirName, packageName); + return path; + } + + + [Test] + public void PackagingService_Can_ImportPackage() + { + var packagingService = (PackagingService)ServiceContext.PackagingService; + + const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; + + string testPackagePath = GetTestPackagePath(documentTypePickerUmb); + + InstallationSummary installationSummary = packagingService.InstallPackage(testPackagePath); + + Assert.IsNotNull(installationSummary); + } + + + [Test] + public void PackagingService_Can_GetPackageMetaData() + { + var packagingService = (PackagingService)ServiceContext.PackagingService; + + const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; + + string testPackagePath = GetTestPackagePath(documentTypePickerUmb); + + MetaData packageMetaData = packagingService.GetPackageMetaData(testPackagePath); + Assert.IsNotNull(packageMetaData); + } + + [Test] + public void PackagingService_Can_GetPackageWarnings() + { + var packagingService = (PackagingService)ServiceContext.PackagingService; + + const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; + + string testPackagePath = GetTestPackagePath(documentTypePickerUmb); + + PreInstallWarnings preInstallWarnings = packagingService.GetPackageWarnings(testPackagePath); + Assert.IsNotNull(preInstallWarnings); + } + + private void CreateDictionaryData() + { + var languageNbNo = new Language("nb-NO") { CultureName = "nb-NO" }; + ServiceContext.LocalizationService.Save(languageNbNo); + + var languageEnGb = new Language("en-GB") { CultureName = "en-GB" }; + ServiceContext.LocalizationService.Save(languageEnGb); + + var parentItem = new DictionaryItem("Parent"); + var parentTranslations = new List + { + new DictionaryTranslation(languageNbNo, "ForelderVerdi"), + new DictionaryTranslation(languageEnGb, "ParentValue") + }; + parentItem.Translations = parentTranslations; + ServiceContext.LocalizationService.Save(parentItem); + + var childItem = new DictionaryItem(parentItem.Key, "Child"); + var childTranslations = new List + { + new DictionaryTranslation(languageNbNo, "BarnVerdi"), + new DictionaryTranslation(languageEnGb, "ChildValue") + }; + childItem.Translations = childTranslations; + ServiceContext.LocalizationService.Save(childItem); + } + } +} diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index 090198330b..c0daaa8fb9 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -1,366 +1,366 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using NPoco; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; -using Umbraco.Tests.TestHelpers.Stubs; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Services -{ - /// - /// Tests covering all methods in the ContentService class. - /// This is more of an integration test as it involves multiple layers - /// as well as configuration. - /// - [TestFixture] - [Apartment(ApartmentState.STA)] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - [NUnit.Framework.Ignore("These should not be run by the server, only directly as they are only benchmark tests")] - public class PerformanceTests : TestWithDatabaseBase - { - // fixme probably making little sense in places due to scope creating a transaction?! - - protected override string GetDbConnectionString() - { - return @"server=.\SQLEXPRESS;database=UmbTest;user id=sa;password=test"; - } - - protected override string GetDbProviderName() - { - return Constants.DbProviderNames.SqlServer; - } - - - /// - /// don't create anything, we're testing against our own server - /// - protected override void CreateSqlCeDatabase() - { - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } - - private static ProfilingLogger GetTestProfilingLogger() - { - var logger = new DebugDiagnosticsLogger(); - var profiler = new TestProfiler(); - return new ProfilingLogger(logger, profiler); - } - - [Test] - public void Get_All_Published_Content() - { - var result = PrimeDbWithLotsOfContent(); - var contentSvc = (ContentService) ServiceContext.ContentService; - - var countOfPublished = result.Count(x => x.Published); - var contentTypeId = result.First().ContentTypeId; - - var proflog = GetTestProfilingLogger(); - using (proflog.DebugDuration("Getting published content normally")) - { - //do this 10x! - for (var i = 0; i < 10; i++) - { - - var published = new List(); - //get all content items that are published - var rootContent = contentSvc.GetRootContent(); - foreach (var content in rootContent.Where(content => content.Published)) - { - published.Add(content); - published.AddRange(contentSvc.GetPublishedDescendants(content)); - } - Assert.AreEqual(countOfPublished, published.Count(x => x.ContentTypeId == contentTypeId)); - } - - } - - using (proflog.DebugDuration("Getting published content optimized")) - { - - //do this 10x! - for (var i = 0; i < 10; i++) - { - - //get all content items that are published - var published = contentSvc.GetAllPublished(); - - Assert.AreEqual(countOfPublished, published.Count(x => x.ContentTypeId == contentTypeId)); - } - } - } - - [Test] - public void Get_All_Published_Content_Of_Type() - { - var result = PrimeDbWithLotsOfContent(); - var contentSvc = (ContentService)ServiceContext.ContentService; - - var countOfPublished = result.Count(x => x.Published); - var contentTypeId = result.First().ContentTypeId; - - var proflog = GetTestProfilingLogger(); - using (proflog.DebugDuration("Getting published content of type normally")) - { - //do this 10x! - for (var i = 0; i < 10; i++) - { - - //get all content items that are published of this type - var published = contentSvc.GetByType(contentTypeId).Where(content => content.Published); - Assert.AreEqual(countOfPublished, published.Count(x => x.ContentTypeId == contentTypeId)); - } - } - - using (proflog.DebugDuration("Getting published content of type optimized")) - { - - //do this 10x! - for (var i = 0; i < 10; i++) - { - //get all content items that are published of this type - var published = contentSvc.GetPublishedContentOfContentType(contentTypeId); - Assert.AreEqual(countOfPublished, published.Count(x => x.ContentTypeId == contentTypeId)); - } - } - } - - [Test] - public void Truncate_Insert_Vs_Update_Insert() - { - var customObjectType = Guid.NewGuid(); - //chuck lots of data in the db - var nodes = PrimeDbWithLotsOfContentXmlRecords(customObjectType); - var proflog = GetTestProfilingLogger(); - - //now we need to test the difference between truncating all records and re-inserting them as we do now, - //vs updating them (which might result in checking if they exist for or listening on an exception). - using (proflog.DebugDuration("Starting truncate + normal insert test")) - using (var scope = ScopeProvider.CreateScope()) - { - //do this 10x! - for (var i = 0; i < 10; i++) - { - //clear all the xml entries - scope.Database.Execute(@"DELETE FROM cmsContentXml WHERE nodeId IN - (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml - INNER JOIN cmsContent ON cmsContentXml.nodeId = cmsContent.nodeId)"); - - //now we insert each record for the ones we've deleted like we do in the content service. - var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = UpdatedXmlStructure }).ToList(); - foreach (var xml in xmlItems) - { - var result = scope.Database.Insert(xml); - } - } - - scope.Complete(); - } - - //now, isntead of truncating, we'll attempt to update and if it doesn't work then we insert - using (proflog.DebugDuration("Starting update test")) - using (var scope = ScopeProvider.CreateScope()) - { - //do this 10x! - for (var i = 0; i < 10; i++) - { - //now we insert each record for the ones we've deleted like we do in the content service. - var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = UpdatedXmlStructure }).ToList(); - foreach (var xml in xmlItems) - { - var result = scope.Database.Update(xml); - } - } - - scope.Complete(); - } - - //now, test truncating but then do bulk insertion of records - using (proflog.DebugDuration("Starting truncate + bulk insert test")) - using (var scope = ScopeProvider.CreateScope()) - { - //do this 10x! - for (var i = 0; i < 10; i++) - { - //clear all the xml entries - scope.Database.Execute(@"DELETE FROM cmsContentXml WHERE nodeId IN - (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml - INNER JOIN cmsContent ON cmsContentXml.nodeId = cmsContent.nodeId)"); - - //now we insert each record for the ones we've deleted like we do in the content service. - var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = UpdatedXmlStructure }).ToList(); - scope.Database.BulkInsertRecordsWithTransaction(xmlItems); - } - } - - //now, test truncating but then do bulk insertion of records - using (proflog.DebugDuration("Starting truncate + bulk insert test in one transaction")) - using (var scope = ScopeProvider.CreateScope()) - { - //do this 10x! - for (var i = 0; i < 10; i++) - { - //now we insert each record for the ones we've deleted like we do in the content service. - var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = UpdatedXmlStructure }).ToList(); - - using (var tr = scope.Database.GetTransaction()) - { - //clear all the xml entries - scope.Database.Execute(@"DELETE FROM cmsContentXml WHERE nodeId IN - (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml - INNER JOIN cmsContent ON cmsContentXml.nodeId = cmsContent.nodeId)"); - - - scope.Database.BulkInsertRecords(xmlItems); - - tr.Complete(); - } - } - } - } - - private IEnumerable PrimeDbWithLotsOfContent() - { - var contentType1 = MockedContentTypes.CreateSimpleContentType(); - contentType1.AllowedAsRoot = true; - ServiceContext.ContentTypeService.Save(contentType1); - contentType1.AllowedContentTypes = new List - { - new ContentTypeSort(new Lazy(() => contentType1.Id), 0, contentType1.Alias) - }; - var result = new List(); - ServiceContext.ContentTypeService.Save(contentType1); - IContent lastParent = MockedContent.CreateSimpleContent(contentType1); - lastParent.TryPublishValues(); - ServiceContext.ContentService.SaveAndPublish(lastParent); - result.Add(lastParent); - //create 20 deep - for (var i = 0; i < 20; i++) - { - //for each level, create 20 - IContent content = null; - for (var j = 1; j <= 10; j++) - { - content = MockedContent.CreateSimpleContent(contentType1, "Name" + j, lastParent); - //only publish evens - if (j % 2 == 0) - { - content.TryPublishValues(); - ServiceContext.ContentService.SaveAndPublish(content); - } - else - { - ServiceContext.ContentService.Save(content); - } - result.Add(content); - } - - //assign the last one as the next parent - lastParent = content; - } - return result; - } - - private IEnumerable PrimeDbWithLotsOfContentXmlRecords(Guid customObjectType) - { - var nodes = new List(); - for (var i = 1; i < 10000; i++) - { - nodes.Add(new NodeDto - { - Level = 1, - ParentId = -1, - NodeObjectType = customObjectType, - Text = i.ToString(CultureInfo.InvariantCulture), - UserId = -1, - CreateDate = DateTime.Now, - Trashed = false, - SortOrder = 0, - Path = "" - }); - } - - using (var scope = ScopeProvider.CreateScope()) - { - scope.Database.BulkInsertRecordsWithTransaction(nodes); - - //re-get the nodes with ids - var sql = Current.SqlContext.Sql(); - sql.SelectAll().From().Where(x => x.NodeObjectType == customObjectType); - nodes = scope.Database.Fetch(sql); - - //create the cmsContent data, each with a new content type id (so we can query on it later if needed) - var contentTypeId = 0; - var cmsContentItems = nodes.Select(node => new ContentDto { NodeId = node.NodeId, ContentTypeId = contentTypeId++ }).ToList(); - scope.Database.BulkInsertRecordsWithTransaction(cmsContentItems); - - //create the xml data - var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = TestXmlStructure }).ToList(); - scope.Database.BulkInsertRecordsWithTransaction(xmlItems); - - scope.Complete(); - } - - return nodes; - } - - private const string TestXmlStructure = @" - 0 - Standard Site for Umbraco by Koiak - - - - Built by Creative Founds -

Web ApplicationsCreative Founds design and build first class software solutions that deliver big results. We provide ASP.NET web and mobile applications, Umbraco development service & technical consultancy.

-

www.creativefounds.co.uk

]]>
- Umbraco Development -

UmbracoUmbraco the the leading ASP.NET open source CMS, under pinning over 150,000 websites. Our Certified Developers are experts in developing high performance and feature rich websites.

]]>
- Contact Us -

Contact Us on TwitterWe'd love to hear how this package has helped you and how it can be improved. Get in touch on the project website or via twitter

]]>
- -
Standard Website MVC, Company Address, Glasgow, Postcode
- Copyright &copy; 2012 Your Company - http://www.umbraco.org - /media/1477/umbraco_logo.png - - - - - - - 2013-07-01T00:00:00 -
"; - - private const string UpdatedXmlStructure = @" - 0 - - - - Clients -

This is a standard content page.

-

Vestibulum malesuada aliquet ante, vitae ullamcorper felis faucibus vel. Vestibulum condimentum faucibus tellus porta ultrices. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.

-

Cras at auctor orci. Praesent facilisis erat nec odio consequat at posuere ligula pretium. Nulla eget felis id nisl volutpat pellentesque. Ut id augue id ligula placerat rutrum a nec purus. Maecenas sed lectus ac mi pellentesque luctus quis sit amet turpis. Vestibulum adipiscing convallis vestibulum.

-

Duis condimentum lectus at orci placerat vitae imperdiet lorem cursus. Duis hendrerit porta lorem, non suscipit quam consectetur vitae. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean elit augue, tincidunt nec tincidunt id, elementum vel est.

]]>
- -
"; - - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using NPoco; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Tests.TestHelpers.Stubs; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + /// + /// Tests covering all methods in the ContentService class. + /// This is more of an integration test as it involves multiple layers + /// as well as configuration. + /// + [TestFixture] + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + [NUnit.Framework.Ignore("These should not be run by the server, only directly as they are only benchmark tests")] + public class PerformanceTests : TestWithDatabaseBase + { + // fixme probably making little sense in places due to scope creating a transaction?! + + protected override string GetDbConnectionString() + { + return @"server=.\SQLEXPRESS;database=UmbTest;user id=sa;password=test"; + } + + protected override string GetDbProviderName() + { + return Constants.DbProviderNames.SqlServer; + } + + + /// + /// don't create anything, we're testing against our own server + /// + protected override void CreateSqlCeDatabase() + { + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + private static ProfilingLogger GetTestProfilingLogger() + { + var logger = new DebugDiagnosticsLogger(); + var profiler = new TestProfiler(); + return new ProfilingLogger(logger, profiler); + } + + [Test] + public void Get_All_Published_Content() + { + var result = PrimeDbWithLotsOfContent(); + var contentSvc = (ContentService) ServiceContext.ContentService; + + var countOfPublished = result.Count(x => x.Published); + var contentTypeId = result.First().ContentTypeId; + + var proflog = GetTestProfilingLogger(); + using (proflog.DebugDuration("Getting published content normally")) + { + //do this 10x! + for (var i = 0; i < 10; i++) + { + + var published = new List(); + //get all content items that are published + var rootContent = contentSvc.GetRootContent(); + foreach (var content in rootContent.Where(content => content.Published)) + { + published.Add(content); + published.AddRange(contentSvc.GetPublishedDescendants(content)); + } + Assert.AreEqual(countOfPublished, published.Count(x => x.ContentTypeId == contentTypeId)); + } + + } + + using (proflog.DebugDuration("Getting published content optimized")) + { + + //do this 10x! + for (var i = 0; i < 10; i++) + { + + //get all content items that are published + var published = contentSvc.GetAllPublished(); + + Assert.AreEqual(countOfPublished, published.Count(x => x.ContentTypeId == contentTypeId)); + } + } + } + + [Test] + public void Get_All_Published_Content_Of_Type() + { + var result = PrimeDbWithLotsOfContent(); + var contentSvc = (ContentService)ServiceContext.ContentService; + + var countOfPublished = result.Count(x => x.Published); + var contentTypeId = result.First().ContentTypeId; + + var proflog = GetTestProfilingLogger(); + using (proflog.DebugDuration("Getting published content of type normally")) + { + //do this 10x! + for (var i = 0; i < 10; i++) + { + + //get all content items that are published of this type + var published = contentSvc.GetByType(contentTypeId).Where(content => content.Published); + Assert.AreEqual(countOfPublished, published.Count(x => x.ContentTypeId == contentTypeId)); + } + } + + using (proflog.DebugDuration("Getting published content of type optimized")) + { + + //do this 10x! + for (var i = 0; i < 10; i++) + { + //get all content items that are published of this type + var published = contentSvc.GetPublishedContentOfContentType(contentTypeId); + Assert.AreEqual(countOfPublished, published.Count(x => x.ContentTypeId == contentTypeId)); + } + } + } + + [Test] + public void Truncate_Insert_Vs_Update_Insert() + { + var customObjectType = Guid.NewGuid(); + //chuck lots of data in the db + var nodes = PrimeDbWithLotsOfContentXmlRecords(customObjectType); + var proflog = GetTestProfilingLogger(); + + //now we need to test the difference between truncating all records and re-inserting them as we do now, + //vs updating them (which might result in checking if they exist for or listening on an exception). + using (proflog.DebugDuration("Starting truncate + normal insert test")) + using (var scope = ScopeProvider.CreateScope()) + { + //do this 10x! + for (var i = 0; i < 10; i++) + { + //clear all the xml entries + scope.Database.Execute(@"DELETE FROM cmsContentXml WHERE nodeId IN + (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml + INNER JOIN cmsContent ON cmsContentXml.nodeId = cmsContent.nodeId)"); + + //now we insert each record for the ones we've deleted like we do in the content service. + var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = UpdatedXmlStructure }).ToList(); + foreach (var xml in xmlItems) + { + var result = scope.Database.Insert(xml); + } + } + + scope.Complete(); + } + + //now, isntead of truncating, we'll attempt to update and if it doesn't work then we insert + using (proflog.DebugDuration("Starting update test")) + using (var scope = ScopeProvider.CreateScope()) + { + //do this 10x! + for (var i = 0; i < 10; i++) + { + //now we insert each record for the ones we've deleted like we do in the content service. + var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = UpdatedXmlStructure }).ToList(); + foreach (var xml in xmlItems) + { + var result = scope.Database.Update(xml); + } + } + + scope.Complete(); + } + + //now, test truncating but then do bulk insertion of records + using (proflog.DebugDuration("Starting truncate + bulk insert test")) + using (var scope = ScopeProvider.CreateScope()) + { + //do this 10x! + for (var i = 0; i < 10; i++) + { + //clear all the xml entries + scope.Database.Execute(@"DELETE FROM cmsContentXml WHERE nodeId IN + (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml + INNER JOIN cmsContent ON cmsContentXml.nodeId = cmsContent.nodeId)"); + + //now we insert each record for the ones we've deleted like we do in the content service. + var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = UpdatedXmlStructure }).ToList(); + scope.Database.BulkInsertRecordsWithTransaction(xmlItems); + } + } + + //now, test truncating but then do bulk insertion of records + using (proflog.DebugDuration("Starting truncate + bulk insert test in one transaction")) + using (var scope = ScopeProvider.CreateScope()) + { + //do this 10x! + for (var i = 0; i < 10; i++) + { + //now we insert each record for the ones we've deleted like we do in the content service. + var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = UpdatedXmlStructure }).ToList(); + + using (var tr = scope.Database.GetTransaction()) + { + //clear all the xml entries + scope.Database.Execute(@"DELETE FROM cmsContentXml WHERE nodeId IN + (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml + INNER JOIN cmsContent ON cmsContentXml.nodeId = cmsContent.nodeId)"); + + + scope.Database.BulkInsertRecords(xmlItems); + + tr.Complete(); + } + } + } + } + + private IEnumerable PrimeDbWithLotsOfContent() + { + var contentType1 = MockedContentTypes.CreateSimpleContentType(); + contentType1.AllowedAsRoot = true; + ServiceContext.ContentTypeService.Save(contentType1); + contentType1.AllowedContentTypes = new List + { + new ContentTypeSort(new Lazy(() => contentType1.Id), 0, contentType1.Alias) + }; + var result = new List(); + ServiceContext.ContentTypeService.Save(contentType1); + IContent lastParent = MockedContent.CreateSimpleContent(contentType1); + lastParent.TryPublishValues(); + ServiceContext.ContentService.SaveAndPublish(lastParent); + result.Add(lastParent); + //create 20 deep + for (var i = 0; i < 20; i++) + { + //for each level, create 20 + IContent content = null; + for (var j = 1; j <= 10; j++) + { + content = MockedContent.CreateSimpleContent(contentType1, "Name" + j, lastParent); + //only publish evens + if (j % 2 == 0) + { + content.TryPublishValues(); + ServiceContext.ContentService.SaveAndPublish(content); + } + else + { + ServiceContext.ContentService.Save(content); + } + result.Add(content); + } + + //assign the last one as the next parent + lastParent = content; + } + return result; + } + + private IEnumerable PrimeDbWithLotsOfContentXmlRecords(Guid customObjectType) + { + var nodes = new List(); + for (var i = 1; i < 10000; i++) + { + nodes.Add(new NodeDto + { + Level = 1, + ParentId = -1, + NodeObjectType = customObjectType, + Text = i.ToString(CultureInfo.InvariantCulture), + UserId = -1, + CreateDate = DateTime.Now, + Trashed = false, + SortOrder = 0, + Path = "" + }); + } + + using (var scope = ScopeProvider.CreateScope()) + { + scope.Database.BulkInsertRecordsWithTransaction(nodes); + + //re-get the nodes with ids + var sql = Current.SqlContext.Sql(); + sql.SelectAll().From().Where(x => x.NodeObjectType == customObjectType); + nodes = scope.Database.Fetch(sql); + + //create the cmsContent data, each with a new content type id (so we can query on it later if needed) + var contentTypeId = 0; + var cmsContentItems = nodes.Select(node => new ContentDto { NodeId = node.NodeId, ContentTypeId = contentTypeId++ }).ToList(); + scope.Database.BulkInsertRecordsWithTransaction(cmsContentItems); + + //create the xml data + var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = TestXmlStructure }).ToList(); + scope.Database.BulkInsertRecordsWithTransaction(xmlItems); + + scope.Complete(); + } + + return nodes; + } + + private const string TestXmlStructure = @" + 0 + Standard Site for Umbraco by Koiak + + + + Built by Creative Founds +

Web ApplicationsCreative Founds design and build first class software solutions that deliver big results. We provide ASP.NET web and mobile applications, Umbraco development service & technical consultancy.

+

www.creativefounds.co.uk

]]>
+ Umbraco Development +

UmbracoUmbraco the the leading ASP.NET open source CMS, under pinning over 150,000 websites. Our Certified Developers are experts in developing high performance and feature rich websites.

]]>
+ Contact Us +

Contact Us on TwitterWe'd love to hear how this package has helped you and how it can be improved. Get in touch on the project website or via twitter

]]>
+ +
Standard Website MVC, Company Address, Glasgow, Postcode
+ Copyright &copy; 2012 Your Company + http://www.umbraco.org + /media/1477/umbraco_logo.png + + + + + + + 2013-07-01T00:00:00 +
"; + + private const string UpdatedXmlStructure = @" + 0 + + + + Clients +

This is a standard content page.

+

Vestibulum malesuada aliquet ante, vitae ullamcorper felis faucibus vel. Vestibulum condimentum faucibus tellus porta ultrices. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.

+

Cras at auctor orci. Praesent facilisis erat nec odio consequat at posuere ligula pretium. Nulla eget felis id nisl volutpat pellentesque. Ut id augue id ligula placerat rutrum a nec purus. Maecenas sed lectus ac mi pellentesque luctus quis sit amet turpis. Vestibulum adipiscing convallis vestibulum.

+

Duis condimentum lectus at orci placerat vitae imperdiet lorem cursus. Duis hendrerit porta lorem, non suscipit quam consectetur vitae. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean elit augue, tincidunt nec tincidunt id, elementum vel est.

]]>
+ +
"; + + } +} diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index 984de8d182..33ee2f737a 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -1,236 +1,236 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Services -{ - // these tests tend to fail from time to time esp. on VSTS - // - // read - // Lock Time-out: https://technet.microsoft.com/en-us/library/ms172402.aspx?f=255&MSPPError=-2147217396 - // http://support.x-tensive.com/question/5242/strange-locking-exceptions-with-sqlserverce - // http://debuggingblog.com/wp/2009/05/07/high-cpu-usage-and-windows-forms-application-hang-with-sqlce-database-and-the-sqlcelocktimeoutexception/ - // - // tried to increase it via connection string or via SET LOCK_TIMEOUT - // but still, the test fails on VSTS in most cases, so now ignoring it, - // as I could not figure out _why_ and it does not look like we are - // causing it, getting into __sysObjects locks, no idea why - - [TestFixture] - [Apartment(ApartmentState.STA)] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class ThreadSafetyServiceTest : TestWithDatabaseBase - { - public override void SetUp() - { - base.SetUp(); - CreateTestData(); - } - - // not sure this is doing anything really - protected override string GetDbConnectionString() - { - // need a longer timeout for tests? - return base.GetDbConnectionString() + "default lock timeout=60000;"; - } - - private const int MaxThreadCount = 20; - - private void Save(ContentService service, IContent content) - { - using (var scope = ScopeProvider.CreateScope()) - { - scope.Database.Execute("SET LOCK_TIMEOUT 60000"); - service.Save(content); - scope.Complete(); - } - } - - private void Save(MediaService service, IMedia media) - { - using (var scope = ScopeProvider.CreateScope()) - { - scope.Database.Execute("SET LOCK_TIMEOUT 60000"); - service.Save(media); - scope.Complete(); - } - } - - private ManualResetEventSlim TraceLocks() - { - var done = new ManualResetEventSlim(false); - - // comment out to trace locks - return done; - - //new Thread(() => - //{ - // using (var scope = ScopeProvider.CreateScope()) - // while (done.IsSet == false) - // { - // var db = scope.Database; - // var info = db.Query("SELECT * FROM sys.lock_information;"); - // Console.WriteLine("LOCKS:"); - // foreach (var row in info) - // { - // Console.WriteLine("> " + row.request_spid + " " + row.resource_type + " " + row.resource_description + " " + row.request_mode + " " + row.resource_table + " " + row.resource_table_id + " " + row.request_status); - // } - // Thread.Sleep(50); - // } - //}).Start(); - //return done; - } - - [Test] - public void Ensure_All_Threads_Execute_Successfully_Content_Service() - { - if (Environment.GetEnvironmentVariable("UMBRACO_TMP") != null) - Assert.Ignore("Do not run on VSTS."); - - // the ServiceContext in that each repository in a service (i.e. ContentService) is a singleton - var contentService = (ContentService)ServiceContext.ContentService; - - var threads = new List(); - var exceptions = new List(); - - Debug.WriteLine("Starting..."); - - var done = TraceLocks(); - - for (var i = 0; i < MaxThreadCount; i++) - { - var t = new Thread(() => - { - try - { - Debug.WriteLine("[{0}] Running...", Thread.CurrentThread.ManagedThreadId); - - var name1 = "test-" + Guid.NewGuid(); - var content1 = contentService.Create(name1, -1, "umbTextpage"); - - Debug.WriteLine("[{0}] Saving content #1.", Thread.CurrentThread.ManagedThreadId); - Save(contentService, content1); - - Thread.Sleep(100); //quick pause for maximum overlap! - - var name2 = "test-" + Guid.NewGuid(); - var content2 = contentService.Create(name2, -1, "umbTextpage"); - - Debug.WriteLine("[{0}] Saving content #2.", Thread.CurrentThread.ManagedThreadId); - Save(contentService, content2); - } - catch (Exception e) - { - lock (exceptions) { exceptions.Add(e); } - } - }); - threads.Add(t); - } - - // start all threads - Debug.WriteLine("Starting threads"); - threads.ForEach(x => x.Start()); - - // wait for all to complete - Debug.WriteLine("Joining threads"); - threads.ForEach(x => x.Join()); - - done.Set(); - - Debug.WriteLine("Checking exceptions"); - if (exceptions.Count == 0) - { - //now look up all items, there should be 40! - var items = contentService.GetRootContent(); - Assert.AreEqual(2 * MaxThreadCount, items.Count()); - } - else - { - throw new Exception("Exceptions!", exceptions.First()); // rethrow the first one... - } - } - - [Test] - public void Ensure_All_Threads_Execute_Successfully_Media_Service() - { - if (Environment.GetEnvironmentVariable("UMBRACO_TMP") != null) - Assert.Ignore("Do not run on VSTS."); - // mimick the ServiceContext in that each repository in a service (i.e. ContentService) is a singleton - var mediaService = (MediaService)ServiceContext.MediaService; - - var threads = new List(); - var exceptions = new List(); - - Debug.WriteLine("Starting..."); - - var done = TraceLocks(); - - for (var i = 0; i < MaxThreadCount; i++) - { - var t = new Thread(() => - { - try - { - Debug.WriteLine("[{0}] Running...", Thread.CurrentThread.ManagedThreadId); - - var name1 = "test-" + Guid.NewGuid(); - var media1 = mediaService.CreateMedia(name1, -1, Constants.Conventions.MediaTypes.Folder); - Debug.WriteLine("[{0}] Saving media #1.", Thread.CurrentThread.ManagedThreadId); - Save(mediaService, media1); - - Thread.Sleep(100); //quick pause for maximum overlap! - - var name2 = "test-" + Guid.NewGuid(); - var media2 = mediaService.CreateMedia(name2, -1, Constants.Conventions.MediaTypes.Folder); - Debug.WriteLine("[{0}] Saving media #2.", Thread.CurrentThread.ManagedThreadId); - Save(mediaService, media2); - } - catch (Exception e) - { - lock (exceptions) { exceptions.Add(e); } - } - }); - threads.Add(t); - } - - //start all threads - threads.ForEach(x => x.Start()); - - //wait for all to complete - threads.ForEach(x => x.Join()); - - done.Set(); - - if (exceptions.Count == 0) - { - // now look up all items, there should be 40! - var items = mediaService.GetRootMedia(); - Assert.AreEqual(2 * MaxThreadCount, items.Count()); - } - else - { - throw new Exception("Exceptions!", exceptions.First()); // rethrow the first one... - } - - } - - public void CreateTestData() - { - // Create and Save ContentType "umbTextpage" -> 1045 - var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); - contentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"); - ServiceContext.ContentTypeService.Save(contentType); - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + // these tests tend to fail from time to time esp. on VSTS + // + // read + // Lock Time-out: https://technet.microsoft.com/en-us/library/ms172402.aspx?f=255&MSPPError=-2147217396 + // http://support.x-tensive.com/question/5242/strange-locking-exceptions-with-sqlserverce + // http://debuggingblog.com/wp/2009/05/07/high-cpu-usage-and-windows-forms-application-hang-with-sqlce-database-and-the-sqlcelocktimeoutexception/ + // + // tried to increase it via connection string or via SET LOCK_TIMEOUT + // but still, the test fails on VSTS in most cases, so now ignoring it, + // as I could not figure out _why_ and it does not look like we are + // causing it, getting into __sysObjects locks, no idea why + + [TestFixture] + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class ThreadSafetyServiceTest : TestWithDatabaseBase + { + public override void SetUp() + { + base.SetUp(); + CreateTestData(); + } + + // not sure this is doing anything really + protected override string GetDbConnectionString() + { + // need a longer timeout for tests? + return base.GetDbConnectionString() + "default lock timeout=60000;"; + } + + private const int MaxThreadCount = 20; + + private void Save(ContentService service, IContent content) + { + using (var scope = ScopeProvider.CreateScope()) + { + scope.Database.Execute("SET LOCK_TIMEOUT 60000"); + service.Save(content); + scope.Complete(); + } + } + + private void Save(MediaService service, IMedia media) + { + using (var scope = ScopeProvider.CreateScope()) + { + scope.Database.Execute("SET LOCK_TIMEOUT 60000"); + service.Save(media); + scope.Complete(); + } + } + + private ManualResetEventSlim TraceLocks() + { + var done = new ManualResetEventSlim(false); + + // comment out to trace locks + return done; + + //new Thread(() => + //{ + // using (var scope = ScopeProvider.CreateScope()) + // while (done.IsSet == false) + // { + // var db = scope.Database; + // var info = db.Query("SELECT * FROM sys.lock_information;"); + // Console.WriteLine("LOCKS:"); + // foreach (var row in info) + // { + // Console.WriteLine("> " + row.request_spid + " " + row.resource_type + " " + row.resource_description + " " + row.request_mode + " " + row.resource_table + " " + row.resource_table_id + " " + row.request_status); + // } + // Thread.Sleep(50); + // } + //}).Start(); + //return done; + } + + [Test] + public void Ensure_All_Threads_Execute_Successfully_Content_Service() + { + if (Environment.GetEnvironmentVariable("UMBRACO_TMP") != null) + Assert.Ignore("Do not run on VSTS."); + + // the ServiceContext in that each repository in a service (i.e. ContentService) is a singleton + var contentService = (ContentService)ServiceContext.ContentService; + + var threads = new List(); + var exceptions = new List(); + + Debug.WriteLine("Starting..."); + + var done = TraceLocks(); + + for (var i = 0; i < MaxThreadCount; i++) + { + var t = new Thread(() => + { + try + { + Debug.WriteLine("[{0}] Running...", Thread.CurrentThread.ManagedThreadId); + + var name1 = "test-" + Guid.NewGuid(); + var content1 = contentService.Create(name1, -1, "umbTextpage"); + + Debug.WriteLine("[{0}] Saving content #1.", Thread.CurrentThread.ManagedThreadId); + Save(contentService, content1); + + Thread.Sleep(100); //quick pause for maximum overlap! + + var name2 = "test-" + Guid.NewGuid(); + var content2 = contentService.Create(name2, -1, "umbTextpage"); + + Debug.WriteLine("[{0}] Saving content #2.", Thread.CurrentThread.ManagedThreadId); + Save(contentService, content2); + } + catch (Exception e) + { + lock (exceptions) { exceptions.Add(e); } + } + }); + threads.Add(t); + } + + // start all threads + Debug.WriteLine("Starting threads"); + threads.ForEach(x => x.Start()); + + // wait for all to complete + Debug.WriteLine("Joining threads"); + threads.ForEach(x => x.Join()); + + done.Set(); + + Debug.WriteLine("Checking exceptions"); + if (exceptions.Count == 0) + { + //now look up all items, there should be 40! + var items = contentService.GetRootContent(); + Assert.AreEqual(2 * MaxThreadCount, items.Count()); + } + else + { + throw new Exception("Exceptions!", exceptions.First()); // rethrow the first one... + } + } + + [Test] + public void Ensure_All_Threads_Execute_Successfully_Media_Service() + { + if (Environment.GetEnvironmentVariable("UMBRACO_TMP") != null) + Assert.Ignore("Do not run on VSTS."); + // mimick the ServiceContext in that each repository in a service (i.e. ContentService) is a singleton + var mediaService = (MediaService)ServiceContext.MediaService; + + var threads = new List(); + var exceptions = new List(); + + Debug.WriteLine("Starting..."); + + var done = TraceLocks(); + + for (var i = 0; i < MaxThreadCount; i++) + { + var t = new Thread(() => + { + try + { + Debug.WriteLine("[{0}] Running...", Thread.CurrentThread.ManagedThreadId); + + var name1 = "test-" + Guid.NewGuid(); + var media1 = mediaService.CreateMedia(name1, -1, Constants.Conventions.MediaTypes.Folder); + Debug.WriteLine("[{0}] Saving media #1.", Thread.CurrentThread.ManagedThreadId); + Save(mediaService, media1); + + Thread.Sleep(100); //quick pause for maximum overlap! + + var name2 = "test-" + Guid.NewGuid(); + var media2 = mediaService.CreateMedia(name2, -1, Constants.Conventions.MediaTypes.Folder); + Debug.WriteLine("[{0}] Saving media #2.", Thread.CurrentThread.ManagedThreadId); + Save(mediaService, media2); + } + catch (Exception e) + { + lock (exceptions) { exceptions.Add(e); } + } + }); + threads.Add(t); + } + + //start all threads + threads.ForEach(x => x.Start()); + + //wait for all to complete + threads.ForEach(x => x.Join()); + + done.Set(); + + if (exceptions.Count == 0) + { + // now look up all items, there should be 40! + var items = mediaService.GetRootMedia(); + Assert.AreEqual(2 * MaxThreadCount, items.Count()); + } + else + { + throw new Exception("Exceptions!", exceptions.First()); // rethrow the first one... + } + + } + + public void CreateTestData() + { + // Create and Save ContentType "umbTextpage" -> 1045 + var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); + contentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"); + ServiceContext.ContentTypeService.Save(contentType); + } + } +} diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index a997eaffd8..117b0f9103 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -1,979 +1,979 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Tests.TestHelpers.Entities; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; -using Umbraco.Tests.Testing; -using Umbraco.Web._Legacy.Actions; - -namespace Umbraco.Tests.Services -{ - /// - /// Tests covering the UserService - /// - [TestFixture] - [Apartment(ApartmentState.STA)] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true)] - public class UserServiceTests : TestWithSomeContentBase - { - [Test] - public void Get_User_Permissions_For_Unassigned_Permission_Nodes() - { - // Arrange - var userService = ServiceContext.UserService; - var user = CreateTestUser(out var userGroup); - var contentType = MockedContentTypes.CreateSimpleContentType(); - ServiceContext.ContentTypeService.Save(contentType); - var content = new[] - { - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType) - }; - ServiceContext.ContentService.Save(content); - - // Act - var permissions = userService.GetPermissions(user, content[0].Id, content[1].Id, content[2].Id) - .ToArray(); - - //assert - Assert.AreEqual(3, permissions.Length); - Assert.AreEqual(17, permissions[0].AssignedPermissions.Length); - Assert.AreEqual(17, permissions[1].AssignedPermissions.Length); - Assert.AreEqual(17, permissions[2].AssignedPermissions.Length); - } - - [Test] - public void Get_User_Permissions_For_Assigned_Permission_Nodes() - { - // Arrange - var userService = ServiceContext.UserService; - IUserGroup userGroup; - var user = CreateTestUser(out userGroup); - - var contentType = MockedContentTypes.CreateSimpleContentType(); - ServiceContext.ContentTypeService.Save(contentType); - var content = new[] - { - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType) - }; - ServiceContext.ContentService.Save(content); - ServiceContext.ContentService.SetPermission(content[0], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[0], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[0], ActionMove.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[1], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[1], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[2], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - - // Act - var permissions = userService.GetPermissions(user, content[0].Id, content[1].Id, content[2].Id).ToArray(); - - //assert - Assert.AreEqual(3, permissions.Length); - Assert.AreEqual(3, permissions[0].AssignedPermissions.Length); - Assert.AreEqual(2, permissions[1].AssignedPermissions.Length); - Assert.AreEqual(1, permissions[2].AssignedPermissions.Length); - } - - [Test] - public void Get_UserGroup_Assigned_Permissions() - { - // Arrange - var userService = ServiceContext.UserService; - var userGroup = CreateTestUserGroup(); - - var contentType = MockedContentTypes.CreateSimpleContentType(); - ServiceContext.ContentTypeService.Save(contentType); - var content = new[] - { - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType) - }; - ServiceContext.ContentService.Save(content); - ServiceContext.ContentService.SetPermission(content.ElementAt(0), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content.ElementAt(0), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content.ElementAt(0), ActionMove.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content.ElementAt(1), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content.ElementAt(1), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content.ElementAt(2), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - - // Act - var permissions = userService.GetPermissions(userGroup, false, content[0].Id, content[1].Id, content[2].Id).ToArray(); - - //assert - Assert.AreEqual(3, permissions.Length); - Assert.AreEqual(3, permissions[0].AssignedPermissions.Length); - Assert.AreEqual(2, permissions[1].AssignedPermissions.Length); - Assert.AreEqual(1, permissions[2].AssignedPermissions.Length); - } - - [Test] - public void Get_UserGroup_Assigned_And_Default_Permissions() - { - // Arrange - var userService = ServiceContext.UserService; - var userGroup = CreateTestUserGroup(); - - var contentType = MockedContentTypes.CreateSimpleContentType(); - ServiceContext.ContentTypeService.Save(contentType); - var content = new[] - { - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType) - }; - ServiceContext.ContentService.Save(content); - ServiceContext.ContentService.SetPermission(content[0], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[0], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[0], ActionMove.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[1], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[1], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - - // Act - var permissions = userService.GetPermissions(userGroup, true, content[0].Id, content[1].Id, content[2].Id) - .ToArray(); - - //assert - Assert.AreEqual(3, permissions.Length); - Assert.AreEqual(3, permissions[0].AssignedPermissions.Length); - Assert.AreEqual(2, permissions[1].AssignedPermissions.Length); - Assert.AreEqual(17, permissions[2].AssignedPermissions.Length); - } - - [Test] - public void Get_All_User_Permissions_For_All_Nodes_With_Explicit_Permission() - { - // Arrange - var userService = ServiceContext.UserService; - var userGroup1 = CreateTestUserGroup(); - var userGroup2 = CreateTestUserGroup("test2", "Test 2"); - var userGroup3 = CreateTestUserGroup("test3", "Test 3"); - var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io"); - - var defaultPermissionCount = userGroup3.Permissions.Count(); - - user.AddGroup(userGroup1); - user.AddGroup(userGroup2); - user.AddGroup(userGroup3); - userService.Save(user); - - var contentType = MockedContentTypes.CreateSimpleContentType(); - ServiceContext.ContentTypeService.Save(contentType); - var content = new[] - { - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType) - }; - ServiceContext.ContentService.Save(content); - //assign permissions - we aren't assigning anything explicit for group3 and nothing explicit for content[2] /w group2 - ServiceContext.ContentService.SetPermission(content[0], ActionBrowse.Instance.Letter, new int[] { userGroup1.Id }); - ServiceContext.ContentService.SetPermission(content[0], ActionDelete.Instance.Letter, new int[] { userGroup1.Id }); - ServiceContext.ContentService.SetPermission(content[0], ActionMove.Instance.Letter, new int[] { userGroup2.Id }); - ServiceContext.ContentService.SetPermission(content[1], ActionBrowse.Instance.Letter, new int[] { userGroup1.Id }); - ServiceContext.ContentService.SetPermission(content[1], ActionDelete.Instance.Letter, new int[] { userGroup2.Id }); - ServiceContext.ContentService.SetPermission(content[2], ActionDelete.Instance.Letter, new int[] { userGroup1.Id }); - - // Act - //we don't pass in any nodes so it will return all of them - var result = userService.GetPermissions(user).ToArray(); - var permissions = result - .GroupBy(x => x.EntityId) - .ToDictionary(x => x.Key, x => x.GroupBy(a => a.UserGroupId).ToDictionary(a => a.Key, a => a.ToArray())); - - //assert - - //there will be 3 since that is how many content items there are - Assert.AreEqual(3, permissions.Count); - - //test permissions contains content[0] - Assert.IsTrue(permissions.ContainsKey(content[0].Id)); - //test that this permissions set contains permissions for all groups - Assert.IsTrue(permissions[content[0].Id].ContainsKey(userGroup1.Id)); - Assert.IsTrue(permissions[content[0].Id].ContainsKey(userGroup2.Id)); - Assert.IsTrue(permissions[content[0].Id].ContainsKey(userGroup3.Id)); - //test that the correct number of permissions are returned for each group - Assert.AreEqual(2, permissions[content[0].Id][userGroup1.Id].SelectMany(x => x.AssignedPermissions).Count()); - Assert.AreEqual(1, permissions[content[0].Id][userGroup2.Id].SelectMany(x => x.AssignedPermissions).Count()); - Assert.AreEqual(defaultPermissionCount, permissions[content[0].Id][userGroup3.Id].SelectMany(x => x.AssignedPermissions).Count()); - - //test permissions contains content[1] - Assert.IsTrue(permissions.ContainsKey(content[1].Id)); - //test that this permissions set contains permissions for all groups - Assert.IsTrue(permissions[content[1].Id].ContainsKey(userGroup1.Id)); - Assert.IsTrue(permissions[content[1].Id].ContainsKey(userGroup2.Id)); - Assert.IsTrue(permissions[content[1].Id].ContainsKey(userGroup3.Id)); - //test that the correct number of permissions are returned for each group - Assert.AreEqual(1, permissions[content[1].Id][userGroup1.Id].SelectMany(x => x.AssignedPermissions).Count()); - Assert.AreEqual(1, permissions[content[1].Id][userGroup2.Id].SelectMany(x => x.AssignedPermissions).Count()); - Assert.AreEqual(defaultPermissionCount, permissions[content[1].Id][userGroup3.Id].SelectMany(x => x.AssignedPermissions).Count()); - - //test permissions contains content[2] - Assert.IsTrue(permissions.ContainsKey(content[2].Id)); - //test that this permissions set contains permissions for all groups - Assert.IsTrue(permissions[content[2].Id].ContainsKey(userGroup1.Id)); - Assert.IsTrue(permissions[content[2].Id].ContainsKey(userGroup2.Id)); - Assert.IsTrue(permissions[content[2].Id].ContainsKey(userGroup3.Id)); - //test that the correct number of permissions are returned for each group - Assert.AreEqual(1, permissions[content[2].Id][userGroup1.Id].SelectMany(x => x.AssignedPermissions).Count()); - Assert.AreEqual(defaultPermissionCount, permissions[content[2].Id][userGroup2.Id].SelectMany(x => x.AssignedPermissions).Count()); - Assert.AreEqual(defaultPermissionCount, permissions[content[2].Id][userGroup3.Id].SelectMany(x => x.AssignedPermissions).Count()); - } - - [Test] - public void Get_All_User_Group_Permissions_For_All_Nodes() - { - // Arrange - var userService = ServiceContext.UserService; - var userGroup = CreateTestUserGroup(); - - var contentType = MockedContentTypes.CreateSimpleContentType(); - ServiceContext.ContentTypeService.Save(contentType); - var content = new[] - { - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType) - }; - ServiceContext.ContentService.Save(content); - ServiceContext.ContentService.SetPermission(content[0], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[0], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[0], ActionMove.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[1], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[1], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(content[2], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - - // Act - //we don't pass in any nodes so it will return all of them - var permissions = userService.GetPermissions(userGroup, true) - .GroupBy(x => x.EntityId) - .ToDictionary(x => x.Key, x => x); - - //assert - Assert.AreEqual(3, permissions.Count); - Assert.IsTrue(permissions.ContainsKey(content[0].Id)); - Assert.AreEqual(3, permissions[content[0].Id].SelectMany(x => x.AssignedPermissions).Count()); - Assert.IsTrue(permissions.ContainsKey(content[1].Id)); - Assert.AreEqual(2, permissions[content[1].Id].SelectMany(x => x.AssignedPermissions).Count()); - Assert.IsTrue(permissions.ContainsKey(content[2].Id)); - Assert.AreEqual(1, permissions[content[2].Id].SelectMany(x => x.AssignedPermissions).Count()); - } - - [Test] - public void Calculate_Permissions_For_User_For_Path() - { - //see: http://issues.umbraco.org/issue/U4-10075#comment=67-40085 - // for an overview of what this is testing - - const string path = "-1,1,2,3,4"; - var pathIds = path.GetIdsFromPathReversed(); - - const int groupA = 7; - const int groupB = 8; - const int groupC = 9; - - var userGroups = new Dictionary - { - {groupA, new[] {"S", "D", "F"}}, - {groupB, new[] {"S", "D", "G", "K"}}, - {groupC, new[] {"F", "G"}} - }; - - var permissions = new[] - { - new EntityPermission(groupA, 1, userGroups[groupA], isDefaultPermissions:true), - new EntityPermission(groupA, 2, userGroups[groupA], isDefaultPermissions:true), - new EntityPermission(groupA, 3, userGroups[groupA], isDefaultPermissions:true), - new EntityPermission(groupA, 4, userGroups[groupA], isDefaultPermissions:true), - - new EntityPermission(groupB, 1, userGroups[groupB], isDefaultPermissions:true), - new EntityPermission(groupB, 2, new []{"F", "R"}, isDefaultPermissions:false), - new EntityPermission(groupB, 3, userGroups[groupB], isDefaultPermissions:true), - new EntityPermission(groupB, 4, userGroups[groupB], isDefaultPermissions:true), - - new EntityPermission(groupC, 1, userGroups[groupC], isDefaultPermissions:true), - new EntityPermission(groupC, 2, userGroups[groupC], isDefaultPermissions:true), - new EntityPermission(groupC, 3, new []{"Q", "Z"}, isDefaultPermissions:false), - new EntityPermission(groupC, 4, userGroups[groupC], isDefaultPermissions:true), - }; - - //Permissions for Id 4 - var result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds); - Assert.AreEqual(4, result.EntityId); - var allPermissions = result.GetAllPermissions().ToArray(); - Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" })); - - //Permissions for Id 3 - result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(1).ToArray()); - Assert.AreEqual(3, result.EntityId); - allPermissions = result.GetAllPermissions().ToArray(); - Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" })); - - //Permissions for Id 2 - result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(2).ToArray()); - Assert.AreEqual(2, result.EntityId); - allPermissions = result.GetAllPermissions().ToArray(); - Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "R" })); - - //Permissions for Id 1 - result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(3).ToArray()); - Assert.AreEqual(1, result.EntityId); - allPermissions = result.GetAllPermissions().ToArray(); - Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "K" })); - - } - - [Test] - public void Determine_Deepest_Explicit_Permissions_For_Group_For_Path_1() - { - var path = "-1,1,2,3"; - var pathIds = path.GetIdsFromPathReversed(); - var defaults = new[] { "A", "B" }; - var permissions = new List - { - new EntityPermission(9876, 1, defaults, isDefaultPermissions:true), - new EntityPermission(9876, 2, new []{"B","C", "D"}, isDefaultPermissions:false), - new EntityPermission(9876, 3, defaults, isDefaultPermissions:true) - }; - var result = UserService.GetPermissionsForPathForGroup(permissions, pathIds, fallbackToDefaultPermissions: true); - Assert.AreEqual(3, result.AssignedPermissions.Length); - Assert.IsFalse(result.IsDefaultPermissions); - Assert.IsTrue(result.AssignedPermissions.ContainsAll(new[] { "B", "C", "D" })); - Assert.AreEqual(2, result.EntityId); - Assert.AreEqual(9876, result.UserGroupId); - } - - [Test] - public void Determine_Deepest_Explicit_Permissions_For_Group_For_Path_2() - { - var path = "-1,1,2,3"; - var pathIds = path.GetIdsFromPathReversed(); - var defaults = new[] { "A", "B", "C" }; - var permissions = new List - { - new EntityPermission(9876, 1, defaults, isDefaultPermissions:true), - new EntityPermission(9876, 2, defaults, isDefaultPermissions:true), - new EntityPermission(9876, 3, defaults, isDefaultPermissions:true) - }; - var result = UserService.GetPermissionsForPathForGroup(permissions, pathIds, fallbackToDefaultPermissions: false); - Assert.IsNull(result); - } - - [Test] - public void Determine_Deepest_Explicit_Permissions_For_Group_For_Path_3() - { - var path = "-1,1,2,3"; - var pathIds = path.GetIdsFromPathReversed(); - var defaults = new[] { "A", "B" }; - var permissions = new List - { - new EntityPermission(9876, 1, defaults, isDefaultPermissions:true), - new EntityPermission(9876, 2, defaults, isDefaultPermissions:true), - new EntityPermission(9876, 3, defaults, isDefaultPermissions:true) - }; - var result = UserService.GetPermissionsForPathForGroup(permissions, pathIds, fallbackToDefaultPermissions: true); - Assert.AreEqual(2, result.AssignedPermissions.Length); - Assert.IsTrue(result.IsDefaultPermissions); - Assert.IsTrue(result.AssignedPermissions.ContainsAll(defaults)); - Assert.AreEqual(3, result.EntityId); - Assert.AreEqual(9876, result.UserGroupId); - } - - [Test] - public void Get_User_Implicit_Permissions() - { - // Arrange - var userService = ServiceContext.UserService; - var userGroup = CreateTestUserGroup(); - - var contentType = MockedContentTypes.CreateSimpleContentType(); - ServiceContext.ContentTypeService.Save(contentType); - var parent = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(parent); - var child1 = MockedContent.CreateSimpleContent(contentType, "child1", parent); - ServiceContext.ContentService.Save(child1); - var child2 = MockedContent.CreateSimpleContent(contentType, "child2", child1); - ServiceContext.ContentService.Save(child2); - - ServiceContext.ContentService.SetPermission(parent, ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(parent, ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(parent, ActionMove.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(parent, ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.SetPermission(parent, ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - - // Act - var permissions = userService.GetPermissionsForPath(userGroup, child2.Path); - - //assert - var allPermissions = permissions.GetAllPermissions().ToArray(); - Assert.AreEqual(3, allPermissions.Length); - } - - [Test] - public void Can_Delete_User() - { - var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); - - ServiceContext.UserService.Delete(user, true); - var deleted = ServiceContext.UserService.GetUserById(user.Id); - - // Assert - Assert.That(deleted, Is.Null); - } - - [Test] - public void Disables_User_Instead_Of_Deleting_If_Flag_Not_Set() - { - var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); - - ServiceContext.UserService.Delete(user); - var deleted = ServiceContext.UserService.GetUserById(user.Id); - - // Assert - Assert.That(deleted, Is.Not.Null); - } - - [Test] - public void Exists_By_Username() - { - var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); - var user2 = ServiceContext.UserService.CreateUserWithIdentity("john2@umbraco.io", "john2@umbraco.io"); - Assert.IsTrue(ServiceContext.UserService.Exists("JohnDoe")); - Assert.IsFalse(ServiceContext.UserService.Exists("notFound")); - Assert.IsTrue(ServiceContext.UserService.Exists("john2@umbraco.io")); - } - - [Test] - public void Get_By_Email() - { - var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); - - Assert.IsNotNull(ServiceContext.UserService.GetByEmail(user.Email)); - Assert.IsNull(ServiceContext.UserService.GetByEmail("do@not.find")); - } - - [Test] - public void Get_By_Username() - { - var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); - - Assert.IsNotNull(ServiceContext.UserService.GetByUsername(user.Username)); - Assert.IsNull(ServiceContext.UserService.GetByUsername("notFound")); - } - - [Test] - public void Get_By_Username_With_Backslash() - { - var user = ServiceContext.UserService.CreateUserWithIdentity("mydomain\\JohnDoe", "john@umbraco.io"); - - Assert.IsNotNull(ServiceContext.UserService.GetByUsername(user.Username)); - Assert.IsNull(ServiceContext.UserService.GetByUsername("notFound")); - } - - [Test] - public void Get_By_Object_Id() - { - var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); - - Assert.IsNotNull(ServiceContext.UserService.GetUserById(user.Id)); - Assert.IsNull(ServiceContext.UserService.GetUserById(9876)); - } - - [Test] - public void Find_By_Email_Starts_With() - { - var users = MockedUser.CreateMulipleUsers(10); - ServiceContext.UserService.Save(users); - //don't find this - var customUser = MockedUser.CreateUser(); - customUser.Email = "hello@hello.com"; - ServiceContext.UserService.Save(customUser); - - var found = ServiceContext.UserService.FindByEmail("tes", 0, 100, out _, StringPropertyMatchType.StartsWith); - - Assert.AreEqual(10, found.Count()); - } - - [Test] - public void Find_By_Email_Ends_With() - { - var users = MockedUser.CreateMulipleUsers(10); - ServiceContext.UserService.Save(users); - //include this - var customUser = MockedUser.CreateUser(); - customUser.Email = "hello@test.com"; - ServiceContext.UserService.Save(customUser); - - var found = ServiceContext.UserService.FindByEmail("test.com", 0, 100, out _, StringPropertyMatchType.EndsWith); - - Assert.AreEqual(11, found.Count()); - } - - [Test] - public void Find_By_Email_Contains() - { - var users = MockedUser.CreateMulipleUsers(10); - ServiceContext.UserService.Save(users); - //include this - var customUser = MockedUser.CreateUser(); - customUser.Email = "hello@test.com"; - ServiceContext.UserService.Save(customUser); - - var found = ServiceContext.UserService.FindByEmail("test", 0, 100, out _, StringPropertyMatchType.Contains); - - Assert.AreEqual(11, found.Count()); - } - - [Test] - public void Find_By_Email_Exact() - { - var users = MockedUser.CreateMulipleUsers(10); - ServiceContext.UserService.Save(users); - //include this - var customUser = MockedUser.CreateUser(); - customUser.Email = "hello@test.com"; - ServiceContext.UserService.Save(customUser); - - var found = ServiceContext.UserService.FindByEmail("hello@test.com", 0, 100, out _, StringPropertyMatchType.Exact); - - Assert.AreEqual(1, found.Count()); - } - - [Test] - public void Get_All_Paged_Users() - { - var users = MockedUser.CreateMulipleUsers(10); - ServiceContext.UserService.Save(users); - - var found = ServiceContext.UserService.GetAll(0, 2, out var totalRecs); - - Assert.AreEqual(2, found.Count()); - // + 1 because of the built in admin user - Assert.AreEqual(11, totalRecs); - Assert.AreEqual("admin", found.First().Username); - Assert.AreEqual("test0", found.Last().Username); - } - - [Test] - public void Get_All_Paged_Users_With_Filter() - { - var users = MockedUser.CreateMulipleUsers(10).ToArray(); - ServiceContext.UserService.Save(users); - - var found = ServiceContext.UserService.GetAll(0, 2, out var totalRecs, "username", Direction.Ascending, filter: "test"); - - Assert.AreEqual(2, found.Count()); - Assert.AreEqual(10, totalRecs); - Assert.AreEqual("test0", found.First().Username); - Assert.AreEqual("test1", found.Last().Username); - } - - [Test] - public void Get_All_Paged_Users_For_Group() - { - var userGroup = MockedUserGroup.CreateUserGroup(); - ServiceContext.UserService.Save(userGroup); - - var users = MockedUser.CreateMulipleUsers(10).ToArray(); - for (var i = 0; i < 10;) - { - users[i].AddGroup(userGroup.ToReadOnlyGroup()); - i = i + 2; - } - ServiceContext.UserService.Save(users); - - long totalRecs; - var found = ServiceContext.UserService.GetAll(0, 2, out totalRecs, "username", Direction.Ascending, includeUserGroups: new[] {userGroup.Alias}); - - Assert.AreEqual(2, found.Count()); - Assert.AreEqual(5, totalRecs); - Assert.AreEqual("test0", found.First().Username); - Assert.AreEqual("test2", found.Last().Username); - } - - [Test] - public void Get_All_Paged_Users_For_Group_With_Filter() - { - var userGroup = MockedUserGroup.CreateUserGroup(); - ServiceContext.UserService.Save(userGroup); - - var users = MockedUser.CreateMulipleUsers(10).ToArray(); - for (var i = 0; i < 10;) - { - users[i].AddGroup(userGroup.ToReadOnlyGroup()); - i = i + 2; - } - for (var i = 0; i < 10;) - { - users[i].Name = "blah" + users[i].Name; - i = i + 3; - } - ServiceContext.UserService.Save(users); - - long totalRecs; - var found = ServiceContext.UserService.GetAll(0, 2, out totalRecs, "username", Direction.Ascending, userGroups: new[] { userGroup.Alias }, filter: "blah"); - - Assert.AreEqual(2, found.Count()); - Assert.AreEqual(2, totalRecs); - Assert.AreEqual("test0", found.First().Username); - Assert.AreEqual("test6", found.Last().Username); - } - - [Test] - public void Count_All_Users() - { - var users = MockedUser.CreateMulipleUsers(10); - ServiceContext.UserService.Save(users); - var customUser = MockedUser.CreateUser(); - ServiceContext.UserService.Save(customUser); - - var found = ServiceContext.UserService.GetCount(MemberCountType.All); - - // + 1 because of the built in admin user - Assert.AreEqual(12, found); - } - - [Ignore("why?")] - [Test] - public void Count_All_Online_Users() - { - var users = MockedUser.CreateMulipleUsers(10, (i, member) => member.LastLoginDate = DateTime.Now.AddMinutes(i * -2)); - ServiceContext.UserService.Save(users); - - var customUser = MockedUser.CreateUser(); - throw new NotImplementedException(); - } - - [Test] - public void Count_All_Locked_Users() - { - var users = MockedUser.CreateMulipleUsers(10, (i, member) => member.IsLockedOut = i % 2 == 0); - ServiceContext.UserService.Save(users); - - var customUser = MockedUser.CreateUser(); - customUser.IsLockedOut = true; - ServiceContext.UserService.Save(customUser); - - var found = ServiceContext.UserService.GetCount(MemberCountType.LockedOut); - - Assert.AreEqual(6, found); - } - - [Test] - public void Count_All_Approved_Users() - { - var users = MockedUser.CreateMulipleUsers(10, (i, member) => member.IsApproved = i % 2 == 0); - ServiceContext.UserService.Save(users); - - var customUser = MockedUser.CreateUser(); - customUser.IsApproved = false; - ServiceContext.UserService.Save(customUser); - - var found = ServiceContext.UserService.GetCount(MemberCountType.Approved); - - // + 1 because of the built in admin user - Assert.AreEqual(6, found); - } - - [Test] - public void Can_Persist_New_User() - { - // Arrange - var userService = ServiceContext.UserService; - - // Act - var membershipUser = userService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); - - // Assert - Assert.That(membershipUser.HasIdentity, Is.True); - Assert.That(membershipUser.Id, Is.GreaterThan(0)); - IUser user = membershipUser as User; - Assert.That(user, Is.Not.Null); - } - - [Test] - public void Can_Persist_New_User_With_Hashed_Password() - { - // Arrange - var userService = ServiceContext.UserService; - - // Act - // NOTE: Normally the hash'ing would be handled in the membership provider, so the service just saves the password - var password = "123456"; - var hash = new HMACSHA1(); - hash.Key = Encoding.Unicode.GetBytes(password); - var encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password))); - var membershipUser = new User("JohnDoe", "john@umbraco.io", encodedPassword, encodedPassword); - userService.Save(membershipUser); - - // Assert - Assert.That(membershipUser.HasIdentity, Is.True); - Assert.That(membershipUser.RawPasswordValue, Is.Not.EqualTo(password)); - Assert.That(membershipUser.RawPasswordValue, Is.EqualTo(encodedPassword)); - IUser user = membershipUser as User; - Assert.That(user, Is.Not.Null); - } - - [Test] - public void Can_Add_And_Remove_Sections_From_UserGroup() - { - var userGroup = new UserGroup - { - Alias = "Group1", - Name = "Group 1" - }; - userGroup.AddAllowedSection("content"); - userGroup.AddAllowedSection("mediat"); - ServiceContext.UserService.Save(userGroup); - - var result1 = ServiceContext.UserService.GetUserGroupById(userGroup.Id); - - Assert.AreEqual(2, result1.AllowedSections.Count()); - - //adds some allowed sections - userGroup.AddAllowedSection("test1"); - userGroup.AddAllowedSection("test2"); - userGroup.AddAllowedSection("test3"); - userGroup.AddAllowedSection("test4"); - ServiceContext.UserService.Save(userGroup); - - result1 = ServiceContext.UserService.GetUserGroupById(userGroup.Id); - - Assert.AreEqual(6, result1.AllowedSections.Count()); - - //simulate clearing the sections - foreach (var s in userGroup.AllowedSections) - { - result1.RemoveAllowedSection(s); - } - - //now just re-add a couple - result1.AddAllowedSection("test3"); - result1.AddAllowedSection("test4"); - ServiceContext.UserService.Save(result1); - - //assert - //re-get - result1 = ServiceContext.UserService.GetUserGroupById(userGroup.Id); - Assert.AreEqual(2, result1.AllowedSections.Count()); - } - - [Test] - public void Can_Remove_Section_From_All_Assigned_UserGroups() - { - var userGroup1 = new UserGroup - { - Alias = "Group1", - Name = "Group 1" - }; - var userGroup2 = new UserGroup - { - Alias = "Group2", - Name = "Group 2" - }; - ServiceContext.UserService.Save(userGroup1); - ServiceContext.UserService.Save(userGroup2); - - //adds some allowed sections - userGroup1.AddAllowedSection("test"); - userGroup2.AddAllowedSection("test"); - ServiceContext.UserService.Save(userGroup1); - ServiceContext.UserService.Save(userGroup2); - - //now clear the section from all users - ServiceContext.UserService.DeleteSectionFromAllUserGroups("test"); - - //assert - var result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id); - var result2 = ServiceContext.UserService.GetUserGroupById(userGroup2.Id); - Assert.IsFalse(result1.AllowedSections.Contains("test")); - Assert.IsFalse(result2.AllowedSections.Contains("test")); - } - - [Test] - public void Can_Add_Section_To_All_UserGroups() - { - var userGroup1 = new UserGroup - { - Alias = "Group1", - Name = "Group 1" - }; - userGroup1.AddAllowedSection("test"); - - var userGroup2 = new UserGroup - { - Alias = "Group2", - Name = "Group 2" - }; - userGroup2.AddAllowedSection("test"); - - var userGroup3 = new UserGroup - { - Alias = "Group3", - Name = "Group 3" - }; - ServiceContext.UserService.Save(userGroup1); - ServiceContext.UserService.Save(userGroup2); - ServiceContext.UserService.Save(userGroup3); - - //assert - var result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id); - var result2 = ServiceContext.UserService.GetUserGroupById(userGroup2.Id); - var result3 = ServiceContext.UserService.GetUserGroupById(userGroup3.Id); - Assert.IsTrue(result1.AllowedSections.Contains("test")); - Assert.IsTrue(result2.AllowedSections.Contains("test")); - Assert.IsFalse(result3.AllowedSections.Contains("test")); - - //now add the section to all groups - foreach (var userGroup in new[] { userGroup1, userGroup2, userGroup3 }) - { - userGroup.AddAllowedSection("test"); - ServiceContext.UserService.Save(userGroup); - } - - //assert - result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id); - result2 = ServiceContext.UserService.GetUserGroupById(userGroup2.Id); - result3 = ServiceContext.UserService.GetUserGroupById(userGroup3.Id); - Assert.IsTrue(result1.AllowedSections.Contains("test")); - Assert.IsTrue(result2.AllowedSections.Contains("test")); - Assert.IsTrue(result3.AllowedSections.Contains("test")); - } - - [Test] - public void Cannot_Create_User_With_Empty_Username() - { - // Arrange - var userService = ServiceContext.UserService; - - // Act & Assert - Assert.Throws(() => userService.CreateUserWithIdentity(string.Empty, "john@umbraco.io")); - } - - [Test] - public void Cannot_Save_User_With_Empty_Username() - { - // Arrange - var userService = ServiceContext.UserService; - var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io"); - user.Username = string.Empty; - - // Act & Assert - Assert.Throws(() => userService.Save(user)); - } - - [Test] - public void Cannot_Save_User_With_Empty_Name() - { - // Arrange - var userService = ServiceContext.UserService; - var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io"); - user.Name = string.Empty; - - // Act & Assert - Assert.Throws(() => userService.Save(user)); - } - - [Test] - public void Get_By_Profile_Username() - { - // Arrange - var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); - - // Act - - var profile = ServiceContext.UserService.GetProfileByUserName(user.Username); - - // Assert - Assert.IsNotNull(profile); - Assert.AreEqual(user.Username, profile.Name); - Assert.AreEqual(user.Id, profile.Id); - } - - [Test] - public void Get_By_Profile_Id() - { - // Arrange - var user = (IUser)ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); - - // Act - - var profile = ServiceContext.UserService.GetProfileById((int)user.Id); - - // Assert - Assert.IsNotNull(profile); - Assert.AreEqual(user.Username, profile.Name); - Assert.AreEqual(user.Id, profile.Id); - } - - [Test] - public void Get_User_By_Username() - { - // Arrange - IUserGroup userGroup; - var originalUser = CreateTestUser(out userGroup); - - // Act - - var updatedItem = (User) ServiceContext.UserService.GetByUsername(originalUser.Username); - - // Assert - Assert.IsNotNull(updatedItem); - Assert.That(updatedItem.Id, Is.EqualTo(originalUser.Id)); - Assert.That(updatedItem.Name, Is.EqualTo(originalUser.Name)); - Assert.That(updatedItem.Language, Is.EqualTo(originalUser.Language)); - Assert.That(updatedItem.IsApproved, Is.EqualTo(originalUser.IsApproved)); - Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(originalUser.RawPasswordValue)); - Assert.That(updatedItem.IsLockedOut, Is.EqualTo(originalUser.IsLockedOut)); - Assert.IsTrue(updatedItem.StartContentIds.UnsortedSequenceEqual(originalUser.StartContentIds)); - Assert.IsTrue(updatedItem.StartMediaIds.UnsortedSequenceEqual(originalUser.StartMediaIds)); - Assert.That(updatedItem.Email, Is.EqualTo(originalUser.Email)); - Assert.That(updatedItem.Username, Is.EqualTo(originalUser.Username)); - Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(originalUser.AllowedSections.Count())); - } - - private IUser CreateTestUser(out IUserGroup userGroup) - { - userGroup = CreateTestUserGroup(); - - var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); - user.AddGroup(userGroup.ToReadOnlyGroup()); - ServiceContext.UserService.Save(user); - return user; - } - - private UserGroup CreateTestUserGroup(string alias = "testGroup", string name = "Test Group") - { - var userGroup = new UserGroup - { - Alias = alias, - Name = name, - Permissions = "ABCDEFGHIJ1234567".ToCharArray().Select(x => x.ToString()) - }; - - userGroup.AddAllowedSection("content"); - userGroup.AddAllowedSection("media"); - - ServiceContext.UserService.Save(userGroup); - - return userGroup; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Tests.Testing; +using Umbraco.Web._Legacy.Actions; + +namespace Umbraco.Tests.Services +{ + /// + /// Tests covering the UserService + /// + [TestFixture] + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true)] + public class UserServiceTests : TestWithSomeContentBase + { + [Test] + public void Get_User_Permissions_For_Unassigned_Permission_Nodes() + { + // Arrange + var userService = ServiceContext.UserService; + var user = CreateTestUser(out var userGroup); + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var content = new[] + { + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType) + }; + ServiceContext.ContentService.Save(content); + + // Act + var permissions = userService.GetPermissions(user, content[0].Id, content[1].Id, content[2].Id) + .ToArray(); + + //assert + Assert.AreEqual(3, permissions.Length); + Assert.AreEqual(17, permissions[0].AssignedPermissions.Length); + Assert.AreEqual(17, permissions[1].AssignedPermissions.Length); + Assert.AreEqual(17, permissions[2].AssignedPermissions.Length); + } + + [Test] + public void Get_User_Permissions_For_Assigned_Permission_Nodes() + { + // Arrange + var userService = ServiceContext.UserService; + IUserGroup userGroup; + var user = CreateTestUser(out userGroup); + + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var content = new[] + { + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType) + }; + ServiceContext.ContentService.Save(content); + ServiceContext.ContentService.SetPermission(content[0], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[0], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[0], ActionMove.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[1], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[1], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[2], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + + // Act + var permissions = userService.GetPermissions(user, content[0].Id, content[1].Id, content[2].Id).ToArray(); + + //assert + Assert.AreEqual(3, permissions.Length); + Assert.AreEqual(3, permissions[0].AssignedPermissions.Length); + Assert.AreEqual(2, permissions[1].AssignedPermissions.Length); + Assert.AreEqual(1, permissions[2].AssignedPermissions.Length); + } + + [Test] + public void Get_UserGroup_Assigned_Permissions() + { + // Arrange + var userService = ServiceContext.UserService; + var userGroup = CreateTestUserGroup(); + + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var content = new[] + { + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType) + }; + ServiceContext.ContentService.Save(content); + ServiceContext.ContentService.SetPermission(content.ElementAt(0), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content.ElementAt(0), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content.ElementAt(0), ActionMove.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content.ElementAt(1), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content.ElementAt(1), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content.ElementAt(2), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + + // Act + var permissions = userService.GetPermissions(userGroup, false, content[0].Id, content[1].Id, content[2].Id).ToArray(); + + //assert + Assert.AreEqual(3, permissions.Length); + Assert.AreEqual(3, permissions[0].AssignedPermissions.Length); + Assert.AreEqual(2, permissions[1].AssignedPermissions.Length); + Assert.AreEqual(1, permissions[2].AssignedPermissions.Length); + } + + [Test] + public void Get_UserGroup_Assigned_And_Default_Permissions() + { + // Arrange + var userService = ServiceContext.UserService; + var userGroup = CreateTestUserGroup(); + + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var content = new[] + { + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType) + }; + ServiceContext.ContentService.Save(content); + ServiceContext.ContentService.SetPermission(content[0], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[0], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[0], ActionMove.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[1], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[1], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + + // Act + var permissions = userService.GetPermissions(userGroup, true, content[0].Id, content[1].Id, content[2].Id) + .ToArray(); + + //assert + Assert.AreEqual(3, permissions.Length); + Assert.AreEqual(3, permissions[0].AssignedPermissions.Length); + Assert.AreEqual(2, permissions[1].AssignedPermissions.Length); + Assert.AreEqual(17, permissions[2].AssignedPermissions.Length); + } + + [Test] + public void Get_All_User_Permissions_For_All_Nodes_With_Explicit_Permission() + { + // Arrange + var userService = ServiceContext.UserService; + var userGroup1 = CreateTestUserGroup(); + var userGroup2 = CreateTestUserGroup("test2", "Test 2"); + var userGroup3 = CreateTestUserGroup("test3", "Test 3"); + var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io"); + + var defaultPermissionCount = userGroup3.Permissions.Count(); + + user.AddGroup(userGroup1); + user.AddGroup(userGroup2); + user.AddGroup(userGroup3); + userService.Save(user); + + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var content = new[] + { + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType) + }; + ServiceContext.ContentService.Save(content); + //assign permissions - we aren't assigning anything explicit for group3 and nothing explicit for content[2] /w group2 + ServiceContext.ContentService.SetPermission(content[0], ActionBrowse.Instance.Letter, new int[] { userGroup1.Id }); + ServiceContext.ContentService.SetPermission(content[0], ActionDelete.Instance.Letter, new int[] { userGroup1.Id }); + ServiceContext.ContentService.SetPermission(content[0], ActionMove.Instance.Letter, new int[] { userGroup2.Id }); + ServiceContext.ContentService.SetPermission(content[1], ActionBrowse.Instance.Letter, new int[] { userGroup1.Id }); + ServiceContext.ContentService.SetPermission(content[1], ActionDelete.Instance.Letter, new int[] { userGroup2.Id }); + ServiceContext.ContentService.SetPermission(content[2], ActionDelete.Instance.Letter, new int[] { userGroup1.Id }); + + // Act + //we don't pass in any nodes so it will return all of them + var result = userService.GetPermissions(user).ToArray(); + var permissions = result + .GroupBy(x => x.EntityId) + .ToDictionary(x => x.Key, x => x.GroupBy(a => a.UserGroupId).ToDictionary(a => a.Key, a => a.ToArray())); + + //assert + + //there will be 3 since that is how many content items there are + Assert.AreEqual(3, permissions.Count); + + //test permissions contains content[0] + Assert.IsTrue(permissions.ContainsKey(content[0].Id)); + //test that this permissions set contains permissions for all groups + Assert.IsTrue(permissions[content[0].Id].ContainsKey(userGroup1.Id)); + Assert.IsTrue(permissions[content[0].Id].ContainsKey(userGroup2.Id)); + Assert.IsTrue(permissions[content[0].Id].ContainsKey(userGroup3.Id)); + //test that the correct number of permissions are returned for each group + Assert.AreEqual(2, permissions[content[0].Id][userGroup1.Id].SelectMany(x => x.AssignedPermissions).Count()); + Assert.AreEqual(1, permissions[content[0].Id][userGroup2.Id].SelectMany(x => x.AssignedPermissions).Count()); + Assert.AreEqual(defaultPermissionCount, permissions[content[0].Id][userGroup3.Id].SelectMany(x => x.AssignedPermissions).Count()); + + //test permissions contains content[1] + Assert.IsTrue(permissions.ContainsKey(content[1].Id)); + //test that this permissions set contains permissions for all groups + Assert.IsTrue(permissions[content[1].Id].ContainsKey(userGroup1.Id)); + Assert.IsTrue(permissions[content[1].Id].ContainsKey(userGroup2.Id)); + Assert.IsTrue(permissions[content[1].Id].ContainsKey(userGroup3.Id)); + //test that the correct number of permissions are returned for each group + Assert.AreEqual(1, permissions[content[1].Id][userGroup1.Id].SelectMany(x => x.AssignedPermissions).Count()); + Assert.AreEqual(1, permissions[content[1].Id][userGroup2.Id].SelectMany(x => x.AssignedPermissions).Count()); + Assert.AreEqual(defaultPermissionCount, permissions[content[1].Id][userGroup3.Id].SelectMany(x => x.AssignedPermissions).Count()); + + //test permissions contains content[2] + Assert.IsTrue(permissions.ContainsKey(content[2].Id)); + //test that this permissions set contains permissions for all groups + Assert.IsTrue(permissions[content[2].Id].ContainsKey(userGroup1.Id)); + Assert.IsTrue(permissions[content[2].Id].ContainsKey(userGroup2.Id)); + Assert.IsTrue(permissions[content[2].Id].ContainsKey(userGroup3.Id)); + //test that the correct number of permissions are returned for each group + Assert.AreEqual(1, permissions[content[2].Id][userGroup1.Id].SelectMany(x => x.AssignedPermissions).Count()); + Assert.AreEqual(defaultPermissionCount, permissions[content[2].Id][userGroup2.Id].SelectMany(x => x.AssignedPermissions).Count()); + Assert.AreEqual(defaultPermissionCount, permissions[content[2].Id][userGroup3.Id].SelectMany(x => x.AssignedPermissions).Count()); + } + + [Test] + public void Get_All_User_Group_Permissions_For_All_Nodes() + { + // Arrange + var userService = ServiceContext.UserService; + var userGroup = CreateTestUserGroup(); + + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var content = new[] + { + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType) + }; + ServiceContext.ContentService.Save(content); + ServiceContext.ContentService.SetPermission(content[0], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[0], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[0], ActionMove.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[1], ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[1], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(content[2], ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + + // Act + //we don't pass in any nodes so it will return all of them + var permissions = userService.GetPermissions(userGroup, true) + .GroupBy(x => x.EntityId) + .ToDictionary(x => x.Key, x => x); + + //assert + Assert.AreEqual(3, permissions.Count); + Assert.IsTrue(permissions.ContainsKey(content[0].Id)); + Assert.AreEqual(3, permissions[content[0].Id].SelectMany(x => x.AssignedPermissions).Count()); + Assert.IsTrue(permissions.ContainsKey(content[1].Id)); + Assert.AreEqual(2, permissions[content[1].Id].SelectMany(x => x.AssignedPermissions).Count()); + Assert.IsTrue(permissions.ContainsKey(content[2].Id)); + Assert.AreEqual(1, permissions[content[2].Id].SelectMany(x => x.AssignedPermissions).Count()); + } + + [Test] + public void Calculate_Permissions_For_User_For_Path() + { + //see: http://issues.umbraco.org/issue/U4-10075#comment=67-40085 + // for an overview of what this is testing + + const string path = "-1,1,2,3,4"; + var pathIds = path.GetIdsFromPathReversed(); + + const int groupA = 7; + const int groupB = 8; + const int groupC = 9; + + var userGroups = new Dictionary + { + {groupA, new[] {"S", "D", "F"}}, + {groupB, new[] {"S", "D", "G", "K"}}, + {groupC, new[] {"F", "G"}} + }; + + var permissions = new[] + { + new EntityPermission(groupA, 1, userGroups[groupA], isDefaultPermissions:true), + new EntityPermission(groupA, 2, userGroups[groupA], isDefaultPermissions:true), + new EntityPermission(groupA, 3, userGroups[groupA], isDefaultPermissions:true), + new EntityPermission(groupA, 4, userGroups[groupA], isDefaultPermissions:true), + + new EntityPermission(groupB, 1, userGroups[groupB], isDefaultPermissions:true), + new EntityPermission(groupB, 2, new []{"F", "R"}, isDefaultPermissions:false), + new EntityPermission(groupB, 3, userGroups[groupB], isDefaultPermissions:true), + new EntityPermission(groupB, 4, userGroups[groupB], isDefaultPermissions:true), + + new EntityPermission(groupC, 1, userGroups[groupC], isDefaultPermissions:true), + new EntityPermission(groupC, 2, userGroups[groupC], isDefaultPermissions:true), + new EntityPermission(groupC, 3, new []{"Q", "Z"}, isDefaultPermissions:false), + new EntityPermission(groupC, 4, userGroups[groupC], isDefaultPermissions:true), + }; + + //Permissions for Id 4 + var result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds); + Assert.AreEqual(4, result.EntityId); + var allPermissions = result.GetAllPermissions().ToArray(); + Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions)); + Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" })); + + //Permissions for Id 3 + result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(1).ToArray()); + Assert.AreEqual(3, result.EntityId); + allPermissions = result.GetAllPermissions().ToArray(); + Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions)); + Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" })); + + //Permissions for Id 2 + result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(2).ToArray()); + Assert.AreEqual(2, result.EntityId); + allPermissions = result.GetAllPermissions().ToArray(); + Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions)); + Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "R" })); + + //Permissions for Id 1 + result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(3).ToArray()); + Assert.AreEqual(1, result.EntityId); + allPermissions = result.GetAllPermissions().ToArray(); + Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions)); + Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "K" })); + + } + + [Test] + public void Determine_Deepest_Explicit_Permissions_For_Group_For_Path_1() + { + var path = "-1,1,2,3"; + var pathIds = path.GetIdsFromPathReversed(); + var defaults = new[] { "A", "B" }; + var permissions = new List + { + new EntityPermission(9876, 1, defaults, isDefaultPermissions:true), + new EntityPermission(9876, 2, new []{"B","C", "D"}, isDefaultPermissions:false), + new EntityPermission(9876, 3, defaults, isDefaultPermissions:true) + }; + var result = UserService.GetPermissionsForPathForGroup(permissions, pathIds, fallbackToDefaultPermissions: true); + Assert.AreEqual(3, result.AssignedPermissions.Length); + Assert.IsFalse(result.IsDefaultPermissions); + Assert.IsTrue(result.AssignedPermissions.ContainsAll(new[] { "B", "C", "D" })); + Assert.AreEqual(2, result.EntityId); + Assert.AreEqual(9876, result.UserGroupId); + } + + [Test] + public void Determine_Deepest_Explicit_Permissions_For_Group_For_Path_2() + { + var path = "-1,1,2,3"; + var pathIds = path.GetIdsFromPathReversed(); + var defaults = new[] { "A", "B", "C" }; + var permissions = new List + { + new EntityPermission(9876, 1, defaults, isDefaultPermissions:true), + new EntityPermission(9876, 2, defaults, isDefaultPermissions:true), + new EntityPermission(9876, 3, defaults, isDefaultPermissions:true) + }; + var result = UserService.GetPermissionsForPathForGroup(permissions, pathIds, fallbackToDefaultPermissions: false); + Assert.IsNull(result); + } + + [Test] + public void Determine_Deepest_Explicit_Permissions_For_Group_For_Path_3() + { + var path = "-1,1,2,3"; + var pathIds = path.GetIdsFromPathReversed(); + var defaults = new[] { "A", "B" }; + var permissions = new List + { + new EntityPermission(9876, 1, defaults, isDefaultPermissions:true), + new EntityPermission(9876, 2, defaults, isDefaultPermissions:true), + new EntityPermission(9876, 3, defaults, isDefaultPermissions:true) + }; + var result = UserService.GetPermissionsForPathForGroup(permissions, pathIds, fallbackToDefaultPermissions: true); + Assert.AreEqual(2, result.AssignedPermissions.Length); + Assert.IsTrue(result.IsDefaultPermissions); + Assert.IsTrue(result.AssignedPermissions.ContainsAll(defaults)); + Assert.AreEqual(3, result.EntityId); + Assert.AreEqual(9876, result.UserGroupId); + } + + [Test] + public void Get_User_Implicit_Permissions() + { + // Arrange + var userService = ServiceContext.UserService; + var userGroup = CreateTestUserGroup(); + + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var parent = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(parent); + var child1 = MockedContent.CreateSimpleContent(contentType, "child1", parent); + ServiceContext.ContentService.Save(child1); + var child2 = MockedContent.CreateSimpleContent(contentType, "child2", child1); + ServiceContext.ContentService.Save(child2); + + ServiceContext.ContentService.SetPermission(parent, ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(parent, ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(parent, ActionMove.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(parent, ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.SetPermission(parent, ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + + // Act + var permissions = userService.GetPermissionsForPath(userGroup, child2.Path); + + //assert + var allPermissions = permissions.GetAllPermissions().ToArray(); + Assert.AreEqual(3, allPermissions.Length); + } + + [Test] + public void Can_Delete_User() + { + var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); + + ServiceContext.UserService.Delete(user, true); + var deleted = ServiceContext.UserService.GetUserById(user.Id); + + // Assert + Assert.That(deleted, Is.Null); + } + + [Test] + public void Disables_User_Instead_Of_Deleting_If_Flag_Not_Set() + { + var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); + + ServiceContext.UserService.Delete(user); + var deleted = ServiceContext.UserService.GetUserById(user.Id); + + // Assert + Assert.That(deleted, Is.Not.Null); + } + + [Test] + public void Exists_By_Username() + { + var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); + var user2 = ServiceContext.UserService.CreateUserWithIdentity("john2@umbraco.io", "john2@umbraco.io"); + Assert.IsTrue(ServiceContext.UserService.Exists("JohnDoe")); + Assert.IsFalse(ServiceContext.UserService.Exists("notFound")); + Assert.IsTrue(ServiceContext.UserService.Exists("john2@umbraco.io")); + } + + [Test] + public void Get_By_Email() + { + var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); + + Assert.IsNotNull(ServiceContext.UserService.GetByEmail(user.Email)); + Assert.IsNull(ServiceContext.UserService.GetByEmail("do@not.find")); + } + + [Test] + public void Get_By_Username() + { + var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); + + Assert.IsNotNull(ServiceContext.UserService.GetByUsername(user.Username)); + Assert.IsNull(ServiceContext.UserService.GetByUsername("notFound")); + } + + [Test] + public void Get_By_Username_With_Backslash() + { + var user = ServiceContext.UserService.CreateUserWithIdentity("mydomain\\JohnDoe", "john@umbraco.io"); + + Assert.IsNotNull(ServiceContext.UserService.GetByUsername(user.Username)); + Assert.IsNull(ServiceContext.UserService.GetByUsername("notFound")); + } + + [Test] + public void Get_By_Object_Id() + { + var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); + + Assert.IsNotNull(ServiceContext.UserService.GetUserById(user.Id)); + Assert.IsNull(ServiceContext.UserService.GetUserById(9876)); + } + + [Test] + public void Find_By_Email_Starts_With() + { + var users = MockedUser.CreateMulipleUsers(10); + ServiceContext.UserService.Save(users); + //don't find this + var customUser = MockedUser.CreateUser(); + customUser.Email = "hello@hello.com"; + ServiceContext.UserService.Save(customUser); + + var found = ServiceContext.UserService.FindByEmail("tes", 0, 100, out _, StringPropertyMatchType.StartsWith); + + Assert.AreEqual(10, found.Count()); + } + + [Test] + public void Find_By_Email_Ends_With() + { + var users = MockedUser.CreateMulipleUsers(10); + ServiceContext.UserService.Save(users); + //include this + var customUser = MockedUser.CreateUser(); + customUser.Email = "hello@test.com"; + ServiceContext.UserService.Save(customUser); + + var found = ServiceContext.UserService.FindByEmail("test.com", 0, 100, out _, StringPropertyMatchType.EndsWith); + + Assert.AreEqual(11, found.Count()); + } + + [Test] + public void Find_By_Email_Contains() + { + var users = MockedUser.CreateMulipleUsers(10); + ServiceContext.UserService.Save(users); + //include this + var customUser = MockedUser.CreateUser(); + customUser.Email = "hello@test.com"; + ServiceContext.UserService.Save(customUser); + + var found = ServiceContext.UserService.FindByEmail("test", 0, 100, out _, StringPropertyMatchType.Contains); + + Assert.AreEqual(11, found.Count()); + } + + [Test] + public void Find_By_Email_Exact() + { + var users = MockedUser.CreateMulipleUsers(10); + ServiceContext.UserService.Save(users); + //include this + var customUser = MockedUser.CreateUser(); + customUser.Email = "hello@test.com"; + ServiceContext.UserService.Save(customUser); + + var found = ServiceContext.UserService.FindByEmail("hello@test.com", 0, 100, out _, StringPropertyMatchType.Exact); + + Assert.AreEqual(1, found.Count()); + } + + [Test] + public void Get_All_Paged_Users() + { + var users = MockedUser.CreateMulipleUsers(10); + ServiceContext.UserService.Save(users); + + var found = ServiceContext.UserService.GetAll(0, 2, out var totalRecs); + + Assert.AreEqual(2, found.Count()); + // + 1 because of the built in admin user + Assert.AreEqual(11, totalRecs); + Assert.AreEqual("admin", found.First().Username); + Assert.AreEqual("test0", found.Last().Username); + } + + [Test] + public void Get_All_Paged_Users_With_Filter() + { + var users = MockedUser.CreateMulipleUsers(10).ToArray(); + ServiceContext.UserService.Save(users); + + var found = ServiceContext.UserService.GetAll(0, 2, out var totalRecs, "username", Direction.Ascending, filter: "test"); + + Assert.AreEqual(2, found.Count()); + Assert.AreEqual(10, totalRecs); + Assert.AreEqual("test0", found.First().Username); + Assert.AreEqual("test1", found.Last().Username); + } + + [Test] + public void Get_All_Paged_Users_For_Group() + { + var userGroup = MockedUserGroup.CreateUserGroup(); + ServiceContext.UserService.Save(userGroup); + + var users = MockedUser.CreateMulipleUsers(10).ToArray(); + for (var i = 0; i < 10;) + { + users[i].AddGroup(userGroup.ToReadOnlyGroup()); + i = i + 2; + } + ServiceContext.UserService.Save(users); + + long totalRecs; + var found = ServiceContext.UserService.GetAll(0, 2, out totalRecs, "username", Direction.Ascending, includeUserGroups: new[] {userGroup.Alias}); + + Assert.AreEqual(2, found.Count()); + Assert.AreEqual(5, totalRecs); + Assert.AreEqual("test0", found.First().Username); + Assert.AreEqual("test2", found.Last().Username); + } + + [Test] + public void Get_All_Paged_Users_For_Group_With_Filter() + { + var userGroup = MockedUserGroup.CreateUserGroup(); + ServiceContext.UserService.Save(userGroup); + + var users = MockedUser.CreateMulipleUsers(10).ToArray(); + for (var i = 0; i < 10;) + { + users[i].AddGroup(userGroup.ToReadOnlyGroup()); + i = i + 2; + } + for (var i = 0; i < 10;) + { + users[i].Name = "blah" + users[i].Name; + i = i + 3; + } + ServiceContext.UserService.Save(users); + + long totalRecs; + var found = ServiceContext.UserService.GetAll(0, 2, out totalRecs, "username", Direction.Ascending, userGroups: new[] { userGroup.Alias }, filter: "blah"); + + Assert.AreEqual(2, found.Count()); + Assert.AreEqual(2, totalRecs); + Assert.AreEqual("test0", found.First().Username); + Assert.AreEqual("test6", found.Last().Username); + } + + [Test] + public void Count_All_Users() + { + var users = MockedUser.CreateMulipleUsers(10); + ServiceContext.UserService.Save(users); + var customUser = MockedUser.CreateUser(); + ServiceContext.UserService.Save(customUser); + + var found = ServiceContext.UserService.GetCount(MemberCountType.All); + + // + 1 because of the built in admin user + Assert.AreEqual(12, found); + } + + [Ignore("why?")] + [Test] + public void Count_All_Online_Users() + { + var users = MockedUser.CreateMulipleUsers(10, (i, member) => member.LastLoginDate = DateTime.Now.AddMinutes(i * -2)); + ServiceContext.UserService.Save(users); + + var customUser = MockedUser.CreateUser(); + throw new NotImplementedException(); + } + + [Test] + public void Count_All_Locked_Users() + { + var users = MockedUser.CreateMulipleUsers(10, (i, member) => member.IsLockedOut = i % 2 == 0); + ServiceContext.UserService.Save(users); + + var customUser = MockedUser.CreateUser(); + customUser.IsLockedOut = true; + ServiceContext.UserService.Save(customUser); + + var found = ServiceContext.UserService.GetCount(MemberCountType.LockedOut); + + Assert.AreEqual(6, found); + } + + [Test] + public void Count_All_Approved_Users() + { + var users = MockedUser.CreateMulipleUsers(10, (i, member) => member.IsApproved = i % 2 == 0); + ServiceContext.UserService.Save(users); + + var customUser = MockedUser.CreateUser(); + customUser.IsApproved = false; + ServiceContext.UserService.Save(customUser); + + var found = ServiceContext.UserService.GetCount(MemberCountType.Approved); + + // + 1 because of the built in admin user + Assert.AreEqual(6, found); + } + + [Test] + public void Can_Persist_New_User() + { + // Arrange + var userService = ServiceContext.UserService; + + // Act + var membershipUser = userService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io"); + + // Assert + Assert.That(membershipUser.HasIdentity, Is.True); + Assert.That(membershipUser.Id, Is.GreaterThan(0)); + IUser user = membershipUser as User; + Assert.That(user, Is.Not.Null); + } + + [Test] + public void Can_Persist_New_User_With_Hashed_Password() + { + // Arrange + var userService = ServiceContext.UserService; + + // Act + // NOTE: Normally the hash'ing would be handled in the membership provider, so the service just saves the password + var password = "123456"; + var hash = new HMACSHA1(); + hash.Key = Encoding.Unicode.GetBytes(password); + var encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password))); + var membershipUser = new User("JohnDoe", "john@umbraco.io", encodedPassword, encodedPassword); + userService.Save(membershipUser); + + // Assert + Assert.That(membershipUser.HasIdentity, Is.True); + Assert.That(membershipUser.RawPasswordValue, Is.Not.EqualTo(password)); + Assert.That(membershipUser.RawPasswordValue, Is.EqualTo(encodedPassword)); + IUser user = membershipUser as User; + Assert.That(user, Is.Not.Null); + } + + [Test] + public void Can_Add_And_Remove_Sections_From_UserGroup() + { + var userGroup = new UserGroup + { + Alias = "Group1", + Name = "Group 1" + }; + userGroup.AddAllowedSection("content"); + userGroup.AddAllowedSection("mediat"); + ServiceContext.UserService.Save(userGroup); + + var result1 = ServiceContext.UserService.GetUserGroupById(userGroup.Id); + + Assert.AreEqual(2, result1.AllowedSections.Count()); + + //adds some allowed sections + userGroup.AddAllowedSection("test1"); + userGroup.AddAllowedSection("test2"); + userGroup.AddAllowedSection("test3"); + userGroup.AddAllowedSection("test4"); + ServiceContext.UserService.Save(userGroup); + + result1 = ServiceContext.UserService.GetUserGroupById(userGroup.Id); + + Assert.AreEqual(6, result1.AllowedSections.Count()); + + //simulate clearing the sections + foreach (var s in userGroup.AllowedSections) + { + result1.RemoveAllowedSection(s); + } + + //now just re-add a couple + result1.AddAllowedSection("test3"); + result1.AddAllowedSection("test4"); + ServiceContext.UserService.Save(result1); + + //assert + //re-get + result1 = ServiceContext.UserService.GetUserGroupById(userGroup.Id); + Assert.AreEqual(2, result1.AllowedSections.Count()); + } + + [Test] + public void Can_Remove_Section_From_All_Assigned_UserGroups() + { + var userGroup1 = new UserGroup + { + Alias = "Group1", + Name = "Group 1" + }; + var userGroup2 = new UserGroup + { + Alias = "Group2", + Name = "Group 2" + }; + ServiceContext.UserService.Save(userGroup1); + ServiceContext.UserService.Save(userGroup2); + + //adds some allowed sections + userGroup1.AddAllowedSection("test"); + userGroup2.AddAllowedSection("test"); + ServiceContext.UserService.Save(userGroup1); + ServiceContext.UserService.Save(userGroup2); + + //now clear the section from all users + ServiceContext.UserService.DeleteSectionFromAllUserGroups("test"); + + //assert + var result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id); + var result2 = ServiceContext.UserService.GetUserGroupById(userGroup2.Id); + Assert.IsFalse(result1.AllowedSections.Contains("test")); + Assert.IsFalse(result2.AllowedSections.Contains("test")); + } + + [Test] + public void Can_Add_Section_To_All_UserGroups() + { + var userGroup1 = new UserGroup + { + Alias = "Group1", + Name = "Group 1" + }; + userGroup1.AddAllowedSection("test"); + + var userGroup2 = new UserGroup + { + Alias = "Group2", + Name = "Group 2" + }; + userGroup2.AddAllowedSection("test"); + + var userGroup3 = new UserGroup + { + Alias = "Group3", + Name = "Group 3" + }; + ServiceContext.UserService.Save(userGroup1); + ServiceContext.UserService.Save(userGroup2); + ServiceContext.UserService.Save(userGroup3); + + //assert + var result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id); + var result2 = ServiceContext.UserService.GetUserGroupById(userGroup2.Id); + var result3 = ServiceContext.UserService.GetUserGroupById(userGroup3.Id); + Assert.IsTrue(result1.AllowedSections.Contains("test")); + Assert.IsTrue(result2.AllowedSections.Contains("test")); + Assert.IsFalse(result3.AllowedSections.Contains("test")); + + //now add the section to all groups + foreach (var userGroup in new[] { userGroup1, userGroup2, userGroup3 }) + { + userGroup.AddAllowedSection("test"); + ServiceContext.UserService.Save(userGroup); + } + + //assert + result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id); + result2 = ServiceContext.UserService.GetUserGroupById(userGroup2.Id); + result3 = ServiceContext.UserService.GetUserGroupById(userGroup3.Id); + Assert.IsTrue(result1.AllowedSections.Contains("test")); + Assert.IsTrue(result2.AllowedSections.Contains("test")); + Assert.IsTrue(result3.AllowedSections.Contains("test")); + } + + [Test] + public void Cannot_Create_User_With_Empty_Username() + { + // Arrange + var userService = ServiceContext.UserService; + + // Act & Assert + Assert.Throws(() => userService.CreateUserWithIdentity(string.Empty, "john@umbraco.io")); + } + + [Test] + public void Cannot_Save_User_With_Empty_Username() + { + // Arrange + var userService = ServiceContext.UserService; + var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io"); + user.Username = string.Empty; + + // Act & Assert + Assert.Throws(() => userService.Save(user)); + } + + [Test] + public void Cannot_Save_User_With_Empty_Name() + { + // Arrange + var userService = ServiceContext.UserService; + var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io"); + user.Name = string.Empty; + + // Act & Assert + Assert.Throws(() => userService.Save(user)); + } + + [Test] + public void Get_By_Profile_Username() + { + // Arrange + var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); + + // Act + + var profile = ServiceContext.UserService.GetProfileByUserName(user.Username); + + // Assert + Assert.IsNotNull(profile); + Assert.AreEqual(user.Username, profile.Name); + Assert.AreEqual(user.Id, profile.Id); + } + + [Test] + public void Get_By_Profile_Id() + { + // Arrange + var user = (IUser)ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); + + // Act + + var profile = ServiceContext.UserService.GetProfileById((int)user.Id); + + // Assert + Assert.IsNotNull(profile); + Assert.AreEqual(user.Username, profile.Name); + Assert.AreEqual(user.Id, profile.Id); + } + + [Test] + public void Get_User_By_Username() + { + // Arrange + IUserGroup userGroup; + var originalUser = CreateTestUser(out userGroup); + + // Act + + var updatedItem = (User) ServiceContext.UserService.GetByUsername(originalUser.Username); + + // Assert + Assert.IsNotNull(updatedItem); + Assert.That(updatedItem.Id, Is.EqualTo(originalUser.Id)); + Assert.That(updatedItem.Name, Is.EqualTo(originalUser.Name)); + Assert.That(updatedItem.Language, Is.EqualTo(originalUser.Language)); + Assert.That(updatedItem.IsApproved, Is.EqualTo(originalUser.IsApproved)); + Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(originalUser.RawPasswordValue)); + Assert.That(updatedItem.IsLockedOut, Is.EqualTo(originalUser.IsLockedOut)); + Assert.IsTrue(updatedItem.StartContentIds.UnsortedSequenceEqual(originalUser.StartContentIds)); + Assert.IsTrue(updatedItem.StartMediaIds.UnsortedSequenceEqual(originalUser.StartMediaIds)); + Assert.That(updatedItem.Email, Is.EqualTo(originalUser.Email)); + Assert.That(updatedItem.Username, Is.EqualTo(originalUser.Username)); + Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(originalUser.AllowedSections.Count())); + } + + private IUser CreateTestUser(out IUserGroup userGroup) + { + userGroup = CreateTestUserGroup(); + + var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); + user.AddGroup(userGroup.ToReadOnlyGroup()); + ServiceContext.UserService.Save(user); + return user; + } + + private UserGroup CreateTestUserGroup(string alias = "testGroup", string name = "Test Group") + { + var userGroup = new UserGroup + { + Alias = alias, + Name = name, + Permissions = "ABCDEFGHIJ1234567".ToCharArray().Select(x => x.ToString()) + }; + + userGroup.AddAllowedSection("content"); + userGroup.AddAllowedSection("media"); + + ServiceContext.UserService.Save(userGroup); + + return userGroup; + } + } +} diff --git a/src/Umbraco.Tests/TeamCity.proj b/src/Umbraco.Tests/TeamCity.proj index 0bc83a35f4..42cdd909ae 100644 --- a/src/Umbraco.Tests/TeamCity.proj +++ b/src/Umbraco.Tests/TeamCity.proj @@ -1,19 +1,19 @@ - - - - ..\..\tools\MSBuildCommunityTasks - - - - - - - - - - - - - - + + + + ..\..\tools\MSBuildCommunityTasks + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Tests/Templates/MasterPageHelperTests.cs b/src/Umbraco.Tests/Templates/MasterPageHelperTests.cs index 0adf29670d..a8344893c0 100644 --- a/src/Umbraco.Tests/Templates/MasterPageHelperTests.cs +++ b/src/Umbraco.Tests/Templates/MasterPageHelperTests.cs @@ -1,21 +1,21 @@ -using NUnit.Framework; -using Umbraco.Core.IO; - -namespace Umbraco.Tests.Templates -{ - [TestFixture] - public class MasterPageHelperTests - { - - [TestCase(@"<%@ master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] - [TestCase(@"<%@ Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] - [TestCase(@"<%@Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] - [TestCase(@"<%@ Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] - [TestCase(@"<%@master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] - public void IsMasterPageSyntax(string design) - { - Assert.IsTrue(MasterPageHelper.IsMasterPageSyntax(design)); - } - - } -} +using NUnit.Framework; +using Umbraco.Core.IO; + +namespace Umbraco.Tests.Templates +{ + [TestFixture] + public class MasterPageHelperTests + { + + [TestCase(@"<%@ master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] + [TestCase(@"<%@ Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] + [TestCase(@"<%@Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] + [TestCase(@"<%@ Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] + [TestCase(@"<%@master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] + public void IsMasterPageSyntax(string design) + { + Assert.IsTrue(MasterPageHelper.IsMasterPageSyntax(design)); + } + + } +} diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index e28e3f7c29..bc29139918 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -1,71 +1,71 @@ -using LightInject; -using Moq; -using NPoco; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Profiling; -using Umbraco.Core.Composing; -using Umbraco.Core.Persistence; - -namespace Umbraco.Tests.TestHelpers -{ - [TestFixture] - public abstract class BaseUsingSqlCeSyntax - { - protected IMapperCollection Mappers { get; private set; } - - protected ISqlContext SqlContext { get; private set; } - - internal TestObjects TestObjects = new TestObjects(null); - - protected Sql Sql() - { - return NPoco.Sql.BuilderFor(SqlContext); - } - - [SetUp] - public virtual void Initialize() - { +using LightInject; +using Moq; +using NPoco; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Profiling; +using Umbraco.Core.Composing; +using Umbraco.Core.Persistence; + +namespace Umbraco.Tests.TestHelpers +{ + [TestFixture] + public abstract class BaseUsingSqlCeSyntax + { + protected IMapperCollection Mappers { get; private set; } + + protected ISqlContext SqlContext { get; private set; } + + internal TestObjects TestObjects = new TestObjects(null); + + protected Sql Sql() + { + return NPoco.Sql.BuilderFor(SqlContext); + } + + [SetUp] + public virtual void Initialize() + { Current.Reset(); - - var sqlSyntax = new SqlCeSyntaxProvider(); - - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); - - container.RegisterSingleton(factory => Mock.Of()); - container.RegisterSingleton(factory => Mock.Of()); - - var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); + + var sqlSyntax = new SqlCeSyntaxProvider(); + + var container = new ServiceContainer(); + container.ConfigureUmbracoCore(); + + container.RegisterSingleton(factory => Mock.Of()); + container.RegisterSingleton(factory => Mock.Of()); + + var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); var pluginManager = new TypeLoader(NullCacheProvider.Instance, - SettingsForTests.GenerateMockGlobalSettings(), - logger, - false); - container.RegisterInstance(pluginManager); - - container.RegisterCollectionBuilder() - .Add(() => Current.TypeLoader.GetAssignedMapperTypes()); - Mappers = container.GetInstance(); - - var pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; - var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, pocoMappers).Init()); - SqlContext = new SqlContext(sqlSyntax, DatabaseType.SQLCe, pocoDataFactory, Mappers); - - SetUp(); - } - - public virtual void SetUp() - {} - - [TearDown] - public virtual void TearDown() - { - //MappingResolver.Reset(); - Current.Reset(); - } - } -} + SettingsForTests.GenerateMockGlobalSettings(), + logger, + false); + container.RegisterInstance(pluginManager); + + container.RegisterCollectionBuilder() + .Add(() => Current.TypeLoader.GetAssignedMapperTypes()); + Mappers = container.GetInstance(); + + var pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; + var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, pocoMappers).Init()); + SqlContext = new SqlContext(sqlSyntax, DatabaseType.SQLCe, pocoDataFactory, Mappers); + + SetUp(); + } + + public virtual void SetUp() + {} + + [TearDown] + public virtual void TearDown() + { + //MappingResolver.Reset(); + Current.Reset(); + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index d99fde9ca3..5eea6bcf72 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -1,105 +1,105 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using LightInject; -using Moq; -using NUnit.Framework; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Tests.PublishedContent; -using Umbraco.Tests.TestHelpers.Stubs; -using Umbraco.Tests.Testing.Objects.Accessors; -using Umbraco.Web.Models.PublishedContent; -using Umbraco.Web.Routing; - -namespace Umbraco.Tests.TestHelpers -{ - [TestFixture] - [Apartment(ApartmentState.STA)] - public abstract class BaseWebTest : TestWithDatabaseBase - { - protected override void Compose() - { +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using LightInject; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Tests.PublishedContent; +using Umbraco.Tests.TestHelpers.Stubs; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web.Models.PublishedContent; +using Umbraco.Web.Routing; + +namespace Umbraco.Tests.TestHelpers +{ + [TestFixture] + [Apartment(ApartmentState.STA)] + public abstract class BaseWebTest : TestWithDatabaseBase + { + protected override void Compose() + { base.Compose(); - + Container.RegisterSingleton(); - Container.RegisterSingleton(); - } - - protected override void Initialize() - { - base.Initialize(); - - // need to specify a custom callback for unit tests - // AutoPublishedContentTypes generates properties automatically - - var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); - - var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); - var type = new AutoPublishedContentType(0, "anything", new PublishedPropertyType[] { }); - ContentTypesCache.GetPublishedContentTypeByAlias = alias => GetPublishedContentTypeByAlias(alias) ?? type; - } - - protected virtual PublishedContentType GetPublishedContentTypeByAlias(string alias) => null; - - protected override string GetXmlContent(int templateId) - { - return @" - - - - -]> - - - - - 1 - - This is some content]]> - - - - - - - - - - - - - - - - - - -"; - } - - internal PublishedRouter CreatePublishedRouter(IServiceContainer container = null, ContentFinderCollection contentFinders = null) - { - return CreatePublishedRouter(TestObjects.GetUmbracoSettings().WebRouting, container, contentFinders); - } - - internal static PublishedRouter CreatePublishedRouter(IWebRoutingSection webRoutingSection, IServiceContainer container = null, ContentFinderCollection contentFinders = null) - { - return new PublishedRouter( - webRoutingSection, - contentFinders ?? new ContentFinderCollection(Enumerable.Empty()), + Container.RegisterSingleton(); + } + + protected override void Initialize() + { + base.Initialize(); + + // need to specify a custom callback for unit tests + // AutoPublishedContentTypes generates properties automatically + + var dataTypeService = new TestObjects.TestDataTypeService( + new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); + + var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); + var type = new AutoPublishedContentType(0, "anything", new PublishedPropertyType[] { }); + ContentTypesCache.GetPublishedContentTypeByAlias = alias => GetPublishedContentTypeByAlias(alias) ?? type; + } + + protected virtual PublishedContentType GetPublishedContentTypeByAlias(string alias) => null; + + protected override string GetXmlContent(int templateId) + { + return @" + + + + +]> + + + + + 1 + + This is some content]]> + + + + + + + + + + + + + + + + + + +"; + } + + internal PublishedRouter CreatePublishedRouter(IServiceContainer container = null, ContentFinderCollection contentFinders = null) + { + return CreatePublishedRouter(TestObjects.GetUmbracoSettings().WebRouting, container, contentFinders); + } + + internal static PublishedRouter CreatePublishedRouter(IWebRoutingSection webRoutingSection, IServiceContainer container = null, ContentFinderCollection contentFinders = null) + { + return new PublishedRouter( + webRoutingSection, + contentFinders ?? new ContentFinderCollection(Enumerable.Empty()), new TestLastChanceFinder(), - new TestVariationContextAccessor(), - container?.TryGetInstance() ?? new ServiceContext(), - new ProfilingLogger(Mock.Of(), Mock.Of())); - } - } -} + new TestVariationContextAccessor(), + container?.TryGetInstance() ?? new ServiceContext(), + new ProfilingLogger(Mock.Of(), Mock.Of())); + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs index 14d914be1d..faf4acf8a4 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs @@ -1,168 +1,168 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public class MockedContent - { - public static Content CreateBasicContent(IContentType contentType) - { - var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; - - content.ResetDirtyProperties(false); - - return content; - } - - public static Content CreateSimpleContent(IContentType contentType) - { - var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; - object obj = - new - { - title = "Welcome to our Home page", - bodyText = "This is the welcome message on the first page", - author = "John Doe" - }; - - content.PropertyValues(obj); - - content.ResetDirtyProperties(false); - - return content; - } - - public static Content CreateSimpleContent(IContentType contentType, string name, int parentId = -1, string culture = null, string segment = null) - { - var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 }; - object obj = - new - { - title = name + " Subpage", - bodyText = "This is a subpage", - author = "John Doe" - }; - - content.PropertyValues(obj, culture, segment); - - content.ResetDirtyProperties(false); - - return content; - } - - public static Content CreateSimpleContent(IContentType contentType, string name, IContent parent, string culture = null, string segment = null, bool setPropertyValues = true) - { +using System; +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.TestHelpers.Entities +{ + public class MockedContent + { + public static Content CreateBasicContent(IContentType contentType) + { + var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; + + content.ResetDirtyProperties(false); + + return content; + } + + public static Content CreateSimpleContent(IContentType contentType) + { + var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; + object obj = + new + { + title = "Welcome to our Home page", + bodyText = "This is the welcome message on the first page", + author = "John Doe" + }; + + content.PropertyValues(obj); + + content.ResetDirtyProperties(false); + + return content; + } + + public static Content CreateSimpleContent(IContentType contentType, string name, int parentId = -1, string culture = null, string segment = null) + { + var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 }; + object obj = + new + { + title = name + " Subpage", + bodyText = "This is a subpage", + author = "John Doe" + }; + + content.PropertyValues(obj, culture, segment); + + content.ResetDirtyProperties(false); + + return content; + } + + public static Content CreateSimpleContent(IContentType contentType, string name, IContent parent, string culture = null, string segment = null, bool setPropertyValues = true) + { var content = new Content(name, parent, contentType, culture) { CreatorId = 0, WriterId = 0 }; if (setPropertyValues) { - object obj = - new - { - title = name + " Subpage", - bodyText = "This is a subpage", - author = "John Doe" + object obj = + new + { + title = name + " Subpage", + bodyText = "This is a subpage", + author = "John Doe" }; content.PropertyValues(obj, culture, segment); } - - content.ResetDirtyProperties(false); - - return content; - } - - public static Content CreateTextpageContent(IContentType contentType, string name, int parentId) - { - var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0}; - object obj = - new - { - title = name + " textpage", - bodyText = string.Format("This is a textpage based on the {0} ContentType", contentType.Alias), - keywords = "text,page,meta", - description = "This is the meta description for a textpage" - }; - - content.PropertyValues(obj); - - content.ResetDirtyProperties(false); - - return content; - } - - public static Content CreateSimpleContentWithSpecialDatabaseTypes(IContentType contentType, string name, int parentId, string decimalValue, string intValue, DateTime datetimeValue) - { - var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 }; - object obj = new - { - decimalProperty = decimalValue, - intProperty = intValue, - datetimeProperty = datetimeValue - }; - - content.PropertyValues(obj); - content.ResetDirtyProperties(false); - return content; - } - - public static Content CreateAllTypesContent(IContentType contentType, string name, int parentId) - { - var content = new Content("Random Content Name", parentId, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; - - content.SetValue("isTrue", true); - content.SetValue("number", 42); - content.SetValue("bodyText", "Lorem Ipsum Body Text Test"); - content.SetValue("singleLineText", "Single Line Text Test"); - content.SetValue("multilineText", "Multiple lines \n in one box"); - content.SetValue("upload", "/media/1234/koala.jpg"); - content.SetValue("label", "Non-editable label"); - content.SetValue("dateTime", DateTime.Now.AddDays(-20)); - content.SetValue("colorPicker", "black"); - //that one is gone in 7.4 - //content.SetValue("folderBrowser", ""); - content.SetValue("ddlMultiple", "1234,1235"); - content.SetValue("rbList", "random"); - content.SetValue("date", DateTime.Now.AddDays(-10)); - content.SetValue("ddl", "1234"); - content.SetValue("chklist", "randomc"); - content.SetValue("contentPicker", Udi.Create(Constants.UdiEntityType.Document, new Guid("74ECA1D4-934E-436A-A7C7-36CC16D4095C")).ToString()); - content.SetValue("mediaPicker", Udi.Create(Constants.UdiEntityType.Media, new Guid("44CB39C8-01E5-45EB-9CF8-E70AAF2D1691")).ToString()); - content.SetValue("memberPicker", Udi.Create(Constants.UdiEntityType.Member, new Guid("9A50A448-59C0-4D42-8F93-4F1D55B0F47D")).ToString()); - content.SetValue("relatedLinks", ""); - content.SetValue("tags", "this,is,tags"); - - return content; - } - - public static IEnumerable CreateTextpageContent(IContentType contentType, int parentId, int amount) - { - var list = new List(); - - for (int i = 0; i < amount; i++) - { - var name = "Textpage No-" + i; - var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 }; - object obj = - new - { - title = name + " title", - bodyText = string.Format("This is a textpage based on the {0} ContentType", contentType.Alias), - keywords = "text,page,meta", - description = "This is the meta description for a textpage" - }; - - content.PropertyValues(obj); - - content.ResetDirtyProperties(false); - - list.Add(content); - } - - return list; - } - } -} + + content.ResetDirtyProperties(false); + + return content; + } + + public static Content CreateTextpageContent(IContentType contentType, string name, int parentId) + { + var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0}; + object obj = + new + { + title = name + " textpage", + bodyText = string.Format("This is a textpage based on the {0} ContentType", contentType.Alias), + keywords = "text,page,meta", + description = "This is the meta description for a textpage" + }; + + content.PropertyValues(obj); + + content.ResetDirtyProperties(false); + + return content; + } + + public static Content CreateSimpleContentWithSpecialDatabaseTypes(IContentType contentType, string name, int parentId, string decimalValue, string intValue, DateTime datetimeValue) + { + var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 }; + object obj = new + { + decimalProperty = decimalValue, + intProperty = intValue, + datetimeProperty = datetimeValue + }; + + content.PropertyValues(obj); + content.ResetDirtyProperties(false); + return content; + } + + public static Content CreateAllTypesContent(IContentType contentType, string name, int parentId) + { + var content = new Content("Random Content Name", parentId, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; + + content.SetValue("isTrue", true); + content.SetValue("number", 42); + content.SetValue("bodyText", "Lorem Ipsum Body Text Test"); + content.SetValue("singleLineText", "Single Line Text Test"); + content.SetValue("multilineText", "Multiple lines \n in one box"); + content.SetValue("upload", "/media/1234/koala.jpg"); + content.SetValue("label", "Non-editable label"); + content.SetValue("dateTime", DateTime.Now.AddDays(-20)); + content.SetValue("colorPicker", "black"); + //that one is gone in 7.4 + //content.SetValue("folderBrowser", ""); + content.SetValue("ddlMultiple", "1234,1235"); + content.SetValue("rbList", "random"); + content.SetValue("date", DateTime.Now.AddDays(-10)); + content.SetValue("ddl", "1234"); + content.SetValue("chklist", "randomc"); + content.SetValue("contentPicker", Udi.Create(Constants.UdiEntityType.Document, new Guid("74ECA1D4-934E-436A-A7C7-36CC16D4095C")).ToString()); + content.SetValue("mediaPicker", Udi.Create(Constants.UdiEntityType.Media, new Guid("44CB39C8-01E5-45EB-9CF8-E70AAF2D1691")).ToString()); + content.SetValue("memberPicker", Udi.Create(Constants.UdiEntityType.Member, new Guid("9A50A448-59C0-4D42-8F93-4F1D55B0F47D")).ToString()); + content.SetValue("relatedLinks", ""); + content.SetValue("tags", "this,is,tags"); + + return content; + } + + public static IEnumerable CreateTextpageContent(IContentType contentType, int parentId, int amount) + { + var list = new List(); + + for (int i = 0; i < amount; i++) + { + var name = "Textpage No-" + i; + var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 }; + object obj = + new + { + title = name + " title", + bodyText = string.Format("This is a textpage based on the {0} ContentType", contentType.Alias), + keywords = "text,page,meta", + description = "This is the meta description for a textpage" + }; + + content.PropertyValues(obj); + + content.ResetDirtyProperties(false); + + list.Add(content); + } + + return list; + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index d9c87f10c9..752d0ac97e 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -1,496 +1,496 @@ -using System; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public class MockedContentTypes - { - public static ContentType CreateBasicContentType(string alias = "basePage", string name = "Base Page", IContentType parent = null) - { - var contentType = parent == null ? new ContentType(-1) : new ContentType(parent, alias); - - contentType.Alias = alias; - contentType.Name = name; - contentType.Description = "ContentType used for basic pages"; - contentType.Icon = ".sprTreeDoc3"; - contentType.Thumbnail = "doc2.png"; - contentType.SortOrder = 1; - contentType.CreatorId = 0; - contentType.Trashed = false; - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateTextpageContentType(string alias = "textPage", string name = "Text Page") - { - var contentType = new ContentType(-1) - { - Alias = alias, - Name = name, - Description = "ContentType used for Text pages", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); - - var metaCollection = new PropertyTypeCollection(true); - metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -89 }); - - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - contentType.SetDefaultTemplate(new Template("Textpage", "textpage")); - - return contentType; - } - - public static ContentType CreateMetaContentType() - { - var contentType = new ContentType(-1) - { - Alias = "meta", - Name = "Meta", - Description = "ContentType used for Meta tags", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var metaCollection = new PropertyTypeCollection(true); - metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "metakeywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "metadescription", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -89 }); - - contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateContentMetaContentType() - { - var contentType = new ContentType(-1) - { - Alias = "contentMeta", - Name = "Content Meta", - Description = "ContentType used for Content Meta", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var metaCollection = new PropertyTypeCollection(true); - metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - - contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Content", SortOrder = 2 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSeoContentType() - { - var contentType = new ContentType(-1) - { - Alias = "seo", - Name = "Seo", - Description = "ContentType used for Seo", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var metaCollection = new PropertyTypeCollection(true); - metaCollection.Add(new PropertyType("seotest", ValueStorageType.Ntext) { Alias = "seokeywords", Name = "Seo Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - metaCollection.Add(new PropertyType("seotest", ValueStorageType.Ntext) { Alias = "seodescription", Name = "Seo Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -89 }); - - contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Seo", SortOrder = 5 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType() - { - var contentType = new ContentType(-1) - { - Alias = "simple", - Name = "Simple Page", - Description = "ContentType used for simple text pages", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); - - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType3(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") - { - var contentType = CreateSimpleContentType(alias, name, parent, randomizeAliases, propertyGroupName); - - var propertyType = new PropertyType(Constants.PropertyEditors.Aliases.Tags, ValueStorageType.Nvarchar) - { - Alias = RandomAlias("tags", randomizeAliases), - Name = "Tags", - Description = "Tags", - Mandatory = false, - SortOrder = 99, - DataTypeId = Constants.DataTypes.Tags - }; - contentType.AddPropertyType(propertyType); - - return contentType; - } - - public static ContentType CreateSimpleContentType2(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") - { - var contentType = CreateSimpleContentType(alias, name, parent, randomizeAliases, propertyGroupName); - - var propertyType = new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) - { - Alias = RandomAlias("gen", randomizeAliases), - Name = "Gen", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88 - }; - contentType.AddPropertyType(propertyType); - - return contentType; - } - - public static ContentType CreateSimpleContentType(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") - { - var contentType = parent == null ? new ContentType(-1) : new ContentType(parent, alias); - - contentType.Alias = alias; - contentType.Name = name; - contentType.Description = "ContentType used for simple text pages"; - contentType.Icon = ".sprTreeDoc3"; - contentType.Thumbnail = "doc2.png"; - contentType.SortOrder = 1; - contentType.CreatorId = 0; - contentType.Trashed = false; - - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("author", randomizeAliases) , Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); - - var pg = new PropertyGroup(contentCollection) {Name = propertyGroupName, SortOrder = 1}; - contentType.PropertyGroups.Add(pg); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - contentType.SetDefaultTemplate(new Template("Textpage", "textpage")); - - return contentType; - } - - public static MediaType CreateSimpleMediaType(string alias, string name, IMediaType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") - { - var contentType = parent == null ? new MediaType(-1) : new MediaType(parent, alias); - - contentType.Alias = alias; - contentType.Name = name; - contentType.Description = "ContentType used for simple text pages"; - contentType.Icon = ".sprTreeDoc3"; - contentType.Thumbnail = "doc2.png"; - contentType.SortOrder = 1; - contentType.CreatorId = 0; - contentType.Trashed = false; - - var contentCollection = new PropertyTypeCollection(false); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("author", randomizeAliases), Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); - - var pg = new PropertyGroup(contentCollection) { Name = propertyGroupName, SortOrder = 1 }; - contentType.PropertyGroups.Add(pg); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType(string alias, string name, bool mandatory) - { - var contentType = new ContentType(-1) - { - Alias = alias, - Name = name, - Description = "ContentType used for simple text pages", - Icon = ".sprTreeDoc3", - Thumbnail = "doc2.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = mandatory, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = mandatory, SortOrder = 2, DataTypeId = -87 }); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = mandatory, SortOrder = 3, DataTypeId = -88 }); - - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType(string alias, string name, PropertyTypeCollection collection) - { - var contentType = new ContentType(-1) - { - Alias = alias, - Name = name, - Description = "ContentType used for simple text pages", - Icon = ".sprTreeDoc3", - Thumbnail = "doc3.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - contentType.PropertyGroups.Add(new PropertyGroup(collection) { Name = "Content", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType(string alias, string name, PropertyTypeCollection collection, string propertyGroupName, IContentType parent = null) - { - var contentType = parent == null ? new ContentType(-1) : new ContentType(parent, alias); - - contentType.Alias = alias; - contentType.Name = name; - contentType.Description = "ContentType used for simple text pages"; - contentType.Icon = ".sprTreeDoc3"; - contentType.Thumbnail = "doc2.png"; - contentType.SortOrder = 1; - contentType.CreatorId = 0; - contentType.Trashed = false; - - contentType.PropertyGroups.Add(new PropertyGroup(collection) { Name = propertyGroupName, SortOrder = 1 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateSimpleContentType(string alias, string name, PropertyTypeCollection groupedCollection, PropertyTypeCollection nonGroupedCollection) - { - var contentType = CreateSimpleContentType(alias, name, groupedCollection); - //now add the non-grouped properties - foreach (var x in nonGroupedCollection) - contentType.AddPropertyType(x); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static ContentType CreateAllTypesContentType(string alias, string name) - { - var contentType = new ContentType(-1) - { - Alias = alias, - Name = name, - Description = "ContentType containing all standard DataTypes", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Boolean, ValueStorageType.Integer) { Alias = "isTrue", Name = "Is True or False", Mandatory = false, SortOrder = 1, DataTypeId = -49 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Integer, ValueStorageType.Integer) { Alias = "number", Name = "Number", Mandatory = false, SortOrder = 2, DataTypeId = -51 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Mandatory = false, SortOrder = 3, DataTypeId = -87 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Nvarchar) { Alias = "singleLineText", Name = "Text String", Mandatory = false, SortOrder = 4, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextArea, ValueStorageType.Ntext) { Alias = "multilineText", Name = "Multiple Text Strings", Mandatory = false, SortOrder = 5, DataTypeId = -89 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = "upload", Name = "Upload Field", Mandatory = false, SortOrder = 6, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar) { Alias = "label", Name = "Label", Mandatory = false, SortOrder = 7, DataTypeId = -92 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.DateTime, ValueStorageType.Date) { Alias = "dateTime", Name = "Date Time", Mandatory = false, SortOrder = 8, DataTypeId = -36 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ColorPicker, ValueStorageType.Nvarchar) { Alias = "colorPicker", Name = "Color Picker", Mandatory = false, SortOrder = 9, DataTypeId = -37 }); - //that one is gone in 7.4 - //contentCollection.Add(new PropertyType(Constants.PropertyEditors.FolderBrowserAlias, DataTypeDatabaseType.Nvarchar) { Alias = "folderBrowser", Name = "Folder Browser", Mandatory = false, SortOrder = 10, DataTypeDefinitionId = -38 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.DropDownListMultiple, ValueStorageType.Nvarchar) { Alias = "ddlMultiple", Name = "Dropdown List Multiple", Mandatory = false, SortOrder = 11, DataTypeId = -39 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.RadioButtonList, ValueStorageType.Nvarchar) { Alias = "rbList", Name = "Radio Button List", Mandatory = false, SortOrder = 12, DataTypeId = -40 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Date, ValueStorageType.Date) { Alias = "date", Name = "Date", Mandatory = false, SortOrder = 13, DataTypeId = -41 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.DropDownList, ValueStorageType.Integer) { Alias = "ddl", Name = "Dropdown List", Mandatory = false, SortOrder = 14, DataTypeId = -42 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.CheckBoxList, ValueStorageType.Nvarchar) { Alias = "chklist", Name = "Checkbox List", Mandatory = false, SortOrder = 15, DataTypeId = -43 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ContentPicker, ValueStorageType.Integer) { Alias = "contentPicker", Name = "Content Picker", Mandatory = false, SortOrder = 16, DataTypeId = 1046 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.MediaPicker, ValueStorageType.Integer) { Alias = "mediaPicker", Name = "Media Picker", Mandatory = false, SortOrder = 17, DataTypeId = 1048 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.MemberPicker, ValueStorageType.Integer) { Alias = "memberPicker", Name = "Member Picker", Mandatory = false, SortOrder = 18, DataTypeId = 1047 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.RelatedLinks, ValueStorageType.Ntext) { Alias = "relatedLinks", Name = "Related Links", Mandatory = false, SortOrder = 21, DataTypeId = 1050 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Tags, ValueStorageType.Ntext) { Alias = "tags", Name = "Tags", Mandatory = false, SortOrder = 22, DataTypeId = 1041 }); - - //contentCollection.Add(new PropertyType(Constants.PropertyEditors.UltraSimpleEditorAlias, DataTypeDatabaseType.Ntext) { Alias = "simpleEditor", Name = "Ultra Simple Editor", Mandatory = false, SortOrder = 19, DataTypeDefinitionId = 1038 }); - //contentCollection.Add(new PropertyType(Constants.PropertyEditors.UltimatePickerAlias, DataTypeDatabaseType.Ntext) { Alias = "ultimatePicker", Name = "Ultimate Picker", Mandatory = false, SortOrder = 20, DataTypeDefinitionId = 1039 }); - //contentCollection.Add(new PropertyType(Constants.PropertyEditors.MacroContainerAlias, DataTypeDatabaseType.Ntext) { Alias = "macroContainer", Name = "Macro Container", Mandatory = false, SortOrder = 23, DataTypeDefinitionId = 1042 }); - //contentCollection.Add(new PropertyType(Constants.PropertyEditors.ImageCropperAlias, DataTypeDatabaseType.Ntext) { Alias = "imgCropper", Name = "Image Cropper", Mandatory = false, SortOrder = 24, DataTypeDefinitionId = 1043 }); - - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - - return contentType; - } - - public static MediaType CreateVideoMediaType() - { - var mediaType = new MediaType(-1) - { - Alias = "video", - Name = "Video", - Description = "ContentType used for videos", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(false); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType("test", ValueStorageType.Nvarchar) { Alias = "videoFile", Name = "Video File", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - - mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - mediaType.ResetDirtyProperties(false); - - return mediaType; - } - - public static MediaType CreateImageMediaType(string alias = Constants.Conventions.MediaTypes.Image) - { - var mediaType = new MediaType(-1) - { - Alias = alias, - Name = "Image", - Description = "ContentType used for images", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(false); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - - mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - mediaType.ResetDirtyProperties(false); - - return mediaType; - } - - public static MemberType CreateSimpleMemberType(string alias = null, string name = null) - { - var contentType = new MemberType(-1) - { - Alias = alias ?? "simple", - Name = name ?? "Simple Page", - Description = "Some member type", - Icon = ".sprTreeDoc3", - Thumbnail = "doc.png", - SortOrder = 1, - CreatorId = 0, - Trashed = false - }; - - var contentCollection = new PropertyTypeCollection(false); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); - - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - - return contentType; - } - - public static void EnsureAllIds(ContentTypeCompositionBase contentType, int seedId) - { - //ensure everything has ids - contentType.Id = seedId; - var itemid = seedId + 1; - foreach (var propertyGroup in contentType.PropertyGroups) - { - propertyGroup.Id = itemid++; - } - foreach (var propertyType in contentType.PropertyTypes) - { - propertyType.Id = itemid++; - } - } - - - private static string RandomAlias(string alias, bool randomizeAliases) - { - if (randomizeAliases) - { - return string.Concat(alias, Guid.NewGuid().ToString("N")); - } - - return alias; - } - } -} +using System; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.TestHelpers.Entities +{ + public class MockedContentTypes + { + public static ContentType CreateBasicContentType(string alias = "basePage", string name = "Base Page", IContentType parent = null) + { + var contentType = parent == null ? new ContentType(-1) : new ContentType(parent, alias); + + contentType.Alias = alias; + contentType.Name = name; + contentType.Description = "ContentType used for basic pages"; + contentType.Icon = ".sprTreeDoc3"; + contentType.Thumbnail = "doc2.png"; + contentType.SortOrder = 1; + contentType.CreatorId = 0; + contentType.Trashed = false; + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static ContentType CreateTextpageContentType(string alias = "textPage", string name = "Text Page") + { + var contentType = new ContentType(-1) + { + Alias = alias, + Name = name, + Description = "ContentType used for Text pages", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); + + var metaCollection = new PropertyTypeCollection(true); + metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -89 }); + + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + contentType.SetDefaultTemplate(new Template("Textpage", "textpage")); + + return contentType; + } + + public static ContentType CreateMetaContentType() + { + var contentType = new ContentType(-1) + { + Alias = "meta", + Name = "Meta", + Description = "ContentType used for Meta tags", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var metaCollection = new PropertyTypeCollection(true); + metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "metakeywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "metadescription", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -89 }); + + contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static ContentType CreateContentMetaContentType() + { + var contentType = new ContentType(-1) + { + Alias = "contentMeta", + Name = "Content Meta", + Description = "ContentType used for Content Meta", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var metaCollection = new PropertyTypeCollection(true); + metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + + contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Content", SortOrder = 2 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static ContentType CreateSeoContentType() + { + var contentType = new ContentType(-1) + { + Alias = "seo", + Name = "Seo", + Description = "ContentType used for Seo", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var metaCollection = new PropertyTypeCollection(true); + metaCollection.Add(new PropertyType("seotest", ValueStorageType.Ntext) { Alias = "seokeywords", Name = "Seo Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + metaCollection.Add(new PropertyType("seotest", ValueStorageType.Ntext) { Alias = "seodescription", Name = "Seo Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -89 }); + + contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Seo", SortOrder = 5 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static ContentType CreateSimpleContentType() + { + var contentType = new ContentType(-1) + { + Alias = "simple", + Name = "Simple Page", + Description = "ContentType used for simple text pages", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); + + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static ContentType CreateSimpleContentType3(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") + { + var contentType = CreateSimpleContentType(alias, name, parent, randomizeAliases, propertyGroupName); + + var propertyType = new PropertyType(Constants.PropertyEditors.Aliases.Tags, ValueStorageType.Nvarchar) + { + Alias = RandomAlias("tags", randomizeAliases), + Name = "Tags", + Description = "Tags", + Mandatory = false, + SortOrder = 99, + DataTypeId = Constants.DataTypes.Tags + }; + contentType.AddPropertyType(propertyType); + + return contentType; + } + + public static ContentType CreateSimpleContentType2(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") + { + var contentType = CreateSimpleContentType(alias, name, parent, randomizeAliases, propertyGroupName); + + var propertyType = new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) + { + Alias = RandomAlias("gen", randomizeAliases), + Name = "Gen", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88 + }; + contentType.AddPropertyType(propertyType); + + return contentType; + } + + public static ContentType CreateSimpleContentType(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") + { + var contentType = parent == null ? new ContentType(-1) : new ContentType(parent, alias); + + contentType.Alias = alias; + contentType.Name = name; + contentType.Description = "ContentType used for simple text pages"; + contentType.Icon = ".sprTreeDoc3"; + contentType.Thumbnail = "doc2.png"; + contentType.SortOrder = 1; + contentType.CreatorId = 0; + contentType.Trashed = false; + + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("author", randomizeAliases) , Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); + + var pg = new PropertyGroup(contentCollection) {Name = propertyGroupName, SortOrder = 1}; + contentType.PropertyGroups.Add(pg); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + contentType.SetDefaultTemplate(new Template("Textpage", "textpage")); + + return contentType; + } + + public static MediaType CreateSimpleMediaType(string alias, string name, IMediaType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") + { + var contentType = parent == null ? new MediaType(-1) : new MediaType(parent, alias); + + contentType.Alias = alias; + contentType.Name = name; + contentType.Description = "ContentType used for simple text pages"; + contentType.Icon = ".sprTreeDoc3"; + contentType.Thumbnail = "doc2.png"; + contentType.SortOrder = 1; + contentType.CreatorId = 0; + contentType.Trashed = false; + + var contentCollection = new PropertyTypeCollection(false); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("author", randomizeAliases), Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); + + var pg = new PropertyGroup(contentCollection) { Name = propertyGroupName, SortOrder = 1 }; + contentType.PropertyGroups.Add(pg); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static ContentType CreateSimpleContentType(string alias, string name, bool mandatory) + { + var contentType = new ContentType(-1) + { + Alias = alias, + Name = name, + Description = "ContentType used for simple text pages", + Icon = ".sprTreeDoc3", + Thumbnail = "doc2.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = mandatory, SortOrder = 1, DataTypeId = -88 }); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = mandatory, SortOrder = 2, DataTypeId = -87 }); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = mandatory, SortOrder = 3, DataTypeId = -88 }); + + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static ContentType CreateSimpleContentType(string alias, string name, PropertyTypeCollection collection) + { + var contentType = new ContentType(-1) + { + Alias = alias, + Name = name, + Description = "ContentType used for simple text pages", + Icon = ".sprTreeDoc3", + Thumbnail = "doc3.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + contentType.PropertyGroups.Add(new PropertyGroup(collection) { Name = "Content", SortOrder = 1 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static ContentType CreateSimpleContentType(string alias, string name, PropertyTypeCollection collection, string propertyGroupName, IContentType parent = null) + { + var contentType = parent == null ? new ContentType(-1) : new ContentType(parent, alias); + + contentType.Alias = alias; + contentType.Name = name; + contentType.Description = "ContentType used for simple text pages"; + contentType.Icon = ".sprTreeDoc3"; + contentType.Thumbnail = "doc2.png"; + contentType.SortOrder = 1; + contentType.CreatorId = 0; + contentType.Trashed = false; + + contentType.PropertyGroups.Add(new PropertyGroup(collection) { Name = propertyGroupName, SortOrder = 1 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static ContentType CreateSimpleContentType(string alias, string name, PropertyTypeCollection groupedCollection, PropertyTypeCollection nonGroupedCollection) + { + var contentType = CreateSimpleContentType(alias, name, groupedCollection); + //now add the non-grouped properties + foreach (var x in nonGroupedCollection) + contentType.AddPropertyType(x); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static ContentType CreateAllTypesContentType(string alias, string name) + { + var contentType = new ContentType(-1) + { + Alias = alias, + Name = name, + Description = "ContentType containing all standard DataTypes", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Boolean, ValueStorageType.Integer) { Alias = "isTrue", Name = "Is True or False", Mandatory = false, SortOrder = 1, DataTypeId = -49 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Integer, ValueStorageType.Integer) { Alias = "number", Name = "Number", Mandatory = false, SortOrder = 2, DataTypeId = -51 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Mandatory = false, SortOrder = 3, DataTypeId = -87 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Nvarchar) { Alias = "singleLineText", Name = "Text String", Mandatory = false, SortOrder = 4, DataTypeId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextArea, ValueStorageType.Ntext) { Alias = "multilineText", Name = "Multiple Text Strings", Mandatory = false, SortOrder = 5, DataTypeId = -89 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = "upload", Name = "Upload Field", Mandatory = false, SortOrder = 6, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar) { Alias = "label", Name = "Label", Mandatory = false, SortOrder = 7, DataTypeId = -92 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.DateTime, ValueStorageType.Date) { Alias = "dateTime", Name = "Date Time", Mandatory = false, SortOrder = 8, DataTypeId = -36 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ColorPicker, ValueStorageType.Nvarchar) { Alias = "colorPicker", Name = "Color Picker", Mandatory = false, SortOrder = 9, DataTypeId = -37 }); + //that one is gone in 7.4 + //contentCollection.Add(new PropertyType(Constants.PropertyEditors.FolderBrowserAlias, DataTypeDatabaseType.Nvarchar) { Alias = "folderBrowser", Name = "Folder Browser", Mandatory = false, SortOrder = 10, DataTypeDefinitionId = -38 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.DropDownListMultiple, ValueStorageType.Nvarchar) { Alias = "ddlMultiple", Name = "Dropdown List Multiple", Mandatory = false, SortOrder = 11, DataTypeId = -39 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.RadioButtonList, ValueStorageType.Nvarchar) { Alias = "rbList", Name = "Radio Button List", Mandatory = false, SortOrder = 12, DataTypeId = -40 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Date, ValueStorageType.Date) { Alias = "date", Name = "Date", Mandatory = false, SortOrder = 13, DataTypeId = -41 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.DropDownList, ValueStorageType.Integer) { Alias = "ddl", Name = "Dropdown List", Mandatory = false, SortOrder = 14, DataTypeId = -42 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.CheckBoxList, ValueStorageType.Nvarchar) { Alias = "chklist", Name = "Checkbox List", Mandatory = false, SortOrder = 15, DataTypeId = -43 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ContentPicker, ValueStorageType.Integer) { Alias = "contentPicker", Name = "Content Picker", Mandatory = false, SortOrder = 16, DataTypeId = 1046 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.MediaPicker, ValueStorageType.Integer) { Alias = "mediaPicker", Name = "Media Picker", Mandatory = false, SortOrder = 17, DataTypeId = 1048 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.MemberPicker, ValueStorageType.Integer) { Alias = "memberPicker", Name = "Member Picker", Mandatory = false, SortOrder = 18, DataTypeId = 1047 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.RelatedLinks, ValueStorageType.Ntext) { Alias = "relatedLinks", Name = "Related Links", Mandatory = false, SortOrder = 21, DataTypeId = 1050 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Tags, ValueStorageType.Ntext) { Alias = "tags", Name = "Tags", Mandatory = false, SortOrder = 22, DataTypeId = 1041 }); + + //contentCollection.Add(new PropertyType(Constants.PropertyEditors.UltraSimpleEditorAlias, DataTypeDatabaseType.Ntext) { Alias = "simpleEditor", Name = "Ultra Simple Editor", Mandatory = false, SortOrder = 19, DataTypeDefinitionId = 1038 }); + //contentCollection.Add(new PropertyType(Constants.PropertyEditors.UltimatePickerAlias, DataTypeDatabaseType.Ntext) { Alias = "ultimatePicker", Name = "Ultimate Picker", Mandatory = false, SortOrder = 20, DataTypeDefinitionId = 1039 }); + //contentCollection.Add(new PropertyType(Constants.PropertyEditors.MacroContainerAlias, DataTypeDatabaseType.Ntext) { Alias = "macroContainer", Name = "Macro Container", Mandatory = false, SortOrder = 23, DataTypeDefinitionId = 1042 }); + //contentCollection.Add(new PropertyType(Constants.PropertyEditors.ImageCropperAlias, DataTypeDatabaseType.Ntext) { Alias = "imgCropper", Name = "Image Cropper", Mandatory = false, SortOrder = 24, DataTypeDefinitionId = 1043 }); + + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + + return contentType; + } + + public static MediaType CreateVideoMediaType() + { + var mediaType = new MediaType(-1) + { + Alias = "video", + Name = "Video", + Description = "ContentType used for videos", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var contentCollection = new PropertyTypeCollection(false); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + contentCollection.Add(new PropertyType("test", ValueStorageType.Nvarchar) { Alias = "videoFile", Name = "Video File", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + + mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); + + //ensure that nothing is marked as dirty + mediaType.ResetDirtyProperties(false); + + return mediaType; + } + + public static MediaType CreateImageMediaType(string alias = Constants.Conventions.MediaTypes.Image) + { + var mediaType = new MediaType(-1) + { + Alias = alias, + Name = "Image", + Description = "ContentType used for images", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var contentCollection = new PropertyTypeCollection(false); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + + mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); + + //ensure that nothing is marked as dirty + mediaType.ResetDirtyProperties(false); + + return mediaType; + } + + public static MemberType CreateSimpleMemberType(string alias = null, string name = null) + { + var contentType = new MemberType(-1) + { + Alias = alias ?? "simple", + Name = name ?? "Simple Page", + Description = "Some member type", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var contentCollection = new PropertyTypeCollection(false); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); + + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static void EnsureAllIds(ContentTypeCompositionBase contentType, int seedId) + { + //ensure everything has ids + contentType.Id = seedId; + var itemid = seedId + 1; + foreach (var propertyGroup in contentType.PropertyGroups) + { + propertyGroup.Id = itemid++; + } + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = itemid++; + } + } + + + private static string RandomAlias(string alias, bool randomizeAliases) + { + if (randomizeAliases) + { + return string.Concat(alias, Guid.NewGuid().ToString("N")); + } + + return alias; + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedEntity.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedEntity.cs index e78980aa83..22f6376760 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedEntity.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedEntity.cs @@ -1,26 +1,26 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - [Serializable] - [DataContract(IsReference = true)] - public class MockedEntity : EntityBase - { - [DataMember] - public string Alias { get; set; } - - [DataMember] - public string Name { get; set; } - - [DataMember] - public string Value { get; set; } - } - - public class CustomMockedEntity : MockedEntity - { - [DataMember] - public string Title { get; set; } - } -} +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Tests.TestHelpers.Entities +{ + [Serializable] + [DataContract(IsReference = true)] + public class MockedEntity : EntityBase + { + [DataMember] + public string Alias { get; set; } + + [DataMember] + public string Name { get; set; } + + [DataMember] + public string Value { get; set; } + } + + public class CustomMockedEntity : MockedEntity + { + [DataMember] + public string Title { get; set; } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs index 62329eac64..e7b3f68c85 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs @@ -1,83 +1,83 @@ -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public static class MockedMedia - { - public static Media CreateSimpleMedia(IMediaType contentType, string name, int parentId) - { - var content = new Media(name, parentId, contentType) { CreatorId = 0 }; - object obj = - new - { - title = name + " Subpage", - bodyText = "This is a subpage", - author = "John Doe" - }; - - content.PropertyValues(obj); - - content.ResetDirtyProperties(false); - - return content; - } - - public static Media CreateMediaImage(IMediaType mediaType, int parentId) - { - var media = new Media("Test Image", parentId, mediaType) - { - CreatorId = 0 - }; - - media.SetValue(Constants.Conventions.Media.File, "/media/test-image.png"); - media.SetValue(Constants.Conventions.Media.Width, "200"); - media.SetValue(Constants.Conventions.Media.Height, "200"); - media.SetValue(Constants.Conventions.Media.Bytes, "100"); - media.SetValue(Constants.Conventions.Media.Extension, "png"); - - return media; - } - - public static Media CreateMediaFile(IMediaType mediaType, int parentId) - { - var media = new Media("Test File", parentId, mediaType) - { - CreatorId = 0 - }; - - media.SetValue(Constants.Conventions.Media.File, "/media/test-file.txt"); - media.SetValue(Constants.Conventions.Media.Bytes, "4"); - media.SetValue(Constants.Conventions.Media.Extension, "txt"); - - return media; - } - - public static Media CreateMediaImageWithCrop(IMediaType mediaType, int parentId) - { - var media = new Media("Test Image", parentId, mediaType) - { - CreatorId = 0 - }; - - media.SetValue(Constants.Conventions.Media.File, "{src: '/media/test-image.png', crops: []}"); - media.SetValue(Constants.Conventions.Media.Width, "200"); - media.SetValue(Constants.Conventions.Media.Height, "200"); - media.SetValue(Constants.Conventions.Media.Bytes, "100"); - media.SetValue(Constants.Conventions.Media.Extension, "png"); - - return media; - } - - public static Media CreateMediaFolder(IMediaType mediaType, int parentId) - { - var media = new Media("Test Folder", parentId, mediaType) - { - CreatorId = 0 - }; - - return media; - } - } -} +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.TestHelpers.Entities +{ + public static class MockedMedia + { + public static Media CreateSimpleMedia(IMediaType contentType, string name, int parentId) + { + var content = new Media(name, parentId, contentType) { CreatorId = 0 }; + object obj = + new + { + title = name + " Subpage", + bodyText = "This is a subpage", + author = "John Doe" + }; + + content.PropertyValues(obj); + + content.ResetDirtyProperties(false); + + return content; + } + + public static Media CreateMediaImage(IMediaType mediaType, int parentId) + { + var media = new Media("Test Image", parentId, mediaType) + { + CreatorId = 0 + }; + + media.SetValue(Constants.Conventions.Media.File, "/media/test-image.png"); + media.SetValue(Constants.Conventions.Media.Width, "200"); + media.SetValue(Constants.Conventions.Media.Height, "200"); + media.SetValue(Constants.Conventions.Media.Bytes, "100"); + media.SetValue(Constants.Conventions.Media.Extension, "png"); + + return media; + } + + public static Media CreateMediaFile(IMediaType mediaType, int parentId) + { + var media = new Media("Test File", parentId, mediaType) + { + CreatorId = 0 + }; + + media.SetValue(Constants.Conventions.Media.File, "/media/test-file.txt"); + media.SetValue(Constants.Conventions.Media.Bytes, "4"); + media.SetValue(Constants.Conventions.Media.Extension, "txt"); + + return media; + } + + public static Media CreateMediaImageWithCrop(IMediaType mediaType, int parentId) + { + var media = new Media("Test Image", parentId, mediaType) + { + CreatorId = 0 + }; + + media.SetValue(Constants.Conventions.Media.File, "{src: '/media/test-image.png', crops: []}"); + media.SetValue(Constants.Conventions.Media.Width, "200"); + media.SetValue(Constants.Conventions.Media.Height, "200"); + media.SetValue(Constants.Conventions.Media.Bytes, "100"); + media.SetValue(Constants.Conventions.Media.Extension, "png"); + + return media; + } + + public static Media CreateMediaFolder(IMediaType mediaType, int parentId) + { + var media = new Media("Test Folder", parentId, mediaType) + { + CreatorId = 0 + }; + + return media; + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs index 9817578617..b721b508ba 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs @@ -1,82 +1,82 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public class MockedMember - { - public static Member CreateSimpleMember(IMemberType contentType, string name, string email, string password, string username, Guid? key = null) - { - var member = new Member(name, email, username, password, contentType) - { - CreatorId = 0, - Email = email, - RawPasswordValue = password, - Username = username - }; - - if (key.HasValue) - { - member.Key = key.Value; - } - - member.SetValue("title", name + " member"); - member.SetValue("bodyText", "This is a subpage"); - member.SetValue("author", "John Doe"); - - member.ResetDirtyProperties(false); - - return member; - } - - public static Member CreateSimpleMember(IMemberType contentType, string name, string email, string username, Guid? key = null) - { - var member = new Member(name, email, username, contentType) - { - CreatorId = 0, - Email = email, - Username = username - }; - - if (key.HasValue) - { - member.Key = key.Value; - } - - member.SetValue("title", name + " member"); - member.SetValue("bodyText", "This is a subpage"); - member.SetValue("author", "John Doe"); - - member.ResetDirtyProperties(false); - - return member; - } - - public static IEnumerable CreateSimpleMember(IMemberType memberType, int amount, Action onCreating = null) - { - var list = new List(); - - for (int i = 0; i < amount; i++) - { - var name = "Member No-" + i; - var member = new Member(name, "test" + i + "@test.com", "test" + i, "test" + i, memberType); - member.SetValue("title", name + " member" + i); - member.SetValue("bodyText", "This is a subpage" + i); - member.SetValue("author", "John Doe" + i); - - if (onCreating != null) - { - onCreating(i, member); - } - - member.ResetDirtyProperties(false); - - list.Add(member); - } - - return list; - } - } -} +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Tests.TestHelpers.Entities +{ + public class MockedMember + { + public static Member CreateSimpleMember(IMemberType contentType, string name, string email, string password, string username, Guid? key = null) + { + var member = new Member(name, email, username, password, contentType) + { + CreatorId = 0, + Email = email, + RawPasswordValue = password, + Username = username + }; + + if (key.HasValue) + { + member.Key = key.Value; + } + + member.SetValue("title", name + " member"); + member.SetValue("bodyText", "This is a subpage"); + member.SetValue("author", "John Doe"); + + member.ResetDirtyProperties(false); + + return member; + } + + public static Member CreateSimpleMember(IMemberType contentType, string name, string email, string username, Guid? key = null) + { + var member = new Member(name, email, username, contentType) + { + CreatorId = 0, + Email = email, + Username = username + }; + + if (key.HasValue) + { + member.Key = key.Value; + } + + member.SetValue("title", name + " member"); + member.SetValue("bodyText", "This is a subpage"); + member.SetValue("author", "John Doe"); + + member.ResetDirtyProperties(false); + + return member; + } + + public static IEnumerable CreateSimpleMember(IMemberType memberType, int amount, Action onCreating = null) + { + var list = new List(); + + for (int i = 0; i < amount; i++) + { + var name = "Member No-" + i; + var member = new Member(name, "test" + i + "@test.com", "test" + i, "test" + i, memberType); + member.SetValue("title", name + " member" + i); + member.SetValue("bodyText", "This is a subpage" + i); + member.SetValue("author", "John Doe" + i); + + if (onCreating != null) + { + onCreating(i, member); + } + + member.ResetDirtyProperties(false); + + list.Add(member); + } + + return list; + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs index 1b11b9c929..7293c53b72 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedUser.cs @@ -1,44 +1,44 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.Membership; - -namespace Umbraco.Tests.TestHelpers.Entities -{ - public class MockedUser - { - internal static User CreateUser(string suffix = "") - { - var user = new User - { - Language = "en", - IsApproved = true, - Name = "TestUser" + suffix, - RawPasswordValue = "testing", - IsLockedOut = false, - Email = "test" + suffix + "@test.com", - Username = "TestUser" + suffix - }; - - return user; - } - - internal static IEnumerable CreateMulipleUsers(int amount, Action onCreating = null) - { - var list = new List(); - - for (int i = 0; i < amount; i++) - { - var name = "Member No-" + i; - var user = new User(name, "test" + i + "@test.com", "test" + i, "test" + i); - - onCreating?.Invoke(i, user); - - user.ResetDirtyProperties(false); - - list.Add(user); - } - - return list; - } - } -} +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Tests.TestHelpers.Entities +{ + public class MockedUser + { + internal static User CreateUser(string suffix = "") + { + var user = new User + { + Language = "en", + IsApproved = true, + Name = "TestUser" + suffix, + RawPasswordValue = "testing", + IsLockedOut = false, + Email = "test" + suffix + "@test.com", + Username = "TestUser" + suffix + }; + + return user; + } + + internal static IEnumerable CreateMulipleUsers(int amount, Action onCreating = null) + { + var list = new List(); + + for (int i = 0; i < amount; i++) + { + var name = "Member No-" + i; + var user = new User(name, "test" + i + "@test.com", "test" + i, "test" + i); + + onCreating?.Invoke(i, user); + + user.ResetDirtyProperties(false); + + list.Add(user); + } + + return list; + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs b/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs index fdc250e31e..45f7042e31 100644 --- a/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs @@ -1,121 +1,121 @@ -using System; -using System.Collections; -using System.Collections.Specialized; -using System.Security; -using System.Security.Principal; -using System.Web; -using System.Web.Routing; -using Moq; -using Umbraco.Core; -using Umbraco.Core.Configuration; - -namespace Umbraco.Tests.TestHelpers -{ - /// - /// Creates a mock http context with supporting other contexts to test against - /// - public class FakeHttpContextFactory - { - - [SecuritySafeCritical] - public FakeHttpContextFactory(Uri fullUrl) - { - CreateContext(fullUrl); - } - - [SecuritySafeCritical] - public FakeHttpContextFactory(string path) - { - if (path.StartsWith("http://") || path.StartsWith("https://")) - CreateContext(new Uri(path)); - else - CreateContext(new Uri("http://mysite" + VirtualPathUtility.ToAbsolute(path, "/"))); - } - - [SecuritySafeCritical] - public FakeHttpContextFactory(string path, RouteData routeData) - { - if (path.StartsWith("http://") || path.StartsWith("https://")) - CreateContext(new Uri(path), routeData); - else - CreateContext(new Uri("http://mysite" + VirtualPathUtility.ToAbsolute(path, "/")), routeData); - } - - public HttpContextBase HttpContext { get; private set; } - public RequestContext RequestContext { get; private set; } - - /// - /// Mocks the http context to test against - /// - /// - /// - /// - private void CreateContext(Uri fullUrl, RouteData routeData = null) - { - //Request context - - var requestContextMock = new Mock(); - - RequestContext = requestContextMock.Object; - - //Cookie collection - var cookieCollection = new HttpCookieCollection(); - cookieCollection.Add(new HttpCookie("UMB_UCONTEXT", "FBA996E7-D6BE-489B-B199-2B0F3D2DD826")); - - //Request - var requestMock = new Mock(); - requestMock.Setup(x => x.AppRelativeCurrentExecutionFilePath).Returns("~" + fullUrl.AbsolutePath); - requestMock.Setup(x => x.PathInfo).Returns(string.Empty); - requestMock.Setup(x => x.RawUrl).Returns(VirtualPathUtility.ToAbsolute("~" + fullUrl.AbsolutePath, "/")); - requestMock.Setup(x => x.RequestContext).Returns(RequestContext); - requestMock.Setup(x => x.Url).Returns(fullUrl); - requestMock.Setup(x => x.ApplicationPath).Returns("/"); - requestMock.Setup(x => x.Cookies).Returns(cookieCollection); - requestMock.Setup(x => x.ServerVariables).Returns(new NameValueCollection()); - var queryStrings = HttpUtility.ParseQueryString(fullUrl.Query); - requestMock.Setup(x => x.QueryString).Returns(queryStrings); - requestMock.Setup(x => x.Form).Returns(new NameValueCollection()); - - //Cache - var cacheMock = new Mock(); - - //Response - //var response = new FakeHttpResponse(); - var responseMock = new Mock(); - responseMock.Setup(x => x.ApplyAppPathModifier(It.IsAny())).Returns((string s) => s); - responseMock.Setup(x => x.Cache).Returns(cacheMock.Object); - - //Server - - var serverMock = new Mock(); - serverMock.Setup(x => x.MapPath(It.IsAny())).Returns(Environment.CurrentDirectory); - - //User - var user = new Mock().Object; - - //HTTP Context - - var httpContextMock = new Mock(); - httpContextMock.Setup(x => x.Cache).Returns(HttpRuntime.Cache); - //note: foreach on Items should return DictionaryEntries! - //httpContextMock.Setup(x => x.Items).Returns(new Dictionary()); - httpContextMock.Setup(x => x.Items).Returns(new Hashtable()); - httpContextMock.Setup(x => x.Request).Returns(requestMock.Object); - httpContextMock.Setup(x => x.Server).Returns(serverMock.Object); - httpContextMock.Setup(x => x.Response).Returns(responseMock.Object); - httpContextMock.Setup(x => x.User).Returns(user); - - HttpContext = httpContextMock.Object; - - requestContextMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object); - - if (routeData != null) - { - requestContextMock.Setup(x => x.RouteData).Returns(routeData); - } - - - } - - } -} +using System; +using System.Collections; +using System.Collections.Specialized; +using System.Security; +using System.Security.Principal; +using System.Web; +using System.Web.Routing; +using Moq; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Tests.TestHelpers +{ + /// + /// Creates a mock http context with supporting other contexts to test against + /// + public class FakeHttpContextFactory + { + + [SecuritySafeCritical] + public FakeHttpContextFactory(Uri fullUrl) + { + CreateContext(fullUrl); + } + + [SecuritySafeCritical] + public FakeHttpContextFactory(string path) + { + if (path.StartsWith("http://") || path.StartsWith("https://")) + CreateContext(new Uri(path)); + else + CreateContext(new Uri("http://mysite" + VirtualPathUtility.ToAbsolute(path, "/"))); + } + + [SecuritySafeCritical] + public FakeHttpContextFactory(string path, RouteData routeData) + { + if (path.StartsWith("http://") || path.StartsWith("https://")) + CreateContext(new Uri(path), routeData); + else + CreateContext(new Uri("http://mysite" + VirtualPathUtility.ToAbsolute(path, "/")), routeData); + } + + public HttpContextBase HttpContext { get; private set; } + public RequestContext RequestContext { get; private set; } + + /// + /// Mocks the http context to test against + /// + /// + /// + /// + private void CreateContext(Uri fullUrl, RouteData routeData = null) + { + //Request context + + var requestContextMock = new Mock(); + + RequestContext = requestContextMock.Object; + + //Cookie collection + var cookieCollection = new HttpCookieCollection(); + cookieCollection.Add(new HttpCookie("UMB_UCONTEXT", "FBA996E7-D6BE-489B-B199-2B0F3D2DD826")); + + //Request + var requestMock = new Mock(); + requestMock.Setup(x => x.AppRelativeCurrentExecutionFilePath).Returns("~" + fullUrl.AbsolutePath); + requestMock.Setup(x => x.PathInfo).Returns(string.Empty); + requestMock.Setup(x => x.RawUrl).Returns(VirtualPathUtility.ToAbsolute("~" + fullUrl.AbsolutePath, "/")); + requestMock.Setup(x => x.RequestContext).Returns(RequestContext); + requestMock.Setup(x => x.Url).Returns(fullUrl); + requestMock.Setup(x => x.ApplicationPath).Returns("/"); + requestMock.Setup(x => x.Cookies).Returns(cookieCollection); + requestMock.Setup(x => x.ServerVariables).Returns(new NameValueCollection()); + var queryStrings = HttpUtility.ParseQueryString(fullUrl.Query); + requestMock.Setup(x => x.QueryString).Returns(queryStrings); + requestMock.Setup(x => x.Form).Returns(new NameValueCollection()); + + //Cache + var cacheMock = new Mock(); + + //Response + //var response = new FakeHttpResponse(); + var responseMock = new Mock(); + responseMock.Setup(x => x.ApplyAppPathModifier(It.IsAny())).Returns((string s) => s); + responseMock.Setup(x => x.Cache).Returns(cacheMock.Object); + + //Server + + var serverMock = new Mock(); + serverMock.Setup(x => x.MapPath(It.IsAny())).Returns(Environment.CurrentDirectory); + + //User + var user = new Mock().Object; + + //HTTP Context + + var httpContextMock = new Mock(); + httpContextMock.Setup(x => x.Cache).Returns(HttpRuntime.Cache); + //note: foreach on Items should return DictionaryEntries! + //httpContextMock.Setup(x => x.Items).Returns(new Dictionary()); + httpContextMock.Setup(x => x.Items).Returns(new Hashtable()); + httpContextMock.Setup(x => x.Request).Returns(requestMock.Object); + httpContextMock.Setup(x => x.Server).Returns(serverMock.Object); + httpContextMock.Setup(x => x.Response).Returns(responseMock.Object); + httpContextMock.Setup(x => x.User).Returns(user); + + HttpContext = httpContextMock.Object; + + requestContextMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object); + + if (routeData != null) + { + requestContextMock.Setup(x => x.RouteData).Returns(routeData); + } + + + } + + } +} diff --git a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs index 35d3d72183..c34439ecaf 100644 --- a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs +++ b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs @@ -1,167 +1,167 @@ -using System.Collections.Generic; -using System.IO; -using System.Configuration; -using Moq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; - -namespace Umbraco.Tests.TestHelpers -{ - public class SettingsForTests - { - public static void ConfigureSettings(IGlobalSettings settings) - { - UmbracoConfig.For.SetGlobalConfig(settings); +using System.Collections.Generic; +using System.IO; +using System.Configuration; +using Moq; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; + +namespace Umbraco.Tests.TestHelpers +{ + public class SettingsForTests + { + public static void ConfigureSettings(IGlobalSettings settings) + { + UmbracoConfig.For.SetGlobalConfig(settings); } - - // umbracoSettings - - /// - /// Sets the umbraco settings singleton to the object specified - /// - /// - public static void ConfigureSettings(IUmbracoSettingsSection settings) - { - UmbracoConfig.For.SetUmbracoSettings(settings); - } - - public static IGlobalSettings GenerateMockGlobalSettings() - { - var config = Mock.Of( - settings => - settings.ConfigurationStatus == UmbracoVersion.SemanticVersion.ToSemanticString() && - settings.UseHttps == false && - settings.HideTopLevelNodeFromPath == false && - settings.Path == IOHelper.ResolveUrl("~/umbraco") && - settings.UseDirectoryUrls == true && - settings.TimeOutInMinutes == 20 && + + // umbracoSettings + + /// + /// Sets the umbraco settings singleton to the object specified + /// + /// + public static void ConfigureSettings(IUmbracoSettingsSection settings) + { + UmbracoConfig.For.SetUmbracoSettings(settings); + } + + public static IGlobalSettings GenerateMockGlobalSettings() + { + var config = Mock.Of( + settings => + settings.ConfigurationStatus == UmbracoVersion.SemanticVersion.ToSemanticString() && + settings.UseHttps == false && + settings.HideTopLevelNodeFromPath == false && + settings.Path == IOHelper.ResolveUrl("~/umbraco") && + settings.UseDirectoryUrls == true && + settings.TimeOutInMinutes == 20 && settings.DefaultUILanguage == "en" && settings.LocalTempStorageLocation == LocalTempStorage.Default && settings.ReservedPaths == (GlobalSettings.StaticReservedPaths + "~/umbraco") && - settings.ReservedUrls == GlobalSettings.StaticReservedUrls); - return config; + settings.ReservedUrls == GlobalSettings.StaticReservedUrls); + return config; } - - /// - /// Returns generated settings which can be stubbed to return whatever values necessary - /// - /// - public static IUmbracoSettingsSection GenerateMockUmbracoSettings() - { - var settings = new Mock(); - - var content = new Mock(); - var security = new Mock(); - var requestHandler = new Mock(); - var templates = new Mock(); - var logging = new Mock(); - var tasks = new Mock(); - var providers = new Mock(); - var routing = new Mock(); - - settings.Setup(x => x.Content).Returns(content.Object); - settings.Setup(x => x.Security).Returns(security.Object); - settings.Setup(x => x.RequestHandler).Returns(requestHandler.Object); - settings.Setup(x => x.Templates).Returns(templates.Object); - settings.Setup(x => x.Logging).Returns(logging.Object); - settings.Setup(x => x.ScheduledTasks).Returns(tasks.Object); - settings.Setup(x => x.Providers).Returns(providers.Object); - settings.Setup(x => x.WebRouting).Returns(routing.Object); - - //Now configure some defaults - the defaults in the config section classes do NOT pertain to the mocked data!! - settings.Setup(x => x.Content.ForceSafeAliases).Returns(true); - settings.Setup(x => x.Content.ImageAutoFillProperties).Returns(ContentImagingElement.GetDefaultImageAutoFillProperties()); - settings.Setup(x => x.Content.ImageFileTypes).Returns(ContentImagingElement.GetDefaultImageFileTypes()); - settings.Setup(x => x.RequestHandler.AddTrailingSlash).Returns(true); - settings.Setup(x => x.RequestHandler.UseDomainPrefixes).Returns(false); - settings.Setup(x => x.RequestHandler.CharCollection).Returns(RequestHandlerElement.GetDefaultCharReplacements()); - settings.Setup(x => x.Content.UmbracoLibraryCacheDuration).Returns(1800); - settings.Setup(x => x.WebRouting.UrlProviderMode).Returns("AutoLegacy"); - settings.Setup(x => x.Templates.DefaultRenderingEngine).Returns(RenderingEngine.Mvc); - settings.Setup(x => x.Providers.DefaultBackOfficeUserProvider).Returns("UsersMembershipProvider"); - - return settings.Object; - } - - //// from appSettings - - //private static readonly IDictionary SavedAppSettings = new Dictionary(); - - //static void SaveSetting(string key) - //{ - // SavedAppSettings[key] = ConfigurationManager.AppSettings[key]; - //} - - //static void SaveSettings() - //{ - // SaveSetting("umbracoHideTopLevelNodeFromPath"); - // SaveSetting("umbracoUseDirectoryUrls"); - // SaveSetting("umbracoPath"); - // SaveSetting("umbracoReservedPaths"); - // SaveSetting("umbracoReservedUrls"); - // SaveSetting("umbracoConfigurationStatus"); - //} - - - - // reset & defaults - - //static SettingsForTests() - //{ - // //SaveSettings(); - //} - - public static void Reset() - { - ResetSettings(); - GlobalSettings.Reset(); - - //foreach (var kvp in SavedAppSettings) - // ConfigurationManager.AppSettings.Set(kvp.Key, kvp.Value); - - //// set some defaults that are wrong in the config file?! - //// this is annoying, really - //HideTopLevelNodeFromPath = false; - } - - /// - /// This sets all settings back to default settings - /// - private static void ResetSettings() - { - _defaultGlobalSettings = null; - ConfigureSettings(GetDefaultUmbracoSettings()); - ConfigureSettings(GetDefaultGlobalSettings()); - } - - private static IUmbracoSettingsSection _defaultUmbracoSettings; - private static IGlobalSettings _defaultGlobalSettings; - - internal static IGlobalSettings GetDefaultGlobalSettings() - { - if (_defaultGlobalSettings == null) - { - _defaultGlobalSettings = GenerateMockGlobalSettings(); - } - return _defaultGlobalSettings; + + /// + /// Returns generated settings which can be stubbed to return whatever values necessary + /// + /// + public static IUmbracoSettingsSection GenerateMockUmbracoSettings() + { + var settings = new Mock(); + + var content = new Mock(); + var security = new Mock(); + var requestHandler = new Mock(); + var templates = new Mock(); + var logging = new Mock(); + var tasks = new Mock(); + var providers = new Mock(); + var routing = new Mock(); + + settings.Setup(x => x.Content).Returns(content.Object); + settings.Setup(x => x.Security).Returns(security.Object); + settings.Setup(x => x.RequestHandler).Returns(requestHandler.Object); + settings.Setup(x => x.Templates).Returns(templates.Object); + settings.Setup(x => x.Logging).Returns(logging.Object); + settings.Setup(x => x.ScheduledTasks).Returns(tasks.Object); + settings.Setup(x => x.Providers).Returns(providers.Object); + settings.Setup(x => x.WebRouting).Returns(routing.Object); + + //Now configure some defaults - the defaults in the config section classes do NOT pertain to the mocked data!! + settings.Setup(x => x.Content.ForceSafeAliases).Returns(true); + settings.Setup(x => x.Content.ImageAutoFillProperties).Returns(ContentImagingElement.GetDefaultImageAutoFillProperties()); + settings.Setup(x => x.Content.ImageFileTypes).Returns(ContentImagingElement.GetDefaultImageFileTypes()); + settings.Setup(x => x.RequestHandler.AddTrailingSlash).Returns(true); + settings.Setup(x => x.RequestHandler.UseDomainPrefixes).Returns(false); + settings.Setup(x => x.RequestHandler.CharCollection).Returns(RequestHandlerElement.GetDefaultCharReplacements()); + settings.Setup(x => x.Content.UmbracoLibraryCacheDuration).Returns(1800); + settings.Setup(x => x.WebRouting.UrlProviderMode).Returns("AutoLegacy"); + settings.Setup(x => x.Templates.DefaultRenderingEngine).Returns(RenderingEngine.Mvc); + settings.Setup(x => x.Providers.DefaultBackOfficeUserProvider).Returns("UsersMembershipProvider"); + + return settings.Object; } - - internal static IUmbracoSettingsSection GetDefaultUmbracoSettings() - { - if (_defaultUmbracoSettings == null) + + //// from appSettings + + //private static readonly IDictionary SavedAppSettings = new Dictionary(); + + //static void SaveSetting(string key) + //{ + // SavedAppSettings[key] = ConfigurationManager.AppSettings[key]; + //} + + //static void SaveSettings() + //{ + // SaveSetting("umbracoHideTopLevelNodeFromPath"); + // SaveSetting("umbracoUseDirectoryUrls"); + // SaveSetting("umbracoPath"); + // SaveSetting("umbracoReservedPaths"); + // SaveSetting("umbracoReservedUrls"); + // SaveSetting("umbracoConfigurationStatus"); + //} + + + + // reset & defaults + + //static SettingsForTests() + //{ + // //SaveSettings(); + //} + + public static void Reset() + { + ResetSettings(); + GlobalSettings.Reset(); + + //foreach (var kvp in SavedAppSettings) + // ConfigurationManager.AppSettings.Set(kvp.Key, kvp.Value); + + //// set some defaults that are wrong in the config file?! + //// this is annoying, really + //HideTopLevelNodeFromPath = false; + } + + /// + /// This sets all settings back to default settings + /// + private static void ResetSettings() + { + _defaultGlobalSettings = null; + ConfigureSettings(GetDefaultUmbracoSettings()); + ConfigureSettings(GetDefaultGlobalSettings()); + } + + private static IUmbracoSettingsSection _defaultUmbracoSettings; + private static IGlobalSettings _defaultGlobalSettings; + + internal static IGlobalSettings GetDefaultGlobalSettings() + { + if (_defaultGlobalSettings == null) + { + _defaultGlobalSettings = GenerateMockGlobalSettings(); + } + return _defaultGlobalSettings; + } + + internal static IUmbracoSettingsSection GetDefaultUmbracoSettings() + { + if (_defaultUmbracoSettings == null) { //TODO: Just make this mocks instead of reading from the config - - var config = new FileInfo(TestHelper.MapPathForTest("~/Configurations/UmbracoSettings/web.config")); - - var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = config.FullName }; - var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); - _defaultUmbracoSettings = configuration.GetSection("umbracoConfiguration/defaultSettings") as UmbracoSettingsSection; - } - - return _defaultUmbracoSettings; - } - } -} + + var config = new FileInfo(TestHelper.MapPathForTest("~/Configurations/UmbracoSettings/web.config")); + + var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = config.FullName }; + var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); + _defaultUmbracoSettings = configuration.GetSection("umbracoConfiguration/defaultSettings") as UmbracoSettingsSection; + } + + return _defaultUmbracoSettings; + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index 5e2ce24863..00e1a363b8 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -1,71 +1,71 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Web.Mvc; -using System.Web.Routing; -using System.Web.SessionState; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Composing; -using Umbraco.Web; - -namespace Umbraco.Tests.TestHelpers.Stubs -{ - /// - /// Used in place of the UmbracoControllerFactory which relies on BuildManager which throws exceptions in a unit test context - /// - internal class TestControllerFactory : IControllerFactory - { - private readonly UmbracoContext _umbracoContext; - private readonly ILogger _logger; - - public TestControllerFactory(UmbracoContext umbracoContext, ILogger logger) - { - _umbracoContext = umbracoContext; - _logger = logger; - } - - public IController CreateController(RequestContext requestContext, string controllerName) - { - var types = TypeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); - - var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); - var t = controllerTypes.SingleOrDefault(); - - if (t == null) - return null; - - var possibleParams = new object[] - { - _umbracoContext, _logger - }; - var ctors = t.GetConstructors(); - foreach (var ctor in ctors.OrderByDescending(x => x.GetParameters().Length)) - { - var args = new List(); - var allParams = ctor.GetParameters().ToArray(); - foreach (var parameter in allParams) - { - var found = possibleParams.SingleOrDefault(x => x.GetType() == parameter.ParameterType); - if (found != null) args.Add(found); - } - if (args.Count == allParams.Length) - { - return Activator.CreateInstance(t, args.ToArray()) as IController; - } - } - return null; - } - - public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) - { - return SessionStateBehavior.Disabled; - } - - public void ReleaseController(IController controller) - { - controller.DisposeIfDisposable(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Web.Mvc; +using System.Web.Routing; +using System.Web.SessionState; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Composing; +using Umbraco.Web; + +namespace Umbraco.Tests.TestHelpers.Stubs +{ + /// + /// Used in place of the UmbracoControllerFactory which relies on BuildManager which throws exceptions in a unit test context + /// + internal class TestControllerFactory : IControllerFactory + { + private readonly UmbracoContext _umbracoContext; + private readonly ILogger _logger; + + public TestControllerFactory(UmbracoContext umbracoContext, ILogger logger) + { + _umbracoContext = umbracoContext; + _logger = logger; + } + + public IController CreateController(RequestContext requestContext, string controllerName) + { + var types = TypeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); + + var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); + var t = controllerTypes.SingleOrDefault(); + + if (t == null) + return null; + + var possibleParams = new object[] + { + _umbracoContext, _logger + }; + var ctors = t.GetConstructors(); + foreach (var ctor in ctors.OrderByDescending(x => x.GetParameters().Length)) + { + var args = new List(); + var allParams = ctor.GetParameters().ToArray(); + foreach (var parameter in allParams) + { + var found = possibleParams.SingleOrDefault(x => x.GetType() == parameter.ParameterType); + if (found != null) args.Add(found); + } + if (args.Count == allParams.Length) + { + return Activator.CreateInstance(t, args.ToArray()) as IController; + } + } + return null; + } + + public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) + { + return SessionStateBehavior.Disabled; + } + + public void ReleaseController(IController controller) + { + controller.DisposeIfDisposable(); + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 5380a747f5..7dcb1df77c 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -1,238 +1,238 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.PropertyEditors; -using File = System.IO.File; - -namespace Umbraco.Tests.TestHelpers -{ - /// - /// Common helper properties and methods useful to testing - /// - public static class TestHelper - { - - - - /// - /// Gets the current assembly directory. - /// - /// The assembly directory. - static public string CurrentAssemblyDirectory - { - get - { - var codeBase = typeof(TestHelper).Assembly.CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - return Path.GetDirectoryName(path); - } - } - - /// - /// Maps the given making it rooted on . must start with ~/ - /// - /// The relative path. - /// - public static string MapPathForTest(string relativePath) - { - if (!relativePath.StartsWith("~/")) - throw new ArgumentException("relativePath must start with '~/'", "relativePath"); - - return relativePath.Replace("~/", CurrentAssemblyDirectory + "/"); - } - - public static void InitializeContentDirectories() - { - CreateDirectories(new[] { SystemDirectories.Masterpages, SystemDirectories.MvcViews, SystemDirectories.Media, SystemDirectories.AppPlugins }); - } - - public static void CleanContentDirectories() - { - CleanDirectories(new[] { SystemDirectories.Masterpages, SystemDirectories.MvcViews, SystemDirectories.Media }); - } - - public static void CreateDirectories(string[] directories) - { - foreach (var directory in directories) - { - var directoryInfo = new DirectoryInfo(IOHelper.MapPath(directory)); - if (directoryInfo.Exists == false) - Directory.CreateDirectory(IOHelper.MapPath(directory)); - } - } - - public static void CleanDirectories(string[] directories) - { - var preserves = new Dictionary - { - { SystemDirectories.Masterpages, new[] {"dummy.txt"} }, - { SystemDirectories.MvcViews, new[] {"dummy.txt"} } - }; - foreach (var directory in directories) - { - var directoryInfo = new DirectoryInfo(IOHelper.MapPath(directory)); - var preserve = preserves.ContainsKey(directory) ? preserves[directory] : null; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.PropertyEditors; +using File = System.IO.File; + +namespace Umbraco.Tests.TestHelpers +{ + /// + /// Common helper properties and methods useful to testing + /// + public static class TestHelper + { + + + + /// + /// Gets the current assembly directory. + /// + /// The assembly directory. + static public string CurrentAssemblyDirectory + { + get + { + var codeBase = typeof(TestHelper).Assembly.CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + return Path.GetDirectoryName(path); + } + } + + /// + /// Maps the given making it rooted on . must start with ~/ + /// + /// The relative path. + /// + public static string MapPathForTest(string relativePath) + { + if (!relativePath.StartsWith("~/")) + throw new ArgumentException("relativePath must start with '~/'", "relativePath"); + + return relativePath.Replace("~/", CurrentAssemblyDirectory + "/"); + } + + public static void InitializeContentDirectories() + { + CreateDirectories(new[] { SystemDirectories.Masterpages, SystemDirectories.MvcViews, SystemDirectories.Media, SystemDirectories.AppPlugins }); + } + + public static void CleanContentDirectories() + { + CleanDirectories(new[] { SystemDirectories.Masterpages, SystemDirectories.MvcViews, SystemDirectories.Media }); + } + + public static void CreateDirectories(string[] directories) + { + foreach (var directory in directories) + { + var directoryInfo = new DirectoryInfo(IOHelper.MapPath(directory)); + if (directoryInfo.Exists == false) + Directory.CreateDirectory(IOHelper.MapPath(directory)); + } + } + + public static void CleanDirectories(string[] directories) + { + var preserves = new Dictionary + { + { SystemDirectories.Masterpages, new[] {"dummy.txt"} }, + { SystemDirectories.MvcViews, new[] {"dummy.txt"} } + }; + foreach (var directory in directories) + { + var directoryInfo = new DirectoryInfo(IOHelper.MapPath(directory)); + var preserve = preserves.ContainsKey(directory) ? preserves[directory] : null; if (directoryInfo.Exists) foreach (var x in directoryInfo.GetFiles().Where(x => preserve == null || preserve.Contains(x.Name) == false)) - x.Delete(); - } - } - - public static void CleanUmbracoSettingsConfig() - { - var currDir = new DirectoryInfo(CurrentAssemblyDirectory); - - var umbracoSettingsFile = Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"); - if (File.Exists(umbracoSettingsFile)) - File.Delete(umbracoSettingsFile); - } - - // fixme obsolete the dateTimeFormat thing and replace with dateDelta - public static void AssertPropertyValuesAreEqual(object actual, object expected, string dateTimeFormat = null, Func sorter = null, string[] ignoreProperties = null) - { - const int dateDeltaMilliseconds = 500; // .5s - - var properties = expected.GetType().GetProperties(); - foreach (var property in properties) - { - // ignore properties that are attributed with EditorBrowsableState.Never - var att = property.GetCustomAttribute(false); - if (att != null && att.State == EditorBrowsableState.Never) - continue; - - // ignore explicitely ignored properties - if (ignoreProperties != null && ignoreProperties.Contains(property.Name)) - continue; - - var actualValue = property.GetValue(actual, null); - var expectedValue = property.GetValue(expected, null); - - AssertAreEqual(property, expectedValue, actualValue, sorter, dateDeltaMilliseconds); - } - } - - private static void AssertAreEqual(PropertyInfo property, object expected, object actual, Func sorter = null, int dateDeltaMilliseconds = 0) - { - if (!(expected is string) && expected is IEnumerable) - { - // compare lists - AssertListsAreEqual(property, (IEnumerable) actual, (IEnumerable) expected, sorter, dateDeltaMilliseconds); - } - else if (expected is DateTime expectedDateTime) - { - // compare date & time with delta - var actualDateTime = (DateTime) actual; - var delta = (actualDateTime - expectedDateTime).TotalMilliseconds; - Assert.IsTrue(Math.Abs(delta) <= dateDeltaMilliseconds, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expected, actual); - } - else if (expected is Property expectedProperty) - { - // compare values - var actualProperty = (Property) actual; - var expectedPropertyValues = expectedProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); - var actualPropertyValues = actualProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); - if (expectedPropertyValues.Length != actualPropertyValues.Length) - Assert.Fail($"{property.DeclaringType.Name}.{property.Name}: Expected {expectedPropertyValues.Length} but got {actualPropertyValues.Length}."); - for (var i = 0; i < expectedPropertyValues.Length; i++) - { - Assert.AreEqual(expectedPropertyValues[i].EditedValue, actualPropertyValues[i].EditedValue, $"{property.DeclaringType.Name}.{property.Name}: Expected draft value \"{expectedPropertyValues[i].EditedValue}\" but got \"{actualPropertyValues[i].EditedValue}\"."); - Assert.AreEqual(expectedPropertyValues[i].PublishedValue, actualPropertyValues[i].PublishedValue, $"{property.DeclaringType.Name}.{property.Name}: Expected published value \"{expectedPropertyValues[i].EditedValue}\" but got \"{actualPropertyValues[i].EditedValue}\"."); - } - } - else if (expected is IDataEditor expectedEditor) - { - Assert.IsInstanceOf(actual); - var actualEditor = (IDataEditor) actual; - Assert.AreEqual(expectedEditor.Alias, actualEditor.Alias); - // what else shall we test? - } - else - { - // directly compare values - Assert.AreEqual(expected, actual, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, - expected?.ToString() ?? "", actual?.ToString() ?? ""); - } - } - - private static void AssertListsAreEqual(PropertyInfo property, IEnumerable expected, IEnumerable actual, Func sorter = null, int dateDeltaMilliseconds = 0) - { - if (sorter == null) - { - // this is pretty hackerific but saves us some code to write - sorter = enumerable => - { - // semi-generic way of ensuring any collection of IEntity are sorted by Ids for comparison - var entities = enumerable.OfType().ToList(); - return entities.Count > 0 ? (IEnumerable) entities.OrderBy(x => x.Id) : entities; - }; - } - - var expectedListEx = sorter(expected).Cast().ToList(); - var actualListEx = sorter(actual).Cast().ToList(); - - if (actualListEx.Count != expectedListEx.Count) - Assert.Fail("Collection {0}.{1} does not match. Expected IEnumerable containing {2} elements but was IEnumerable containing {3} elements", property.PropertyType.Name, property.Name, expectedListEx.Count, actualListEx.Count); - - for (var i = 0; i < actualListEx.Count; i++) - AssertAreEqual(property, expectedListEx[i], actualListEx[i], sorter, dateDeltaMilliseconds); - } - - public static void DeleteDirectory(string path) - { - Try(() => - { - if (Directory.Exists(path) == false) return; - foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) - File.Delete(file); - }); - - Try(() => - { - if (Directory.Exists(path) == false) return; - Directory.Delete(path, true); - }); - } - - public static void TryAssert(Action action, int maxTries = 5, int waitMilliseconds = 200) - { - Try(action, maxTries, waitMilliseconds); - } - - public static void Try(Action action, int maxTries = 5, int waitMilliseconds = 200) - { - Try(action, maxTries, waitMilliseconds); - } - - public static void Try(Action action, int maxTries = 5, int waitMilliseconds = 200) - where T : Exception - { - var tries = 0; - while (true) - { - try - { - action(); - break; - } - catch (T) - { - if (tries++ > maxTries) - throw; - Thread.Sleep(waitMilliseconds); - } - } - } - } -} + x.Delete(); + } + } + + public static void CleanUmbracoSettingsConfig() + { + var currDir = new DirectoryInfo(CurrentAssemblyDirectory); + + var umbracoSettingsFile = Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"); + if (File.Exists(umbracoSettingsFile)) + File.Delete(umbracoSettingsFile); + } + + // fixme obsolete the dateTimeFormat thing and replace with dateDelta + public static void AssertPropertyValuesAreEqual(object actual, object expected, string dateTimeFormat = null, Func sorter = null, string[] ignoreProperties = null) + { + const int dateDeltaMilliseconds = 500; // .5s + + var properties = expected.GetType().GetProperties(); + foreach (var property in properties) + { + // ignore properties that are attributed with EditorBrowsableState.Never + var att = property.GetCustomAttribute(false); + if (att != null && att.State == EditorBrowsableState.Never) + continue; + + // ignore explicitely ignored properties + if (ignoreProperties != null && ignoreProperties.Contains(property.Name)) + continue; + + var actualValue = property.GetValue(actual, null); + var expectedValue = property.GetValue(expected, null); + + AssertAreEqual(property, expectedValue, actualValue, sorter, dateDeltaMilliseconds); + } + } + + private static void AssertAreEqual(PropertyInfo property, object expected, object actual, Func sorter = null, int dateDeltaMilliseconds = 0) + { + if (!(expected is string) && expected is IEnumerable) + { + // compare lists + AssertListsAreEqual(property, (IEnumerable) actual, (IEnumerable) expected, sorter, dateDeltaMilliseconds); + } + else if (expected is DateTime expectedDateTime) + { + // compare date & time with delta + var actualDateTime = (DateTime) actual; + var delta = (actualDateTime - expectedDateTime).TotalMilliseconds; + Assert.IsTrue(Math.Abs(delta) <= dateDeltaMilliseconds, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expected, actual); + } + else if (expected is Property expectedProperty) + { + // compare values + var actualProperty = (Property) actual; + var expectedPropertyValues = expectedProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); + var actualPropertyValues = actualProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); + if (expectedPropertyValues.Length != actualPropertyValues.Length) + Assert.Fail($"{property.DeclaringType.Name}.{property.Name}: Expected {expectedPropertyValues.Length} but got {actualPropertyValues.Length}."); + for (var i = 0; i < expectedPropertyValues.Length; i++) + { + Assert.AreEqual(expectedPropertyValues[i].EditedValue, actualPropertyValues[i].EditedValue, $"{property.DeclaringType.Name}.{property.Name}: Expected draft value \"{expectedPropertyValues[i].EditedValue}\" but got \"{actualPropertyValues[i].EditedValue}\"."); + Assert.AreEqual(expectedPropertyValues[i].PublishedValue, actualPropertyValues[i].PublishedValue, $"{property.DeclaringType.Name}.{property.Name}: Expected published value \"{expectedPropertyValues[i].EditedValue}\" but got \"{actualPropertyValues[i].EditedValue}\"."); + } + } + else if (expected is IDataEditor expectedEditor) + { + Assert.IsInstanceOf(actual); + var actualEditor = (IDataEditor) actual; + Assert.AreEqual(expectedEditor.Alias, actualEditor.Alias); + // what else shall we test? + } + else + { + // directly compare values + Assert.AreEqual(expected, actual, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, + expected?.ToString() ?? "", actual?.ToString() ?? ""); + } + } + + private static void AssertListsAreEqual(PropertyInfo property, IEnumerable expected, IEnumerable actual, Func sorter = null, int dateDeltaMilliseconds = 0) + { + if (sorter == null) + { + // this is pretty hackerific but saves us some code to write + sorter = enumerable => + { + // semi-generic way of ensuring any collection of IEntity are sorted by Ids for comparison + var entities = enumerable.OfType().ToList(); + return entities.Count > 0 ? (IEnumerable) entities.OrderBy(x => x.Id) : entities; + }; + } + + var expectedListEx = sorter(expected).Cast().ToList(); + var actualListEx = sorter(actual).Cast().ToList(); + + if (actualListEx.Count != expectedListEx.Count) + Assert.Fail("Collection {0}.{1} does not match. Expected IEnumerable containing {2} elements but was IEnumerable containing {3} elements", property.PropertyType.Name, property.Name, expectedListEx.Count, actualListEx.Count); + + for (var i = 0; i < actualListEx.Count; i++) + AssertAreEqual(property, expectedListEx[i], actualListEx[i], sorter, dateDeltaMilliseconds); + } + + public static void DeleteDirectory(string path) + { + Try(() => + { + if (Directory.Exists(path) == false) return; + foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) + File.Delete(file); + }); + + Try(() => + { + if (Directory.Exists(path) == false) return; + Directory.Delete(path, true); + }); + } + + public static void TryAssert(Action action, int maxTries = 5, int waitMilliseconds = 200) + { + Try(action, maxTries, waitMilliseconds); + } + + public static void Try(Action action, int maxTries = 5, int waitMilliseconds = 200) + { + Try(action, maxTries, waitMilliseconds); + } + + public static void Try(Action action, int maxTries = 5, int waitMilliseconds = 200) + where T : Exception + { + var tries = 0; + while (true) + { + try + { + action(); + break; + } + catch (T) + { + if (tries++ > maxTries) + throw; + Thread.Sleep(waitMilliseconds); + } + } + } + } +} diff --git a/src/Umbraco.Tests/TreesAndSections/ApplicationTreeTest.cs b/src/Umbraco.Tests/TreesAndSections/ApplicationTreeTest.cs index 6a43dfbae2..951246c535 100644 --- a/src/Umbraco.Tests/TreesAndSections/ApplicationTreeTest.cs +++ b/src/Umbraco.Tests/TreesAndSections/ApplicationTreeTest.cs @@ -1,397 +1,397 @@ -using System.IO; -using NUnit.Framework; -using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers; -using System; -using System.Linq; -using System.Threading; -using Umbraco.Tests.Testing; -using Umbraco.Web.Services; -using Current = Umbraco.Web.Composing.Current; - -namespace Umbraco.Tests.TreesAndSections -{ - - - /// - ///This is a test class for ApplicationTreeTest and is intended - ///to contain all ApplicationTreeTest Unit Tests - /// - [TestFixture] - [Apartment(ApartmentState.STA)] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class ApplicationTreeTest : TestWithDatabaseBase - { - public override void SetUp() - { - base.SetUp(); - - var treesConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/trees.config"); - var appConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/applications.config"); - Directory.CreateDirectory(TestHelper.MapPathForTest("~/TEMP/TreesAndSections")); - using (var writer = File.CreateText(treesConfig)) - { - writer.Write(ResourceFiles.trees); - } - using (var writer = File.CreateText(appConfig)) - { - writer.Write(ResourceFiles.applications); - } - - ApplicationTreeService.TreeConfigFilePath = treesConfig; - SectionService.AppConfigFilePath = appConfig; - } - - public override void TearDown() - { - base.TearDown(); - - if (Directory.Exists(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"))) - { - Directory.Delete(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"), true); - } - ApplicationTreeService.TreeConfigFilePath = null; - SectionService.AppConfigFilePath = null; - } - - /// - /// Creates a new app tree linked to an application, then delete the application and make sure the tree is gone as well - /// - [Test()] - public void ApplicationTree_Make_New_Then_Delete_App() - { - //create new app - var appName = Guid.NewGuid().ToString("N"); - var treeName = Guid.NewGuid().ToString("N"); - Current.Services.SectionService.MakeNew(appName, appName, "icon.jpg"); - - //check if it exists - var app = Current.Services.SectionService.GetByAlias(appName); - Assert.IsNotNull(app); - - //create the new app tree assigned to the new app - Current.Services.ApplicationTreeService.MakeNew(false, 0, app.Alias, treeName, treeName, "icon.jpg", "icon.jpg", "Umbraco.Web.Trees.ContentTreeController, Umbraco.Web"); - var tree = Current.Services.ApplicationTreeService.GetByAlias(treeName); - Assert.IsNotNull(tree); - - //now delete the app - Current.Services.SectionService.DeleteSection(app); - - //check that the tree is gone - Assert.AreEqual(0, Current.Services.ApplicationTreeService.GetApplicationTrees(treeName).Count()); - } - - - #region Tests to write - ///// - /////A test for ApplicationTree Constructor - ///// - //[TestMethod()] - //public void ApplicationTreeConstructorTest() - //{ - // bool silent = false; // TODO: Initialize to an appropriate value - // bool initialize = false; // TODO: Initialize to an appropriate value - // byte sortOrder = 0; // TODO: Initialize to an appropriate value - // string applicationAlias = string.Empty; // TODO: Initialize to an appropriate value - // string alias = string.Empty; // TODO: Initialize to an appropriate value - // string title = string.Empty; // TODO: Initialize to an appropriate value - // string iconClosed = string.Empty; // TODO: Initialize to an appropriate value - // string iconOpened = string.Empty; // TODO: Initialize to an appropriate value - // string assemblyName = string.Empty; // TODO: Initialize to an appropriate value - // string type = string.Empty; // TODO: Initialize to an appropriate value - // string action = string.Empty; // TODO: Initialize to an appropriate value - // ApplicationTree target = new ApplicationTree(silent, initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, assemblyName, type, action); - // Assert.Inconclusive("TODO: Implement code to verify target"); - //} - - ///// - /////A test for ApplicationTree Constructor - ///// - //[TestMethod()] - //public void ApplicationTreeConstructorTest1() - //{ - // ApplicationTree target = new ApplicationTree(); - // Assert.Inconclusive("TODO: Implement code to verify target"); - //} - - ///// - /////A test for Delete - ///// - //[TestMethod()] - //public void DeleteTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // target.Delete(); - // Assert.Inconclusive("A method that does not return a value cannot be verified."); - //} - - - ///// - /////A test for Save - ///// - //[TestMethod()] - //public void SaveTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // target.Save(); - // Assert.Inconclusive("A method that does not return a value cannot be verified."); - //} - - ///// - /////A test for getAll - ///// - //[TestMethod()] - //public void getAllTest() - //{ - // ApplicationTree[] expected = null; // TODO: Initialize to an appropriate value - // ApplicationTree[] actual; - // actual = ApplicationTree.getAll(); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for getApplicationTree - ///// - //[TestMethod()] - //public void getApplicationTreeTest() - //{ - // string applicationAlias = string.Empty; // TODO: Initialize to an appropriate value - // ApplicationTree[] expected = null; // TODO: Initialize to an appropriate value - // ApplicationTree[] actual; - // actual = ApplicationTree.getApplicationTree(applicationAlias); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for getApplicationTree - ///// - //[TestMethod()] - //public void getApplicationTreeTest1() - //{ - // string applicationAlias = string.Empty; // TODO: Initialize to an appropriate value - // bool onlyInitializedApplications = false; // TODO: Initialize to an appropriate value - // ApplicationTree[] expected = null; // TODO: Initialize to an appropriate value - // ApplicationTree[] actual; - // actual = ApplicationTree.getApplicationTree(applicationAlias, onlyInitializedApplications); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for getByAlias - ///// - //[TestMethod()] - //public void getByAliasTest() - //{ - // string treeAlias = string.Empty; // TODO: Initialize to an appropriate value - // ApplicationTree expected = null; // TODO: Initialize to an appropriate value - // ApplicationTree actual; - // actual = ApplicationTree.getByAlias(treeAlias); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Action - ///// - //[TestMethod()] - //public void ActionTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.Action = expected; - // actual = target.Action; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Alias - ///// - //[TestMethod()] - //public void AliasTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string actual; - // actual = target.Alias; - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for ApplicationAlias - ///// - //[TestMethod()] - //public void ApplicationAliasTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string actual; - // actual = target.ApplicationAlias; - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for AssemblyName - ///// - //[TestMethod()] - //public void AssemblyNameTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.AssemblyName = expected; - // actual = target.AssemblyName; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for IconClosed - ///// - //[TestMethod()] - //public void IconClosedTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.IconClosed = expected; - // actual = target.IconClosed; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for IconOpened - ///// - //[TestMethod()] - //public void IconOpenedTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.IconOpened = expected; - // actual = target.IconOpened; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Initialize - ///// - //[TestMethod()] - //public void InitializeTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // bool expected = false; // TODO: Initialize to an appropriate value - // bool actual; - // target.Initialize = expected; - // actual = target.Initialize; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Silent - ///// - //[TestMethod()] - //public void SilentTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // bool expected = false; // TODO: Initialize to an appropriate value - // bool actual; - // target.Silent = expected; - // actual = target.Silent; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for SortOrder - ///// - //[TestMethod()] - //public void SortOrderTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // byte expected = 0; // TODO: Initialize to an appropriate value - // byte actual; - // target.SortOrder = expected; - // actual = target.SortOrder; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for SqlHelper - ///// - //[TestMethod()] - //public void SqlHelperTest() - //{ - // ISqlHelper actual; - // actual = ApplicationTree.SqlHelper; - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Title - ///// - //[TestMethod()] - //public void TitleTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.Title = expected; - // actual = target.Title; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for Type - ///// - //[TestMethod()] - //public void TypeTest() - //{ - // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.Type = expected; - // actual = target.Type; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - #endregion - - #region Additional test attributes - // - //You can use the following additional attributes as you write your tests: - // - //Use ClassInitialize to run code before running the first test in the class - //[ClassInitialize()] - //public static void MyClassInitialize(TestContext testContext) - //{ - //} - // - //Use ClassCleanup to run code after all tests in a class have run - //[ClassCleanup()] - //public static void MyClassCleanup() - //{ - //} - // - //Use TestInitialize to run code before running each test - //[TestInitialize()] - //public void MyTestInitialize() - //{ - //} - // - //Use TestCleanup to run code after each test has run - //[TestCleanup()] - //public void MyTestCleanup() - //{ - //} - // - #endregion - } -} +using System.IO; +using NUnit.Framework; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using System; +using System.Linq; +using System.Threading; +using Umbraco.Tests.Testing; +using Umbraco.Web.Services; +using Current = Umbraco.Web.Composing.Current; + +namespace Umbraco.Tests.TreesAndSections +{ + + + /// + ///This is a test class for ApplicationTreeTest and is intended + ///to contain all ApplicationTreeTest Unit Tests + /// + [TestFixture] + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class ApplicationTreeTest : TestWithDatabaseBase + { + public override void SetUp() + { + base.SetUp(); + + var treesConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/trees.config"); + var appConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/applications.config"); + Directory.CreateDirectory(TestHelper.MapPathForTest("~/TEMP/TreesAndSections")); + using (var writer = File.CreateText(treesConfig)) + { + writer.Write(ResourceFiles.trees); + } + using (var writer = File.CreateText(appConfig)) + { + writer.Write(ResourceFiles.applications); + } + + ApplicationTreeService.TreeConfigFilePath = treesConfig; + SectionService.AppConfigFilePath = appConfig; + } + + public override void TearDown() + { + base.TearDown(); + + if (Directory.Exists(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"))) + { + Directory.Delete(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"), true); + } + ApplicationTreeService.TreeConfigFilePath = null; + SectionService.AppConfigFilePath = null; + } + + /// + /// Creates a new app tree linked to an application, then delete the application and make sure the tree is gone as well + /// + [Test()] + public void ApplicationTree_Make_New_Then_Delete_App() + { + //create new app + var appName = Guid.NewGuid().ToString("N"); + var treeName = Guid.NewGuid().ToString("N"); + Current.Services.SectionService.MakeNew(appName, appName, "icon.jpg"); + + //check if it exists + var app = Current.Services.SectionService.GetByAlias(appName); + Assert.IsNotNull(app); + + //create the new app tree assigned to the new app + Current.Services.ApplicationTreeService.MakeNew(false, 0, app.Alias, treeName, treeName, "icon.jpg", "icon.jpg", "Umbraco.Web.Trees.ContentTreeController, Umbraco.Web"); + var tree = Current.Services.ApplicationTreeService.GetByAlias(treeName); + Assert.IsNotNull(tree); + + //now delete the app + Current.Services.SectionService.DeleteSection(app); + + //check that the tree is gone + Assert.AreEqual(0, Current.Services.ApplicationTreeService.GetApplicationTrees(treeName).Count()); + } + + + #region Tests to write + ///// + /////A test for ApplicationTree Constructor + ///// + //[TestMethod()] + //public void ApplicationTreeConstructorTest() + //{ + // bool silent = false; // TODO: Initialize to an appropriate value + // bool initialize = false; // TODO: Initialize to an appropriate value + // byte sortOrder = 0; // TODO: Initialize to an appropriate value + // string applicationAlias = string.Empty; // TODO: Initialize to an appropriate value + // string alias = string.Empty; // TODO: Initialize to an appropriate value + // string title = string.Empty; // TODO: Initialize to an appropriate value + // string iconClosed = string.Empty; // TODO: Initialize to an appropriate value + // string iconOpened = string.Empty; // TODO: Initialize to an appropriate value + // string assemblyName = string.Empty; // TODO: Initialize to an appropriate value + // string type = string.Empty; // TODO: Initialize to an appropriate value + // string action = string.Empty; // TODO: Initialize to an appropriate value + // ApplicationTree target = new ApplicationTree(silent, initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, assemblyName, type, action); + // Assert.Inconclusive("TODO: Implement code to verify target"); + //} + + ///// + /////A test for ApplicationTree Constructor + ///// + //[TestMethod()] + //public void ApplicationTreeConstructorTest1() + //{ + // ApplicationTree target = new ApplicationTree(); + // Assert.Inconclusive("TODO: Implement code to verify target"); + //} + + ///// + /////A test for Delete + ///// + //[TestMethod()] + //public void DeleteTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // target.Delete(); + // Assert.Inconclusive("A method that does not return a value cannot be verified."); + //} + + + ///// + /////A test for Save + ///// + //[TestMethod()] + //public void SaveTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // target.Save(); + // Assert.Inconclusive("A method that does not return a value cannot be verified."); + //} + + ///// + /////A test for getAll + ///// + //[TestMethod()] + //public void getAllTest() + //{ + // ApplicationTree[] expected = null; // TODO: Initialize to an appropriate value + // ApplicationTree[] actual; + // actual = ApplicationTree.getAll(); + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for getApplicationTree + ///// + //[TestMethod()] + //public void getApplicationTreeTest() + //{ + // string applicationAlias = string.Empty; // TODO: Initialize to an appropriate value + // ApplicationTree[] expected = null; // TODO: Initialize to an appropriate value + // ApplicationTree[] actual; + // actual = ApplicationTree.getApplicationTree(applicationAlias); + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for getApplicationTree + ///// + //[TestMethod()] + //public void getApplicationTreeTest1() + //{ + // string applicationAlias = string.Empty; // TODO: Initialize to an appropriate value + // bool onlyInitializedApplications = false; // TODO: Initialize to an appropriate value + // ApplicationTree[] expected = null; // TODO: Initialize to an appropriate value + // ApplicationTree[] actual; + // actual = ApplicationTree.getApplicationTree(applicationAlias, onlyInitializedApplications); + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for getByAlias + ///// + //[TestMethod()] + //public void getByAliasTest() + //{ + // string treeAlias = string.Empty; // TODO: Initialize to an appropriate value + // ApplicationTree expected = null; // TODO: Initialize to an appropriate value + // ApplicationTree actual; + // actual = ApplicationTree.getByAlias(treeAlias); + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for Action + ///// + //[TestMethod()] + //public void ActionTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // string expected = string.Empty; // TODO: Initialize to an appropriate value + // string actual; + // target.Action = expected; + // actual = target.Action; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for Alias + ///// + //[TestMethod()] + //public void AliasTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // string actual; + // actual = target.Alias; + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for ApplicationAlias + ///// + //[TestMethod()] + //public void ApplicationAliasTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // string actual; + // actual = target.ApplicationAlias; + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for AssemblyName + ///// + //[TestMethod()] + //public void AssemblyNameTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // string expected = string.Empty; // TODO: Initialize to an appropriate value + // string actual; + // target.AssemblyName = expected; + // actual = target.AssemblyName; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for IconClosed + ///// + //[TestMethod()] + //public void IconClosedTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // string expected = string.Empty; // TODO: Initialize to an appropriate value + // string actual; + // target.IconClosed = expected; + // actual = target.IconClosed; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for IconOpened + ///// + //[TestMethod()] + //public void IconOpenedTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // string expected = string.Empty; // TODO: Initialize to an appropriate value + // string actual; + // target.IconOpened = expected; + // actual = target.IconOpened; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for Initialize + ///// + //[TestMethod()] + //public void InitializeTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // bool expected = false; // TODO: Initialize to an appropriate value + // bool actual; + // target.Initialize = expected; + // actual = target.Initialize; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for Silent + ///// + //[TestMethod()] + //public void SilentTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // bool expected = false; // TODO: Initialize to an appropriate value + // bool actual; + // target.Silent = expected; + // actual = target.Silent; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for SortOrder + ///// + //[TestMethod()] + //public void SortOrderTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // byte expected = 0; // TODO: Initialize to an appropriate value + // byte actual; + // target.SortOrder = expected; + // actual = target.SortOrder; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for SqlHelper + ///// + //[TestMethod()] + //public void SqlHelperTest() + //{ + // ISqlHelper actual; + // actual = ApplicationTree.SqlHelper; + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for Title + ///// + //[TestMethod()] + //public void TitleTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // string expected = string.Empty; // TODO: Initialize to an appropriate value + // string actual; + // target.Title = expected; + // actual = target.Title; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for Type + ///// + //[TestMethod()] + //public void TypeTest() + //{ + // ApplicationTree target = new ApplicationTree(); // TODO: Initialize to an appropriate value + // string expected = string.Empty; // TODO: Initialize to an appropriate value + // string actual; + // target.Type = expected; + // actual = target.Type; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + #endregion + + #region Additional test attributes + // + //You can use the following additional attributes as you write your tests: + // + //Use ClassInitialize to run code before running the first test in the class + //[ClassInitialize()] + //public static void MyClassInitialize(TestContext testContext) + //{ + //} + // + //Use ClassCleanup to run code after all tests in a class have run + //[ClassCleanup()] + //public static void MyClassCleanup() + //{ + //} + // + //Use TestInitialize to run code before running each test + //[TestInitialize()] + //public void MyTestInitialize() + //{ + //} + // + //Use TestCleanup to run code after each test has run + //[TestCleanup()] + //public void MyTestCleanup() + //{ + //} + // + #endregion + } +} diff --git a/src/Umbraco.Tests/TreesAndSections/SectionTests.cs b/src/Umbraco.Tests/TreesAndSections/SectionTests.cs index f91108b302..e35bd5fd45 100644 --- a/src/Umbraco.Tests/TreesAndSections/SectionTests.cs +++ b/src/Umbraco.Tests/TreesAndSections/SectionTests.cs @@ -1,253 +1,253 @@ -using System.IO; -using NUnit.Framework; -using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers; -using System; -using System.Linq; -using Umbraco.Core; -using Umbraco.Tests.Testing; -using Umbraco.Web.Services; - -namespace Umbraco.Tests.TreesAndSections -{ - /// - ///This is a test class for ApplicationTest and is intended - ///to contain all ApplicationTest Unit Tests - /// - [TestFixture] - [UmbracoTest(AutoMapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class SectionTests : TestWithDatabaseBase - { - protected override void Compose() - { - base.Compose(); - Container.Register(); - } - - public override void SetUp() - { - base.SetUp(); - - var treesConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/trees.config"); - var appConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/applications.config"); - Directory.CreateDirectory(TestHelper.MapPathForTest("~/TEMP/TreesAndSections")); - using (var writer = File.CreateText(treesConfig)) - { - writer.Write(ResourceFiles.trees); - } - using (var writer = File.CreateText(appConfig)) - { - writer.Write(ResourceFiles.applications); - } - - ApplicationTreeService.TreeConfigFilePath = treesConfig; - SectionService.AppConfigFilePath = appConfig; - } - - public override void TearDown() - { - base.TearDown(); - - if (Directory.Exists(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"))) - { - Directory.Delete(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"), true); - } - ApplicationTreeService.TreeConfigFilePath = null; - SectionService.AppConfigFilePath = null; - } - - /// - /// Create a new application and delete it - /// - [Test()] - public void Application_Make_New() - { - var name = Guid.NewGuid().ToString("N"); - ServiceContext.SectionService.MakeNew(name, name, "icon.jpg"); - - //check if it exists - var app = ServiceContext.SectionService.GetByAlias(name); - Assert.IsNotNull(app); - - //now remove it - ServiceContext.SectionService.DeleteSection(app); - Assert.IsNull(ServiceContext.SectionService.GetByAlias(name)); - } - - #region Tests to write - - - ///// - /////A test for Application Constructor - ///// - //[TestMethod()] - //public void ApplicationConstructorTest() - //{ - // string name = string.Empty; // TODO: Initialize to an appropriate value - // string alias = string.Empty; // TODO: Initialize to an appropriate value - // string icon = string.Empty; // TODO: Initialize to an appropriate value - // Application target = new Application(name, alias, icon); - // Assert.Inconclusive("TODO: Implement code to verify target"); - //} - - ///// - /////A test for Application Constructor - ///// - //[TestMethod()] - //public void ApplicationConstructorTest1() - //{ - // Application target = new Application(); - // Assert.Inconclusive("TODO: Implement code to verify target"); - //} - - ///// - /////A test for Delete - ///// - //[TestMethod()] - //public void DeleteTest() - //{ - // Application target = new Application(); // TODO: Initialize to an appropriate value - // target.Delete(); - // Assert.Inconclusive("A method that does not return a value cannot be verified."); - //} - - - - ///// - /////A test for MakeNew - ///// - //[TestMethod()] - //public void MakeNewTest1() - //{ - // string name = string.Empty; // TODO: Initialize to an appropriate value - // string alias = string.Empty; // TODO: Initialize to an appropriate value - // string icon = string.Empty; // TODO: Initialize to an appropriate value - // Application.MakeNew(name, alias, icon); - // Assert.Inconclusive("A method that does not return a value cannot be verified."); - //} - - ///// - /////A test for RegisterIApplications - ///// - //[TestMethod()] - //public void RegisterIApplicationsTest() - //{ - // Application.RegisterIApplications(); - // Assert.Inconclusive("A method that does not return a value cannot be verified."); - //} - - ///// - /////A test for getAll - ///// - //[TestMethod()] - //public void getAllTest() - //{ - // List expected = null; // TODO: Initialize to an appropriate value - // List actual; - // actual = Application.getAll(); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for getByAlias - ///// - //[TestMethod()] - //public void getByAliasTest() - //{ - // string appAlias = string.Empty; // TODO: Initialize to an appropriate value - // Application expected = null; // TODO: Initialize to an appropriate value - // Application actual; - // actual = Application.getByAlias(appAlias); - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for SqlHelper - ///// - //[TestMethod()] - //public void SqlHelperTest() - //{ - // ISqlHelper actual; - // actual = Application.SqlHelper; - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for alias - ///// - //[TestMethod()] - //public void aliasTest() - //{ - // Application target = new Application(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.alias = expected; - // actual = target.alias; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for icon - ///// - //[TestMethod()] - //public void iconTest() - //{ - // Application target = new Application(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.icon = expected; - // actual = target.icon; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - - ///// - /////A test for name - ///// - //[TestMethod()] - //public void nameTest() - //{ - // Application target = new Application(); // TODO: Initialize to an appropriate value - // string expected = string.Empty; // TODO: Initialize to an appropriate value - // string actual; - // target.name = expected; - // actual = target.name; - // Assert.AreEqual(expected, actual); - // Assert.Inconclusive("Verify the correctness of this test method."); - //} - #endregion - - #region Additional test attributes - // - //You can use the following additional attributes as you write your tests: - // - //Use ClassInitialize to run code before running the first test in the class - //[ClassInitialize()] - //public static void MyClassInitialize(TestContext testContext) - //{ - //} - // - //Use ClassCleanup to run code after all tests in a class have run - //[ClassCleanup()] - //public static void MyClassCleanup() - //{ - //} - // - //Use TestInitialize to run code before running each test - //[TestInitialize()] - //public void MyTestInitialize() - //{ - //} - // - //Use TestCleanup to run code after each test has run - //[TestCleanup()] - //public void MyTestCleanup() - //{ - //} - // - #endregion - } -} +using System.IO; +using NUnit.Framework; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using System; +using System.Linq; +using Umbraco.Core; +using Umbraco.Tests.Testing; +using Umbraco.Web.Services; + +namespace Umbraco.Tests.TreesAndSections +{ + /// + ///This is a test class for ApplicationTest and is intended + ///to contain all ApplicationTest Unit Tests + /// + [TestFixture] + [UmbracoTest(AutoMapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class SectionTests : TestWithDatabaseBase + { + protected override void Compose() + { + base.Compose(); + Container.Register(); + } + + public override void SetUp() + { + base.SetUp(); + + var treesConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/trees.config"); + var appConfig = TestHelper.MapPathForTest("~/TEMP/TreesAndSections/applications.config"); + Directory.CreateDirectory(TestHelper.MapPathForTest("~/TEMP/TreesAndSections")); + using (var writer = File.CreateText(treesConfig)) + { + writer.Write(ResourceFiles.trees); + } + using (var writer = File.CreateText(appConfig)) + { + writer.Write(ResourceFiles.applications); + } + + ApplicationTreeService.TreeConfigFilePath = treesConfig; + SectionService.AppConfigFilePath = appConfig; + } + + public override void TearDown() + { + base.TearDown(); + + if (Directory.Exists(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"))) + { + Directory.Delete(TestHelper.MapPathForTest("~/TEMP/TreesAndSections"), true); + } + ApplicationTreeService.TreeConfigFilePath = null; + SectionService.AppConfigFilePath = null; + } + + /// + /// Create a new application and delete it + /// + [Test()] + public void Application_Make_New() + { + var name = Guid.NewGuid().ToString("N"); + ServiceContext.SectionService.MakeNew(name, name, "icon.jpg"); + + //check if it exists + var app = ServiceContext.SectionService.GetByAlias(name); + Assert.IsNotNull(app); + + //now remove it + ServiceContext.SectionService.DeleteSection(app); + Assert.IsNull(ServiceContext.SectionService.GetByAlias(name)); + } + + #region Tests to write + + + ///// + /////A test for Application Constructor + ///// + //[TestMethod()] + //public void ApplicationConstructorTest() + //{ + // string name = string.Empty; // TODO: Initialize to an appropriate value + // string alias = string.Empty; // TODO: Initialize to an appropriate value + // string icon = string.Empty; // TODO: Initialize to an appropriate value + // Application target = new Application(name, alias, icon); + // Assert.Inconclusive("TODO: Implement code to verify target"); + //} + + ///// + /////A test for Application Constructor + ///// + //[TestMethod()] + //public void ApplicationConstructorTest1() + //{ + // Application target = new Application(); + // Assert.Inconclusive("TODO: Implement code to verify target"); + //} + + ///// + /////A test for Delete + ///// + //[TestMethod()] + //public void DeleteTest() + //{ + // Application target = new Application(); // TODO: Initialize to an appropriate value + // target.Delete(); + // Assert.Inconclusive("A method that does not return a value cannot be verified."); + //} + + + + ///// + /////A test for MakeNew + ///// + //[TestMethod()] + //public void MakeNewTest1() + //{ + // string name = string.Empty; // TODO: Initialize to an appropriate value + // string alias = string.Empty; // TODO: Initialize to an appropriate value + // string icon = string.Empty; // TODO: Initialize to an appropriate value + // Application.MakeNew(name, alias, icon); + // Assert.Inconclusive("A method that does not return a value cannot be verified."); + //} + + ///// + /////A test for RegisterIApplications + ///// + //[TestMethod()] + //public void RegisterIApplicationsTest() + //{ + // Application.RegisterIApplications(); + // Assert.Inconclusive("A method that does not return a value cannot be verified."); + //} + + ///// + /////A test for getAll + ///// + //[TestMethod()] + //public void getAllTest() + //{ + // List expected = null; // TODO: Initialize to an appropriate value + // List actual; + // actual = Application.getAll(); + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for getByAlias + ///// + //[TestMethod()] + //public void getByAliasTest() + //{ + // string appAlias = string.Empty; // TODO: Initialize to an appropriate value + // Application expected = null; // TODO: Initialize to an appropriate value + // Application actual; + // actual = Application.getByAlias(appAlias); + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for SqlHelper + ///// + //[TestMethod()] + //public void SqlHelperTest() + //{ + // ISqlHelper actual; + // actual = Application.SqlHelper; + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for alias + ///// + //[TestMethod()] + //public void aliasTest() + //{ + // Application target = new Application(); // TODO: Initialize to an appropriate value + // string expected = string.Empty; // TODO: Initialize to an appropriate value + // string actual; + // target.alias = expected; + // actual = target.alias; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for icon + ///// + //[TestMethod()] + //public void iconTest() + //{ + // Application target = new Application(); // TODO: Initialize to an appropriate value + // string expected = string.Empty; // TODO: Initialize to an appropriate value + // string actual; + // target.icon = expected; + // actual = target.icon; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + + ///// + /////A test for name + ///// + //[TestMethod()] + //public void nameTest() + //{ + // Application target = new Application(); // TODO: Initialize to an appropriate value + // string expected = string.Empty; // TODO: Initialize to an appropriate value + // string actual; + // target.name = expected; + // actual = target.name; + // Assert.AreEqual(expected, actual); + // Assert.Inconclusive("Verify the correctness of this test method."); + //} + #endregion + + #region Additional test attributes + // + //You can use the following additional attributes as you write your tests: + // + //Use ClassInitialize to run code before running the first test in the class + //[ClassInitialize()] + //public static void MyClassInitialize(TestContext testContext) + //{ + //} + // + //Use ClassCleanup to run code after all tests in a class have run + //[ClassCleanup()] + //public static void MyClassCleanup() + //{ + //} + // + //Use TestInitialize to run code before running each test + //[TestInitialize()] + //public void MyTestInitialize() + //{ + //} + // + //Use TestCleanup to run code after each test has run + //[TestCleanup()] + //public void MyTestCleanup() + //{ + //} + // + #endregion + } +} diff --git a/src/Umbraco.Tests/UI/LegacyDialogTests.cs b/src/Umbraco.Tests/UI/LegacyDialogTests.cs index bcc3805c7b..ffbca1e0e8 100644 --- a/src/Umbraco.Tests/UI/LegacyDialogTests.cs +++ b/src/Umbraco.Tests/UI/LegacyDialogTests.cs @@ -1,37 +1,37 @@ -using System; -using NUnit.Framework; -using umbraco; -using Umbraco.Core; -using Umbraco.Core.Composing; +using System; +using NUnit.Framework; +using umbraco; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Web; -using Umbraco.Web._Legacy.UI; - -namespace Umbraco.Tests.UI -{ - [TestFixture] - public class LegacyDialogTests - { - - [Test] - public void Ensure_All_Tasks_Are_Secured() - { - var allTasks = TypeFinder.FindClassesOfType(); - - foreach (var t in allTasks) - { - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(t), "The type " + t + " is not of type " + typeof(LegacyDialogTask)); - } - } - - [TestCase(typeof(MemberGroupTasks), Constants.Applications.Members)] - [TestCase(typeof(dictionaryTasks), Constants.Applications.Settings)] - [TestCase(typeof(macroTasks), Constants.Applications.Developer)] - [TestCase(typeof(CreatedPackageTasks), Constants.Applications.Developer)] - public void Check_Assigned_Apps_For_Tasks(Type taskType, string app) - { - var task = (LegacyDialogTask)Activator.CreateInstance(taskType); - Assert.AreEqual(task.AssignedApp, app); - } - - } -} +using Umbraco.Web._Legacy.UI; + +namespace Umbraco.Tests.UI +{ + [TestFixture] + public class LegacyDialogTests + { + + [Test] + public void Ensure_All_Tasks_Are_Secured() + { + var allTasks = TypeFinder.FindClassesOfType(); + + foreach (var t in allTasks) + { + Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(t), "The type " + t + " is not of type " + typeof(LegacyDialogTask)); + } + } + + [TestCase(typeof(MemberGroupTasks), Constants.Applications.Members)] + [TestCase(typeof(dictionaryTasks), Constants.Applications.Settings)] + [TestCase(typeof(macroTasks), Constants.Applications.Developer)] + [TestCase(typeof(CreatedPackageTasks), Constants.Applications.Developer)] + public void Check_Assigned_Apps_For_Tasks(Type taskType, string app) + { + var task = (LegacyDialogTask)Activator.CreateInstance(taskType); + Assert.AreEqual(task.AssignedApp, app); + } + + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 801e33cca5..c86cf5d2d0 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -1,604 +1,604 @@ - - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {5D3B8245-ADA6-453F-A008-50ED04BFE770} - Library - Properties - Umbraco.Tests - Umbraco.Tests - v4.7.2 - 512 - ..\ - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - latest - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - latest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - SqlResources.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - ResourceFiles.resx - - - - - - - - - - - - - - - - - - - - True - True - ImportResources.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - TestFiles.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - Designer - Always - - - Designer - Always - - - Designer - Always - - - Designer - Always - - - - - - Designer - - - - Always - - - - - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} - Umbraco.Core - - - {651E1350-91B6-44B7-BD60-7207006D7003} - Umbraco.Web - - - {07fbc26b-2927-4a22-8d96-d644c667fecc} - Umbraco.Examine - - - - - ResXFileCodeGenerator - SqlResources.Designer.cs - Designer - - - ResXFileCodeGenerator - ImportResources.Designer.cs - Designer - - - ResXFileCodeGenerator - ResourceFiles.Designer.cs - - - ResXFileCodeGenerator - TestFiles.Designer.cs - Designer - - - - - Designer - Always - - - - - - - Designer - - - - - - - - - - Designer - - - - - - - - - - - - - $(NuGetPackageFolders.Split(';')[0]) - - - - - - - - - + + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {5D3B8245-ADA6-453F-A008-50ED04BFE770} + Library + Properties + Umbraco.Tests + Umbraco.Tests + v4.7.2 + 512 + ..\ + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + latest + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + latest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + SqlResources.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + ResourceFiles.resx + + + + + + + + + + + + + + + + + + + + True + True + ImportResources.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + TestFiles.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + Designer + Always + + + Designer + Always + + + Designer + Always + + + Designer + Always + + + + + + Designer + + + + Always + + + + + {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} + Umbraco.Core + + + {651E1350-91B6-44B7-BD60-7207006D7003} + Umbraco.Web + + + {07fbc26b-2927-4a22-8d96-d644c667fecc} + Umbraco.Examine + + + + + ResXFileCodeGenerator + SqlResources.Designer.cs + Designer + + + ResXFileCodeGenerator + ImportResources.Designer.cs + Designer + + + ResXFileCodeGenerator + ResourceFiles.Designer.cs + + + ResXFileCodeGenerator + TestFiles.Designer.cs + Designer + + + + + Designer + Always + + + + + + + Designer + + + + + + + + + + Designer + + + + + + + + + + + + + $(NuGetPackageFolders.Split(';')[0]) + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs index f22ae6ef3f..e2279ee833 100644 --- a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs @@ -1,46 +1,46 @@ -using System; -using System.Linq; -using Examine; -using Lucene.Net.Store; -using NUnit.Framework; -using Umbraco.Tests.Testing; -using Umbraco.Examine; - -namespace Umbraco.Tests.UmbracoExamine -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class EventsTest : ExamineBaseTest - { - [Test] - public void Events_Ignoring_Node() - { - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, - //make parent id 999 so all are ignored - options: new UmbracoContentIndexerOptions(false, false, 999))) - using (indexer.ProcessNonAsync()) - { - var searcher = indexer.GetSearcher(); - - var contentService = new ExamineDemoDataContentService(); - //get a node from the data repo - var node = contentService.GetPublishedContentByXPath("//*[string-length(@id)>0 and number(@id)>0]") - .Root - .Elements() - .First(); - - var valueSet = node.ConvertToValueSet(IndexTypes.Content); - indexer.IndexItems(new[] {valueSet}); - - var found = searcher.Search(searcher.CreateCriteria().Id((string) node.Attribute("id")).Compile()); - - Assert.AreEqual(0, found.TotalItemCount); - } - - - - } - - } -} +using System; +using System.Linq; +using Examine; +using Lucene.Net.Store; +using NUnit.Framework; +using Umbraco.Tests.Testing; +using Umbraco.Examine; + +namespace Umbraco.Tests.UmbracoExamine +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class EventsTest : ExamineBaseTest + { + [Test] + public void Events_Ignoring_Node() + { + using (var luceneDir = new RandomIdRamDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, + //make parent id 999 so all are ignored + options: new UmbracoContentIndexerOptions(false, false, 999))) + using (indexer.ProcessNonAsync()) + { + var searcher = indexer.GetSearcher(); + + var contentService = new ExamineDemoDataContentService(); + //get a node from the data repo + var node = contentService.GetPublishedContentByXPath("//*[string-length(@id)>0 and number(@id)>0]") + .Root + .Elements() + .First(); + + var valueSet = node.ConvertToValueSet(IndexTypes.Content); + indexer.IndexItems(new[] {valueSet}); + + var found = searcher.Search(searcher.CreateCriteria().Id((string) node.Attribute("id")).Compile()); + + Assert.AreEqual(0, found.TotalItemCount); + } + + + + } + + } +} diff --git a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs index ef51a9e071..154b587a47 100644 --- a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs @@ -1,45 +1,45 @@ -using Moq; -using System.IO; -using LightInject; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Profiling; -using Umbraco.Core.Strings; -using Umbraco.Tests.TestHelpers; -using Umbraco.Examine; - -namespace Umbraco.Tests.UmbracoExamine -{ - [TestFixture] - public abstract class ExamineBaseTest : TestWithDatabaseBase - { - [OneTimeSetUp] - public void InitializeFixture() - { - var logger = new Logger(new FileInfo(TestHelper.MapPathForTest("~/unit-test-log4net.config"))); - _profilingLogger = new ProfilingLogger(logger, new LogProfiler(logger)); - } +using Moq; +using System.IO; +using LightInject; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Profiling; +using Umbraco.Core.Strings; +using Umbraco.Tests.TestHelpers; +using Umbraco.Examine; - private ProfilingLogger _profilingLogger; +namespace Umbraco.Tests.UmbracoExamine +{ + [TestFixture] + public abstract class ExamineBaseTest : TestWithDatabaseBase + { + [OneTimeSetUp] + public void InitializeFixture() + { + var logger = new Logger(new FileInfo(TestHelper.MapPathForTest("~/unit-test-log4net.config"))); + _profilingLogger = new ProfilingLogger(logger, new LogProfiler(logger)); + } + + private ProfilingLogger _profilingLogger; protected override ProfilingLogger ProfilingLogger { get { return _profilingLogger; } - } - - /// - /// sets up resolvers before resolution is frozen - /// - protected override void Compose() - { - base.Compose(); - - Container.RegisterSingleton(_ => new DefaultShortStringHelper(SettingsForTests.GetDefaultUmbracoSettings())); - } - } -} + } + + /// + /// sets up resolvers before resolution is frozen + /// + protected override void Compose() + { + base.Compose(); + + Container.RegisterSingleton(_ => new DefaultShortStringHelper(SettingsForTests.GetDefaultUmbracoSettings())); + } + } +} diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index c7599e63e4..585e9a17d6 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -1,220 +1,220 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Examine; -using Examine.LuceneEngine.Providers; -using Lucene.Net.Analysis; -using Lucene.Net.Analysis.Standard; -using Lucene.Net.Store; -using Moq; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Umbraco.Examine; -using IContentService = Umbraco.Core.Services.IContentService; -using IMediaService = Umbraco.Core.Services.IMediaService; -using Version = Lucene.Net.Util.Version; - -namespace Umbraco.Tests.UmbracoExamine -{ - /// - /// Used internally by test classes to initialize a new index from the template - /// - internal static class IndexInitializer - { - public static UmbracoContentIndexer GetUmbracoIndexer( - ProfilingLogger profilingLogger, - Directory luceneDir, - ISqlContext sqlContext, - Analyzer analyzer = null, - IContentService contentService = null, - IMediaService mediaService = null, - IMemberService memberService = null, - IUserService userService = null, - IContentTypeService contentTypeService = null, - IMediaTypeService mediaTypeService = null, - UmbracoContentIndexerOptions options = null) - { - if (contentService == null) - { - long longTotalRecs; - var demoData = new ExamineDemoDataContentService(); - - var allRecs = demoData.GetLatestContentByXPath("//*[@isDoc]") - .Root - .Elements() - .Select(x => Mock.Of( - m => - m.Id == (int)x.Attribute("id") && - m.ParentId == (int)x.Attribute("parentID") && - m.Level == (int)x.Attribute("level") && - m.CreatorId == 0 && - m.SortOrder == (int)x.Attribute("sortOrder") && - m.CreateDate == (DateTime)x.Attribute("createDate") && - m.UpdateDate == (DateTime)x.Attribute("updateDate") && +using System; +using System.Collections.Generic; +using System.Linq; +using Examine; +using Examine.LuceneEngine.Providers; +using Lucene.Net.Analysis; +using Lucene.Net.Analysis.Standard; +using Lucene.Net.Store; +using Moq; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Examine; +using IContentService = Umbraco.Core.Services.IContentService; +using IMediaService = Umbraco.Core.Services.IMediaService; +using Version = Lucene.Net.Util.Version; + +namespace Umbraco.Tests.UmbracoExamine +{ + /// + /// Used internally by test classes to initialize a new index from the template + /// + internal static class IndexInitializer + { + public static UmbracoContentIndexer GetUmbracoIndexer( + ProfilingLogger profilingLogger, + Directory luceneDir, + ISqlContext sqlContext, + Analyzer analyzer = null, + IContentService contentService = null, + IMediaService mediaService = null, + IMemberService memberService = null, + IUserService userService = null, + IContentTypeService contentTypeService = null, + IMediaTypeService mediaTypeService = null, + UmbracoContentIndexerOptions options = null) + { + if (contentService == null) + { + long longTotalRecs; + var demoData = new ExamineDemoDataContentService(); + + var allRecs = demoData.GetLatestContentByXPath("//*[@isDoc]") + .Root + .Elements() + .Select(x => Mock.Of( + m => + m.Id == (int)x.Attribute("id") && + m.ParentId == (int)x.Attribute("parentID") && + m.Level == (int)x.Attribute("level") && + m.CreatorId == 0 && + m.SortOrder == (int)x.Attribute("sortOrder") && + m.CreateDate == (DateTime)x.Attribute("createDate") && + m.UpdateDate == (DateTime)x.Attribute("updateDate") && m.Name == (string)x.Attribute("nodeName") && - m.GetName(It.IsAny()) == (string)x.Attribute("nodeName") && - m.Path == (string)x.Attribute("path") && - m.Properties == new PropertyCollection() && - m.ContentType == Mock.Of(mt => - mt.Icon == "test" && - mt.Alias == x.Name.LocalName && - mt.Id == (int)x.Attribute("nodeType")))) - .ToArray(); - - - contentService = Mock.Of( - x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) - == - allRecs - && x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) - == - allRecs); - } - if (userService == null) - { - userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == 0 && p.Name == "admin")); - } - - if (mediaService == null) - { - long totalRecs; - - var demoData = new ExamineDemoDataMediaService(); - - var allRecs = demoData.GetLatestMediaByXpath("//node") - .Root - .Elements() - .Select(x => Mock.Of( - m => - m.Id == (int) x.Attribute("id") && - m.ParentId == (int) x.Attribute("parentID") && - m.Level == (int) x.Attribute("level") && - m.CreatorId == 0 && - m.SortOrder == (int) x.Attribute("sortOrder") && - m.CreateDate == (DateTime) x.Attribute("createDate") && - m.UpdateDate == (DateTime) x.Attribute("updateDate") && - m.Name == (string) x.Attribute("nodeName") && - m.GetName(It.IsAny()) == (string)x.Attribute("nodeName") && - m.Path == (string) x.Attribute("path") && - m.Properties == new PropertyCollection() && - m.ContentType == Mock.Of(mt => - mt.Alias == (string) x.Attribute("nodeTypeAlias") && - mt.Id == (int) x.Attribute("nodeType")))) - .ToArray(); - - // MOCK! - var mediaServiceMock = new Mock(); - - mediaServiceMock - .Setup(x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny()) - ).Returns(() => allRecs); - - mediaServiceMock - .Setup(x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) - ).Returns(() => allRecs); - - //mediaServiceMock.Setup(service => service.GetPagedXmlEntries(It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs)) - // .Returns(() => allRecs.Select(x => x.ToXml())); - - mediaService = mediaServiceMock.Object; - - } - - if (analyzer == null) - { - analyzer = new StandardAnalyzer(Version.LUCENE_30); - } - - //var indexSet = new IndexSet(); - // var indexCriteria = indexSet.ToIndexCriteria(dataService, UmbracoContentIndexer.IndexFieldPolicies); - - //var i = new UmbracoContentIndexer(indexCriteria, - // luceneDir, //custom lucene directory - // dataService, - // contentService, - // mediaService, - // dataTypeService, - // userService, - // new[] { new DefaultUrlSegmentProvider() }, - // analyzer, - // false); - - //i.IndexSecondsInterval = 1; - - if (options == null) - { - options = new UmbracoContentIndexerOptions(false, false, null); - } - - if (mediaTypeService == null) - { - var mediaTypeServiceMock = new Mock(); - mediaTypeServiceMock.Setup(x => x.GetAll()) - .Returns(new List - { - new MediaType(-1) {Alias = "Folder", Name = "Folder", Id = 1031, Icon = "icon-folder"}, - new MediaType(-1) {Alias = "Image", Name = "Image", Id = 1032, Icon = "icon-picture"} - }); - mediaTypeService = mediaTypeServiceMock.Object; - } - - // fixme oops?! - //var query = new Mock>(); - //query - // .Setup(x => x.GetWhereClauses()) - // .Returns(new List> { new Tuple($"{Constants.DatabaseSchema.Tables.Document}.published", new object[] { 1 }) }); - - //scopeProvider - // .Setup(x => x.Query()) - // .Returns(query.Object); - - var i = new UmbracoContentIndexer( - "testIndexer", - Enumerable.Empty(), - luceneDir, - analyzer, - profilingLogger, - contentService, - mediaService, - userService, - sqlContext, - new[] {new DefaultUrlSegmentProvider()}, - new UmbracoContentValueSetValidator(options, Mock.Of()), - options); - - i.IndexingError += IndexingError; - - return i; - } - - public static LuceneSearcher GetLuceneSearcher(Directory luceneDir) - { - return new LuceneSearcher("testSearcher", luceneDir, new StandardAnalyzer(Version.LUCENE_29)); - } - - public static MultiIndexSearcher GetMultiSearcher(Directory pdfDir, Directory simpleDir, Directory conventionDir, Directory cwsDir) - { - var i = new MultiIndexSearcher("testSearcher", new[] { pdfDir, simpleDir, conventionDir, cwsDir }, new StandardAnalyzer(Version.LUCENE_29)); - return i; - } - - - internal static void IndexingError(object sender, IndexingErrorEventArgs e) - { - throw new ApplicationException(e.Message, e.InnerException); - } - - - } -} + m.GetName(It.IsAny()) == (string)x.Attribute("nodeName") && + m.Path == (string)x.Attribute("path") && + m.Properties == new PropertyCollection() && + m.ContentType == Mock.Of(mt => + mt.Icon == "test" && + mt.Alias == x.Name.LocalName && + mt.Id == (int)x.Attribute("nodeType")))) + .ToArray(); + + + contentService = Mock.Of( + x => x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + == + allRecs + && x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) + == + allRecs); + } + if (userService == null) + { + userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == 0 && p.Name == "admin")); + } + + if (mediaService == null) + { + long totalRecs; + + var demoData = new ExamineDemoDataMediaService(); + + var allRecs = demoData.GetLatestMediaByXpath("//node") + .Root + .Elements() + .Select(x => Mock.Of( + m => + m.Id == (int) x.Attribute("id") && + m.ParentId == (int) x.Attribute("parentID") && + m.Level == (int) x.Attribute("level") && + m.CreatorId == 0 && + m.SortOrder == (int) x.Attribute("sortOrder") && + m.CreateDate == (DateTime) x.Attribute("createDate") && + m.UpdateDate == (DateTime) x.Attribute("updateDate") && + m.Name == (string) x.Attribute("nodeName") && + m.GetName(It.IsAny()) == (string)x.Attribute("nodeName") && + m.Path == (string) x.Attribute("path") && + m.Properties == new PropertyCollection() && + m.ContentType == Mock.Of(mt => + mt.Alias == (string) x.Attribute("nodeTypeAlias") && + mt.Id == (int) x.Attribute("nodeType")))) + .ToArray(); + + // MOCK! + var mediaServiceMock = new Mock(); + + mediaServiceMock + .Setup(x => x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + ).Returns(() => allRecs); + + mediaServiceMock + .Setup(x => x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) + ).Returns(() => allRecs); + + //mediaServiceMock.Setup(service => service.GetPagedXmlEntries(It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs)) + // .Returns(() => allRecs.Select(x => x.ToXml())); + + mediaService = mediaServiceMock.Object; + + } + + if (analyzer == null) + { + analyzer = new StandardAnalyzer(Version.LUCENE_30); + } + + //var indexSet = new IndexSet(); + // var indexCriteria = indexSet.ToIndexCriteria(dataService, UmbracoContentIndexer.IndexFieldPolicies); + + //var i = new UmbracoContentIndexer(indexCriteria, + // luceneDir, //custom lucene directory + // dataService, + // contentService, + // mediaService, + // dataTypeService, + // userService, + // new[] { new DefaultUrlSegmentProvider() }, + // analyzer, + // false); + + //i.IndexSecondsInterval = 1; + + if (options == null) + { + options = new UmbracoContentIndexerOptions(false, false, null); + } + + if (mediaTypeService == null) + { + var mediaTypeServiceMock = new Mock(); + mediaTypeServiceMock.Setup(x => x.GetAll()) + .Returns(new List + { + new MediaType(-1) {Alias = "Folder", Name = "Folder", Id = 1031, Icon = "icon-folder"}, + new MediaType(-1) {Alias = "Image", Name = "Image", Id = 1032, Icon = "icon-picture"} + }); + mediaTypeService = mediaTypeServiceMock.Object; + } + + // fixme oops?! + //var query = new Mock>(); + //query + // .Setup(x => x.GetWhereClauses()) + // .Returns(new List> { new Tuple($"{Constants.DatabaseSchema.Tables.Document}.published", new object[] { 1 }) }); + + //scopeProvider + // .Setup(x => x.Query()) + // .Returns(query.Object); + + var i = new UmbracoContentIndexer( + "testIndexer", + Enumerable.Empty(), + luceneDir, + analyzer, + profilingLogger, + contentService, + mediaService, + userService, + sqlContext, + new[] {new DefaultUrlSegmentProvider()}, + new UmbracoContentValueSetValidator(options, Mock.Of()), + options); + + i.IndexingError += IndexingError; + + return i; + } + + public static LuceneSearcher GetLuceneSearcher(Directory luceneDir) + { + return new LuceneSearcher("testSearcher", luceneDir, new StandardAnalyzer(Version.LUCENE_29)); + } + + public static MultiIndexSearcher GetMultiSearcher(Directory pdfDir, Directory simpleDir, Directory conventionDir, Directory cwsDir) + { + var i = new MultiIndexSearcher("testSearcher", new[] { pdfDir, simpleDir, conventionDir, cwsDir }, new StandardAnalyzer(Version.LUCENE_29)); + return i; + } + + + internal static void IndexingError(object sender, IndexingErrorEventArgs e) + { + throw new ApplicationException(e.Message, e.InnerException); + } + + + } +} diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 8eb347c214..db6f174f59 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -1,237 +1,237 @@ -using System.Linq; -using System.Threading; -using Examine; -using Examine.LuceneEngine.Providers; -using Lucene.Net.Index; -using Lucene.Net.Search; -using Lucene.Net.Store; -using NUnit.Framework; -using Umbraco.Tests.Testing; -using Umbraco.Examine; - -namespace Umbraco.Tests.UmbracoExamine -{ - /// - /// Tests the standard indexing capabilities - /// +using System.Linq; +using System.Threading; +using Examine; +using Examine.LuceneEngine.Providers; +using Lucene.Net.Index; +using Lucene.Net.Search; +using Lucene.Net.Store; +using NUnit.Framework; +using Umbraco.Tests.Testing; +using Umbraco.Examine; + +namespace Umbraco.Tests.UmbracoExamine +{ + /// + /// Tests the standard indexing capabilities + /// [TestFixture] - [Apartment(ApartmentState.STA)] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class IndexTest : ExamineBaseTest - { - - [Test] - public void Rebuild_Index() - { - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, options: new UmbracoContentIndexerOptions(true, false, null))) - using (indexer.ProcessNonAsync()) - { - var searcher = indexer.GetSearcher(); - - //create the whole thing - indexer.RebuildIndex(); - - var result = searcher.Search(searcher.CreateCriteria().All().Compile()); - - Assert.AreEqual(29, result.TotalItemCount); - } - } - - ///// - /// - /// Check that the node signalled as protected in the content service is not present in the index. - /// - [Test] - public void Index_Protected_Content_Not_Indexed() - { - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext)) - using (indexer.ProcessNonAsync()) - using (var searcher = ((LuceneSearcher)indexer.GetSearcher()).GetLuceneSearcher()) - { - //create the whole thing - indexer.RebuildIndex(); - - var protectedQuery = new BooleanQuery(); - protectedQuery.Add( - new BooleanClause( - new TermQuery(new Term(LuceneIndexer.CategoryFieldName, IndexTypes.Content)), - Occur.MUST)); - - protectedQuery.Add( - new BooleanClause( - new TermQuery(new Term(LuceneIndexer.ItemIdFieldName, ExamineDemoDataContentService.ProtectedNode.ToString())), - Occur.MUST)); - - var collector = TopScoreDocCollector.Create(100, true); - - searcher.Search(protectedQuery, collector); - - Assert.AreEqual(0, collector.TotalHits, "Protected node should not be indexed"); - } - - } - - [Test] - public void Index_Move_Media_From_Non_Indexable_To_Indexable_ParentID() - { - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, - //make parent id 1116 - options: new UmbracoContentIndexerOptions(false, false, 1116))) - using (indexer.ProcessNonAsync()) - { - var searcher = indexer.GetSearcher(); - - //get a node from the data repo (this one exists underneath 2222) - var node = _mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]") - .Root - .Elements() - .Where(x => (int)x.Attribute("id") == 2112) - .First(); - - var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112 - Assert.AreEqual("-1,1111,2222,2112", currPath); - - //ensure it's indexed - indexer.IndexItems(new []{ node.ConvertToValueSet(IndexTypes.Media) }); - - //it will not exist because it exists under 2222 - var results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); - Assert.AreEqual(0, results.Count()); - - //now mimic moving 2112 to 1116 - //node.SetAttributeValue("path", currPath.Replace("2222", "1116")); - node.SetAttributeValue("path", "-1,1116,2112"); - node.SetAttributeValue("parentID", "1116"); - - //now reindex the node, this should first delete it and then WILL add it because of the parent id constraint - indexer.IndexItems(new[] { node.ConvertToValueSet(IndexTypes.Media) }); - - //now ensure it exists - results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); - Assert.AreEqual(1, results.Count()); - } - } - - [Test] - public void Index_Move_Media_To_Non_Indexable_ParentID() - { - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer1 = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, - //make parent id 2222 - options: new UmbracoContentIndexerOptions(false, false, 2222))) - using (indexer1.ProcessNonAsync()) - { - var searcher = indexer1.GetSearcher(); - - //get a node from the data repo (this one exists underneath 2222) - var node = _mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]") - .Root - .Elements() - .Where(x => (int)x.Attribute("id") == 2112) - .First(); - - var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112 - Assert.AreEqual("-1,1111,2222,2112", currPath); - - //ensure it's indexed - indexer1.IndexItems(new[] { node.ConvertToValueSet(IndexTypes.Media) }); - - - - //it will exist because it exists under 2222 - var results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); - Assert.AreEqual(1, results.Count()); - - //now mimic moving the node underneath 1116 instead of 2222 - node.SetAttributeValue("path", currPath.Replace("2222", "1116")); - node.SetAttributeValue("parentID", "1116"); - - //now reindex the node, this should first delete it and then NOT add it because of the parent id constraint - indexer1.IndexItems(new[] { node.ConvertToValueSet(IndexTypes.Media) }); - - - - //now ensure it's deleted - results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); - Assert.AreEqual(0, results.Count()); - } - } - - - /// - /// This will ensure that all 'Content' (not media) is cleared from the index using the Lucene API directly. - /// We then call the Examine method to re-index Content and do some comparisons to ensure that it worked correctly. - /// - [Test] - public void Index_Reindex_Content() - { - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, options: new UmbracoContentIndexerOptions(true, false, null))) - using (indexer.ProcessNonAsync()) - { - var searcher = indexer.GetSearcher(); - - //create the whole thing - indexer.RebuildIndex(); - - - var result = searcher.Search(searcher.CreateCriteria().Field(LuceneIndexer.CategoryFieldName, IndexTypes.Content).Compile()); - Assert.AreEqual(21, result.TotalItemCount); - - //delete all content - foreach (var r in result) - { - indexer.DeleteFromIndex(r.Id); - } - - - //ensure it's all gone - result = searcher.Search(searcher.CreateCriteria().Field(LuceneIndexer.CategoryFieldName, IndexTypes.Content).Compile()); - Assert.AreEqual(0, result.TotalItemCount); - - //call our indexing methods - indexer.IndexAll(IndexTypes.Content); - - - - result = searcher.Search(searcher.CreateCriteria().Field(LuceneIndexer.CategoryFieldName, IndexTypes.Content).Compile()); - Assert.AreEqual(21, result.TotalItemCount); - } - } - - /// - /// This will delete an item from the index and ensure that all children of the node are deleted too! - /// - [Test] - public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed() - { - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext)) - using (indexer.ProcessNonAsync()) - { - var searcher = indexer.GetSearcher(); - - //create the whole thing - indexer.RebuildIndex(); - - - //now delete a node that has children - - indexer.DeleteFromIndex(1140.ToString()); - //this node had children: 1141 & 1142, let's ensure they are also removed - - var results = searcher.Search(searcher.CreateCriteria().Id(1141).Compile()); - Assert.AreEqual(0, results.Count()); - - results = searcher.Search(searcher.CreateCriteria().Id(1142).Compile()); - Assert.AreEqual(0, results.Count()); - - } - } - - private readonly ExamineDemoDataMediaService _mediaService = new ExamineDemoDataMediaService(); - } -} + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class IndexTest : ExamineBaseTest + { + + [Test] + public void Rebuild_Index() + { + using (var luceneDir = new RandomIdRamDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, options: new UmbracoContentIndexerOptions(true, false, null))) + using (indexer.ProcessNonAsync()) + { + var searcher = indexer.GetSearcher(); + + //create the whole thing + indexer.RebuildIndex(); + + var result = searcher.Search(searcher.CreateCriteria().All().Compile()); + + Assert.AreEqual(29, result.TotalItemCount); + } + } + + ///// + /// + /// Check that the node signalled as protected in the content service is not present in the index. + /// + [Test] + public void Index_Protected_Content_Not_Indexed() + { + using (var luceneDir = new RandomIdRamDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext)) + using (indexer.ProcessNonAsync()) + using (var searcher = ((LuceneSearcher)indexer.GetSearcher()).GetLuceneSearcher()) + { + //create the whole thing + indexer.RebuildIndex(); + + var protectedQuery = new BooleanQuery(); + protectedQuery.Add( + new BooleanClause( + new TermQuery(new Term(LuceneIndexer.CategoryFieldName, IndexTypes.Content)), + Occur.MUST)); + + protectedQuery.Add( + new BooleanClause( + new TermQuery(new Term(LuceneIndexer.ItemIdFieldName, ExamineDemoDataContentService.ProtectedNode.ToString())), + Occur.MUST)); + + var collector = TopScoreDocCollector.Create(100, true); + + searcher.Search(protectedQuery, collector); + + Assert.AreEqual(0, collector.TotalHits, "Protected node should not be indexed"); + } + + } + + [Test] + public void Index_Move_Media_From_Non_Indexable_To_Indexable_ParentID() + { + using (var luceneDir = new RandomIdRamDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, + //make parent id 1116 + options: new UmbracoContentIndexerOptions(false, false, 1116))) + using (indexer.ProcessNonAsync()) + { + var searcher = indexer.GetSearcher(); + + //get a node from the data repo (this one exists underneath 2222) + var node = _mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]") + .Root + .Elements() + .Where(x => (int)x.Attribute("id") == 2112) + .First(); + + var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112 + Assert.AreEqual("-1,1111,2222,2112", currPath); + + //ensure it's indexed + indexer.IndexItems(new []{ node.ConvertToValueSet(IndexTypes.Media) }); + + //it will not exist because it exists under 2222 + var results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); + Assert.AreEqual(0, results.Count()); + + //now mimic moving 2112 to 1116 + //node.SetAttributeValue("path", currPath.Replace("2222", "1116")); + node.SetAttributeValue("path", "-1,1116,2112"); + node.SetAttributeValue("parentID", "1116"); + + //now reindex the node, this should first delete it and then WILL add it because of the parent id constraint + indexer.IndexItems(new[] { node.ConvertToValueSet(IndexTypes.Media) }); + + //now ensure it exists + results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); + Assert.AreEqual(1, results.Count()); + } + } + + [Test] + public void Index_Move_Media_To_Non_Indexable_ParentID() + { + using (var luceneDir = new RandomIdRamDirectory()) + using (var indexer1 = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, + //make parent id 2222 + options: new UmbracoContentIndexerOptions(false, false, 2222))) + using (indexer1.ProcessNonAsync()) + { + var searcher = indexer1.GetSearcher(); + + //get a node from the data repo (this one exists underneath 2222) + var node = _mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]") + .Root + .Elements() + .Where(x => (int)x.Attribute("id") == 2112) + .First(); + + var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112 + Assert.AreEqual("-1,1111,2222,2112", currPath); + + //ensure it's indexed + indexer1.IndexItems(new[] { node.ConvertToValueSet(IndexTypes.Media) }); + + + + //it will exist because it exists under 2222 + var results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); + Assert.AreEqual(1, results.Count()); + + //now mimic moving the node underneath 1116 instead of 2222 + node.SetAttributeValue("path", currPath.Replace("2222", "1116")); + node.SetAttributeValue("parentID", "1116"); + + //now reindex the node, this should first delete it and then NOT add it because of the parent id constraint + indexer1.IndexItems(new[] { node.ConvertToValueSet(IndexTypes.Media) }); + + + + //now ensure it's deleted + results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); + Assert.AreEqual(0, results.Count()); + } + } + + + /// + /// This will ensure that all 'Content' (not media) is cleared from the index using the Lucene API directly. + /// We then call the Examine method to re-index Content and do some comparisons to ensure that it worked correctly. + /// + [Test] + public void Index_Reindex_Content() + { + using (var luceneDir = new RandomIdRamDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, options: new UmbracoContentIndexerOptions(true, false, null))) + using (indexer.ProcessNonAsync()) + { + var searcher = indexer.GetSearcher(); + + //create the whole thing + indexer.RebuildIndex(); + + + var result = searcher.Search(searcher.CreateCriteria().Field(LuceneIndexer.CategoryFieldName, IndexTypes.Content).Compile()); + Assert.AreEqual(21, result.TotalItemCount); + + //delete all content + foreach (var r in result) + { + indexer.DeleteFromIndex(r.Id); + } + + + //ensure it's all gone + result = searcher.Search(searcher.CreateCriteria().Field(LuceneIndexer.CategoryFieldName, IndexTypes.Content).Compile()); + Assert.AreEqual(0, result.TotalItemCount); + + //call our indexing methods + indexer.IndexAll(IndexTypes.Content); + + + + result = searcher.Search(searcher.CreateCriteria().Field(LuceneIndexer.CategoryFieldName, IndexTypes.Content).Compile()); + Assert.AreEqual(21, result.TotalItemCount); + } + } + + /// + /// This will delete an item from the index and ensure that all children of the node are deleted too! + /// + [Test] + public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed() + { + using (var luceneDir = new RandomIdRamDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext)) + using (indexer.ProcessNonAsync()) + { + var searcher = indexer.GetSearcher(); + + //create the whole thing + indexer.RebuildIndex(); + + + //now delete a node that has children + + indexer.DeleteFromIndex(1140.ToString()); + //this node had children: 1141 & 1142, let's ensure they are also removed + + var results = searcher.Search(searcher.CreateCriteria().Id(1141).Compile()); + Assert.AreEqual(0, results.Count()); + + results = searcher.Search(searcher.CreateCriteria().Id(1142).Compile()); + Assert.AreEqual(0, results.Count()); + + } + } + + private readonly ExamineDemoDataMediaService _mediaService = new ExamineDemoDataMediaService(); + } +} diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index 80dbe08343..e08c6c5f05 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -1,117 +1,117 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Examine; -using Lucene.Net.Store; -using NUnit.Framework; -using Examine.LuceneEngine.SearchCriteria; -using Moq; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Services; -using Umbraco.Examine; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.UmbracoExamine -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class SearchTests : ExamineBaseTest - { - - [Test] - public void Test_Sort_Order_Sorting() - { - long totalRecs; - var demoData = new ExamineDemoDataContentService(TestFiles.umbraco_sort); - var allRecs = demoData.GetLatestContentByXPath("//*[@isDoc]") - .Root - .Elements() - .Select(x => Mock.Of( - m => - m.Id == (int)x.Attribute("id") && - m.ParentId == (int)x.Attribute("parentID") && - m.Level == (int)x.Attribute("level") && - m.CreatorId == 0 && - m.SortOrder == (int)x.Attribute("sortOrder") && - m.CreateDate == (DateTime)x.Attribute("createDate") && - m.UpdateDate == (DateTime)x.Attribute("updateDate") && - m.Name == (string)x.Attribute("nodeName") && - m.GetName(It.IsAny()) == (string)x.Attribute("nodeName") && - m.Path == (string)x.Attribute("path") && - m.Properties == new PropertyCollection() && - m.Published == true && - m.ContentType == Mock.Of(mt => - mt.Icon == "test" && - mt.Alias == x.Name.LocalName && - mt.Id == (int)x.Attribute("nodeType")))) - .ToArray(); - var contentService = Mock.Of( - x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) - == - allRecs); - - using (var luceneDir = new RandomIdRamDirectory()) - using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, contentService: contentService)) - using (indexer.ProcessNonAsync()) - { - indexer.RebuildIndex(); - - - var searcher = indexer.GetSearcher(); - - var numberSortedCriteria = searcher.CreateCriteria() - .ParentId(1148).And() - .OrderBy(new SortableField("sortOrder", SortType.Int)); - var numberSortedResult = searcher.Search(numberSortedCriteria.Compile()); - - var stringSortedCriteria = searcher.CreateCriteria() - .ParentId(1148).And() - .OrderBy("sortOrder"); //will default to string - var stringSortedResult = searcher.Search(stringSortedCriteria.Compile()); - - Assert.AreEqual(12, numberSortedResult.TotalItemCount); - Assert.AreEqual(12, stringSortedResult.TotalItemCount); - - Assert.IsTrue(IsSortedByNumber(numberSortedResult)); - Assert.IsFalse(IsSortedByNumber(stringSortedResult)); - } - } - - private bool IsSortedByNumber(IEnumerable results) - { - var currentSort = 0; - foreach (var searchResult in results) - { - var sort = int.Parse(searchResult.Fields["sortOrder"]); - if (currentSort >= sort) - { - return false; - } - currentSort = sort; - } - return true; - } - - //[Test] - //public void Test_Index_Type_With_German_Analyzer() - //{ - // using (var luceneDir = new RandomIdRamDirectory()) - // { - // var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir, - // new GermanAnalyzer()); - // indexer.RebuildIndex(); - // var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - // } - //} - - //private readonly TestContentService _contentService = new TestContentService(); - //private readonly TestMediaService _mediaService = new TestMediaService(); - //private static UmbracoExamineSearcher _searcher; - //private static UmbracoContentIndexer _indexer; - //private Lucene.Net.Store.Directory _luceneDir; - - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Examine; +using Lucene.Net.Store; +using NUnit.Framework; +using Examine.LuceneEngine.SearchCriteria; +using Moq; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Services; +using Umbraco.Examine; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.UmbracoExamine +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class SearchTests : ExamineBaseTest + { + + [Test] + public void Test_Sort_Order_Sorting() + { + long totalRecs; + var demoData = new ExamineDemoDataContentService(TestFiles.umbraco_sort); + var allRecs = demoData.GetLatestContentByXPath("//*[@isDoc]") + .Root + .Elements() + .Select(x => Mock.Of( + m => + m.Id == (int)x.Attribute("id") && + m.ParentId == (int)x.Attribute("parentID") && + m.Level == (int)x.Attribute("level") && + m.CreatorId == 0 && + m.SortOrder == (int)x.Attribute("sortOrder") && + m.CreateDate == (DateTime)x.Attribute("createDate") && + m.UpdateDate == (DateTime)x.Attribute("updateDate") && + m.Name == (string)x.Attribute("nodeName") && + m.GetName(It.IsAny()) == (string)x.Attribute("nodeName") && + m.Path == (string)x.Attribute("path") && + m.Properties == new PropertyCollection() && + m.Published == true && + m.ContentType == Mock.Of(mt => + mt.Icon == "test" && + mt.Alias == x.Name.LocalName && + mt.Id == (int)x.Attribute("nodeType")))) + .ToArray(); + var contentService = Mock.Of( + x => x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) + == + allRecs); + + using (var luceneDir = new RandomIdRamDirectory()) + using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, ScopeProvider.SqlContext, contentService: contentService)) + using (indexer.ProcessNonAsync()) + { + indexer.RebuildIndex(); + + + var searcher = indexer.GetSearcher(); + + var numberSortedCriteria = searcher.CreateCriteria() + .ParentId(1148).And() + .OrderBy(new SortableField("sortOrder", SortType.Int)); + var numberSortedResult = searcher.Search(numberSortedCriteria.Compile()); + + var stringSortedCriteria = searcher.CreateCriteria() + .ParentId(1148).And() + .OrderBy("sortOrder"); //will default to string + var stringSortedResult = searcher.Search(stringSortedCriteria.Compile()); + + Assert.AreEqual(12, numberSortedResult.TotalItemCount); + Assert.AreEqual(12, stringSortedResult.TotalItemCount); + + Assert.IsTrue(IsSortedByNumber(numberSortedResult)); + Assert.IsFalse(IsSortedByNumber(stringSortedResult)); + } + } + + private bool IsSortedByNumber(IEnumerable results) + { + var currentSort = 0; + foreach (var searchResult in results) + { + var sort = int.Parse(searchResult.Fields["sortOrder"]); + if (currentSort >= sort) + { + return false; + } + currentSort = sort; + } + return true; + } + + //[Test] + //public void Test_Index_Type_With_German_Analyzer() + //{ + // using (var luceneDir = new RandomIdRamDirectory()) + // { + // var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir, + // new GermanAnalyzer()); + // indexer.RebuildIndex(); + // var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); + // } + //} + + //private readonly TestContentService _contentService = new TestContentService(); + //private readonly TestMediaService _mediaService = new TestMediaService(); + //private static UmbracoExamineSearcher _searcher; + //private static UmbracoContentIndexer _indexer; + //private Lucene.Net.Store.Directory _luceneDir; + + } +} diff --git a/src/Umbraco.Tests/UmbracoExamine/TestDataService.cs b/src/Umbraco.Tests/UmbracoExamine/TestDataService.cs index f7019ad15a..856c3e99aa 100644 --- a/src/Umbraco.Tests/UmbracoExamine/TestDataService.cs +++ b/src/Umbraco.Tests/UmbracoExamine/TestDataService.cs @@ -1,32 +1,32 @@ -//using System.IO; -//using Umbraco.Tests.TestHelpers; -//using UmbracoExamine.DataServices; - -//namespace Umbraco.Tests.UmbracoExamine -//{ -// public class TestDataService : IDataService -// { - -// public TestDataService() -// { -// ContentService = new TestContentService(); -// LogService = new TestLogService(); -// MediaService = new TestMediaService(); -// } - -// #region IDataService Members - -// public IContentService ContentService { get; internal set; } - -// public ILogService LogService { get; internal set; } - -// public IMediaService MediaService { get; internal set; } - -// public string MapPath(string virtualPath) -// { -// return new DirectoryInfo(TestHelper.CurrentAssemblyDirectory) + "\\" + virtualPath.Replace("/", "\\"); -// } - -// #endregion -// } -//} +//using System.IO; +//using Umbraco.Tests.TestHelpers; +//using UmbracoExamine.DataServices; + +//namespace Umbraco.Tests.UmbracoExamine +//{ +// public class TestDataService : IDataService +// { + +// public TestDataService() +// { +// ContentService = new TestContentService(); +// LogService = new TestLogService(); +// MediaService = new TestMediaService(); +// } + +// #region IDataService Members + +// public IContentService ContentService { get; internal set; } + +// public ILogService LogService { get; internal set; } + +// public IMediaService MediaService { get; internal set; } + +// public string MapPath(string virtualPath) +// { +// return new DirectoryInfo(TestHelper.CurrentAssemblyDirectory) + "\\" + virtualPath.Replace("/", "\\"); +// } + +// #endregion +// } +//} diff --git a/src/Umbraco.Tests/UmbracoExamine/TestFiles.resx b/src/Umbraco.Tests/UmbracoExamine/TestFiles.resx index 7dfde4fbad..e23540252a 100644 --- a/src/Umbraco.Tests/UmbracoExamine/TestFiles.resx +++ b/src/Umbraco.Tests/UmbracoExamine/TestFiles.resx @@ -1,130 +1,130 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - testfiles\media.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 - - - testfiles\umbraco.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 - - - testfiles\umbraco-sort.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + testfiles\media.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + testfiles\umbraco.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + testfiles\umbraco-sort.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/TestFiles/media.xml b/src/Umbraco.Tests/UmbracoExamine/TestFiles/media.xml index 630433ef66..2c6e23de8c 100644 --- a/src/Umbraco.Tests/UmbracoExamine/TestFiles/media.xml +++ b/src/Umbraco.Tests/UmbracoExamine/TestFiles/media.xml @@ -1,51 +1,51 @@ - - - - - - - - - 115 - 268 - 10726 - jpg - - - 115 - 268 - 10726 - jpg - - - - - - 115 - 268 - 10726 - jpg - - - - 115 - 268 - 10726 - jpg - - - - 115 - 268 - 10726 - jpg - - - - 115 - 268 - 10726 - jpg - - + + + + + + + + + 115 + 268 + 10726 + jpg + + + 115 + 268 + 10726 + jpg + + + + + + 115 + 268 + 10726 + jpg + + + + 115 + 268 + 10726 + jpg + + + + 115 + 268 + 10726 + jpg + + + + 115 + 268 + 10726 + jpg + + \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/TestFiles/umbraco-sort.config b/src/Umbraco.Tests/UmbracoExamine/TestFiles/umbraco-sort.config index 238bff2f8e..cb94e94329 100644 --- a/src/Umbraco.Tests/UmbracoExamine/TestFiles/umbraco-sort.config +++ b/src/Umbraco.Tests/UmbracoExamine/TestFiles/umbraco-sort.config @@ -1,130 +1,130 @@ - - - - - - - - - - - - - - - - - - - - - - - -]> - - - - - - Credit: Douglas Robar - /media/825/darren-ferguson_david-conlisk.jpg - /media/835/darren-ferguson_david-conlisk_thumb.jpg - - - - - - - - - 0 - - - Credit: Douglas Robar - /media/879/christian-palm.jpg - /media/889/christian-palm_thumb.jpg - - - Credit: Warren Buckley - /media/394/codegarden08-t-shirt.jpg - /media/799/codegarden08-t-shirt_thumb.jpg - - - - - - - - - 0 - - - Credit: Douglas Robar - /media/852/bingo-callers.jpg - /media/871/bingo-callers_thumb.jpg - - - - - - - - - 0 - - - Credit: Douglas Robar - /media/879/christian-palm.jpg - /media/889/christian-palm_thumb.jpg - - - - - - - - - 0 - - - Credit: Douglas Robar - /media/879/christian-palm.jpg - /media/889/christian-palm_thumb.jpg - - - Credit: Douglas Robar - /media/879/christian-palm.jpg - /media/889/christian-palm_thumb.jpg - - - Credit: Douglas Robar - /media/879/christian-palm.jpg - /media/889/christian-palm_thumb.jpg - - - Credit: Douglas Robar - /media/879/christian-palm.jpg - /media/889/christian-palm_thumb.jpg - - - Credit: Douglas Robar - /media/879/christian-palm.jpg - /media/889/christian-palm_thumb.jpg - - - Credit: Douglas Robar - /media/879/christian-palm.jpg - /media/889/christian-palm_thumb.jpg - - - Credit: Douglas Robar - /media/879/christian-palm.jpg - /media/889/christian-palm_thumb.jpg - - - - - + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + + + Credit: Douglas Robar + /media/825/darren-ferguson_david-conlisk.jpg + /media/835/darren-ferguson_david-conlisk_thumb.jpg + + + + + + + + + 0 + + + Credit: Douglas Robar + /media/879/christian-palm.jpg + /media/889/christian-palm_thumb.jpg + + + Credit: Warren Buckley + /media/394/codegarden08-t-shirt.jpg + /media/799/codegarden08-t-shirt_thumb.jpg + + + + + + + + + 0 + + + Credit: Douglas Robar + /media/852/bingo-callers.jpg + /media/871/bingo-callers_thumb.jpg + + + + + + + + + 0 + + + Credit: Douglas Robar + /media/879/christian-palm.jpg + /media/889/christian-palm_thumb.jpg + + + + + + + + + 0 + + + Credit: Douglas Robar + /media/879/christian-palm.jpg + /media/889/christian-palm_thumb.jpg + + + Credit: Douglas Robar + /media/879/christian-palm.jpg + /media/889/christian-palm_thumb.jpg + + + Credit: Douglas Robar + /media/879/christian-palm.jpg + /media/889/christian-palm_thumb.jpg + + + Credit: Douglas Robar + /media/879/christian-palm.jpg + /media/889/christian-palm_thumb.jpg + + + Credit: Douglas Robar + /media/879/christian-palm.jpg + /media/889/christian-palm_thumb.jpg + + + Credit: Douglas Robar + /media/879/christian-palm.jpg + /media/889/christian-palm_thumb.jpg + + + Credit: Douglas Robar + /media/879/christian-palm.jpg + /media/889/christian-palm_thumb.jpg + + + + + \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/TestFiles/umbraco.config b/src/Umbraco.Tests/UmbracoExamine/TestFiles/umbraco.config index 94af72c3c4..15a322622a 100644 --- a/src/Umbraco.Tests/UmbracoExamine/TestFiles/umbraco.config +++ b/src/Umbraco.Tests/UmbracoExamine/TestFiles/umbraco.config @@ -1,617 +1,617 @@ - - - - - - - - - - - - - - - - - - - - - - - -]> - - - - - - - - - - - - - 0 - It's the KING of all CMS's, what more do you -need to know about it?

-]]>
- /media/171/umbraco_tshirt.jpg - Umbraco CMS is free - -

It means that you are not bound and locked to the licensing -rules about the number of content items / processors / web / -domains, etc. Many products are very expensive as the license cost -escalates with the number of websites. In many cases, one is soon -up in the 500' before one can even start to implement the -solution.

- -

Umbraco is Open Source

- -

In an Open Source product one has at any time full access to -source code. This provides insight and leads to better quality, as -"shortcuts" and bad code can easily be penetrated. Moreover, one -has the ability to influence and further develop the product.

- -

Umbraco is based on open source and is therefore not -lisence-plated or subject to an enterprise's ownership. The Umbraco -publishing tool places great emphasis on simplicity, standards, -flexibility and integration. Umbraco was launched in 2005 and has -had a tremendous growth since that time. Umbraco is today among the -most popular systems based on open source for Microsoft. NET -platform.

- -

100% Microsoft .NET

- -

Umbraco is built 100% on the Microsoft. NET 2.0. This means that -you can use 3rd party .NET components directly in Umbraco. -Moreover, integration with other .NET-based solutions is well -adapted from Umbracos part. Umbraco won an award for the -Integration possibilities on BNP Awards 2006.

- -

Fast results

- -

Umbraco is a powerful tool with a focus on core functionality -and openness. This efficiently allows you to achieve exactly what -you want - rather than be restricted to a "finished" module, which -makes only half of what you really wanted.

- -

Easy-to-use

- -

Umbraco has a focus on content and facilitate rapid and -intuitive content management. The system strives to be elegant, -usable and effective.

- -

Based on standards

- -

Umbraco is based on standards from the W3C as XHTML, CSS, XML -and XSLT. This gives a greater flexibility and independence, which -in turn provides more value for the money.

- -

Integration

- -

Umbraco is known to be among the best CMS on integration. Much -of the reason for this is the way to expose Microsoft ASP.NET -components as elements of, or so-called "macro's" for use in -templates, as well as in the WYSIWYG editor that writers and -editors use. Elements is also possible to "cache" on several levels -and is based on standard Microsoft .NET technology. Below are some -examples:

- -
    -
  • External XML sources (ex RSS) can be directly implemented via -XSLT elements (macros).
  • - -
  • ASP.NET controls that retrieve data from external or internal -posterior systems via standard Web Services.
  • -
- -

 

- -

 

-]]>
-
- - /media/227/warren-buckley.jpg - /media/228/sam-grady.jpg - Sam Grady - -

Sam Grady, me, is a graphic designer. I loves graphic design. -Great graphic design and simple solutions makes this man very -happy. Photography is also very close to my heart and I'd like to -be better, so I practice a lot and annoy people with my -requests.
-
- I haven't got a blog, yet, don't Twitter and cancelled my Facebook -account as I felt uncomfortable with people knowing my business. -Anyone else feel that way? I do, however, have a Flickr account, so -go check it out: www.flickr.com/photos/mrgrady
-
- I also have my own business, G72, which has a website that -desperately needs updating. Also, as of today (03/03/09), it's -currently down due to inept web hosts. Hopefully it won't be if you -want to take a look: www.g-72.co.uk
-
- Regarding this project, Warren asked me if I wanted to design his -website starter package for the fantastic Umbraco CMS, which I -jumped at the chance to do. Hopefully you'll find the design -appropriate and the package very useful, as that's what we really -want from this project.
-
- Finally, Warren says the layout is work in progress and I have a, -typically fussy, list of designer layout requests that I've asked -Warren to do, but time being what it is, these changes won't be -made on version 2.0, so if you spot anything, do mail the man as -we'll be compiling a list of tweaks to be made.
-
- Enjoy.

-]]>
- Warren Buckley - -

Warren Buckley is a web developer who specialises in using the -Umbraco CMS platform to build content managed websites for Xeed in -Norway. I run a blog called Creative Web -Specialist, where I mainly write tips and tutorial articles -around Umbraco.

- -

I have teamed up with ex-collegue Sam Grady to help me design -the new version of CWS as he produces wonderfully sexy designs for -the web. If you have seen my previous version/s of CWS you will -know that my design skills are far from stunning.

- -

I decided to create this website starter site nicknamed CWS -(Creative Website Starter site) as a learning tool to help you -understand how all the elements of a site work together in -Umbraco.

- -

From this NEW CWS package comes the -following:

- -
    -
  • Obviously a BRAND NEW spanking design from -ex-collegue Sam Grady of G-72
  • - -
  • A focus for this site to be used to help teach new users to the -Umbraco CMS platform.
  • - -
  • .NET usercontrols written in C#
  • - -
  • XSLT & .NET code heavily commented to help understand what -is going on.
  • - -
  • With supporting documentation coming in the near future.
  • -
- -

Want to see where else you can find me on the net?
- Blog - www.creativewebspecialist.co.uk
- Flickr - www.flickr.com/photos/warrenbuckley
- Last.FM - www.last.fm/user/warrenbuckley
- Twitter - twitter.com/warrenbuckley

-]]>
- - - - - - - - - - 1 - 2 - 3 - 4 - 5 - - - - - - 0 - This website has been produced to help you understand -Umbraco.

-]]>
-
- - - - - - 0 - This is a good place to put a service message or something to help define your site or company.

]]>
- - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam ullamcorper condimentum lorem. Curabitur placerat nunc ut leo. Integer eros ligula, vestibulum at, eleifend id, dignissim vel, est.

-

Fusce tristique. Cras faucibus porta nunc. Aliquam ultrices, arcu quis ornare sagittis, lectus augue ornare nulla, eu lobortis velit lectus id nibh. Aliquam condimentum aliquet purus. Quisque blandit ante non sapien. Sed justo libero, sollicitudin ac, pretium et, luctus nec, nisl. Nulla semper neque nec magna. Cras ut nibh ut urna bibendum sodales. Quisque venenatis euismod lacus. Pellentesque dapibus turpis at urna.

-

Sub Header

-

Sed scelerisque adipiscing mauris. Mauris egestas dapibus quam. Integer in libero eget eros dignissim pretium. Proin luctus sem nec lorem. Praesent lorem. Vivamus eget nunc quis sapien condimentum egestas. Maecenas facilisis, nunc at sodales facilisis, quam magna pretium nibh, vel ultrices lectus lorem quis neque. Nam sit amet leo ac lectus gravida convallis. Phasellus at enim vel dui porta porttitor. Morbi id dolor adipiscing erat egestas consequat.

-

Phasellus diam. Morbi dolor. Donec consequat sodales nunc. Nam dapibus lectus id lectus. Sed ultrices metus sit amet est. Morbi porttitor. Proin vel risus. Phasellus sodales convallis justo. Sed luctus hendrerit risus. Sed est lorem, feugiat et, rutrum quis, condimentum non, nunc. Aenean urna leo, sagittis a, commodo eget, lobortis nec, eros. Vivamus pharetra, lectus eu ultrices pulvinar, nunc quam consectetur nibh, sed pulvinar leo dolor ut felis.

-
    -
  1. Item one
  2. -
  3. Item two
  4. -
  5. Item three
  6. -
-

Nullam lobortis, mi nec feugiat congue, dolor diam cursus lacus, a elementum ligula dolor vitae sem. Suspendisse at quam. Praesent neque. Vestibulum at justo. Nulla rutrum velit et eros.

-
    -
  • Integer convallis augue in tellus
  • -
  • Magna quam sollicitudin mauris
  • -
-

Nullam lobortis, mi nec feugiat congue, dolor diam cursus lacus, a elementum ligula dolor vitae sem. Suspendisse at quam.

]]>
-
- - - - - - - - - - - 0 - The Bookhouse Boys. Live at the ICA, London.

-]]>
- - - /media/1239/bookhouse-boys_gallery.jpg - - Credit: Sam Grady - /media/1277/bookhouse-boys_3.jpg - /media/1314/bookhouse-boys_3_thumb.jpg - - - - - - - - - 0 - - - Credit: Sam Grady - /media/1250/bookhouse-boys_1.jpg - /media/1296/bookhouse-boys_1_thumb.jpg - - - - - - - - - 0 - - - Credit: Sam Grady - /media/1331/bookhouse-boys_2.jpg - /media/1350/bookhouse-boys_2_thumb.jpg - - - - - - - - - 0 - -
- - - - - - - - - - 0 - Where all the great minds of umbraco meet!

-]]>
- - - /media/993/codegarden-08_gallery.jpg - - Credit: Douglas Robar - /media/825/darren-ferguson_david-conlisk.jpg - /media/835/darren-ferguson_david-conlisk_thumb.jpg - - - - - - - - - 0 - - - Credit: Warren Buckley - /media/394/codegarden08-t-shirt.jpg - /media/799/codegarden08-t-shirt_thumb.jpg - - - - - - - - - 0 - - - Credit: Douglas Robar - /media/852/bingo-callers.jpg - /media/871/bingo-callers_thumb.jpg - - - - - - - - - 0 - - - Credit: Douglas Robar - /media/879/christian-palm.jpg - /media/889/christian-palm_thumb.jpg - - - - - - - - - 0 - -
- - - - - - - - - - 0 - Photos from a trip to Bath in November 2008

-]]>
- - - /media/935/bath_gallery.jpg - - Credit: Warren Buckley - /media/995/royal-crescent.jpg - /media/1014/royal-crescent_thumb.jpg - - - - - - - - - 0 - -
- - - - - - 0 - This first page displays all the albums featured within your site.
Please have a browse through.

]]>
- - -
- - - 2009-06-22T00:00:00 - - Come join the annual Umbraco developer conference in Wonderful -Odense on May 22nd - 24th. Two days for insights, eye-openers, -great conversations and friendly people. - -

The who's who of umbraco will be gathered and share their -knowledge through talks, tutorials and via un-conference formats -like open space or hacking sessions. We have booked a cool, old and -huge industrial venue in central Copenhagen where we'll have one -huge room, three rooms for breakout sessions and a Café that -serves your taste of espresso.

- -

Come join the annual Umbraco developer conference in Wonderful -Odense on May 22nd - 24th. Two days for insights, eye-openers, -great conversations and friendly people.

- -

We've planned a program that fits both experienced umbraco users -as well as new comers. The conference is kicked off with a Keynote -and after that we'll have two or three tracks of sessions and the -first day is rounded with an open Q/A session with the umbraco core -team.

- -

The second day features an un-conference format called open -space, which we also used with great success at last year's -conference. It's a format where everybody can suggest a topic and -as we have plenty of break-out rooms rest assured that there'll be -room for your topic as well. Last year's more than 20 topics -covered Silverlight, High performance websites with umbraco, -building custom data types and much more.

- -

But it doesn't ends there. We've ensured that we can stay at the -venue until midnight both days which means that when the first day -ends, the fun begins. With more than hundred people gathered at a -place with loads of umbraco knowledge, wifi, great food and plenty -of space, who knows what could happen. A BBQ, new packages, -improvised demos and talks, or...?

- -

 

-]]>
- /media/445/umbraco_tshirt.jpg - - - - - - - - - 0 -
- - After Warren Buckley's success of the first Creative Website -Starter site package, with over 20K downloads he has decided to -work on CWS2. - -

As of today Wednesday the 4th March 2009, the CWS2 package is -now available to download from the built in package repository -inside Umbraco.

- -

With this new version comes the following:

- -
    -
  • A BRAND NEW spanking design from ex-collegue -Sam Grady of G-72
  • - -
  • A focus for this site to be used to help teach new users to the -Umbraco CMS platform.
  • - -
  • .NET usercontrols written in C#
  • - -
  • XSLT & .NET code heavily commented to help understand what -is going on.
  • - -
  • With supporting documentation coming in the near future.
  • -
-]]>
- - - - - - - - - - - 0 -
- - - - - - 0 - - - Please browse through our news archive and event listings -below.

-]]>
-
- - you@yourcompany.co.uk - Email from Contact form on website - - you@yourcompany.co.uk - Thank you for your message - - 0 - - - - - - 0 - Everything you need to
- get in touch.

-]]>
- Enquiry Form - -

If you have a particular enquiry, please fill out the form below -and provide as much information as you can, so that one of our -representatives can deal with your enquiry as effciently as -possible.

-]]>
- Thank you. We will be in touch shortly.

-]]>
- Thanks for filling out our contact form we will get back to you -shortly.

- -

Regards,
- My Company

-]]>
-
- - you@yourcompany.co.uk - [YourName] has sent you a link to read - - 0 - - - - - - 1 - Send a page onto your friend.

]]>
- Thank you. We appreciate the love you are giving our site.

]]>
- Thanks for sending that link onto your friend, we really appreciate it here at My Company.

]]>
-
- - - 0 - Your logo/name - Sam Grady designed this for Warren Buckley. "This" idea was first created by the incredible Robert Brownjohn and has been copied many times since.

]]>
- - Thank you for installing the umbraco website package created and developed by Warren Buckley. This should website package be used to help you understand how all the components of an Umbraco site works.

-

test1

]]>
- - - - 0 -
-
+ + + + + + + + + + + + + + + + + + + + + + + +]> + + + + + + + + + + + + + 0 + It's the KING of all CMS's, what more do you +need to know about it?

+]]>
+ /media/171/umbraco_tshirt.jpg + Umbraco CMS is free + +

It means that you are not bound and locked to the licensing +rules about the number of content items / processors / web / +domains, etc. Many products are very expensive as the license cost +escalates with the number of websites. In many cases, one is soon +up in the 500' before one can even start to implement the +solution.

+ +

Umbraco is Open Source

+ +

In an Open Source product one has at any time full access to +source code. This provides insight and leads to better quality, as +"shortcuts" and bad code can easily be penetrated. Moreover, one +has the ability to influence and further develop the product.

+ +

Umbraco is based on open source and is therefore not +lisence-plated or subject to an enterprise's ownership. The Umbraco +publishing tool places great emphasis on simplicity, standards, +flexibility and integration. Umbraco was launched in 2005 and has +had a tremendous growth since that time. Umbraco is today among the +most popular systems based on open source for Microsoft. NET +platform.

+ +

100% Microsoft .NET

+ +

Umbraco is built 100% on the Microsoft. NET 2.0. This means that +you can use 3rd party .NET components directly in Umbraco. +Moreover, integration with other .NET-based solutions is well +adapted from Umbracos part. Umbraco won an award for the +Integration possibilities on BNP Awards 2006.

+ +

Fast results

+ +

Umbraco is a powerful tool with a focus on core functionality +and openness. This efficiently allows you to achieve exactly what +you want - rather than be restricted to a "finished" module, which +makes only half of what you really wanted.

+ +

Easy-to-use

+ +

Umbraco has a focus on content and facilitate rapid and +intuitive content management. The system strives to be elegant, +usable and effective.

+ +

Based on standards

+ +

Umbraco is based on standards from the W3C as XHTML, CSS, XML +and XSLT. This gives a greater flexibility and independence, which +in turn provides more value for the money.

+ +

Integration

+ +

Umbraco is known to be among the best CMS on integration. Much +of the reason for this is the way to expose Microsoft ASP.NET +components as elements of, or so-called "macro's" for use in +templates, as well as in the WYSIWYG editor that writers and +editors use. Elements is also possible to "cache" on several levels +and is based on standard Microsoft .NET technology. Below are some +examples:

+ +
    +
  • External XML sources (ex RSS) can be directly implemented via +XSLT elements (macros).
  • + +
  • ASP.NET controls that retrieve data from external or internal +posterior systems via standard Web Services.
  • +
+ +

 

+ +

 

+]]>
+
+ + /media/227/warren-buckley.jpg + /media/228/sam-grady.jpg + Sam Grady + +

Sam Grady, me, is a graphic designer. I loves graphic design. +Great graphic design and simple solutions makes this man very +happy. Photography is also very close to my heart and I'd like to +be better, so I practice a lot and annoy people with my +requests.
+
+ I haven't got a blog, yet, don't Twitter and cancelled my Facebook +account as I felt uncomfortable with people knowing my business. +Anyone else feel that way? I do, however, have a Flickr account, so +go check it out: www.flickr.com/photos/mrgrady
+
+ I also have my own business, G72, which has a website that +desperately needs updating. Also, as of today (03/03/09), it's +currently down due to inept web hosts. Hopefully it won't be if you +want to take a look: www.g-72.co.uk
+
+ Regarding this project, Warren asked me if I wanted to design his +website starter package for the fantastic Umbraco CMS, which I +jumped at the chance to do. Hopefully you'll find the design +appropriate and the package very useful, as that's what we really +want from this project.
+
+ Finally, Warren says the layout is work in progress and I have a, +typically fussy, list of designer layout requests that I've asked +Warren to do, but time being what it is, these changes won't be +made on version 2.0, so if you spot anything, do mail the man as +we'll be compiling a list of tweaks to be made.
+
+ Enjoy.

+]]>
+ Warren Buckley + +

Warren Buckley is a web developer who specialises in using the +Umbraco CMS platform to build content managed websites for Xeed in +Norway. I run a blog called Creative Web +Specialist, where I mainly write tips and tutorial articles +around Umbraco.

+ +

I have teamed up with ex-collegue Sam Grady to help me design +the new version of CWS as he produces wonderfully sexy designs for +the web. If you have seen my previous version/s of CWS you will +know that my design skills are far from stunning.

+ +

I decided to create this website starter site nicknamed CWS +(Creative Website Starter site) as a learning tool to help you +understand how all the elements of a site work together in +Umbraco.

+ +

From this NEW CWS package comes the +following:

+ +
    +
  • Obviously a BRAND NEW spanking design from +ex-collegue Sam Grady of G-72
  • + +
  • A focus for this site to be used to help teach new users to the +Umbraco CMS platform.
  • + +
  • .NET usercontrols written in C#
  • + +
  • XSLT & .NET code heavily commented to help understand what +is going on.
  • + +
  • With supporting documentation coming in the near future.
  • +
+ +

Want to see where else you can find me on the net?
+ Blog - www.creativewebspecialist.co.uk
+ Flickr - www.flickr.com/photos/warrenbuckley
+ Last.FM - www.last.fm/user/warrenbuckley
+ Twitter - twitter.com/warrenbuckley

+]]>
+ + + + + + + + + + 1 + 2 + 3 + 4 + 5 + + + + + + 0 + This website has been produced to help you understand +Umbraco.

+]]>
+
+ + + + + + 0 + This is a good place to put a service message or something to help define your site or company.

]]>
+ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam ullamcorper condimentum lorem. Curabitur placerat nunc ut leo. Integer eros ligula, vestibulum at, eleifend id, dignissim vel, est.

+

Fusce tristique. Cras faucibus porta nunc. Aliquam ultrices, arcu quis ornare sagittis, lectus augue ornare nulla, eu lobortis velit lectus id nibh. Aliquam condimentum aliquet purus. Quisque blandit ante non sapien. Sed justo libero, sollicitudin ac, pretium et, luctus nec, nisl. Nulla semper neque nec magna. Cras ut nibh ut urna bibendum sodales. Quisque venenatis euismod lacus. Pellentesque dapibus turpis at urna.

+

Sub Header

+

Sed scelerisque adipiscing mauris. Mauris egestas dapibus quam. Integer in libero eget eros dignissim pretium. Proin luctus sem nec lorem. Praesent lorem. Vivamus eget nunc quis sapien condimentum egestas. Maecenas facilisis, nunc at sodales facilisis, quam magna pretium nibh, vel ultrices lectus lorem quis neque. Nam sit amet leo ac lectus gravida convallis. Phasellus at enim vel dui porta porttitor. Morbi id dolor adipiscing erat egestas consequat.

+

Phasellus diam. Morbi dolor. Donec consequat sodales nunc. Nam dapibus lectus id lectus. Sed ultrices metus sit amet est. Morbi porttitor. Proin vel risus. Phasellus sodales convallis justo. Sed luctus hendrerit risus. Sed est lorem, feugiat et, rutrum quis, condimentum non, nunc. Aenean urna leo, sagittis a, commodo eget, lobortis nec, eros. Vivamus pharetra, lectus eu ultrices pulvinar, nunc quam consectetur nibh, sed pulvinar leo dolor ut felis.

+
    +
  1. Item one
  2. +
  3. Item two
  4. +
  5. Item three
  6. +
+

Nullam lobortis, mi nec feugiat congue, dolor diam cursus lacus, a elementum ligula dolor vitae sem. Suspendisse at quam. Praesent neque. Vestibulum at justo. Nulla rutrum velit et eros.

+
    +
  • Integer convallis augue in tellus
  • +
  • Magna quam sollicitudin mauris
  • +
+

Nullam lobortis, mi nec feugiat congue, dolor diam cursus lacus, a elementum ligula dolor vitae sem. Suspendisse at quam.

]]>
+
+ + + + + + + + + + + 0 + The Bookhouse Boys. Live at the ICA, London.

+]]>
+ + + /media/1239/bookhouse-boys_gallery.jpg + + Credit: Sam Grady + /media/1277/bookhouse-boys_3.jpg + /media/1314/bookhouse-boys_3_thumb.jpg + + + + + + + + + 0 + + + Credit: Sam Grady + /media/1250/bookhouse-boys_1.jpg + /media/1296/bookhouse-boys_1_thumb.jpg + + + + + + + + + 0 + + + Credit: Sam Grady + /media/1331/bookhouse-boys_2.jpg + /media/1350/bookhouse-boys_2_thumb.jpg + + + + + + + + + 0 + +
+ + + + + + + + + + 0 + Where all the great minds of umbraco meet!

+]]>
+ + + /media/993/codegarden-08_gallery.jpg + + Credit: Douglas Robar + /media/825/darren-ferguson_david-conlisk.jpg + /media/835/darren-ferguson_david-conlisk_thumb.jpg + + + + + + + + + 0 + + + Credit: Warren Buckley + /media/394/codegarden08-t-shirt.jpg + /media/799/codegarden08-t-shirt_thumb.jpg + + + + + + + + + 0 + + + Credit: Douglas Robar + /media/852/bingo-callers.jpg + /media/871/bingo-callers_thumb.jpg + + + + + + + + + 0 + + + Credit: Douglas Robar + /media/879/christian-palm.jpg + /media/889/christian-palm_thumb.jpg + + + + + + + + + 0 + +
+ + + + + + + + + + 0 + Photos from a trip to Bath in November 2008

+]]>
+ + + /media/935/bath_gallery.jpg + + Credit: Warren Buckley + /media/995/royal-crescent.jpg + /media/1014/royal-crescent_thumb.jpg + + + + + + + + + 0 + +
+ + + + + + 0 + This first page displays all the albums featured within your site.
Please have a browse through.

]]>
+ + +
+ + + 2009-06-22T00:00:00 + + Come join the annual Umbraco developer conference in Wonderful +Odense on May 22nd - 24th. Two days for insights, eye-openers, +great conversations and friendly people. + +

The who's who of umbraco will be gathered and share their +knowledge through talks, tutorials and via un-conference formats +like open space or hacking sessions. We have booked a cool, old and +huge industrial venue in central Copenhagen where we'll have one +huge room, three rooms for breakout sessions and a Café that +serves your taste of espresso.

+ +

Come join the annual Umbraco developer conference in Wonderful +Odense on May 22nd - 24th. Two days for insights, eye-openers, +great conversations and friendly people.

+ +

We've planned a program that fits both experienced umbraco users +as well as new comers. The conference is kicked off with a Keynote +and after that we'll have two or three tracks of sessions and the +first day is rounded with an open Q/A session with the umbraco core +team.

+ +

The second day features an un-conference format called open +space, which we also used with great success at last year's +conference. It's a format where everybody can suggest a topic and +as we have plenty of break-out rooms rest assured that there'll be +room for your topic as well. Last year's more than 20 topics +covered Silverlight, High performance websites with umbraco, +building custom data types and much more.

+ +

But it doesn't ends there. We've ensured that we can stay at the +venue until midnight both days which means that when the first day +ends, the fun begins. With more than hundred people gathered at a +place with loads of umbraco knowledge, wifi, great food and plenty +of space, who knows what could happen. A BBQ, new packages, +improvised demos and talks, or...?

+ +

 

+]]>
+ /media/445/umbraco_tshirt.jpg + + + + + + + + + 0 +
+ + After Warren Buckley's success of the first Creative Website +Starter site package, with over 20K downloads he has decided to +work on CWS2. + +

As of today Wednesday the 4th March 2009, the CWS2 package is +now available to download from the built in package repository +inside Umbraco.

+ +

With this new version comes the following:

+ +
    +
  • A BRAND NEW spanking design from ex-collegue +Sam Grady of G-72
  • + +
  • A focus for this site to be used to help teach new users to the +Umbraco CMS platform.
  • + +
  • .NET usercontrols written in C#
  • + +
  • XSLT & .NET code heavily commented to help understand what +is going on.
  • + +
  • With supporting documentation coming in the near future.
  • +
+]]>
+ + + + + + + + + + + 0 +
+ + + + + + 0 + + + Please browse through our news archive and event listings +below.

+]]>
+
+ + you@yourcompany.co.uk + Email from Contact form on website + + you@yourcompany.co.uk + Thank you for your message + + 0 + + + + + + 0 + Everything you need to
+ get in touch.

+]]>
+ Enquiry Form + +

If you have a particular enquiry, please fill out the form below +and provide as much information as you can, so that one of our +representatives can deal with your enquiry as effciently as +possible.

+]]>
+ Thank you. We will be in touch shortly.

+]]>
+ Thanks for filling out our contact form we will get back to you +shortly.

+ +

Regards,
+ My Company

+]]>
+
+ + you@yourcompany.co.uk + [YourName] has sent you a link to read + + 0 + + + + + + 1 + Send a page onto your friend.

]]>
+ Thank you. We appreciate the love you are giving our site.

]]>
+ Thanks for sending that link onto your friend, we really appreciate it here at My Company.

]]>
+
+ + + 0 + Your logo/name + Sam Grady designed this for Warren Buckley. "This" idea was first created by the incredible Robert Brownjohn and has been copied many times since.

]]>
+ + Thank you for installing the umbraco website package created and developed by Warren Buckley. This should website package be used to help you understand how all the components of an Umbraco site works.

+

test1

]]>
+ + + + 0 +
+
diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs index d5ac55827d..a4b210dbef 100644 --- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs @@ -1,465 +1,465 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Web.Mvc; -using System.Web.Routing; -using LightInject; -using Moq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; -using Umbraco.Tests.Testing.Objects.Accessors; -using Umbraco.Web; -using Umbraco.Web.Models; -using Umbraco.Web.Mvc; -using Umbraco.Web.PublishedCache.XmlPublishedCache; -using Umbraco.Web.Routing; -using Umbraco.Web.Security; - -namespace Umbraco.Tests.Web.Mvc -{ - [TestFixture] - [UmbracoTest(WithApplication = true)] - public class UmbracoViewPageTests : UmbracoTestBase - { - private PublishedSnapshotService _service; - - [TearDown] - public override void TearDown() - { - if (_service == null) return; - _service.Dispose(); - _service = null; - } - - #region RenderModel To ... - - [Test] - public void RenderModel_To_RenderModel() - { - var content = new ContentType1(null); - var model = new ContentModel(content); - var view = new RenderModelTestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.AreSame(model, view.Model); - } - - [Test] - public void RenderModel_ContentType1_To_ContentType1() - { - var content = new ContentType1(null); - var model = new ContentModel(content); - var view = new ContentType1TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf(view.Model); - } - - [Test] - public void RenderModel_ContentType2_To_ContentType1() - { - var content = new ContentType2(null); - var model = new ContentModel(content); - var view = new ContentType1TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf(view.Model); - } - - [Test] - public void RenderModel_ContentType1_To_ContentType2() - { - var content = new ContentType1(null); - var model = new ContentModel(content); - var view = new ContentType2TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - - Assert.Throws(() => view.SetViewDataX(viewData)); - } - - [Test] - public void RenderModel_ContentType1_To_RenderModelOf_ContentType1() - { - var content = new ContentType1(null); - var model = new ContentModel(content); - var view = new RenderModelOfContentType1TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf>(view.Model); - Assert.IsInstanceOf(view.Model.Content); - } - - [Test] - public void RenderModel_ContentType2_To_RenderModelOf_ContentType1() - { - var content = new ContentType2(null); - var model = new ContentModel(content); - var view = new RenderModelOfContentType1TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf>(view.Model); - Assert.IsInstanceOf(view.Model.Content); - } - - [Test] - public void RenderModel_ContentType1_To_RenderModelOf_ContentType2() - { - var content = new ContentType1(null); - var model = new ContentModel(content); - var view = new RenderModelOfContentType2TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - - Assert.Throws(() => view.SetViewDataX(viewData)); - } - - #endregion - - #region RenderModelOf To ... - - [Test] - public void RenderModelOf_ContentType1_To_RenderModel() - { - var content = new ContentType1(null); - var model = new ContentModel(content); - var view = new RenderModelTestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.AreSame(model, view.Model); - } - - [Test] - public void RenderModelOf_ContentType1_To_ContentType1() - { - var content = new ContentType1(null); - var model = new ContentModel(content); - var view = new ContentType1TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf(view.Model); - } - - [Test] - public void RenderModelOf_ContentType2_To_ContentType1() - { - var content = new ContentType2(null); - var model = new ContentModel(content); - var view = new ContentType1TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf(view.Model); - } - - [Test] - public void RenderModelOf_ContentType1_To_ContentType2() - { - var content = new ContentType1(null); - var model = new ContentModel(content); - var view = new ContentType2TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - Assert.Throws(() => view.SetViewDataX(viewData)); - } - - [Test] - public void RenderModelOf_ContentType1_To_RenderModelOf_ContentType1() - { - var content = new ContentType1(null); - var model = new ContentModel(content); - var view = new RenderModelOfContentType1TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf>(view.Model); - Assert.IsInstanceOf(view.Model.Content); - } - - [Test] - public void RenderModelOf_ContentType2_To_RenderModelOf_ContentType1() - { - var content = new ContentType2(null); - var model = new ContentModel(content); - var view = new RenderModelOfContentType1TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf>(view.Model); - Assert.IsInstanceOf(view.Model.Content); - } - - [Test] - public void RenderModelOf_ContentType1_To_RenderModelOf_ContentType2() - { - var content = new ContentType1(null); - var model = new ContentModel(content); - var view = new RenderModelOfContentType2TestPage(); - var viewData = new ViewDataDictionary(model); - - view.ViewContext = GetViewContext(); - Assert.Throws(() => view.SetViewDataX(viewData)); - } - - #endregion - - #region ContentType To ... - - [Test] - public void ContentType1_To_RenderModel() - { - var content = new ContentType1(null); - var view = new RenderModelTestPage(); - var viewData = new ViewDataDictionary(content); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf(view.Model); - } - - [Test] - public void ContentType1_To_RenderModelOf_ContentType1() - { - var content = new ContentType1(null); - var view = new RenderModelOfContentType1TestPage(); - var viewData = new ViewDataDictionary(content); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf>(view.Model); - Assert.IsInstanceOf(view.Model.Content); - } - - [Test] - public void ContentType2_To_RenderModelOf_ContentType1() - { - var content = new ContentType2(null); - var view = new RenderModelOfContentType1TestPage(); - var viewData = new ViewDataDictionary(content); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf>(view.Model); - Assert.IsInstanceOf(view.Model.Content); - } - - [Test] - public void ContentType1_To_RenderModelOf_ContentType2() - { - var content = new ContentType1(null); - var view = new RenderModelOfContentType2TestPage(); - var viewData = new ViewDataDictionary(content); - - view.ViewContext = GetViewContext(); - Assert.Throws(() =>view.SetViewDataX(viewData)); - } - - [Test] - public void ContentType1_To_ContentType1() - { - var content = new ContentType1(null); - var view = new ContentType1TestPage(); - var viewData = new ViewDataDictionary(content); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf(view.Model); - } - - [Test] - public void ContentType1_To_ContentType2() - { - var content = new ContentType1(null); - var view = new ContentType2TestPage(); - var viewData = new ViewDataDictionary(content); - - view.ViewContext = GetViewContext(); - Assert.Throws(() => view.SetViewDataX(viewData)); - } - - [Test] - public void ContentType2_To_ContentType1() - { - var content = new ContentType2(null); - var view = new ContentType1TestPage(); - var viewData = new ViewDataDictionary(content); - - view.ViewContext = GetViewContext(); - view.SetViewDataX(viewData); - - Assert.IsInstanceOf(view.Model); - } - - #endregion - - #region Test elements - - public class TestPage : UmbracoViewPage - { - public override void Execute() - { - throw new NotImplementedException(); - } - - public void SetViewDataX(ViewDataDictionary viewData) - { - SetViewData(viewData); - } - } - - public class RenderModelTestPage : TestPage - { } - - public class RenderModelOfContentType1TestPage : TestPage> - { } - - public class RenderModelOfContentType2TestPage : TestPage> - { } - - public class ContentType1TestPage : TestPage - { } - - public class ContentType2TestPage : TestPage - { } - - public class ContentType1 : PublishedContentWrapped - { - public ContentType1(IPublishedContent content) : base(content) {} - } - - public class ContentType2 : ContentType1 - { - public ContentType2(IPublishedContent content) : base(content) { } - } - - #endregion - - #region Test helpers - - ServiceContext GetServiceContext() - { - return TestObjects.GetServiceContextMock(); - } - - ViewContext GetViewContext() - { - var settings = SettingsForTests.GetDefaultUmbracoSettings(); - var logger = Mock.Of(); - var umbracoContext = GetUmbracoContext( - logger, settings, - "/dang", 0); - - var publishedRouter = BaseWebTest.CreatePublishedRouter(TestObjects.GetUmbracoSettings().WebRouting); - var frequest = publishedRouter.CreateRequest(umbracoContext, new Uri("http://localhost/dang")); - - frequest.Culture = CultureInfo.InvariantCulture; - umbracoContext.PublishedRequest = frequest; - - var context = new ViewContext(); - context.RouteData = new RouteData(); - context.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbracoContext); - - return context; - } - - protected UmbracoContext GetUmbracoContext(ILogger logger, IUmbracoSettingsSection umbracoSettings, string url, int templateId, RouteData routeData = null, bool setSingleton = false) - { - var svcCtx = GetServiceContext(); - - var databaseFactory = TestObjects.GetDatabaseFactoryMock(); - - //var appCtx = new ApplicationContext( - // new DatabaseContext(databaseFactory, logger, Mock.Of(), Mock.Of()), - // svcCtx, - // CacheHelper.CreateDisabledCacheHelper(), - // new ProfilingLogger(logger, Mock.Of())) { /*IsReady = true*/ }; - - var cache = NullCacheProvider.Instance; - //var provider = new ScopeUnitOfWorkProvider(databaseFactory, new RepositoryFactory(Mock.Of())); - var scopeProvider = TestObjects.GetScopeProvider(Mock.Of()); - var factory = Mock.Of(); - _service = new PublishedSnapshotService(svcCtx, factory, scopeProvider, cache, Enumerable.Empty(), - null, null, - null, null, null, - new TestDefaultCultureAccessor(), - Current.Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper(), null, true, false); // no events - - var http = GetHttpContextFactory(url, routeData).HttpContext; - - var globalSettings = TestObjects.GetGlobalSettings(); - - var ctx = new UmbracoContext( - http, - _service, - new WebSecurity(http, Current.Services.UserService, globalSettings), - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, - new TestVariationContextAccessor()); - - //if (setSingleton) - //{ - // UmbracoContext.Current = ctx; - //} - - return ctx; - } - - protected FakeHttpContextFactory GetHttpContextFactory(string url, RouteData routeData = null) - { - var factory = routeData != null - ? new FakeHttpContextFactory(url, routeData) - : new FakeHttpContextFactory(url); - - return factory; - } - - #endregion - } -} +using System; +using System.Globalization; +using System.Linq; +using System.Web.Mvc; +using System.Web.Routing; +using LightInject; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web; +using Umbraco.Web.Models; +using Umbraco.Web.Mvc; +using Umbraco.Web.PublishedCache.XmlPublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.Web.Mvc +{ + [TestFixture] + [UmbracoTest(WithApplication = true)] + public class UmbracoViewPageTests : UmbracoTestBase + { + private PublishedSnapshotService _service; + + [TearDown] + public override void TearDown() + { + if (_service == null) return; + _service.Dispose(); + _service = null; + } + + #region RenderModel To ... + + [Test] + public void RenderModel_To_RenderModel() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new RenderModelTestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.AreSame(model, view.Model); + } + + [Test] + public void RenderModel_ContentType1_To_ContentType1() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new ContentType1TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf(view.Model); + } + + [Test] + public void RenderModel_ContentType2_To_ContentType1() + { + var content = new ContentType2(null); + var model = new ContentModel(content); + var view = new ContentType1TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf(view.Model); + } + + [Test] + public void RenderModel_ContentType1_To_ContentType2() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new ContentType2TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + + Assert.Throws(() => view.SetViewDataX(viewData)); + } + + [Test] + public void RenderModel_ContentType1_To_RenderModelOf_ContentType1() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new RenderModelOfContentType1TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf(view.Model.Content); + } + + [Test] + public void RenderModel_ContentType2_To_RenderModelOf_ContentType1() + { + var content = new ContentType2(null); + var model = new ContentModel(content); + var view = new RenderModelOfContentType1TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf(view.Model.Content); + } + + [Test] + public void RenderModel_ContentType1_To_RenderModelOf_ContentType2() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new RenderModelOfContentType2TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + + Assert.Throws(() => view.SetViewDataX(viewData)); + } + + #endregion + + #region RenderModelOf To ... + + [Test] + public void RenderModelOf_ContentType1_To_RenderModel() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new RenderModelTestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.AreSame(model, view.Model); + } + + [Test] + public void RenderModelOf_ContentType1_To_ContentType1() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new ContentType1TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf(view.Model); + } + + [Test] + public void RenderModelOf_ContentType2_To_ContentType1() + { + var content = new ContentType2(null); + var model = new ContentModel(content); + var view = new ContentType1TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf(view.Model); + } + + [Test] + public void RenderModelOf_ContentType1_To_ContentType2() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new ContentType2TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + Assert.Throws(() => view.SetViewDataX(viewData)); + } + + [Test] + public void RenderModelOf_ContentType1_To_RenderModelOf_ContentType1() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new RenderModelOfContentType1TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf(view.Model.Content); + } + + [Test] + public void RenderModelOf_ContentType2_To_RenderModelOf_ContentType1() + { + var content = new ContentType2(null); + var model = new ContentModel(content); + var view = new RenderModelOfContentType1TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf(view.Model.Content); + } + + [Test] + public void RenderModelOf_ContentType1_To_RenderModelOf_ContentType2() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new RenderModelOfContentType2TestPage(); + var viewData = new ViewDataDictionary(model); + + view.ViewContext = GetViewContext(); + Assert.Throws(() => view.SetViewDataX(viewData)); + } + + #endregion + + #region ContentType To ... + + [Test] + public void ContentType1_To_RenderModel() + { + var content = new ContentType1(null); + var view = new RenderModelTestPage(); + var viewData = new ViewDataDictionary(content); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf(view.Model); + } + + [Test] + public void ContentType1_To_RenderModelOf_ContentType1() + { + var content = new ContentType1(null); + var view = new RenderModelOfContentType1TestPage(); + var viewData = new ViewDataDictionary(content); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf(view.Model.Content); + } + + [Test] + public void ContentType2_To_RenderModelOf_ContentType1() + { + var content = new ContentType2(null); + var view = new RenderModelOfContentType1TestPage(); + var viewData = new ViewDataDictionary(content); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf(view.Model.Content); + } + + [Test] + public void ContentType1_To_RenderModelOf_ContentType2() + { + var content = new ContentType1(null); + var view = new RenderModelOfContentType2TestPage(); + var viewData = new ViewDataDictionary(content); + + view.ViewContext = GetViewContext(); + Assert.Throws(() =>view.SetViewDataX(viewData)); + } + + [Test] + public void ContentType1_To_ContentType1() + { + var content = new ContentType1(null); + var view = new ContentType1TestPage(); + var viewData = new ViewDataDictionary(content); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf(view.Model); + } + + [Test] + public void ContentType1_To_ContentType2() + { + var content = new ContentType1(null); + var view = new ContentType2TestPage(); + var viewData = new ViewDataDictionary(content); + + view.ViewContext = GetViewContext(); + Assert.Throws(() => view.SetViewDataX(viewData)); + } + + [Test] + public void ContentType2_To_ContentType1() + { + var content = new ContentType2(null); + var view = new ContentType1TestPage(); + var viewData = new ViewDataDictionary(content); + + view.ViewContext = GetViewContext(); + view.SetViewDataX(viewData); + + Assert.IsInstanceOf(view.Model); + } + + #endregion + + #region Test elements + + public class TestPage : UmbracoViewPage + { + public override void Execute() + { + throw new NotImplementedException(); + } + + public void SetViewDataX(ViewDataDictionary viewData) + { + SetViewData(viewData); + } + } + + public class RenderModelTestPage : TestPage + { } + + public class RenderModelOfContentType1TestPage : TestPage> + { } + + public class RenderModelOfContentType2TestPage : TestPage> + { } + + public class ContentType1TestPage : TestPage + { } + + public class ContentType2TestPage : TestPage + { } + + public class ContentType1 : PublishedContentWrapped + { + public ContentType1(IPublishedContent content) : base(content) {} + } + + public class ContentType2 : ContentType1 + { + public ContentType2(IPublishedContent content) : base(content) { } + } + + #endregion + + #region Test helpers + + ServiceContext GetServiceContext() + { + return TestObjects.GetServiceContextMock(); + } + + ViewContext GetViewContext() + { + var settings = SettingsForTests.GetDefaultUmbracoSettings(); + var logger = Mock.Of(); + var umbracoContext = GetUmbracoContext( + logger, settings, + "/dang", 0); + + var publishedRouter = BaseWebTest.CreatePublishedRouter(TestObjects.GetUmbracoSettings().WebRouting); + var frequest = publishedRouter.CreateRequest(umbracoContext, new Uri("http://localhost/dang")); + + frequest.Culture = CultureInfo.InvariantCulture; + umbracoContext.PublishedRequest = frequest; + + var context = new ViewContext(); + context.RouteData = new RouteData(); + context.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbracoContext); + + return context; + } + + protected UmbracoContext GetUmbracoContext(ILogger logger, IUmbracoSettingsSection umbracoSettings, string url, int templateId, RouteData routeData = null, bool setSingleton = false) + { + var svcCtx = GetServiceContext(); + + var databaseFactory = TestObjects.GetDatabaseFactoryMock(); + + //var appCtx = new ApplicationContext( + // new DatabaseContext(databaseFactory, logger, Mock.Of(), Mock.Of()), + // svcCtx, + // CacheHelper.CreateDisabledCacheHelper(), + // new ProfilingLogger(logger, Mock.Of())) { /*IsReady = true*/ }; + + var cache = NullCacheProvider.Instance; + //var provider = new ScopeUnitOfWorkProvider(databaseFactory, new RepositoryFactory(Mock.Of())); + var scopeProvider = TestObjects.GetScopeProvider(Mock.Of()); + var factory = Mock.Of(); + _service = new PublishedSnapshotService(svcCtx, factory, scopeProvider, cache, Enumerable.Empty(), + null, null, + null, null, null, + new TestDefaultCultureAccessor(), + Current.Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper(), null, true, false); // no events + + var http = GetHttpContextFactory(url, routeData).HttpContext; + + var globalSettings = TestObjects.GetGlobalSettings(); + + var ctx = new UmbracoContext( + http, + _service, + new WebSecurity(http, Current.Services.UserService, globalSettings), + TestObjects.GetUmbracoSettings(), + Enumerable.Empty(), + globalSettings, + new TestVariationContextAccessor()); + + //if (setSingleton) + //{ + // UmbracoContext.Current = ctx; + //} + + return ctx; + } + + protected FakeHttpContextFactory GetHttpContextFactory(string url, RouteData routeData = null) + { + var factory = routeData != null + ? new FakeHttpContextFactory(url, routeData) + : new FakeHttpContextFactory(url); + + return factory; + } + + #endregion + } +} diff --git a/src/Umbraco.Tests/unit-test-log4net.config b/src/Umbraco.Tests/unit-test-log4net.config index 25c580bc99..34890fd583 100644 --- a/src/Umbraco.Tests/unit-test-log4net.config +++ b/src/Umbraco.Tests/unit-test-log4net.config @@ -1,41 +1,41 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/docs/src/api/index.ngdoc b/src/Umbraco.Web.UI.Client/docs/src/api/index.ngdoc index 74cc458b90..e7e7f75cc6 100644 --- a/src/Umbraco.Web.UI.Client/docs/src/api/index.ngdoc +++ b/src/Umbraco.Web.UI.Client/docs/src/api/index.ngdoc @@ -1,59 +1,59 @@ -@ngdoc overview -@name Umbraco 7 JS documentation -@description - -#Umbraco Backoffice UI API documentation - -This documentation relates to the angular, js, css and less APIs for developing back office components - -##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 /Umbraco.Belle.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 `/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. - -##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/ - - - - - +@ngdoc overview +@name Umbraco 7 JS documentation +@description + +#Umbraco Backoffice UI API documentation + +This documentation relates to the angular, js, css and less APIs for developing back office components + +##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 /Umbraco.Belle.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 `/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. + +##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/ + + + + + diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.css b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.css index a29c98c72a..9259d26dca 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.css +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.css @@ -1,1058 +1,1058 @@ -/*! - * Bootstrap Responsive v2.1.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */ - -.clearfix { - *zoom: 1; -} - -.clearfix:before, -.clearfix:after { - display: table; - line-height: 0; - content: ""; -} - -.clearfix:after { - clear: both; -} - -.hide-text { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} - -.input-block-level { - display: block; - width: 100%; - min-height: 30px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.hidden { - display: none; - visibility: hidden; -} - -.visible-phone { - display: none !important; -} - -.visible-tablet { - display: none !important; -} - -.hidden-desktop { - display: none !important; -} - -.visible-desktop { - display: inherit !important; -} - -@media (min-width: 768px) and (max-width: 979px) { - .hidden-desktop { - display: inherit !important; - } - .visible-desktop { - display: none !important ; - } - .visible-tablet { - display: inherit !important; - } - .hidden-tablet { - display: none !important; - } -} - -@media (max-width: 767px) { - .hidden-desktop { - display: inherit !important; - } - .visible-desktop { - display: none !important; - } - .visible-phone { - display: inherit !important; - } - .hidden-phone { - display: none !important; - } -} - -@media (min-width: 1200px) { - .row { - margin-left: -30px; - *zoom: 1; - } - .row:before, - .row:after { - display: table; - line-height: 0; - content: ""; - } - .row:after { - clear: both; - } - [class*="span"] { - float: left; - min-height: 1px; - margin-left: 30px; - } - .container, - .navbar-static-top .container, - .navbar-fixed-top .container, - .navbar-fixed-bottom .container { - width: 1170px; - } - .span12 { - width: 1170px; - } - .span11 { - width: 1070px; - } - .span10 { - width: 970px; - } - .span9 { - width: 870px; - } - .span8 { - width: 770px; - } - .span7 { - width: 670px; - } - .span6 { - width: 570px; - } - .span5 { - width: 470px; - } - .span4 { - width: 370px; - } - .span3 { - width: 270px; - } - .span2 { - width: 170px; - } - .span1 { - width: 70px; - } - .offset12 { - margin-left: 1230px; - } - .offset11 { - margin-left: 1130px; - } - .offset10 { - margin-left: 1030px; - } - .offset9 { - margin-left: 930px; - } - .offset8 { - margin-left: 830px; - } - .offset7 { - margin-left: 730px; - } - .offset6 { - margin-left: 630px; - } - .offset5 { - margin-left: 530px; - } - .offset4 { - margin-left: 430px; - } - .offset3 { - margin-left: 330px; - } - .offset2 { - margin-left: 230px; - } - .offset1 { - margin-left: 130px; - } - .row-fluid { - width: 100%; - *zoom: 1; - } - .row-fluid:before, - .row-fluid:after { - display: table; - line-height: 0; - content: ""; - } - .row-fluid:after { - clear: both; - } - .row-fluid [class*="span"] { - display: block; - float: left; - width: 100%; - min-height: 30px; - margin-left: 2.564102564102564%; - *margin-left: 2.5109110747408616%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - } - .row-fluid [class*="span"]:first-child { - margin-left: 0; - } - .row-fluid .span12 { - width: 100%; - *width: 99.94680851063829%; - } - .row-fluid .span11 { - width: 91.45299145299145%; - *width: 91.39979996362975%; - } - .row-fluid .span10 { - width: 82.90598290598291%; - *width: 82.8527914166212%; - } - .row-fluid .span9 { - width: 74.35897435897436%; - *width: 74.30578286961266%; - } - .row-fluid .span8 { - width: 65.81196581196582%; - *width: 65.75877432260411%; - } - .row-fluid .span7 { - width: 57.26495726495726%; - *width: 57.21176577559556%; - } - .row-fluid .span6 { - width: 48.717948717948715%; - *width: 48.664757228587014%; - } - .row-fluid .span5 { - width: 40.17094017094017%; - *width: 40.11774868157847%; - } - .row-fluid .span4 { - width: 31.623931623931625%; - *width: 31.570740134569924%; - } - .row-fluid .span3 { - width: 23.076923076923077%; - *width: 23.023731587561375%; - } - .row-fluid .span2 { - width: 14.52991452991453%; - *width: 14.476723040552828%; - } - .row-fluid .span1 { - width: 5.982905982905983%; - *width: 5.929714493544281%; - } - .row-fluid .offset12 { - margin-left: 105.12820512820512%; - *margin-left: 105.02182214948171%; - } - .row-fluid .offset12:first-child { - margin-left: 102.56410256410257%; - *margin-left: 102.45771958537915%; - } - .row-fluid .offset11 { - margin-left: 96.58119658119658%; - *margin-left: 96.47481360247316%; - } - .row-fluid .offset11:first-child { - margin-left: 94.01709401709402%; - *margin-left: 93.91071103837061%; - } - .row-fluid .offset10 { - margin-left: 88.03418803418803%; - *margin-left: 87.92780505546462%; - } - .row-fluid .offset10:first-child { - margin-left: 85.47008547008548%; - *margin-left: 85.36370249136206%; - } - .row-fluid .offset9 { - margin-left: 79.48717948717949%; - *margin-left: 79.38079650845607%; - } - .row-fluid .offset9:first-child { - margin-left: 76.92307692307693%; - *margin-left: 76.81669394435352%; - } - .row-fluid .offset8 { - margin-left: 70.94017094017094%; - *margin-left: 70.83378796144753%; - } - .row-fluid .offset8:first-child { - margin-left: 68.37606837606839%; - *margin-left: 68.26968539734497%; - } - .row-fluid .offset7 { - margin-left: 62.393162393162385%; - *margin-left: 62.28677941443899%; - } - .row-fluid .offset7:first-child { - margin-left: 59.82905982905982%; - *margin-left: 59.72267685033642%; - } - .row-fluid .offset6 { - margin-left: 53.84615384615384%; - *margin-left: 53.739770867430444%; - } - .row-fluid .offset6:first-child { - margin-left: 51.28205128205128%; - *margin-left: 51.175668303327875%; - } - .row-fluid .offset5 { - margin-left: 45.299145299145295%; - *margin-left: 45.1927623204219%; - } - .row-fluid .offset5:first-child { - margin-left: 42.73504273504273%; - *margin-left: 42.62865975631933%; - } - .row-fluid .offset4 { - margin-left: 36.75213675213675%; - *margin-left: 36.645753773413354%; - } - .row-fluid .offset4:first-child { - margin-left: 34.18803418803419%; - *margin-left: 34.081651209310785%; - } - .row-fluid .offset3 { - margin-left: 28.205128205128204%; - *margin-left: 28.0987452264048%; - } - .row-fluid .offset3:first-child { - margin-left: 25.641025641025642%; - *margin-left: 25.53464266230224%; - } - .row-fluid .offset2 { - margin-left: 19.65811965811966%; - *margin-left: 19.551736679396257%; - } - .row-fluid .offset2:first-child { - margin-left: 17.094017094017094%; - *margin-left: 16.98763411529369%; - } - .row-fluid .offset1 { - margin-left: 11.11111111111111%; - *margin-left: 11.004728132387708%; - } - .row-fluid .offset1:first-child { - margin-left: 8.547008547008547%; - *margin-left: 8.440625568285142%; - } - input, - textarea, - .uneditable-input { - margin-left: 0; - } - .controls-row [class*="span"] + [class*="span"] { - margin-left: 30px; - } - input.span12, - textarea.span12, - .uneditable-input.span12 { - width: 1156px; - } - input.span11, - textarea.span11, - .uneditable-input.span11 { - width: 1056px; - } - input.span10, - textarea.span10, - .uneditable-input.span10 { - width: 956px; - } - input.span9, - textarea.span9, - .uneditable-input.span9 { - width: 856px; - } - input.span8, - textarea.span8, - .uneditable-input.span8 { - width: 756px; - } - input.span7, - textarea.span7, - .uneditable-input.span7 { - width: 656px; - } - input.span6, - textarea.span6, - .uneditable-input.span6 { - width: 556px; - } - input.span5, - textarea.span5, - .uneditable-input.span5 { - width: 456px; - } - input.span4, - textarea.span4, - .uneditable-input.span4 { - width: 356px; - } - input.span3, - textarea.span3, - .uneditable-input.span3 { - width: 256px; - } - input.span2, - textarea.span2, - .uneditable-input.span2 { - width: 156px; - } - input.span1, - textarea.span1, - .uneditable-input.span1 { - width: 56px; - } - .thumbnails { - margin-left: -30px; - } - .thumbnails > li { - margin-left: 30px; - } - .row-fluid .thumbnails { - margin-left: 0; - } -} - -@media (min-width: 768px) and (max-width: 979px) { - .row { - margin-left: -20px; - *zoom: 1; - } - .row:before, - .row:after { - display: table; - line-height: 0; - content: ""; - } - .row:after { - clear: both; - } - [class*="span"] { - float: left; - min-height: 1px; - margin-left: 20px; - } - .container, - .navbar-static-top .container, - .navbar-fixed-top .container, - .navbar-fixed-bottom .container { - width: 724px; - } - .span12 { - width: 724px; - } - .span11 { - width: 662px; - } - .span10 { - width: 600px; - } - .span9 { - width: 538px; - } - .span8 { - width: 476px; - } - .span7 { - width: 414px; - } - .span6 { - width: 352px; - } - .span5 { - width: 290px; - } - .span4 { - width: 228px; - } - .span3 { - width: 166px; - } - .span2 { - width: 104px; - } - .span1 { - width: 42px; - } - .offset12 { - margin-left: 764px; - } - .offset11 { - margin-left: 702px; - } - .offset10 { - margin-left: 640px; - } - .offset9 { - margin-left: 578px; - } - .offset8 { - margin-left: 516px; - } - .offset7 { - margin-left: 454px; - } - .offset6 { - margin-left: 392px; - } - .offset5 { - margin-left: 330px; - } - .offset4 { - margin-left: 268px; - } - .offset3 { - margin-left: 206px; - } - .offset2 { - margin-left: 144px; - } - .offset1 { - margin-left: 82px; - } - .row-fluid { - width: 100%; - *zoom: 1; - } - .row-fluid:before, - .row-fluid:after { - display: table; - line-height: 0; - content: ""; - } - .row-fluid:after { - clear: both; - } - .row-fluid [class*="span"] { - display: block; - float: left; - width: 100%; - min-height: 30px; - margin-left: 2.7624309392265194%; - *margin-left: 2.709239449864817%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - } - .row-fluid [class*="span"]:first-child { - margin-left: 0; - } - .row-fluid .span12 { - width: 100%; - *width: 99.94680851063829%; - } - .row-fluid .span11 { - width: 91.43646408839778%; - *width: 91.38327259903608%; - } - .row-fluid .span10 { - width: 82.87292817679558%; - *width: 82.81973668743387%; - } - .row-fluid .span9 { - width: 74.30939226519337%; - *width: 74.25620077583166%; - } - .row-fluid .span8 { - width: 65.74585635359117%; - *width: 65.69266486422946%; - } - .row-fluid .span7 { - width: 57.18232044198895%; - *width: 57.12912895262725%; - } - .row-fluid .span6 { - width: 48.61878453038674%; - *width: 48.56559304102504%; - } - .row-fluid .span5 { - width: 40.05524861878453%; - *width: 40.00205712942283%; - } - .row-fluid .span4 { - width: 31.491712707182323%; - *width: 31.43852121782062%; - } - .row-fluid .span3 { - width: 22.92817679558011%; - *width: 22.87498530621841%; - } - .row-fluid .span2 { - width: 14.3646408839779%; - *width: 14.311449394616199%; - } - .row-fluid .span1 { - width: 5.801104972375691%; - *width: 5.747913483013988%; - } - .row-fluid .offset12 { - margin-left: 105.52486187845304%; - *margin-left: 105.41847889972962%; - } - .row-fluid .offset12:first-child { - margin-left: 102.76243093922652%; - *margin-left: 102.6560479605031%; - } - .row-fluid .offset11 { - margin-left: 96.96132596685082%; - *margin-left: 96.8549429881274%; - } - .row-fluid .offset11:first-child { - margin-left: 94.1988950276243%; - *margin-left: 94.09251204890089%; - } - .row-fluid .offset10 { - margin-left: 88.39779005524862%; - *margin-left: 88.2914070765252%; - } - .row-fluid .offset10:first-child { - margin-left: 85.6353591160221%; - *margin-left: 85.52897613729868%; - } - .row-fluid .offset9 { - margin-left: 79.8342541436464%; - *margin-left: 79.72787116492299%; - } - .row-fluid .offset9:first-child { - margin-left: 77.07182320441989%; - *margin-left: 76.96544022569647%; - } - .row-fluid .offset8 { - margin-left: 71.2707182320442%; - *margin-left: 71.16433525332079%; - } - .row-fluid .offset8:first-child { - margin-left: 68.50828729281768%; - *margin-left: 68.40190431409427%; - } - .row-fluid .offset7 { - margin-left: 62.70718232044199%; - *margin-left: 62.600799341718584%; - } - .row-fluid .offset7:first-child { - margin-left: 59.94475138121547%; - *margin-left: 59.838368402492065%; - } - .row-fluid .offset6 { - margin-left: 54.14364640883978%; - *margin-left: 54.037263430116376%; - } - .row-fluid .offset6:first-child { - margin-left: 51.38121546961326%; - *margin-left: 51.27483249088986%; - } - .row-fluid .offset5 { - margin-left: 45.58011049723757%; - *margin-left: 45.47372751851417%; - } - .row-fluid .offset5:first-child { - margin-left: 42.81767955801105%; - *margin-left: 42.71129657928765%; - } - .row-fluid .offset4 { - margin-left: 37.01657458563536%; - *margin-left: 36.91019160691196%; - } - .row-fluid .offset4:first-child { - margin-left: 34.25414364640884%; - *margin-left: 34.14776066768544%; - } - .row-fluid .offset3 { - margin-left: 28.45303867403315%; - *margin-left: 28.346655695309746%; - } - .row-fluid .offset3:first-child { - margin-left: 25.69060773480663%; - *margin-left: 25.584224756083227%; - } - .row-fluid .offset2 { - margin-left: 19.88950276243094%; - *margin-left: 19.783119783707537%; - } - .row-fluid .offset2:first-child { - margin-left: 17.12707182320442%; - *margin-left: 17.02068884448102%; - } - .row-fluid .offset1 { - margin-left: 11.32596685082873%; - *margin-left: 11.219583872105325%; - } - .row-fluid .offset1:first-child { - margin-left: 8.56353591160221%; - *margin-left: 8.457152932878806%; - } - input, - textarea, - .uneditable-input { - margin-left: 0; - } - .controls-row [class*="span"] + [class*="span"] { - margin-left: 20px; - } - input.span12, - textarea.span12, - .uneditable-input.span12 { - width: 710px; - } - input.span11, - textarea.span11, - .uneditable-input.span11 { - width: 648px; - } - input.span10, - textarea.span10, - .uneditable-input.span10 { - width: 586px; - } - input.span9, - textarea.span9, - .uneditable-input.span9 { - width: 524px; - } - input.span8, - textarea.span8, - .uneditable-input.span8 { - width: 462px; - } - input.span7, - textarea.span7, - .uneditable-input.span7 { - width: 400px; - } - input.span6, - textarea.span6, - .uneditable-input.span6 { - width: 338px; - } - input.span5, - textarea.span5, - .uneditable-input.span5 { - width: 276px; - } - input.span4, - textarea.span4, - .uneditable-input.span4 { - width: 214px; - } - input.span3, - textarea.span3, - .uneditable-input.span3 { - width: 152px; - } - input.span2, - textarea.span2, - .uneditable-input.span2 { - width: 90px; - } - input.span1, - textarea.span1, - .uneditable-input.span1 { - width: 28px; - } -} - -@media (max-width: 767px) { - body { - padding-right: 20px; - padding-left: 20px; - } - .navbar-fixed-top, - .navbar-fixed-bottom, - .navbar-static-top { - margin-right: -20px; - margin-left: -20px; - } - .container-fluid { - padding: 0; - } - .dl-horizontal dt { - float: none; - width: auto; - clear: none; - text-align: left; - } - .dl-horizontal dd { - margin-left: 0; - } - .container { - width: auto; - } - .row-fluid { - width: 100%; - } - .row, - .thumbnails { - margin-left: 0; - } - .thumbnails > li { - float: none; - margin-left: 0; - } - [class*="span"], - .row-fluid [class*="span"] { - display: block; - float: none; - width: 100%; - margin-left: 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - } - .span12, - .row-fluid .span12 { - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - } - .input-large, - .input-xlarge, - .input-xxlarge, - input[class*="span"], - select[class*="span"], - textarea[class*="span"], - .uneditable-input { - display: block; - width: 100%; - min-height: 30px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - } - .input-prepend input, - .input-append input, - .input-prepend input[class*="span"], - .input-append input[class*="span"] { - display: inline-block; - width: auto; - } - .controls-row [class*="span"] + [class*="span"] { - margin-left: 0; - } - .modal { - position: fixed; - top: 20px; - right: 20px; - left: 20px; - width: auto; - margin: 0; - } - .modal.fade.in { - top: auto; - } -} - -@media (max-width: 480px) { - .nav-collapse { - -webkit-transform: translate3d(0, 0, 0); - } - .page-header h1 small { - display: block; - line-height: 20px; - } - input[type="checkbox"], - input[type="radio"] { - border: 1px solid #ccc; - } - .form-horizontal .control-label { - float: none; - width: auto; - padding-top: 0; - text-align: left; - } - .form-horizontal .controls { - margin-left: 0; - } - .form-horizontal .control-list { - padding-top: 0; - } - .form-horizontal .form-actions { - padding-right: 10px; - padding-left: 10px; - } - .modal { - top: 10px; - right: 10px; - left: 10px; - } - .modal-header .close { - padding: 10px; - margin: -10px; - } - .carousel-caption { - position: static; - } -} - -@media (max-width: 979px) { - body { - padding-top: 0; - } - .navbar-fixed-top, - .navbar-fixed-bottom { - position: static; - } - .navbar-fixed-top { - margin-bottom: 20px; - } - .navbar-fixed-bottom { - margin-top: 20px; - } - .navbar-fixed-top .navbar-inner, - .navbar-fixed-bottom .navbar-inner { - padding: 5px; - } - .navbar .container { - width: auto; - padding: 0; - } - .navbar .brand { - padding-right: 10px; - padding-left: 10px; - margin: 0 0 0 -5px; - } - .nav-collapse { - clear: both; - } - .nav-collapse .nav { - float: none; - margin: 0 0 10px; - } - .nav-collapse .nav > li { - float: none; - } - .nav-collapse .nav > li > a { - margin-bottom: 2px; - } - .nav-collapse .nav > .divider-vertical { - display: none; - } - .nav-collapse .nav .nav-header { - color: #777777; - text-shadow: none; - } - .nav-collapse .nav > li > a, - .nav-collapse .dropdown-menu a { - padding: 9px 15px; - font-weight: bold; - color: #777777; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - } - .nav-collapse .btn { - padding: 4px 10px 4px; - font-weight: normal; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - } - .nav-collapse .dropdown-menu li + li a { - margin-bottom: 2px; - } - .nav-collapse .nav > li > a:hover, - .nav-collapse .dropdown-menu a:hover { - background-color: #f2f2f2; - } - .navbar-inverse .nav-collapse .nav > li > a:hover, - .navbar-inverse .nav-collapse .dropdown-menu a:hover { - background-color: #111111; - } - .nav-collapse.in .btn-group { - padding: 0; - margin-top: 5px; - } - .nav-collapse .dropdown-menu { - position: static; - top: auto; - left: auto; - display: block; - float: none; - max-width: none; - padding: 0; - margin: 0 15px; - background-color: transparent; - border: none; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; - } - .nav-collapse .dropdown-menu:before, - .nav-collapse .dropdown-menu:after { - display: none; - } - .nav-collapse .dropdown-menu .divider { - display: none; - } - .nav-collapse .nav > li > .dropdown-menu:before, - .nav-collapse .nav > li > .dropdown-menu:after { - display: none; - } - .nav-collapse .navbar-form, - .nav-collapse .navbar-search { - float: none; - padding: 10px 15px; - margin: 10px 0; - border-top: 1px solid #f2f2f2; - border-bottom: 1px solid #f2f2f2; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); - } - .navbar-inverse .nav-collapse .navbar-form, - .navbar-inverse .nav-collapse .navbar-search { - border-top-color: #111111; - border-bottom-color: #111111; - } - .navbar .nav-collapse .nav.pull-right { - float: none; - margin-left: 0; - } - .nav-collapse, - .nav-collapse.collapse { - height: 0; - overflow: hidden; - } - .navbar .btn-navbar { - display: block; - } - .navbar-static .navbar-inner { - padding-right: 10px; - padding-left: 10px; - } -} - -@media (min-width: 980px) { - .nav-collapse.collapse { - height: auto !important; - overflow: visible !important; - } -} +/*! + * Bootstrap Responsive v2.1.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + +.clearfix { + *zoom: 1; +} + +.clearfix:before, +.clearfix:after { + display: table; + line-height: 0; + content: ""; +} + +.clearfix:after { + clear: both; +} + +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.input-block-level { + display: block; + width: 100%; + min-height: 30px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.hidden { + display: none; + visibility: hidden; +} + +.visible-phone { + display: none !important; +} + +.visible-tablet { + display: none !important; +} + +.hidden-desktop { + display: none !important; +} + +.visible-desktop { + display: inherit !important; +} + +@media (min-width: 768px) and (max-width: 979px) { + .hidden-desktop { + display: inherit !important; + } + .visible-desktop { + display: none !important ; + } + .visible-tablet { + display: inherit !important; + } + .hidden-tablet { + display: none !important; + } +} + +@media (max-width: 767px) { + .hidden-desktop { + display: inherit !important; + } + .visible-desktop { + display: none !important; + } + .visible-phone { + display: inherit !important; + } + .hidden-phone { + display: none !important; + } +} + +@media (min-width: 1200px) { + .row { + margin-left: -30px; + *zoom: 1; + } + .row:before, + .row:after { + display: table; + line-height: 0; + content: ""; + } + .row:after { + clear: both; + } + [class*="span"] { + float: left; + min-height: 1px; + margin-left: 30px; + } + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { + width: 1170px; + } + .span12 { + width: 1170px; + } + .span11 { + width: 1070px; + } + .span10 { + width: 970px; + } + .span9 { + width: 870px; + } + .span8 { + width: 770px; + } + .span7 { + width: 670px; + } + .span6 { + width: 570px; + } + .span5 { + width: 470px; + } + .span4 { + width: 370px; + } + .span3 { + width: 270px; + } + .span2 { + width: 170px; + } + .span1 { + width: 70px; + } + .offset12 { + margin-left: 1230px; + } + .offset11 { + margin-left: 1130px; + } + .offset10 { + margin-left: 1030px; + } + .offset9 { + margin-left: 930px; + } + .offset8 { + margin-left: 830px; + } + .offset7 { + margin-left: 730px; + } + .offset6 { + margin-left: 630px; + } + .offset5 { + margin-left: 530px; + } + .offset4 { + margin-left: 430px; + } + .offset3 { + margin-left: 330px; + } + .offset2 { + margin-left: 230px; + } + .offset1 { + margin-left: 130px; + } + .row-fluid { + width: 100%; + *zoom: 1; + } + .row-fluid:before, + .row-fluid:after { + display: table; + line-height: 0; + content: ""; + } + .row-fluid:after { + clear: both; + } + .row-fluid [class*="span"] { + display: block; + float: left; + width: 100%; + min-height: 30px; + margin-left: 2.564102564102564%; + *margin-left: 2.5109110747408616%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .row-fluid [class*="span"]:first-child { + margin-left: 0; + } + .row-fluid .span12 { + width: 100%; + *width: 99.94680851063829%; + } + .row-fluid .span11 { + width: 91.45299145299145%; + *width: 91.39979996362975%; + } + .row-fluid .span10 { + width: 82.90598290598291%; + *width: 82.8527914166212%; + } + .row-fluid .span9 { + width: 74.35897435897436%; + *width: 74.30578286961266%; + } + .row-fluid .span8 { + width: 65.81196581196582%; + *width: 65.75877432260411%; + } + .row-fluid .span7 { + width: 57.26495726495726%; + *width: 57.21176577559556%; + } + .row-fluid .span6 { + width: 48.717948717948715%; + *width: 48.664757228587014%; + } + .row-fluid .span5 { + width: 40.17094017094017%; + *width: 40.11774868157847%; + } + .row-fluid .span4 { + width: 31.623931623931625%; + *width: 31.570740134569924%; + } + .row-fluid .span3 { + width: 23.076923076923077%; + *width: 23.023731587561375%; + } + .row-fluid .span2 { + width: 14.52991452991453%; + *width: 14.476723040552828%; + } + .row-fluid .span1 { + width: 5.982905982905983%; + *width: 5.929714493544281%; + } + .row-fluid .offset12 { + margin-left: 105.12820512820512%; + *margin-left: 105.02182214948171%; + } + .row-fluid .offset12:first-child { + margin-left: 102.56410256410257%; + *margin-left: 102.45771958537915%; + } + .row-fluid .offset11 { + margin-left: 96.58119658119658%; + *margin-left: 96.47481360247316%; + } + .row-fluid .offset11:first-child { + margin-left: 94.01709401709402%; + *margin-left: 93.91071103837061%; + } + .row-fluid .offset10 { + margin-left: 88.03418803418803%; + *margin-left: 87.92780505546462%; + } + .row-fluid .offset10:first-child { + margin-left: 85.47008547008548%; + *margin-left: 85.36370249136206%; + } + .row-fluid .offset9 { + margin-left: 79.48717948717949%; + *margin-left: 79.38079650845607%; + } + .row-fluid .offset9:first-child { + margin-left: 76.92307692307693%; + *margin-left: 76.81669394435352%; + } + .row-fluid .offset8 { + margin-left: 70.94017094017094%; + *margin-left: 70.83378796144753%; + } + .row-fluid .offset8:first-child { + margin-left: 68.37606837606839%; + *margin-left: 68.26968539734497%; + } + .row-fluid .offset7 { + margin-left: 62.393162393162385%; + *margin-left: 62.28677941443899%; + } + .row-fluid .offset7:first-child { + margin-left: 59.82905982905982%; + *margin-left: 59.72267685033642%; + } + .row-fluid .offset6 { + margin-left: 53.84615384615384%; + *margin-left: 53.739770867430444%; + } + .row-fluid .offset6:first-child { + margin-left: 51.28205128205128%; + *margin-left: 51.175668303327875%; + } + .row-fluid .offset5 { + margin-left: 45.299145299145295%; + *margin-left: 45.1927623204219%; + } + .row-fluid .offset5:first-child { + margin-left: 42.73504273504273%; + *margin-left: 42.62865975631933%; + } + .row-fluid .offset4 { + margin-left: 36.75213675213675%; + *margin-left: 36.645753773413354%; + } + .row-fluid .offset4:first-child { + margin-left: 34.18803418803419%; + *margin-left: 34.081651209310785%; + } + .row-fluid .offset3 { + margin-left: 28.205128205128204%; + *margin-left: 28.0987452264048%; + } + .row-fluid .offset3:first-child { + margin-left: 25.641025641025642%; + *margin-left: 25.53464266230224%; + } + .row-fluid .offset2 { + margin-left: 19.65811965811966%; + *margin-left: 19.551736679396257%; + } + .row-fluid .offset2:first-child { + margin-left: 17.094017094017094%; + *margin-left: 16.98763411529369%; + } + .row-fluid .offset1 { + margin-left: 11.11111111111111%; + *margin-left: 11.004728132387708%; + } + .row-fluid .offset1:first-child { + margin-left: 8.547008547008547%; + *margin-left: 8.440625568285142%; + } + input, + textarea, + .uneditable-input { + margin-left: 0; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 30px; + } + input.span12, + textarea.span12, + .uneditable-input.span12 { + width: 1156px; + } + input.span11, + textarea.span11, + .uneditable-input.span11 { + width: 1056px; + } + input.span10, + textarea.span10, + .uneditable-input.span10 { + width: 956px; + } + input.span9, + textarea.span9, + .uneditable-input.span9 { + width: 856px; + } + input.span8, + textarea.span8, + .uneditable-input.span8 { + width: 756px; + } + input.span7, + textarea.span7, + .uneditable-input.span7 { + width: 656px; + } + input.span6, + textarea.span6, + .uneditable-input.span6 { + width: 556px; + } + input.span5, + textarea.span5, + .uneditable-input.span5 { + width: 456px; + } + input.span4, + textarea.span4, + .uneditable-input.span4 { + width: 356px; + } + input.span3, + textarea.span3, + .uneditable-input.span3 { + width: 256px; + } + input.span2, + textarea.span2, + .uneditable-input.span2 { + width: 156px; + } + input.span1, + textarea.span1, + .uneditable-input.span1 { + width: 56px; + } + .thumbnails { + margin-left: -30px; + } + .thumbnails > li { + margin-left: 30px; + } + .row-fluid .thumbnails { + margin-left: 0; + } +} + +@media (min-width: 768px) and (max-width: 979px) { + .row { + margin-left: -20px; + *zoom: 1; + } + .row:before, + .row:after { + display: table; + line-height: 0; + content: ""; + } + .row:after { + clear: both; + } + [class*="span"] { + float: left; + min-height: 1px; + margin-left: 20px; + } + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { + width: 724px; + } + .span12 { + width: 724px; + } + .span11 { + width: 662px; + } + .span10 { + width: 600px; + } + .span9 { + width: 538px; + } + .span8 { + width: 476px; + } + .span7 { + width: 414px; + } + .span6 { + width: 352px; + } + .span5 { + width: 290px; + } + .span4 { + width: 228px; + } + .span3 { + width: 166px; + } + .span2 { + width: 104px; + } + .span1 { + width: 42px; + } + .offset12 { + margin-left: 764px; + } + .offset11 { + margin-left: 702px; + } + .offset10 { + margin-left: 640px; + } + .offset9 { + margin-left: 578px; + } + .offset8 { + margin-left: 516px; + } + .offset7 { + margin-left: 454px; + } + .offset6 { + margin-left: 392px; + } + .offset5 { + margin-left: 330px; + } + .offset4 { + margin-left: 268px; + } + .offset3 { + margin-left: 206px; + } + .offset2 { + margin-left: 144px; + } + .offset1 { + margin-left: 82px; + } + .row-fluid { + width: 100%; + *zoom: 1; + } + .row-fluid:before, + .row-fluid:after { + display: table; + line-height: 0; + content: ""; + } + .row-fluid:after { + clear: both; + } + .row-fluid [class*="span"] { + display: block; + float: left; + width: 100%; + min-height: 30px; + margin-left: 2.7624309392265194%; + *margin-left: 2.709239449864817%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .row-fluid [class*="span"]:first-child { + margin-left: 0; + } + .row-fluid .span12 { + width: 100%; + *width: 99.94680851063829%; + } + .row-fluid .span11 { + width: 91.43646408839778%; + *width: 91.38327259903608%; + } + .row-fluid .span10 { + width: 82.87292817679558%; + *width: 82.81973668743387%; + } + .row-fluid .span9 { + width: 74.30939226519337%; + *width: 74.25620077583166%; + } + .row-fluid .span8 { + width: 65.74585635359117%; + *width: 65.69266486422946%; + } + .row-fluid .span7 { + width: 57.18232044198895%; + *width: 57.12912895262725%; + } + .row-fluid .span6 { + width: 48.61878453038674%; + *width: 48.56559304102504%; + } + .row-fluid .span5 { + width: 40.05524861878453%; + *width: 40.00205712942283%; + } + .row-fluid .span4 { + width: 31.491712707182323%; + *width: 31.43852121782062%; + } + .row-fluid .span3 { + width: 22.92817679558011%; + *width: 22.87498530621841%; + } + .row-fluid .span2 { + width: 14.3646408839779%; + *width: 14.311449394616199%; + } + .row-fluid .span1 { + width: 5.801104972375691%; + *width: 5.747913483013988%; + } + .row-fluid .offset12 { + margin-left: 105.52486187845304%; + *margin-left: 105.41847889972962%; + } + .row-fluid .offset12:first-child { + margin-left: 102.76243093922652%; + *margin-left: 102.6560479605031%; + } + .row-fluid .offset11 { + margin-left: 96.96132596685082%; + *margin-left: 96.8549429881274%; + } + .row-fluid .offset11:first-child { + margin-left: 94.1988950276243%; + *margin-left: 94.09251204890089%; + } + .row-fluid .offset10 { + margin-left: 88.39779005524862%; + *margin-left: 88.2914070765252%; + } + .row-fluid .offset10:first-child { + margin-left: 85.6353591160221%; + *margin-left: 85.52897613729868%; + } + .row-fluid .offset9 { + margin-left: 79.8342541436464%; + *margin-left: 79.72787116492299%; + } + .row-fluid .offset9:first-child { + margin-left: 77.07182320441989%; + *margin-left: 76.96544022569647%; + } + .row-fluid .offset8 { + margin-left: 71.2707182320442%; + *margin-left: 71.16433525332079%; + } + .row-fluid .offset8:first-child { + margin-left: 68.50828729281768%; + *margin-left: 68.40190431409427%; + } + .row-fluid .offset7 { + margin-left: 62.70718232044199%; + *margin-left: 62.600799341718584%; + } + .row-fluid .offset7:first-child { + margin-left: 59.94475138121547%; + *margin-left: 59.838368402492065%; + } + .row-fluid .offset6 { + margin-left: 54.14364640883978%; + *margin-left: 54.037263430116376%; + } + .row-fluid .offset6:first-child { + margin-left: 51.38121546961326%; + *margin-left: 51.27483249088986%; + } + .row-fluid .offset5 { + margin-left: 45.58011049723757%; + *margin-left: 45.47372751851417%; + } + .row-fluid .offset5:first-child { + margin-left: 42.81767955801105%; + *margin-left: 42.71129657928765%; + } + .row-fluid .offset4 { + margin-left: 37.01657458563536%; + *margin-left: 36.91019160691196%; + } + .row-fluid .offset4:first-child { + margin-left: 34.25414364640884%; + *margin-left: 34.14776066768544%; + } + .row-fluid .offset3 { + margin-left: 28.45303867403315%; + *margin-left: 28.346655695309746%; + } + .row-fluid .offset3:first-child { + margin-left: 25.69060773480663%; + *margin-left: 25.584224756083227%; + } + .row-fluid .offset2 { + margin-left: 19.88950276243094%; + *margin-left: 19.783119783707537%; + } + .row-fluid .offset2:first-child { + margin-left: 17.12707182320442%; + *margin-left: 17.02068884448102%; + } + .row-fluid .offset1 { + margin-left: 11.32596685082873%; + *margin-left: 11.219583872105325%; + } + .row-fluid .offset1:first-child { + margin-left: 8.56353591160221%; + *margin-left: 8.457152932878806%; + } + input, + textarea, + .uneditable-input { + margin-left: 0; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 20px; + } + input.span12, + textarea.span12, + .uneditable-input.span12 { + width: 710px; + } + input.span11, + textarea.span11, + .uneditable-input.span11 { + width: 648px; + } + input.span10, + textarea.span10, + .uneditable-input.span10 { + width: 586px; + } + input.span9, + textarea.span9, + .uneditable-input.span9 { + width: 524px; + } + input.span8, + textarea.span8, + .uneditable-input.span8 { + width: 462px; + } + input.span7, + textarea.span7, + .uneditable-input.span7 { + width: 400px; + } + input.span6, + textarea.span6, + .uneditable-input.span6 { + width: 338px; + } + input.span5, + textarea.span5, + .uneditable-input.span5 { + width: 276px; + } + input.span4, + textarea.span4, + .uneditable-input.span4 { + width: 214px; + } + input.span3, + textarea.span3, + .uneditable-input.span3 { + width: 152px; + } + input.span2, + textarea.span2, + .uneditable-input.span2 { + width: 90px; + } + input.span1, + textarea.span1, + .uneditable-input.span1 { + width: 28px; + } +} + +@media (max-width: 767px) { + body { + padding-right: 20px; + padding-left: 20px; + } + .navbar-fixed-top, + .navbar-fixed-bottom, + .navbar-static-top { + margin-right: -20px; + margin-left: -20px; + } + .container-fluid { + padding: 0; + } + .dl-horizontal dt { + float: none; + width: auto; + clear: none; + text-align: left; + } + .dl-horizontal dd { + margin-left: 0; + } + .container { + width: auto; + } + .row-fluid { + width: 100%; + } + .row, + .thumbnails { + margin-left: 0; + } + .thumbnails > li { + float: none; + margin-left: 0; + } + [class*="span"], + .row-fluid [class*="span"] { + display: block; + float: none; + width: 100%; + margin-left: 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .span12, + .row-fluid .span12 { + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .input-large, + .input-xlarge, + .input-xxlarge, + input[class*="span"], + select[class*="span"], + textarea[class*="span"], + .uneditable-input { + display: block; + width: 100%; + min-height: 30px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .input-prepend input, + .input-append input, + .input-prepend input[class*="span"], + .input-append input[class*="span"] { + display: inline-block; + width: auto; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 0; + } + .modal { + position: fixed; + top: 20px; + right: 20px; + left: 20px; + width: auto; + margin: 0; + } + .modal.fade.in { + top: auto; + } +} + +@media (max-width: 480px) { + .nav-collapse { + -webkit-transform: translate3d(0, 0, 0); + } + .page-header h1 small { + display: block; + line-height: 20px; + } + input[type="checkbox"], + input[type="radio"] { + border: 1px solid #ccc; + } + .form-horizontal .control-label { + float: none; + width: auto; + padding-top: 0; + text-align: left; + } + .form-horizontal .controls { + margin-left: 0; + } + .form-horizontal .control-list { + padding-top: 0; + } + .form-horizontal .form-actions { + padding-right: 10px; + padding-left: 10px; + } + .modal { + top: 10px; + right: 10px; + left: 10px; + } + .modal-header .close { + padding: 10px; + margin: -10px; + } + .carousel-caption { + position: static; + } +} + +@media (max-width: 979px) { + body { + padding-top: 0; + } + .navbar-fixed-top, + .navbar-fixed-bottom { + position: static; + } + .navbar-fixed-top { + margin-bottom: 20px; + } + .navbar-fixed-bottom { + margin-top: 20px; + } + .navbar-fixed-top .navbar-inner, + .navbar-fixed-bottom .navbar-inner { + padding: 5px; + } + .navbar .container { + width: auto; + padding: 0; + } + .navbar .brand { + padding-right: 10px; + padding-left: 10px; + margin: 0 0 0 -5px; + } + .nav-collapse { + clear: both; + } + .nav-collapse .nav { + float: none; + margin: 0 0 10px; + } + .nav-collapse .nav > li { + float: none; + } + .nav-collapse .nav > li > a { + margin-bottom: 2px; + } + .nav-collapse .nav > .divider-vertical { + display: none; + } + .nav-collapse .nav .nav-header { + color: #777777; + text-shadow: none; + } + .nav-collapse .nav > li > a, + .nav-collapse .dropdown-menu a { + padding: 9px 15px; + font-weight: bold; + color: #777777; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + } + .nav-collapse .btn { + padding: 4px 10px 4px; + font-weight: normal; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + } + .nav-collapse .dropdown-menu li + li a { + margin-bottom: 2px; + } + .nav-collapse .nav > li > a:hover, + .nav-collapse .dropdown-menu a:hover { + background-color: #f2f2f2; + } + .navbar-inverse .nav-collapse .nav > li > a:hover, + .navbar-inverse .nav-collapse .dropdown-menu a:hover { + background-color: #111111; + } + .nav-collapse.in .btn-group { + padding: 0; + margin-top: 5px; + } + .nav-collapse .dropdown-menu { + position: static; + top: auto; + left: auto; + display: block; + float: none; + max-width: none; + padding: 0; + margin: 0 15px; + background-color: transparent; + border: none; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + } + .nav-collapse .dropdown-menu:before, + .nav-collapse .dropdown-menu:after { + display: none; + } + .nav-collapse .dropdown-menu .divider { + display: none; + } + .nav-collapse .nav > li > .dropdown-menu:before, + .nav-collapse .nav > li > .dropdown-menu:after { + display: none; + } + .nav-collapse .navbar-form, + .nav-collapse .navbar-search { + float: none; + padding: 10px 15px; + margin: 10px 0; + border-top: 1px solid #f2f2f2; + border-bottom: 1px solid #f2f2f2; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + } + .navbar-inverse .nav-collapse .navbar-form, + .navbar-inverse .nav-collapse .navbar-search { + border-top-color: #111111; + border-bottom-color: #111111; + } + .navbar .nav-collapse .nav.pull-right { + float: none; + margin-left: 0; + } + .nav-collapse, + .nav-collapse.collapse { + height: 0; + overflow: hidden; + } + .navbar .btn-navbar { + display: block; + } + .navbar-static .navbar-inner { + padding-right: 10px; + padding-left: 10px; + } +} + +@media (min-width: 980px) { + .nav-collapse.collapse { + height: auto !important; + overflow: visible !important; + } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.min.css b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.min.css index e98ed70048..d1b7f4b0b8 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.min.css +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap-responsive.min.css @@ -1,9 +1,9 @@ -/*! - * Bootstrap Responsive v2.3.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} +/*! + * Bootstrap Responsive v2.3.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.cosmo.min.css b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.cosmo.min.css index 469f8dd88c..f92b1605d4 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.cosmo.min.css +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.cosmo.min.css @@ -1,20 +1,20 @@ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: local('Open Sans'), local('OpenSans') - , url(../../../font/opensans/OpenSans-Regular.woff) format('woff') - , url(../../../font/opensans/OpenSans-Regular.ttf) format('ttf'); -} - - -/*import url('//fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700');! - - * Bootstrap v2.3.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Open Sans",Calibri,Candara,Arial,sans-serif;font-size:14px;line-height:20px;color:#555;background-color:#fff}a{color:#007fff;text-decoration:none}a:hover,a:focus{color:#06c;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#dfdfdf}a.muted:hover,a.muted:focus{color:#c6c6c6}.text-warning{color:#fff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-error{color:#fff}a.text-error:hover,a.text-error:focus{color:#e6e6e6}.text-info{color:#fff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-success{color:#fff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:300;line-height:20px;color:#080808;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#dfdfdf}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #dfdfdf}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#dfdfdf}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#999;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#dfdfdf}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Open Sans",Calibri,Candara,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#bbb;vertical-align:middle;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #bbb;-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);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-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)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #bbb}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#dfdfdf;cursor:not-allowed;background-color:#fcfcfc;border-color:#bbb;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#bbb}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#bbb}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#bbb}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#fff}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#fff}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#fff;-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)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#fff;background-color:#ff7518;border-color:#fff}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#fff}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#fff}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#fff;-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)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#fff;background-color:#ff0039;border-color:#fff}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#fff}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#fff}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#fff;-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)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#fff;background-color:#3fb618;border-color:#fff}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#fff}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#fff}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#fff;-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)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#fff;background-color:#9954bb;border-color:#fff}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#7b7b7b}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#96ed7a;border-color:#3fb618}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-topleft:0}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:0;border-top-right-radius:0;-moz-border-radius-topright:0}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-topleft:0}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:0;border-top-right-radius:0;-moz-border-radius-topright:0}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#e8f8fd}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#3fb618}.table tbody tr.error>td{background-color:#ff0039}.table tbody tr.warning>td{background-color:#ff7518}.table tbody tr.info>td{background-color:#9954bb}.table-hover tbody tr.success:hover>td{background-color:#379f15}.table-hover tbody tr.error:hover>td{background-color:#e60033}.table-hover tbody tr.warning:hover>td{background-color:#fe6600}.table-hover tbody tr.info:hover>td{background-color:#8d46b0}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-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);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#999;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#007af5;background-image:-moz-linear-gradient(top,#007fff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#0072e6));background-image:-webkit-linear-gradient(top,#007fff,#0072e6);background-image:-o-linear-gradient(top,#007fff,#0072e6);background-image:linear-gradient(to bottom,#007fff,#0072e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff0072e6',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#007af5;background-image:-moz-linear-gradient(top,#007fff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#0072e6));background-image:-webkit-linear-gradient(top,#007fff,#0072e6);background-image:-o-linear-gradient(top,#007fff,#0072e6);background-image:linear-gradient(to bottom,#007fff,#0072e6);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff0072e6',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#dfdfdf}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;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}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#eee;border:1px solid #dcdcdc;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.well-small{padding:9px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#999;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#dfdfdf;*background-color:#c8c8c8;background-image:-moz-linear-gradient(top,#eee,#c8c8c8);background-image:-webkit-gradient(linear,0 0,0 100%,from(#eee),to(#c8c8c8));background-image:-webkit-linear-gradient(top,#eee,#c8c8c8);background-image:-o-linear-gradient(top,#eee,#c8c8c8);background-image:linear-gradient(to bottom,#eee,#c8c8c8);background-repeat:repeat-x;border:1px solid #bbb;*border:0;border-color:#c8c8c8 #c8c8c8 #a2a2a2;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffeeeeee',endColorstr='#ffc8c8c8',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-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)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#999;background-color:#c8c8c8;*background-color:#bbb}.btn:active,.btn.active{background-color:#aeaeae \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#999;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-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)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:22px 30px;font-size:17.5px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:2px 6px;font-size:10.5px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0f82f5;*background-color:#0072e6;background-image:-moz-linear-gradient(top,#1a8cff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#1a8cff),to(#0072e6));background-image:-webkit-linear-gradient(top,#1a8cff,#0072e6);background-image:-o-linear-gradient(top,#1a8cff,#0072e6);background-image:linear-gradient(to bottom,#1a8cff,#0072e6);background-repeat:repeat-x;border-color:#0072e6 #0072e6 #004c99;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff1a8cff',endColorstr='#ff0072e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#0072e6;*background-color:#06c}.btn-primary:active,.btn-primary.active{background-color:#0059b3 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#fe781e;*background-color:#fe6600;background-image:-moz-linear-gradient(top,#ff8432,#fe6600);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff8432),to(#fe6600));background-image:-webkit-linear-gradient(top,#ff8432,#fe6600);background-image:-o-linear-gradient(top,#ff8432,#fe6600);background-image:linear-gradient(to bottom,#ff8432,#fe6600);background-repeat:repeat-x;border-color:#fe6600 #fe6600 #b14700;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff8432',endColorstr='#fffe6600',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#fe6600;*background-color:#e45c00}.btn-warning:active,.btn-warning.active{background-color:#cb5200 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#f50f43;*background-color:#e60033;background-image:-moz-linear-gradient(top,#ff1a4d,#e60033);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff1a4d),to(#e60033));background-image:-webkit-linear-gradient(top,#ff1a4d,#e60033);background-image:-o-linear-gradient(top,#ff1a4d,#e60033);background-image:linear-gradient(to bottom,#ff1a4d,#e60033);background-repeat:repeat-x;border-color:#e60033 #e60033 #902;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff1a4d',endColorstr='#ffe60033',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#e60033;*background-color:#cc002e}.btn-danger:active,.btn-danger.active{background-color:#b30028 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#41bb19;*background-color:#379f15;background-image:-moz-linear-gradient(top,#47cd1b,#379f15);background-image:-webkit-gradient(linear,0 0,0 100%,from(#47cd1b),to(#379f15));background-image:-webkit-linear-gradient(top,#47cd1b,#379f15);background-image:-o-linear-gradient(top,#47cd1b,#379f15);background-image:linear-gradient(to bottom,#47cd1b,#379f15);background-repeat:repeat-x;border-color:#379f15 #379f15 #205c0c;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff47cd1b',endColorstr='#ff379f15',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#379f15;*background-color:#2f8912}.btn-success:active,.btn-success.active{background-color:#28720f \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#9b59bb;*background-color:#8d46b0;background-image:-moz-linear-gradient(top,#a466c2,#8d46b0);background-image:-webkit-gradient(linear,0 0,0 100%,from(#a466c2),to(#8d46b0));background-image:-webkit-linear-gradient(top,#a466c2,#8d46b0);background-image:-o-linear-gradient(top,#a466c2,#8d46b0);background-image:linear-gradient(to bottom,#a466c2,#8d46b0);background-repeat:repeat-x;border-color:#8d46b0 #8d46b0 #613079;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffa466c2',endColorstr='#ff8d46b0',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#8d46b0;*background-color:#7e3f9d}.btn-info:active,.btn-info.active{background-color:#6f378b \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#080808;*background-color:#000;background-image:-moz-linear-gradient(top,#0d0d0d,#000);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0d0d0d),to(#000));background-image:-webkit-linear-gradient(top,#0d0d0d,#000);background-image:-o-linear-gradient(top,#0d0d0d,#000);background-image:linear-gradient(to bottom,#0d0d0d,#000);background-repeat:repeat-x;border-color:#000 #000 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0d0d0d',endColorstr='#ff000000',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#000;*background-color:#000}.btn-inverse:active,.btn-inverse.active{background-color:#000 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#007fff;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#06c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#999;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-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)}.btn-group.open .btn.dropdown-toggle{background-color:#c8c8c8}.btn-group.open .btn-primary.dropdown-toggle{background-color:#0072e6}.btn-group.open .btn-warning.dropdown-toggle{background-color:#fe6600}.btn-group.open .btn-danger.dropdown-toggle{background-color:#e60033}.btn-group.open .btn-success.dropdown-toggle{background-color:#379f15}.btn-group.open .btn-info.dropdown-toggle{background-color:#8d46b0}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#000}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#ff7518;border:1px solid transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert,.alert h4{color:#fff}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#fff;background-color:#3fb618;border-color:transparent}.alert-success h4{color:#fff}.alert-danger,.alert-error{color:#fff;background-color:#ff0039;border-color:transparent}.alert-danger h4,.alert-error h4{color:#fff}.alert-info{color:#fff;background-color:#9954bb;border-color:transparent}.alert-info h4{color:#fff}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#dfdfdf;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#007fff}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#bbb;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#007fff}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#007fff;border-bottom-color:#007fff}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#06c;border-bottom-color:#06c}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#bbb;border-bottom-color:#bbb}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#dfdfdf;border-color:#dfdfdf}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#dfdfdf}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#dfdfdf}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:50px;padding-right:20px;padding-left:20px;background-color:#080808;background-image:-moz-linear-gradient(top,#080808,#080808);background-image:-webkit-gradient(linear,0 0,0 100%,from(#080808),to(#080808));background-image:-webkit-linear-gradient(top,#080808,#080808);background-image:-o-linear-gradient(top,#080808,#080808);background-image:linear-gradient(to bottom,#080808,#080808);background-repeat:repeat-x;border:1px solid transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808',endColorstr='#ff080808',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:15px 20px 15px;margin-left:-20px;font-size:20px;font-weight:200;color:#fff;text-shadow:0 1px 0 #080808}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:50px;color:#fff}.navbar-link{color:#fff}.navbar-link:hover,.navbar-link:focus{color:#bbb}.navbar .divider-vertical{height:50px;margin:0 9px;border-right:1px solid #080808;border-left:1px solid #080808}.navbar .btn,.navbar .btn-group{margin-top:10px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:10px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:10px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Open Sans",Calibri,Candara,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:15px 15px 15px;color:#fff;text-decoration:none;text-shadow:0 1px 0 #080808}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#bbb;text-decoration:none;background-color:rgba(0,0,0,0.05)}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#fff;text-decoration:none;background-color:transparent;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#000;*background-color:#000;background-image:-moz-linear-gradient(top,#000,#000);background-image:-webkit-gradient(linear,0 0,0 100%,from(#000),to(#000));background-image:-webkit-linear-gradient(top,#000,#000);background-image:-o-linear-gradient(top,#000,#000);background-image:linear-gradient(to bottom,#000,#000);background-repeat:repeat-x;border-color:#000 #000 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff000000',endColorstr='#ff000000',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#000;*background-color:#000}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#000 \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#bbb;border-bottom-color:#bbb}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:transparent}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#007fff;background-image:-moz-linear-gradient(top,#007fff,#007fff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#007fff));background-image:-webkit-linear-gradient(top,#007fff,#007fff);background-image:-o-linear-gradient(top,#007fff,#007fff);background-image:linear-gradient(to bottom,#007fff,#007fff);background-repeat:repeat-x;border-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff007fff',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#fff}.navbar-inverse .navbar-text{color:#fff}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:rgba(0,0,0,0.05)}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#007fff}.navbar-inverse .navbar-link{color:#fff}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#007fff;border-left-color:#007fff}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#007fff}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#80bfff;border-color:#007fff;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#999}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#999}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#999}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#999;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0072e6;*background-color:#0072e6;background-image:-moz-linear-gradient(top,#0072e6,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0072e6),to(#0072e6));background-image:-webkit-linear-gradient(top,#0072e6,#0072e6);background-image:-o-linear-gradient(top,#0072e6,#0072e6);background-image:linear-gradient(to bottom,#0072e6,#0072e6);background-repeat:repeat-x;border-color:#0072e6 #0072e6 #004c99;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0072e6',endColorstr='#ff0072e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#0072e6;*background-color:#06c}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#0059b3 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#dfdfdf}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#dfdfdf;border:1px solid transparent;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#007fff}.pagination ul>.active>a,.pagination ul>.active>span{color:#dfdfdf;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#dfdfdf;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:22px 30px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:2px 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#dfdfdf;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-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);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;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;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#ff7518;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);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#ff7518;border-bottom:1px solid #fe6600;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:16px}.popover .arrow:after{border-width:15px;content:""}.popover.top .arrow{bottom:-16px;left:50%;margin-left:-16px;border-top-color:#999;border-top-color:transparent;border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-15px;border-top-color:#ff7518;border-bottom-width:0}.popover.right .arrow{top:50%;left:-16px;margin-top:-16px;border-right-color:#999;border-right-color:transparent;border-left-width:0}.popover.right .arrow:after{bottom:-15px;left:1px;border-right-color:#ff7518;border-left-width:0}.popover.bottom .arrow{top:-16px;left:50%;margin-left:-16px;border-bottom-color:#999;border-bottom-color:transparent;border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-15px;border-bottom-color:#ff7518;border-top-width:0}.popover.left .arrow{top:50%;right:-16px;margin-top:-16px;border-left-color:#999;border-left-color:transparent;border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-15px;border-left-color:#ff7518;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#007fff;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#bbb}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#dfdfdf}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#fff}.label-important[href],.badge-important[href]{background-color:#e6e6e6}.label-warning,.badge-warning{background-color:#ff7518}.label-warning[href],.badge-warning[href]{background-color:#e45c00}.label-success,.badge-success{background-color:#fff}.label-success[href],.badge-success[href]{background-color:#e6e6e6}.label-info,.badge-info{background-color:#fff}.label-info[href],.badge-info[href]{background-color:#e6e6e6}.label-inverse,.badge-inverse{background-color:#999}.label-inverse[href],.badge-inverse[href]{background-color:#808080}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#ff9046;background-image:-moz-linear-gradient(top,#ffa365,#ff7518);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ffa365),to(#ff7518));background-image:-webkit-linear-gradient(top,#ffa365,#ff7518);background-image:-o-linear-gradient(top,#ffa365,#ff7518);background-image:linear-gradient(to bottom,#ffa365,#ff7518);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffa365',endColorstr='#ffff7518',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#ffa365;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#080808;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#999;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}body{font-weight:300}h1{font-size:50px}h2,h3{font-size:26px}h4{font-size:14px}h5,h6{font-size:11px}blockquote{padding:10px 15px;background-color:#eee;border-left-color:#bbb}blockquote.pull-right{padding:10px 15px;border-right-color:#bbb}blockquote small{color:#bbb}.muted{color:#bbb}.text-warning{color:#ff7518}a.text-warning:hover{color:#e45c00}.text-error{color:#ff0039}a.text-error:hover{color:#cc002e}.text-info{color:#9954bb}a.text-info:hover{color:#7e3f9d}.text-success{color:#3fb618}a.text-success:hover{color:#2f8912}.navbar .navbar-inner{background-image:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar .brand:hover{color:#bbb}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{background-color:rgba(0,0,0,0.05);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#fff}.navbar .nav li.dropdown.open>.dropdown-toggle:hover,.navbar .nav li.dropdown.active>.dropdown-toggle:hover,.navbar .nav li.dropdown.open.active>.dropdown-toggle:hover{color:#eee}.navbar .navbar-search .search-query{line-height:normal}.navbar-inverse .brand,.navbar-inverse .nav>li>a{text-shadow:none}.navbar-inverse .brand:hover,.navbar-inverse .nav>.active>a,.navbar-inverse .nav>.active>a:hover,.navbar-inverse .nav>.active>a:focus{color:#fff;background-color:rgba(0,0,0,0.05);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar-inverse .navbar-search .search-query{color:#080808}div.subnav{margin:0 1px;background:#dfdfdf none;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}div.subnav .nav{background-color:transparent}div.subnav .nav>li>a{border-color:transparent}div.subnav .nav>.active>a,div.subnav .nav>.active>a:hover{color:#fff;background-color:#000;border-color:transparent;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}div.subnav-fixed{top:51px;margin:0}.nav .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#007fff}.nav-tabs>li>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li>a:hover{color:#fff;background-color:#007fff}.nav-tabs.nav-stacked>.active>a,.nav-tabs.nav-stacked>.active>a:hover{color:#bbb;background-color:#fff}.nav-tabs.nav-stacked>li:first-child>a,.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.tabs-below>.nav-tabs>li>a,.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-pills>li>a{color:#000;background-color:#dfdfdf;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-pills>li>a:hover{color:#fff;background-color:#000}.nav-pills>.disabled>a,.nav-pills>.disabled>a:hover{color:#999;background-color:#eee}.nav-list>li>a{color:#080808}.nav-list>li>a:hover{color:#fff;text-shadow:none;background-color:#007fff}.nav-list .nav-header{color:#080808}.nav-list .divider{background-color:#bbb;border-bottom:0}.pagination ul{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.pagination ul>li>a,.pagination ul>li>span{margin-right:6px;color:#080808}.pagination ul>li>a:hover,.pagination ul>li>span:hover{color:#fff;background-color:#080808}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{margin-right:0}.pagination ul>.active>a,.pagination ul>.active>span{color:#fff}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999;background-color:#eee}.pager li>a,.pager li>span{color:#080808;background-color:#dfdfdf;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.pager li>a:hover,.pager li>span:hover{color:#fff;background-color:#080808}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>span{color:#999;background-color:#eee}.breadcrumb{background-color:#dfdfdf}.breadcrumb li{text-shadow:none}.breadcrumb .divider,.breadcrumb .active{color:#080808;text-shadow:none}.btn{padding:5px 12px;text-shadow:none;background-image:none;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn.disabled{box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-large{padding:22px 30px}.btn-small{padding:2px 10px}.btn-mini{padding:2px 6px}.btn-group>.btn:first-child,.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.dropdown-toggle{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.table tbody tr.success td{color:#fff}.table tbody tr.error td{color:#fff}.table tbody tr.info td{color:#fff}.table-bordered{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child,.table-bordered tfoot:last-child tr:last-child td:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"]{color:#080808}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#ff7518}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#080808;border-color:#ff7518}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#ff0039}.control-group.error input,.control-group.error select,.control-group.error textarea{color:#080808;border-color:#ff0039}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#3fb618}.control-group.success input,.control-group.success select,.control-group.success textarea{color:#080808;border-color:#3fb618}legend{color:#080808;border-bottom:0}.form-actions{background-color:#eee;border-top:0}.dropdown-menu{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert{text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert-heading,.alert h1,.alert h2,.alert h3,.alert h4,.alert h5,.alert h6{color:#fff}.label{min-width:80px;min-height:80px;font-weight:300;text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.label-success{background-color:#3fb618}.label-important{background-color:#ff0039}.label-info{background-color:#9954bb}.label-inverse{background-color:#000}.badge{font-weight:300;text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.badge-success{background-color:#3fb618}.badge-important{background-color:#ff0039}.badge-info{background-color:#9954bb}.badge-inverse{background-color:#000}.hero-unit{border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.well{border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}[class^="icon-"],[class*=" icon-"]{margin:0 2px;vertical-align:-2px}a.thumbnail{background-color:#dfdfdf}a.thumbnail:hover{background-color:#bbb;border-color:transparent}.progress{height:6px;background-color:#eee;background-image:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.progress .bar{background-color:#007fff;background-image:none}.progress-info{background-color:#9954bb}.progress-success{background-color:#3fb618}.progress-warning{background-color:#ff7518}.progress-danger{background-color:#ff0039}.modal{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.modal-header{border-bottom:0}.modal-footer{background-color:transparent;border-top:0}.popover{color:#fff;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.popover-title{color:#fff;border-bottom:0}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + src: local('Open Sans'), local('OpenSans') + , url(../../../font/opensans/OpenSans-Regular.woff) format('woff') + , url(../../../font/opensans/OpenSans-Regular.ttf) format('ttf'); +} + + +/*import url('//fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700');! + + * Bootstrap v2.3.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Open Sans",Calibri,Candara,Arial,sans-serif;font-size:14px;line-height:20px;color:#555;background-color:#fff}a{color:#007fff;text-decoration:none}a:hover,a:focus{color:#06c;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#dfdfdf}a.muted:hover,a.muted:focus{color:#c6c6c6}.text-warning{color:#fff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-error{color:#fff}a.text-error:hover,a.text-error:focus{color:#e6e6e6}.text-info{color:#fff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-success{color:#fff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:300;line-height:20px;color:#080808;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#dfdfdf}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #dfdfdf}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#dfdfdf}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#999;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#dfdfdf}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Open Sans",Calibri,Candara,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#bbb;vertical-align:middle;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #bbb;-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);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-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)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #bbb}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#dfdfdf;cursor:not-allowed;background-color:#fcfcfc;border-color:#bbb;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#bbb}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#bbb}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#bbb}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#fff}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#fff}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#fff;-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)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#fff;background-color:#ff7518;border-color:#fff}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#fff}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#fff}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#fff;-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)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#fff;background-color:#ff0039;border-color:#fff}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#fff}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#fff}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#fff;-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)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#fff;background-color:#3fb618;border-color:#fff}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#fff}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#fff}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#fff;-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)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#fff;background-color:#9954bb;border-color:#fff}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#7b7b7b}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#96ed7a;border-color:#3fb618}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-topleft:0}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:0;border-top-right-radius:0;-moz-border-radius-topright:0}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-topleft:0}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:0;border-top-right-radius:0;-moz-border-radius-topright:0}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#e8f8fd}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#3fb618}.table tbody tr.error>td{background-color:#ff0039}.table tbody tr.warning>td{background-color:#ff7518}.table tbody tr.info>td{background-color:#9954bb}.table-hover tbody tr.success:hover>td{background-color:#379f15}.table-hover tbody tr.error:hover>td{background-color:#e60033}.table-hover tbody tr.warning:hover>td{background-color:#fe6600}.table-hover tbody tr.info:hover>td{background-color:#8d46b0}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-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);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#999;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#007af5;background-image:-moz-linear-gradient(top,#007fff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#0072e6));background-image:-webkit-linear-gradient(top,#007fff,#0072e6);background-image:-o-linear-gradient(top,#007fff,#0072e6);background-image:linear-gradient(to bottom,#007fff,#0072e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff0072e6',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#007af5;background-image:-moz-linear-gradient(top,#007fff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#0072e6));background-image:-webkit-linear-gradient(top,#007fff,#0072e6);background-image:-o-linear-gradient(top,#007fff,#0072e6);background-image:linear-gradient(to bottom,#007fff,#0072e6);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff0072e6',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#dfdfdf}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;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}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#eee;border:1px solid #dcdcdc;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.well-small{padding:9px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#999;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#dfdfdf;*background-color:#c8c8c8;background-image:-moz-linear-gradient(top,#eee,#c8c8c8);background-image:-webkit-gradient(linear,0 0,0 100%,from(#eee),to(#c8c8c8));background-image:-webkit-linear-gradient(top,#eee,#c8c8c8);background-image:-o-linear-gradient(top,#eee,#c8c8c8);background-image:linear-gradient(to bottom,#eee,#c8c8c8);background-repeat:repeat-x;border:1px solid #bbb;*border:0;border-color:#c8c8c8 #c8c8c8 #a2a2a2;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffeeeeee',endColorstr='#ffc8c8c8',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-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)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#999;background-color:#c8c8c8;*background-color:#bbb}.btn:active,.btn.active{background-color:#aeaeae \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#999;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-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)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:22px 30px;font-size:17.5px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:2px 6px;font-size:10.5px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0f82f5;*background-color:#0072e6;background-image:-moz-linear-gradient(top,#1a8cff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#1a8cff),to(#0072e6));background-image:-webkit-linear-gradient(top,#1a8cff,#0072e6);background-image:-o-linear-gradient(top,#1a8cff,#0072e6);background-image:linear-gradient(to bottom,#1a8cff,#0072e6);background-repeat:repeat-x;border-color:#0072e6 #0072e6 #004c99;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff1a8cff',endColorstr='#ff0072e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#0072e6;*background-color:#06c}.btn-primary:active,.btn-primary.active{background-color:#0059b3 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#fe781e;*background-color:#fe6600;background-image:-moz-linear-gradient(top,#ff8432,#fe6600);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff8432),to(#fe6600));background-image:-webkit-linear-gradient(top,#ff8432,#fe6600);background-image:-o-linear-gradient(top,#ff8432,#fe6600);background-image:linear-gradient(to bottom,#ff8432,#fe6600);background-repeat:repeat-x;border-color:#fe6600 #fe6600 #b14700;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff8432',endColorstr='#fffe6600',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#fe6600;*background-color:#e45c00}.btn-warning:active,.btn-warning.active{background-color:#cb5200 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#f50f43;*background-color:#e60033;background-image:-moz-linear-gradient(top,#ff1a4d,#e60033);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff1a4d),to(#e60033));background-image:-webkit-linear-gradient(top,#ff1a4d,#e60033);background-image:-o-linear-gradient(top,#ff1a4d,#e60033);background-image:linear-gradient(to bottom,#ff1a4d,#e60033);background-repeat:repeat-x;border-color:#e60033 #e60033 #902;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff1a4d',endColorstr='#ffe60033',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#e60033;*background-color:#cc002e}.btn-danger:active,.btn-danger.active{background-color:#b30028 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#41bb19;*background-color:#379f15;background-image:-moz-linear-gradient(top,#47cd1b,#379f15);background-image:-webkit-gradient(linear,0 0,0 100%,from(#47cd1b),to(#379f15));background-image:-webkit-linear-gradient(top,#47cd1b,#379f15);background-image:-o-linear-gradient(top,#47cd1b,#379f15);background-image:linear-gradient(to bottom,#47cd1b,#379f15);background-repeat:repeat-x;border-color:#379f15 #379f15 #205c0c;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff47cd1b',endColorstr='#ff379f15',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#379f15;*background-color:#2f8912}.btn-success:active,.btn-success.active{background-color:#28720f \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#9b59bb;*background-color:#8d46b0;background-image:-moz-linear-gradient(top,#a466c2,#8d46b0);background-image:-webkit-gradient(linear,0 0,0 100%,from(#a466c2),to(#8d46b0));background-image:-webkit-linear-gradient(top,#a466c2,#8d46b0);background-image:-o-linear-gradient(top,#a466c2,#8d46b0);background-image:linear-gradient(to bottom,#a466c2,#8d46b0);background-repeat:repeat-x;border-color:#8d46b0 #8d46b0 #613079;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffa466c2',endColorstr='#ff8d46b0',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#8d46b0;*background-color:#7e3f9d}.btn-info:active,.btn-info.active{background-color:#6f378b \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#080808;*background-color:#000;background-image:-moz-linear-gradient(top,#0d0d0d,#000);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0d0d0d),to(#000));background-image:-webkit-linear-gradient(top,#0d0d0d,#000);background-image:-o-linear-gradient(top,#0d0d0d,#000);background-image:linear-gradient(to bottom,#0d0d0d,#000);background-repeat:repeat-x;border-color:#000 #000 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0d0d0d',endColorstr='#ff000000',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#000;*background-color:#000}.btn-inverse:active,.btn-inverse.active{background-color:#000 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#007fff;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#06c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#999;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-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)}.btn-group.open .btn.dropdown-toggle{background-color:#c8c8c8}.btn-group.open .btn-primary.dropdown-toggle{background-color:#0072e6}.btn-group.open .btn-warning.dropdown-toggle{background-color:#fe6600}.btn-group.open .btn-danger.dropdown-toggle{background-color:#e60033}.btn-group.open .btn-success.dropdown-toggle{background-color:#379f15}.btn-group.open .btn-info.dropdown-toggle{background-color:#8d46b0}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#000}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#ff7518;border:1px solid transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert,.alert h4{color:#fff}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#fff;background-color:#3fb618;border-color:transparent}.alert-success h4{color:#fff}.alert-danger,.alert-error{color:#fff;background-color:#ff0039;border-color:transparent}.alert-danger h4,.alert-error h4{color:#fff}.alert-info{color:#fff;background-color:#9954bb;border-color:transparent}.alert-info h4{color:#fff}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#dfdfdf;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#007fff}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#bbb;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#007fff}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#007fff;border-bottom-color:#007fff}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#06c;border-bottom-color:#06c}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#bbb;border-bottom-color:#bbb}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#dfdfdf;border-color:#dfdfdf}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#dfdfdf}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#dfdfdf}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:50px;padding-right:20px;padding-left:20px;background-color:#080808;background-image:-moz-linear-gradient(top,#080808,#080808);background-image:-webkit-gradient(linear,0 0,0 100%,from(#080808),to(#080808));background-image:-webkit-linear-gradient(top,#080808,#080808);background-image:-o-linear-gradient(top,#080808,#080808);background-image:linear-gradient(to bottom,#080808,#080808);background-repeat:repeat-x;border:1px solid transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808',endColorstr='#ff080808',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:15px 20px 15px;margin-left:-20px;font-size:20px;font-weight:200;color:#fff;text-shadow:0 1px 0 #080808}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:50px;color:#fff}.navbar-link{color:#fff}.navbar-link:hover,.navbar-link:focus{color:#bbb}.navbar .divider-vertical{height:50px;margin:0 9px;border-right:1px solid #080808;border-left:1px solid #080808}.navbar .btn,.navbar .btn-group{margin-top:10px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:10px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:10px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Open Sans",Calibri,Candara,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:15px 15px 15px;color:#fff;text-decoration:none;text-shadow:0 1px 0 #080808}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#bbb;text-decoration:none;background-color:rgba(0,0,0,0.05)}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#fff;text-decoration:none;background-color:transparent;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#000;*background-color:#000;background-image:-moz-linear-gradient(top,#000,#000);background-image:-webkit-gradient(linear,0 0,0 100%,from(#000),to(#000));background-image:-webkit-linear-gradient(top,#000,#000);background-image:-o-linear-gradient(top,#000,#000);background-image:linear-gradient(to bottom,#000,#000);background-repeat:repeat-x;border-color:#000 #000 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff000000',endColorstr='#ff000000',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#000;*background-color:#000}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#000 \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#bbb;border-bottom-color:#bbb}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:transparent}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#007fff;background-image:-moz-linear-gradient(top,#007fff,#007fff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#007fff));background-image:-webkit-linear-gradient(top,#007fff,#007fff);background-image:-o-linear-gradient(top,#007fff,#007fff);background-image:linear-gradient(to bottom,#007fff,#007fff);background-repeat:repeat-x;border-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff007fff',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#fff}.navbar-inverse .navbar-text{color:#fff}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:rgba(0,0,0,0.05)}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#007fff}.navbar-inverse .navbar-link{color:#fff}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#007fff;border-left-color:#007fff}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#007fff}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#80bfff;border-color:#007fff;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#999}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#999}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#999}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#999;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0072e6;*background-color:#0072e6;background-image:-moz-linear-gradient(top,#0072e6,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0072e6),to(#0072e6));background-image:-webkit-linear-gradient(top,#0072e6,#0072e6);background-image:-o-linear-gradient(top,#0072e6,#0072e6);background-image:linear-gradient(to bottom,#0072e6,#0072e6);background-repeat:repeat-x;border-color:#0072e6 #0072e6 #004c99;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0072e6',endColorstr='#ff0072e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#0072e6;*background-color:#06c}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#0059b3 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#dfdfdf}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#dfdfdf;border:1px solid transparent;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#007fff}.pagination ul>.active>a,.pagination ul>.active>span{color:#dfdfdf;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#dfdfdf;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:22px 30px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:2px 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#dfdfdf;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-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);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;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;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#ff7518;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);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#ff7518;border-bottom:1px solid #fe6600;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:16px}.popover .arrow:after{border-width:15px;content:""}.popover.top .arrow{bottom:-16px;left:50%;margin-left:-16px;border-top-color:#999;border-top-color:transparent;border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-15px;border-top-color:#ff7518;border-bottom-width:0}.popover.right .arrow{top:50%;left:-16px;margin-top:-16px;border-right-color:#999;border-right-color:transparent;border-left-width:0}.popover.right .arrow:after{bottom:-15px;left:1px;border-right-color:#ff7518;border-left-width:0}.popover.bottom .arrow{top:-16px;left:50%;margin-left:-16px;border-bottom-color:#999;border-bottom-color:transparent;border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-15px;border-bottom-color:#ff7518;border-top-width:0}.popover.left .arrow{top:50%;right:-16px;margin-top:-16px;border-left-color:#999;border-left-color:transparent;border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-15px;border-left-color:#ff7518;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#007fff;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#bbb}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#dfdfdf}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#fff}.label-important[href],.badge-important[href]{background-color:#e6e6e6}.label-warning,.badge-warning{background-color:#ff7518}.label-warning[href],.badge-warning[href]{background-color:#e45c00}.label-success,.badge-success{background-color:#fff}.label-success[href],.badge-success[href]{background-color:#e6e6e6}.label-info,.badge-info{background-color:#fff}.label-info[href],.badge-info[href]{background-color:#e6e6e6}.label-inverse,.badge-inverse{background-color:#999}.label-inverse[href],.badge-inverse[href]{background-color:#808080}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#ff9046;background-image:-moz-linear-gradient(top,#ffa365,#ff7518);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ffa365),to(#ff7518));background-image:-webkit-linear-gradient(top,#ffa365,#ff7518);background-image:-o-linear-gradient(top,#ffa365,#ff7518);background-image:linear-gradient(to bottom,#ffa365,#ff7518);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffa365',endColorstr='#ffff7518',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#ffa365;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#080808;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#999;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}body{font-weight:300}h1{font-size:50px}h2,h3{font-size:26px}h4{font-size:14px}h5,h6{font-size:11px}blockquote{padding:10px 15px;background-color:#eee;border-left-color:#bbb}blockquote.pull-right{padding:10px 15px;border-right-color:#bbb}blockquote small{color:#bbb}.muted{color:#bbb}.text-warning{color:#ff7518}a.text-warning:hover{color:#e45c00}.text-error{color:#ff0039}a.text-error:hover{color:#cc002e}.text-info{color:#9954bb}a.text-info:hover{color:#7e3f9d}.text-success{color:#3fb618}a.text-success:hover{color:#2f8912}.navbar .navbar-inner{background-image:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar .brand:hover{color:#bbb}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{background-color:rgba(0,0,0,0.05);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#fff}.navbar .nav li.dropdown.open>.dropdown-toggle:hover,.navbar .nav li.dropdown.active>.dropdown-toggle:hover,.navbar .nav li.dropdown.open.active>.dropdown-toggle:hover{color:#eee}.navbar .navbar-search .search-query{line-height:normal}.navbar-inverse .brand,.navbar-inverse .nav>li>a{text-shadow:none}.navbar-inverse .brand:hover,.navbar-inverse .nav>.active>a,.navbar-inverse .nav>.active>a:hover,.navbar-inverse .nav>.active>a:focus{color:#fff;background-color:rgba(0,0,0,0.05);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar-inverse .navbar-search .search-query{color:#080808}div.subnav{margin:0 1px;background:#dfdfdf none;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}div.subnav .nav{background-color:transparent}div.subnav .nav>li>a{border-color:transparent}div.subnav .nav>.active>a,div.subnav .nav>.active>a:hover{color:#fff;background-color:#000;border-color:transparent;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}div.subnav-fixed{top:51px;margin:0}.nav .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#007fff}.nav-tabs>li>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li>a:hover{color:#fff;background-color:#007fff}.nav-tabs.nav-stacked>.active>a,.nav-tabs.nav-stacked>.active>a:hover{color:#bbb;background-color:#fff}.nav-tabs.nav-stacked>li:first-child>a,.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.tabs-below>.nav-tabs>li>a,.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-pills>li>a{color:#000;background-color:#dfdfdf;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-pills>li>a:hover{color:#fff;background-color:#000}.nav-pills>.disabled>a,.nav-pills>.disabled>a:hover{color:#999;background-color:#eee}.nav-list>li>a{color:#080808}.nav-list>li>a:hover{color:#fff;text-shadow:none;background-color:#007fff}.nav-list .nav-header{color:#080808}.nav-list .divider{background-color:#bbb;border-bottom:0}.pagination ul{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.pagination ul>li>a,.pagination ul>li>span{margin-right:6px;color:#080808}.pagination ul>li>a:hover,.pagination ul>li>span:hover{color:#fff;background-color:#080808}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{margin-right:0}.pagination ul>.active>a,.pagination ul>.active>span{color:#fff}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999;background-color:#eee}.pager li>a,.pager li>span{color:#080808;background-color:#dfdfdf;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.pager li>a:hover,.pager li>span:hover{color:#fff;background-color:#080808}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>span{color:#999;background-color:#eee}.breadcrumb{background-color:#dfdfdf}.breadcrumb li{text-shadow:none}.breadcrumb .divider,.breadcrumb .active{color:#080808;text-shadow:none}.btn{padding:5px 12px;text-shadow:none;background-image:none;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn.disabled{box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-large{padding:22px 30px}.btn-small{padding:2px 10px}.btn-mini{padding:2px 6px}.btn-group>.btn:first-child,.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.dropdown-toggle{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.table tbody tr.success td{color:#fff}.table tbody tr.error td{color:#fff}.table tbody tr.info td{color:#fff}.table-bordered{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child,.table-bordered tfoot:last-child tr:last-child td:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"]{color:#080808}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#ff7518}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#080808;border-color:#ff7518}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#ff0039}.control-group.error input,.control-group.error select,.control-group.error textarea{color:#080808;border-color:#ff0039}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#3fb618}.control-group.success input,.control-group.success select,.control-group.success textarea{color:#080808;border-color:#3fb618}legend{color:#080808;border-bottom:0}.form-actions{background-color:#eee;border-top:0}.dropdown-menu{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert{text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert-heading,.alert h1,.alert h2,.alert h3,.alert h4,.alert h5,.alert h6{color:#fff}.label{min-width:80px;min-height:80px;font-weight:300;text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.label-success{background-color:#3fb618}.label-important{background-color:#ff0039}.label-info{background-color:#9954bb}.label-inverse{background-color:#000}.badge{font-weight:300;text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.badge-success{background-color:#3fb618}.badge-important{background-color:#ff0039}.badge-info{background-color:#9954bb}.badge-inverse{background-color:#000}.hero-unit{border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.well{border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}[class^="icon-"],[class*=" icon-"]{margin:0 2px;vertical-align:-2px}a.thumbnail{background-color:#dfdfdf}a.thumbnail:hover{background-color:#bbb;border-color:transparent}.progress{height:6px;background-color:#eee;background-image:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.progress .bar{background-color:#007fff;background-image:none}.progress-info{background-color:#9954bb}.progress-success{background-color:#3fb618}.progress-warning{background-color:#ff7518}.progress-danger{background-color:#ff0039}.modal{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.modal-header{border-bottom:0}.modal-footer{background-color:transparent;border-top:0}.popover{color:#fff;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.popover-title{color:#fff;border-bottom:0}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.css b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.css index e9fbe1883f..b06e082ef1 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.css +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.css @@ -1,9 +1,9 @@ -/*! - * Bootstrap v2.3.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. +/*! + * Bootstrap v2.3.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{max-width:100%;width:auto\9;height:auto;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{text-shadow:none!important;color:#000!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:"";line-height:0}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574468085%;*margin-left:2.074468085106383%}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;content:"";line-height:0}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;content:"";line-height:0}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;*zoom:1;padding-left:5px;padding-right:5px}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;content:"";line-height:0}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;white-space:nowrap}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;vertical-align:middle}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-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);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;*margin-top:0;margin-top:1px \9;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;border:1px solid #ccc;background-color:#fff}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);cursor:not-allowed}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;content:"";line-height:0}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-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)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-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)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-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)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-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)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;content:"";line-height:0}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;vertical-align:middle;font-size:0;white-space:nowrap}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;vertical-align:middle}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";line-height:0}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;-moz-border-radius-bottomright:0;border-bottom-right-radius:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;margin-top:1px}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{background-position:-216px -120px;width:16px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{background-position:-384px -120px;width:16px}.icon-folder-open{background-position:-408px -120px;width:16px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-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);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{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)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;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)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:default}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;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}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#ccc;margin-top:5px;margin-right:-10px}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-left:20px;padding-right:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 12px;margin-bottom:0;font-size:14px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #ccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);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);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#04c;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{border-color:transparent;cursor:pointer;color:#08c;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*zoom:1;font-size:0;vertical-align:middle;white-space:nowrap;*margin-left:.3em}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{font-size:0;margin-top:10px;margin-bottom:10px}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,.125),inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,.125),inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 1px 0 0 rgba(255,255,255,.125),inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);*padding-top:5px;*padding-bottom:5px}.btn-group>.btn-mini+.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:2px;*padding-bottom:2px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{padding-left:12px;padding-right:12px;*padding-top:7px;*padding-bottom:7px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-left-width:5px;border-right-width:5px;border-top-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-left:0;margin-top:-1px}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847}.alert-success h4{color:#468847}.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-left:0;margin-bottom:20px;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";line-height:0}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{border-color:#ddd;z-index:2}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{border-top-color:#08c;border-bottom-color:#08c;margin-top:6px}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;content:"";line-height:0}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-bottom-color:transparent;border-top-color:#ddd}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;background-color:transparent;cursor:default}.navbar{overflow:visible;margin-bottom:20px;*position:relative;*z-index:2}.navbar-inner{min-height:40px;padding-left:20px;padding-right:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065);*zoom:1}.navbar-inner:before,.navbar-inner:after{display:table;content:"";line-height:0}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{float:left;display:block;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-left:1px solid #f2f2f2;border-right:1px solid #fff}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;content:"";line-height:0}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{margin-bottom:0;padding:4px 14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,.1);box-shadow:0 1px 10px rgba(0,0,0,.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,.1);box-shadow:0 -1px 10px rgba(0,0,0,.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{background-color:transparent;color:#333;text-decoration:none}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e5e5e5;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);position:absolute;top:-7px;left:9px}.navbar .nav>li>.dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;top:-6px;left:10px}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);border-bottom:0;bottom:-7px;top:auto}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{border-top:6px solid #fff;border-bottom:0;bottom:-6px;top:auto}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:#e5e5e5;color:#555}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{left:auto;right:0}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{left:auto;right:12px}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{left:auto;right:13px}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{left:auto;right:100%;margin-left:0;margin-right:-1px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0);border-color:#252525}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{background-color:transparent;color:#fff}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-left-color:#111;border-right-color:#222}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{background-color:#111;color:#fff}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1),0 1px 0 rgba(255,255,255,.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1),0 1px 0 rgba(255,255,255,.15);box-shadow:inset 0 1px 2px rgba(0,0,0,.1),0 1px 0 rgba(255,255,255,.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15);outline:0}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#040404;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #fff}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;background-color:transparent;cursor:default}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-top-left-radius:3px;-moz-border-radius-topleft:3px;border-top-left-radius:3px;-webkit-border-bottom-left-radius:3px;-moz-border-radius-bottomleft:3px;border-bottom-left-radius:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;-moz-border-radius-topright:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;-moz-border-radius-bottomright:3px;border-bottom-right-radius:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;list-style:none;text-align:center;*zoom:1}.pager:before,.pager:after{display:table;content:"";line-height:0}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;background-color:#fff;cursor:default}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-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);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:0}.modal.fade{-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out;top:-25%}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;overflow-y:auto;max-height:400px;padding:15px}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;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;*zoom:1}.modal-footer:before,.modal-footer:after{display:table;content:"";line-height:0}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#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);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right .arrow:after{left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom .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}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.popover.left .arrow:after{right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;content:"";line-height:0}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-left:9px;padding-right:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.progress .bar{width:0;height:100%;color:#fff;float:left;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,.15),inset 0 -1px 0 rgba(0,0,0,.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,.15),inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 1px 0 0 rgba(0,0,0,.15),inset 0 -1px 0 rgba(0,0,0,.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{overflow:hidden;width:100%;position:relative}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{left:auto;right:15px}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{color:#fff;line-height:20px}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.min.css b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.min.css index 31b833b3b2..c10c7f417f 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.min.css +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/bootstrap.min.css @@ -1,9 +1,9 @@ -/*! - * Bootstrap v2.3.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-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);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-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)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-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)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-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)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-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)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-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)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-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);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;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)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;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;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;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}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-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)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-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)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;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;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-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)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-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);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;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;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;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);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} +/*! + * Bootstrap v2.3.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-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);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-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)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-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)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-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)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-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)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-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)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-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);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;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)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;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;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;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}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-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)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-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)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;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;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-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)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-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);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;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;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;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);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/responsive.css b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/responsive.css index 75ae132a98..7cc4d69a2a 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/css/responsive.css +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/css/responsive.css @@ -1,9 +1,9 @@ -/*! - * Bootstrap Responsive v2.3.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. +/*! + * Bootstrap Responsive v2.3.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;content:"";line-height:0}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:"";line-height:0}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-left:20px;padding-right:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-left:-20px;margin-right:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;clear:none;width:auto;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{float:none;display:block;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;left:20px;right:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-left:10px;padding-right:10px}.media .pull-left,.media .pull-right{float:none;display:block;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;left:10px;right:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{margin-top:5px;padding:0}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;float:none;display:none;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{overflow:hidden;height:0}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-left:10px;padding-right:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/accordion.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/accordion.less index d6d505c608..d63523bc8c 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/accordion.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/accordion.less @@ -1,34 +1,34 @@ -// -// Accordion -// -------------------------------------------------- - - -// Parent container -.accordion { - margin-bottom: @baseLineHeight; -} - -// Group == heading + body -.accordion-group { - margin-bottom: 2px; - border: 1px solid #e5e5e5; - .border-radius(@baseBorderRadius); -} -.accordion-heading { - border-bottom: 0; -} -.accordion-heading .accordion-toggle { - display: block; - padding: 8px 15px; -} - -// General toggle styles -.accordion-toggle { - cursor: pointer; -} - -// Inner needs the styles because you can't animate properly with any styles on the element -.accordion-inner { - padding: 9px 15px; - border-top: 1px solid #e5e5e5; -} +// +// Accordion +// -------------------------------------------------- + + +// Parent container +.accordion { + margin-bottom: @baseLineHeight; +} + +// Group == heading + body +.accordion-group { + margin-bottom: 2px; + border: 1px solid #e5e5e5; + .border-radius(@baseBorderRadius); +} +.accordion-heading { + border-bottom: 0; +} +.accordion-heading .accordion-toggle { + display: block; + padding: 8px 15px; +} + +// General toggle styles +.accordion-toggle { + cursor: pointer; +} + +// Inner needs the styles because you can't animate properly with any styles on the element +.accordion-inner { + padding: 9px 15px; + border-top: 1px solid #e5e5e5; +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/alerts.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/alerts.less index 56e4785fe4..0116b191b3 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/alerts.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/alerts.less @@ -1,79 +1,79 @@ -// -// Alerts -// -------------------------------------------------- - - -// Base styles -// ------------------------- - -.alert { - padding: 8px 35px 8px 14px; - margin-bottom: @baseLineHeight; - text-shadow: 0 1px 0 rgba(255,255,255,.5); - background-color: @warningBackground; - border: 1px solid @warningBorder; - .border-radius(@baseBorderRadius); -} -.alert, -.alert h4 { - // Specified for the h4 to prevent conflicts of changing @headingsColor - color: @warningText; -} -.alert h4 { - margin: 0; -} - -// Adjust close link position -.alert .close { - position: relative; - top: -2px; - right: -21px; - line-height: @baseLineHeight; -} - - -// Alternate styles -// ------------------------- - -.alert-success { - background-color: @successBackground; - border-color: @successBorder; - color: @successText; -} -.alert-success h4 { - color: @successText; -} -.alert-danger, -.alert-error { - background-color: @errorBackground; - border-color: @errorBorder; - color: @errorText; -} -.alert-danger h4, -.alert-error h4 { - color: @errorText; -} -.alert-info { - background-color: @infoBackground; - border-color: @infoBorder; - color: @infoText; -} -.alert-info h4 { - color: @infoText; -} - - -// Block alerts -// ------------------------- - -.alert-block { - padding-top: 14px; - padding-bottom: 14px; -} -.alert-block > p, -.alert-block > ul { - margin-bottom: 0; -} -.alert-block p + p { - margin-top: 5px; -} +// +// Alerts +// -------------------------------------------------- + + +// Base styles +// ------------------------- + +.alert { + padding: 8px 35px 8px 14px; + margin-bottom: @baseLineHeight; + text-shadow: 0 1px 0 rgba(255,255,255,.5); + background-color: @warningBackground; + border: 1px solid @warningBorder; + .border-radius(@baseBorderRadius); +} +.alert, +.alert h4 { + // Specified for the h4 to prevent conflicts of changing @headingsColor + color: @warningText; +} +.alert h4 { + margin: 0; +} + +// Adjust close link position +.alert .close { + position: relative; + top: -2px; + right: -21px; + line-height: @baseLineHeight; +} + + +// Alternate styles +// ------------------------- + +.alert-success { + background-color: @successBackground; + border-color: @successBorder; + color: @successText; +} +.alert-success h4 { + color: @successText; +} +.alert-danger, +.alert-error { + background-color: @errorBackground; + border-color: @errorBorder; + color: @errorText; +} +.alert-danger h4, +.alert-error h4 { + color: @errorText; +} +.alert-info { + background-color: @infoBackground; + border-color: @infoBorder; + color: @infoText; +} +.alert-info h4 { + color: @infoText; +} + + +// Block alerts +// ------------------------- + +.alert-block { + padding-top: 14px; + padding-bottom: 14px; +} +.alert-block > p, +.alert-block > ul { + margin-bottom: 0; +} +.alert-block p + p { + margin-top: 5px; +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/bootstrap.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/bootstrap.less index 8faf1ab83e..b56327adce 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/bootstrap.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/bootstrap.less @@ -1,63 +1,63 @@ -/*! - * Bootstrap v2.3.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */ - -// Core variables and mixins -@import "variables.less"; // Modify this for custom colors, font-sizes, etc -@import "mixins.less"; - -// CSS Reset -@import "reset.less"; - -// Grid system and page structure -@import "scaffolding.less"; -@import "grid.less"; -@import "layouts.less"; - -// Base CSS -@import "type.less"; -@import "code.less"; -@import "forms.less"; -@import "tables.less"; - -// Components: common -@import "sprites.less"; -@import "dropdowns.less"; -@import "wells.less"; -@import "component-animations.less"; -@import "close.less"; - -// Components: Buttons & Alerts -@import "buttons.less"; -@import "button-groups.less"; -@import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less - -// Components: Nav -@import "navs.less"; -@import "navbar.less"; -@import "breadcrumbs.less"; -@import "pagination.less"; -@import "pager.less"; - -// Components: Popovers -@import "modals.less"; -@import "tooltip.less"; -@import "popovers.less"; - -// Components: Misc -@import "thumbnails.less"; -@import "media.less"; -@import "labels-badges.less"; -@import "progress-bars.less"; -@import "accordion.less"; -@import "carousel.less"; -@import "hero-unit.less"; - -// Utility classes -@import "utilities.less"; // Has to be last to override when necessary +/*! + * Bootstrap v2.3.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + +// Core variables and mixins +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "mixins.less"; + +// CSS Reset +@import "reset.less"; + +// Grid system and page structure +@import "scaffolding.less"; +@import "grid.less"; +@import "layouts.less"; + +// Base CSS +@import "type.less"; +@import "code.less"; +@import "forms.less"; +@import "tables.less"; + +// Components: common +@import "sprites.less"; +@import "dropdowns.less"; +@import "wells.less"; +@import "component-animations.less"; +@import "close.less"; + +// Components: Buttons & Alerts +@import "buttons.less"; +@import "button-groups.less"; +@import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less + +// Components: Nav +@import "navs.less"; +@import "navbar.less"; +@import "breadcrumbs.less"; +@import "pagination.less"; +@import "pager.less"; + +// Components: Popovers +@import "modals.less"; +@import "tooltip.less"; +@import "popovers.less"; + +// Components: Misc +@import "thumbnails.less"; +@import "media.less"; +@import "labels-badges.less"; +@import "progress-bars.less"; +@import "accordion.less"; +@import "carousel.less"; +@import "hero-unit.less"; + +// Utility classes +@import "utilities.less"; // Has to be last to override when necessary diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/breadcrumbs.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/breadcrumbs.less index 4364c06e0a..f753df6be8 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/breadcrumbs.less @@ -1,24 +1,24 @@ -// -// Breadcrumbs -// -------------------------------------------------- - - -.breadcrumb { - padding: 8px 15px; - margin: 0 0 @baseLineHeight; - list-style: none; - background-color: #f5f5f5; - .border-radius(@baseBorderRadius); - > li { - display: inline-block; - .ie7-inline-block(); - text-shadow: 0 1px 0 @white; - > .divider { - padding: 0 5px; - color: #ccc; - } - } - > .active { - color: @grayLight; - } -} +// +// Breadcrumbs +// -------------------------------------------------- + + +.breadcrumb { + padding: 8px 15px; + margin: 0 0 @baseLineHeight; + list-style: none; + background-color: #f5f5f5; + .border-radius(@baseBorderRadius); + > li { + display: inline-block; + .ie7-inline-block(); + text-shadow: 0 1px 0 @white; + > .divider { + padding: 0 5px; + color: #ccc; + } + } + > .active { + color: @grayLight; + } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/button-groups.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/button-groups.less index 5c87069987..55cdc60338 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/button-groups.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/button-groups.less @@ -1,229 +1,229 @@ -// -// Button groups -// -------------------------------------------------- - - -// Make the div behave like a button -.btn-group { - position: relative; - display: inline-block; - .ie7-inline-block(); - font-size: 0; // remove as part 1 of font-size inline-block hack - vertical-align: middle; // match .btn alignment given font-size hack above - white-space: nowrap; // prevent buttons from wrapping when in tight spaces (e.g., the table on the tests page) - .ie7-restore-left-whitespace(); -} - -// Space out series of button groups -.btn-group + .btn-group { - margin-left: 5px; -} - -// Optional: Group multiple button groups together for a toolbar -.btn-toolbar { - font-size: 0; // Hack to remove whitespace that results from using inline-block - margin-top: @baseLineHeight / 2; - margin-bottom: @baseLineHeight / 2; - > .btn + .btn, - > .btn-group + .btn, - > .btn + .btn-group { - margin-left: 5px; - } -} - -// Float them, remove border radius, then re-add to first and last elements -.btn-group > .btn { - position: relative; - .border-radius(0); -} -.btn-group > .btn + .btn { - margin-left: -1px; -} -.btn-group > .btn, -.btn-group > .dropdown-menu, -.btn-group > .popover { - font-size: @baseFontSize; // redeclare as part 2 of font-size inline-block hack -} - -// Reset fonts for other sizes -.btn-group > .btn-mini { - font-size: @fontSizeMini; -} -.btn-group > .btn-small { - font-size: @fontSizeSmall; -} -.btn-group > .btn-large { - font-size: @fontSizeLarge; -} - -// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match -.btn-group > .btn:first-child { - margin-left: 0; - .border-top-left-radius(@baseBorderRadius); - .border-bottom-left-radius(@baseBorderRadius); -} -// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it -.btn-group > .btn:last-child, -.btn-group > .dropdown-toggle { - .border-top-right-radius(@baseBorderRadius); - .border-bottom-right-radius(@baseBorderRadius); -} -// Reset corners for large buttons -.btn-group > .btn.large:first-child { - margin-left: 0; - .border-top-left-radius(@borderRadiusLarge); - .border-bottom-left-radius(@borderRadiusLarge); -} -.btn-group > .btn.large:last-child, -.btn-group > .large.dropdown-toggle { - .border-top-right-radius(@borderRadiusLarge); - .border-bottom-right-radius(@borderRadiusLarge); -} - -// On hover/focus/active, bring the proper btn to front -.btn-group > .btn:hover, -.btn-group > .btn:focus, -.btn-group > .btn:active, -.btn-group > .btn.active { - z-index: 2; -} - -// On active and open, don't show outline -.btn-group .dropdown-toggle:active, -.btn-group.open .dropdown-toggle { - outline: 0; -} - - - -// Split button dropdowns -// ---------------------- - -// Give the line between buttons some depth -.btn-group > .btn + .dropdown-toggle { - padding-left: 8px; - padding-right: 8px; - .box-shadow(~"inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); - *padding-top: 5px; - *padding-bottom: 5px; -} -.btn-group > .btn-mini + .dropdown-toggle { - padding-left: 5px; - padding-right: 5px; - *padding-top: 2px; - *padding-bottom: 2px; -} -.btn-group > .btn-small + .dropdown-toggle { - *padding-top: 5px; - *padding-bottom: 4px; -} -.btn-group > .btn-large + .dropdown-toggle { - padding-left: 12px; - padding-right: 12px; - *padding-top: 7px; - *padding-bottom: 7px; -} - -.btn-group.open { - - // The clickable button for toggling the menu - // Remove the gradient and set the same inset shadow as the :active state - .dropdown-toggle { - background-image: none; - .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); - } - - // Keep the hover's background when dropdown is open - .btn.dropdown-toggle { - background-color: @btnBackgroundHighlight; - } - .btn-primary.dropdown-toggle { - background-color: @btnPrimaryBackgroundHighlight; - } - .btn-warning.dropdown-toggle { - background-color: @btnWarningBackgroundHighlight; - } - .btn-danger.dropdown-toggle { - background-color: @btnDangerBackgroundHighlight; - } - .btn-success.dropdown-toggle { - background-color: @btnSuccessBackgroundHighlight; - } - .btn-info.dropdown-toggle { - background-color: @btnInfoBackgroundHighlight; - } - .btn-inverse.dropdown-toggle { - background-color: @btnInverseBackgroundHighlight; - } -} - - -// Reposition the caret -.btn .caret { - margin-top: 8px; - margin-left: 0; -} -// Carets in other button sizes -.btn-large .caret { - margin-top: 6px; -} -.btn-large .caret { - border-left-width: 5px; - border-right-width: 5px; - border-top-width: 5px; -} -.btn-mini .caret, -.btn-small .caret { - margin-top: 8px; -} -// Upside down carets for .dropup -.dropup .btn-large .caret { - border-bottom-width: 5px; -} - - - -// Account for other colors -.btn-primary, -.btn-warning, -.btn-danger, -.btn-info, -.btn-success, -.btn-inverse { - .caret { - border-top-color: @white; - border-bottom-color: @white; - } -} - - - -// Vertical button groups -// ---------------------- - -.btn-group-vertical { - display: inline-block; // makes buttons only take up the width they need - .ie7-inline-block(); -} -.btn-group-vertical > .btn { - display: block; - float: none; - max-width: 100%; - .border-radius(0); -} -.btn-group-vertical > .btn + .btn { - margin-left: 0; - margin-top: -1px; -} -.btn-group-vertical > .btn:first-child { - .border-radius(@baseBorderRadius @baseBorderRadius 0 0); -} -.btn-group-vertical > .btn:last-child { - .border-radius(0 0 @baseBorderRadius @baseBorderRadius); -} -.btn-group-vertical > .btn-large:first-child { - .border-radius(@borderRadiusLarge @borderRadiusLarge 0 0); -} -.btn-group-vertical > .btn-large:last-child { - .border-radius(0 0 @borderRadiusLarge @borderRadiusLarge); -} +// +// Button groups +// -------------------------------------------------- + + +// Make the div behave like a button +.btn-group { + position: relative; + display: inline-block; + .ie7-inline-block(); + font-size: 0; // remove as part 1 of font-size inline-block hack + vertical-align: middle; // match .btn alignment given font-size hack above + white-space: nowrap; // prevent buttons from wrapping when in tight spaces (e.g., the table on the tests page) + .ie7-restore-left-whitespace(); +} + +// Space out series of button groups +.btn-group + .btn-group { + margin-left: 5px; +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + font-size: 0; // Hack to remove whitespace that results from using inline-block + margin-top: @baseLineHeight / 2; + margin-bottom: @baseLineHeight / 2; + > .btn + .btn, + > .btn-group + .btn, + > .btn + .btn-group { + margin-left: 5px; + } +} + +// Float them, remove border radius, then re-add to first and last elements +.btn-group > .btn { + position: relative; + .border-radius(0); +} +.btn-group > .btn + .btn { + margin-left: -1px; +} +.btn-group > .btn, +.btn-group > .dropdown-menu, +.btn-group > .popover { + font-size: @baseFontSize; // redeclare as part 2 of font-size inline-block hack +} + +// Reset fonts for other sizes +.btn-group > .btn-mini { + font-size: @fontSizeMini; +} +.btn-group > .btn-small { + font-size: @fontSizeSmall; +} +.btn-group > .btn-large { + font-size: @fontSizeLarge; +} + +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + .border-top-left-radius(@baseBorderRadius); + .border-bottom-left-radius(@baseBorderRadius); +} +// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +.btn-group > .btn:last-child, +.btn-group > .dropdown-toggle { + .border-top-right-radius(@baseBorderRadius); + .border-bottom-right-radius(@baseBorderRadius); +} +// Reset corners for large buttons +.btn-group > .btn.large:first-child { + margin-left: 0; + .border-top-left-radius(@borderRadiusLarge); + .border-bottom-left-radius(@borderRadiusLarge); +} +.btn-group > .btn.large:last-child, +.btn-group > .large.dropdown-toggle { + .border-top-right-radius(@borderRadiusLarge); + .border-bottom-right-radius(@borderRadiusLarge); +} + +// On hover/focus/active, bring the proper btn to front +.btn-group > .btn:hover, +.btn-group > .btn:focus, +.btn-group > .btn:active, +.btn-group > .btn.active { + z-index: 2; +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + + + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; + .box-shadow(~"inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); + *padding-top: 5px; + *padding-bottom: 5px; +} +.btn-group > .btn-mini + .dropdown-toggle { + padding-left: 5px; + padding-right: 5px; + *padding-top: 2px; + *padding-bottom: 2px; +} +.btn-group > .btn-small + .dropdown-toggle { + *padding-top: 5px; + *padding-bottom: 4px; +} +.btn-group > .btn-large + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; + *padding-top: 7px; + *padding-bottom: 7px; +} + +.btn-group.open { + + // The clickable button for toggling the menu + // Remove the gradient and set the same inset shadow as the :active state + .dropdown-toggle { + background-image: none; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); + } + + // Keep the hover's background when dropdown is open + .btn.dropdown-toggle { + background-color: @btnBackgroundHighlight; + } + .btn-primary.dropdown-toggle { + background-color: @btnPrimaryBackgroundHighlight; + } + .btn-warning.dropdown-toggle { + background-color: @btnWarningBackgroundHighlight; + } + .btn-danger.dropdown-toggle { + background-color: @btnDangerBackgroundHighlight; + } + .btn-success.dropdown-toggle { + background-color: @btnSuccessBackgroundHighlight; + } + .btn-info.dropdown-toggle { + background-color: @btnInfoBackgroundHighlight; + } + .btn-inverse.dropdown-toggle { + background-color: @btnInverseBackgroundHighlight; + } +} + + +// Reposition the caret +.btn .caret { + margin-top: 8px; + margin-left: 0; +} +// Carets in other button sizes +.btn-large .caret { + margin-top: 6px; +} +.btn-large .caret { + border-left-width: 5px; + border-right-width: 5px; + border-top-width: 5px; +} +.btn-mini .caret, +.btn-small .caret { + margin-top: 8px; +} +// Upside down carets for .dropup +.dropup .btn-large .caret { + border-bottom-width: 5px; +} + + + +// Account for other colors +.btn-primary, +.btn-warning, +.btn-danger, +.btn-info, +.btn-success, +.btn-inverse { + .caret { + border-top-color: @white; + border-bottom-color: @white; + } +} + + + +// Vertical button groups +// ---------------------- + +.btn-group-vertical { + display: inline-block; // makes buttons only take up the width they need + .ie7-inline-block(); +} +.btn-group-vertical > .btn { + display: block; + float: none; + max-width: 100%; + .border-radius(0); +} +.btn-group-vertical > .btn + .btn { + margin-left: 0; + margin-top: -1px; +} +.btn-group-vertical > .btn:first-child { + .border-radius(@baseBorderRadius @baseBorderRadius 0 0); +} +.btn-group-vertical > .btn:last-child { + .border-radius(0 0 @baseBorderRadius @baseBorderRadius); +} +.btn-group-vertical > .btn-large:first-child { + .border-radius(@borderRadiusLarge @borderRadiusLarge 0 0); +} +.btn-group-vertical > .btn-large:last-child { + .border-radius(0 0 @borderRadiusLarge @borderRadiusLarge); +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/buttons.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/buttons.less index 9a5f2c712b..4cd4d862b3 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/buttons.less @@ -1,228 +1,228 @@ -// -// Buttons -// -------------------------------------------------- - - -// Base styles -// -------------------------------------------------- - -// Core -.btn { - display: inline-block; - .ie7-inline-block(); - padding: 4px 12px; - margin-bottom: 0; // For input.btn - font-size: @baseFontSize; - line-height: @baseLineHeight; - text-align: center; - vertical-align: middle; - cursor: pointer; - .buttonBackground(@btnBackground, @btnBackgroundHighlight, @grayDark, 0 1px 1px rgba(255,255,255,.75)); - border: 1px solid @btnBorder; - *border: 0; // Remove the border to prevent IE7's black border on input:focus - border-bottom-color: darken(@btnBorder, 10%); - .border-radius(@baseBorderRadius); - .ie7-restore-left-whitespace(); // Give IE7 some love - .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); - - // Hover/focus state - &:hover, - &:focus { - color: @grayDark; - text-decoration: none; - background-position: 0 -15px; - - // transition is only when going to hover/focus, otherwise the background - // behind the gradient (there for IE<=9 fallback) gets mismatched - .transition(background-position .1s linear); - } - - // Focus state for keyboard and accessibility - &:focus { - .tab-focus(); - } - - // Active state - &.active, - &:active { - background-image: none; - outline: 0; - .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); - } - - // Disabled state - &.disabled, - &[disabled] { - cursor: default; - background-image: none; - .opacity(65); - .box-shadow(none); - } - -} - - - -// Button Sizes -// -------------------------------------------------- - -// Large -.btn-large { - padding: @paddingLarge; - font-size: @fontSizeLarge; - .border-radius(@borderRadiusLarge); -} -.btn-large [class^="icon-"], -.btn-large [class*=" icon-"] { - margin-top: 4px; -} - -// Small -.btn-small { - padding: @paddingSmall; - font-size: @fontSizeSmall; - .border-radius(@borderRadiusSmall); -} -.btn-small [class^="icon-"], -.btn-small [class*=" icon-"] { - margin-top: 0; -} -.btn-mini [class^="icon-"], -.btn-mini [class*=" icon-"] { - margin-top: -1px; -} - -// Mini -.btn-mini { - padding: @paddingMini; - font-size: @fontSizeMini; - .border-radius(@borderRadiusSmall); -} - - -// Block button -// ------------------------- - -.btn-block { - display: block; - width: 100%; - padding-left: 0; - padding-right: 0; - .box-sizing(border-box); -} - -// Vertically space out multiple block buttons -.btn-block + .btn-block { - margin-top: 5px; -} - -// Specificity overrides -input[type="submit"], -input[type="reset"], -input[type="button"] { - &.btn-block { - width: 100%; - } -} - - - -// Alternate buttons -// -------------------------------------------------- - -// Provide *some* extra contrast for those who can get it -.btn-primary.active, -.btn-warning.active, -.btn-danger.active, -.btn-success.active, -.btn-info.active, -.btn-inverse.active { - color: rgba(255,255,255,.75); -} - -// Set the backgrounds -// ------------------------- -.btn-primary { - .buttonBackground(@btnPrimaryBackground, @btnPrimaryBackgroundHighlight); -} -// Warning appears are orange -.btn-warning { - .buttonBackground(@btnWarningBackground, @btnWarningBackgroundHighlight); -} -// Danger and error appear as red -.btn-danger { - .buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight); -} -// Success appears as green -.btn-success { - .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight); -} -// Info appears as a neutral blue -.btn-info { - .buttonBackground(@btnInfoBackground, @btnInfoBackgroundHighlight); -} -// Inverse appears as dark gray -.btn-inverse { - .buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight); -} - - -// Cross-browser Jank -// -------------------------------------------------- - -button.btn, -input[type="submit"].btn { - - // Firefox 3.6 only I believe - &::-moz-focus-inner { - padding: 0; - border: 0; - } - - // IE7 has some default padding on button controls - *padding-top: 3px; - *padding-bottom: 3px; - - &.btn-large { - *padding-top: 7px; - *padding-bottom: 7px; - } - &.btn-small { - *padding-top: 3px; - *padding-bottom: 3px; - } - &.btn-mini { - *padding-top: 1px; - *padding-bottom: 1px; - } -} - - -// Link buttons -// -------------------------------------------------- - -// Make a button look and behave like a link -.btn-link, -.btn-link:active, -.btn-link[disabled] { - background-color: transparent; - background-image: none; - .box-shadow(none); -} -.btn-link { - border-color: transparent; - cursor: pointer; - color: @linkColor; - .border-radius(0); -} -.btn-link:hover, -.btn-link:focus { - color: @linkColorHover; - text-decoration: underline; - background-color: transparent; -} -.btn-link[disabled]:hover, -.btn-link[disabled]:focus { - color: @grayDark; - text-decoration: none; -} +// +// Buttons +// -------------------------------------------------- + + +// Base styles +// -------------------------------------------------- + +// Core +.btn { + display: inline-block; + .ie7-inline-block(); + padding: 4px 12px; + margin-bottom: 0; // For input.btn + font-size: @baseFontSize; + line-height: @baseLineHeight; + text-align: center; + vertical-align: middle; + cursor: pointer; + .buttonBackground(@btnBackground, @btnBackgroundHighlight, @grayDark, 0 1px 1px rgba(255,255,255,.75)); + border: 1px solid @btnBorder; + *border: 0; // Remove the border to prevent IE7's black border on input:focus + border-bottom-color: darken(@btnBorder, 10%); + .border-radius(@baseBorderRadius); + .ie7-restore-left-whitespace(); // Give IE7 some love + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); + + // Hover/focus state + &:hover, + &:focus { + color: @grayDark; + text-decoration: none; + background-position: 0 -15px; + + // transition is only when going to hover/focus, otherwise the background + // behind the gradient (there for IE<=9 fallback) gets mismatched + .transition(background-position .1s linear); + } + + // Focus state for keyboard and accessibility + &:focus { + .tab-focus(); + } + + // Active state + &.active, + &:active { + background-image: none; + outline: 0; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); + } + + // Disabled state + &.disabled, + &[disabled] { + cursor: default; + background-image: none; + .opacity(65); + .box-shadow(none); + } + +} + + + +// Button Sizes +// -------------------------------------------------- + +// Large +.btn-large { + padding: @paddingLarge; + font-size: @fontSizeLarge; + .border-radius(@borderRadiusLarge); +} +.btn-large [class^="icon-"], +.btn-large [class*=" icon-"] { + margin-top: 4px; +} + +// Small +.btn-small { + padding: @paddingSmall; + font-size: @fontSizeSmall; + .border-radius(@borderRadiusSmall); +} +.btn-small [class^="icon-"], +.btn-small [class*=" icon-"] { + margin-top: 0; +} +.btn-mini [class^="icon-"], +.btn-mini [class*=" icon-"] { + margin-top: -1px; +} + +// Mini +.btn-mini { + padding: @paddingMini; + font-size: @fontSizeMini; + .border-radius(@borderRadiusSmall); +} + + +// Block button +// ------------------------- + +.btn-block { + display: block; + width: 100%; + padding-left: 0; + padding-right: 0; + .box-sizing(border-box); +} + +// Vertically space out multiple block buttons +.btn-block + .btn-block { + margin-top: 5px; +} + +// Specificity overrides +input[type="submit"], +input[type="reset"], +input[type="button"] { + &.btn-block { + width: 100%; + } +} + + + +// Alternate buttons +// -------------------------------------------------- + +// Provide *some* extra contrast for those who can get it +.btn-primary.active, +.btn-warning.active, +.btn-danger.active, +.btn-success.active, +.btn-info.active, +.btn-inverse.active { + color: rgba(255,255,255,.75); +} + +// Set the backgrounds +// ------------------------- +.btn-primary { + .buttonBackground(@btnPrimaryBackground, @btnPrimaryBackgroundHighlight); +} +// Warning appears are orange +.btn-warning { + .buttonBackground(@btnWarningBackground, @btnWarningBackgroundHighlight); +} +// Danger and error appear as red +.btn-danger { + .buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight); +} +// Success appears as green +.btn-success { + .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight); +} +// Info appears as a neutral blue +.btn-info { + .buttonBackground(@btnInfoBackground, @btnInfoBackgroundHighlight); +} +// Inverse appears as dark gray +.btn-inverse { + .buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight); +} + + +// Cross-browser Jank +// -------------------------------------------------- + +button.btn, +input[type="submit"].btn { + + // Firefox 3.6 only I believe + &::-moz-focus-inner { + padding: 0; + border: 0; + } + + // IE7 has some default padding on button controls + *padding-top: 3px; + *padding-bottom: 3px; + + &.btn-large { + *padding-top: 7px; + *padding-bottom: 7px; + } + &.btn-small { + *padding-top: 3px; + *padding-bottom: 3px; + } + &.btn-mini { + *padding-top: 1px; + *padding-bottom: 1px; + } +} + + +// Link buttons +// -------------------------------------------------- + +// Make a button look and behave like a link +.btn-link, +.btn-link:active, +.btn-link[disabled] { + background-color: transparent; + background-image: none; + .box-shadow(none); +} +.btn-link { + border-color: transparent; + cursor: pointer; + color: @linkColor; + .border-radius(0); +} +.btn-link:hover, +.btn-link:focus { + color: @linkColorHover; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +.btn-link[disabled]:focus { + color: @grayDark; + text-decoration: none; +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/carousel.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/carousel.less index 9db4e3be0c..55bc050144 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/carousel.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/carousel.less @@ -1,158 +1,158 @@ -// -// Carousel -// -------------------------------------------------- - - -.carousel { - position: relative; - margin-bottom: @baseLineHeight; - line-height: 1; -} - -.carousel-inner { - overflow: hidden; - width: 100%; - position: relative; -} - -.carousel-inner { - - > .item { - display: none; - position: relative; - .transition(.6s ease-in-out left); - - // Account for jankitude on images - > img, - > a > img { - display: block; - line-height: 1; - } - } - - > .active, - > .next, - > .prev { display: block; } - - > .active { - left: 0; - } - - > .next, - > .prev { - position: absolute; - top: 0; - width: 100%; - } - - > .next { - left: 100%; - } - > .prev { - left: -100%; - } - > .next.left, - > .prev.right { - left: 0; - } - - > .active.left { - left: -100%; - } - > .active.right { - left: 100%; - } - -} - -// Left/right controls for nav -// --------------------------- - -.carousel-control { - position: absolute; - top: 40%; - left: 15px; - width: 40px; - height: 40px; - margin-top: -20px; - font-size: 60px; - font-weight: 100; - line-height: 30px; - color: @white; - text-align: center; - background: @grayDarker; - border: 3px solid @white; - .border-radius(23px); - .opacity(50); - - // we can't have this transition here - // because webkit cancels the carousel - // animation if you trip this while - // in the middle of another animation - // ;_; - // .transition(opacity .2s linear); - - // Reposition the right one - &.right { - left: auto; - right: 15px; - } - - // Hover/focus state - &:hover, - &:focus { - color: @white; - text-decoration: none; - .opacity(90); - } -} - -// Carousel indicator pips -// ----------------------------- -.carousel-indicators { - position: absolute; - top: 15px; - right: 15px; - z-index: 5; - margin: 0; - list-style: none; - - li { - display: block; - float: left; - width: 10px; - height: 10px; - margin-left: 5px; - text-indent: -999px; - background-color: #ccc; - background-color: rgba(255,255,255,.25); - border-radius: 5px; - } - .active { - background-color: #fff; - } -} - -// Caption for text below images -// ----------------------------- - -.carousel-caption { - position: absolute; - left: 0; - right: 0; - bottom: 0; - padding: 15px; - background: @grayDark; - background: rgba(0,0,0,.75); -} -.carousel-caption h4, -.carousel-caption p { - color: @white; - line-height: @baseLineHeight; -} -.carousel-caption h4 { - margin: 0 0 5px; -} -.carousel-caption p { - margin-bottom: 0; -} +// +// Carousel +// -------------------------------------------------- + + +.carousel { + position: relative; + margin-bottom: @baseLineHeight; + line-height: 1; +} + +.carousel-inner { + overflow: hidden; + width: 100%; + position: relative; +} + +.carousel-inner { + + > .item { + display: none; + position: relative; + .transition(.6s ease-in-out left); + + // Account for jankitude on images + > img, + > a > img { + display: block; + line-height: 1; + } + } + + > .active, + > .next, + > .prev { display: block; } + + > .active { + left: 0; + } + + > .next, + > .prev { + position: absolute; + top: 0; + width: 100%; + } + + > .next { + left: 100%; + } + > .prev { + left: -100%; + } + > .next.left, + > .prev.right { + left: 0; + } + + > .active.left { + left: -100%; + } + > .active.right { + left: 100%; + } + +} + +// Left/right controls for nav +// --------------------------- + +.carousel-control { + position: absolute; + top: 40%; + left: 15px; + width: 40px; + height: 40px; + margin-top: -20px; + font-size: 60px; + font-weight: 100; + line-height: 30px; + color: @white; + text-align: center; + background: @grayDarker; + border: 3px solid @white; + .border-radius(23px); + .opacity(50); + + // we can't have this transition here + // because webkit cancels the carousel + // animation if you trip this while + // in the middle of another animation + // ;_; + // .transition(opacity .2s linear); + + // Reposition the right one + &.right { + left: auto; + right: 15px; + } + + // Hover/focus state + &:hover, + &:focus { + color: @white; + text-decoration: none; + .opacity(90); + } +} + +// Carousel indicator pips +// ----------------------------- +.carousel-indicators { + position: absolute; + top: 15px; + right: 15px; + z-index: 5; + margin: 0; + list-style: none; + + li { + display: block; + float: left; + width: 10px; + height: 10px; + margin-left: 5px; + text-indent: -999px; + background-color: #ccc; + background-color: rgba(255,255,255,.25); + border-radius: 5px; + } + .active { + background-color: #fff; + } +} + +// Caption for text below images +// ----------------------------- + +.carousel-caption { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 15px; + background: @grayDark; + background: rgba(0,0,0,.75); +} +.carousel-caption h4, +.carousel-caption p { + color: @white; + line-height: @baseLineHeight; +} +.carousel-caption h4 { + margin: 0 0 5px; +} +.carousel-caption p { + margin-bottom: 0; +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/close.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/close.less index 75f968e955..4c626bda6c 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/close.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/close.less @@ -1,32 +1,32 @@ -// -// Close icons -// -------------------------------------------------- - - -.close { - float: right; - font-size: 20px; - font-weight: bold; - line-height: @baseLineHeight; - color: @black; - text-shadow: 0 1px 0 rgba(255,255,255,1); - .opacity(20); - &:hover, - &:focus { - color: @black; - text-decoration: none; - cursor: pointer; - .opacity(40); - } -} - -// Additional properties for button version -// iOS requires the button element instead of an anchor tag. -// If you want the anchor version, it requires `href="#"`. -button.close { - padding: 0; - cursor: pointer; - background: transparent; - border: 0; - -webkit-appearance: none; +// +// Close icons +// -------------------------------------------------- + + +.close { + float: right; + font-size: 20px; + font-weight: bold; + line-height: @baseLineHeight; + color: @black; + text-shadow: 0 1px 0 rgba(255,255,255,1); + .opacity(20); + &:hover, + &:focus { + color: @black; + text-decoration: none; + cursor: pointer; + .opacity(40); + } +} + +// Additional properties for button version +// iOS requires the button element instead of an anchor tag. +// If you want the anchor version, it requires `href="#"`. +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less index 6eea577e45..685523eb0a 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less @@ -1,61 +1,61 @@ -// -// Code (inline and blocK) -// -------------------------------------------------- - - -// Inline and block code styles -code, -pre.code { - padding: 0 3px 2px; - #font > #family > .monospace; - font-size: @baseFontSize - 2; - color: @grayDark; - .border-radius(3px); -} - -// Inline code -code { - padding: 2px 4px; - color: #d14; - background-color: #f7f7f9; - border: 1px solid #e1e1e8; - white-space: nowrap; -} - -// Blocks of code -pre.code { - display: block; - padding: (@baseLineHeight - 1) / 2; - margin: 0 0 @baseLineHeight / 2; - font-size: @baseFontSize - 1; // 14px to 13px - line-height: @baseLineHeight; - word-break: break-all; - word-wrap: break-word; - white-space: pre; - white-space: pre-wrap; - background-color: #f5f5f5; - border: 1px solid #ccc; // fallback for IE7-8 - border: 1px solid rgba(0,0,0,.15); - .border-radius(@baseBorderRadius); - - // Make prettyprint styles more spaced out for readability - &.prettyprint { - margin-bottom: @baseLineHeight; - } - - // Account for some code outputs that place code tags in pre tags - code { - padding: 0; - color: inherit; - white-space: pre; - white-space: pre-wrap; - background-color: transparent; - border: 0; - } -} - -// Enable scrollable blocks of code -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; +// +// Code (inline and blocK) +// -------------------------------------------------- + + +// Inline and block code styles +code, +pre.code { + padding: 0 3px 2px; + #font > #family > .monospace; + font-size: @baseFontSize - 2; + color: @grayDark; + .border-radius(3px); +} + +// Inline code +code { + padding: 2px 4px; + color: #d14; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + white-space: nowrap; +} + +// Blocks of code +pre.code { + display: block; + padding: (@baseLineHeight - 1) / 2; + margin: 0 0 @baseLineHeight / 2; + font-size: @baseFontSize - 1; // 14px to 13px + line-height: @baseLineHeight; + word-break: break-all; + word-wrap: break-word; + white-space: pre; + white-space: pre-wrap; + background-color: #f5f5f5; + border: 1px solid #ccc; // fallback for IE7-8 + border: 1px solid rgba(0,0,0,.15); + .border-radius(@baseBorderRadius); + + // Make prettyprint styles more spaced out for readability + &.prettyprint { + margin-bottom: @baseLineHeight; + } + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + color: inherit; + white-space: pre; + white-space: pre-wrap; + background-color: transparent; + border: 0; + } +} + +// Enable scrollable blocks of code +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/component-animations.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/component-animations.less index 3a93adb012..d614263a76 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/component-animations.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/component-animations.less @@ -1,22 +1,22 @@ -// -// Component animations -// -------------------------------------------------- - - -.fade { - opacity: 0; - .transition(opacity .15s linear); - &.in { - opacity: 1; - } -} - -.collapse { - position: relative; - height: 0; - overflow: hidden; - .transition(height .35s ease); - &.in { - height: auto; - } -} +// +// Component animations +// -------------------------------------------------- + + +.fade { + opacity: 0; + .transition(opacity .15s linear); + &.in { + opacity: 1; + } +} + +.collapse { + position: relative; + height: 0; + overflow: hidden; + .transition(height .35s ease); + &.in { + height: auto; + } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less index 199f0a5c01..bbfe3fd3e3 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less @@ -1,237 +1,237 @@ -// -// Dropdown menus -// -------------------------------------------------- - - -// Use the .menu class on any
  • element within the topbar or ul.tabs and you'll get some superfancy dropdowns -.dropup, -.dropdown { - position: relative; -} -.dropdown-toggle { - // The caret makes the toggle a bit too tall in IE7 - *margin-bottom: -3px; -} -.dropdown-toggle:active, -.open .dropdown-toggle { - outline: 0; -} - -// Dropdown arrow/caret -// -------------------- -.caret { - display: inline-block; - width: 0; - height: 0; - vertical-align: top; - border-top: 4px solid @black; - border-right: 4px solid transparent; - border-left: 4px solid transparent; - content: ""; -} - -// Place the caret -.dropdown .caret { - margin-top: 8px; - margin-left: 2px; -} - -// The dropdown menu (ul) -// ---------------------- -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: @zindexDropdown; - display: none; // none by default, but block on "open" of the menu - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; // override default ul - list-style: none; - background-color: @dropdownBackground; - border: 1px solid #ccc; // Fallback for IE7-8 - border: 1px solid @dropdownBorder; - *border-right-width: 2px; - *border-bottom-width: 2px; - .border-radius(6px); - .box-shadow(0 5px 10px rgba(0,0,0,.2)); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; - - // Aligns the dropdown menu to right - &.pull-right { - right: 0; - left: auto; - } - - // Dividers (basically an hr) within the dropdown - .divider { - .nav-divider(@dropdownDividerTop, @dropdownDividerBottom); - } - - // Links within the dropdown menu - > li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: @baseLineHeight; - color: @dropdownLinkColor; - white-space: nowrap; - } -} - -// Hover/Focus state -// ----------- -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus, -.dropdown-submenu:hover > a, -.dropdown-submenu:focus > a { - text-decoration: none; - color: @dropdownLinkColorHover; - #gradient > .vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%)); -} - -// Active state -// ------------ -.dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { - color: @dropdownLinkColorActive; - text-decoration: none; - outline: 0; - #gradient > .vertical(@dropdownLinkBackgroundActive, darken(@dropdownLinkBackgroundActive, 5%)); -} - -// Disabled state -// -------------- -// Gray out text and ensure the hover/focus state remains gray -.dropdown-menu > .disabled > a, -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - color: @grayLight; -} -// Nuke hover/focus effects -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - text-decoration: none; - background-color: transparent; - background-image: none; // Remove CSS gradient - .reset-filter(); - cursor: default; -} - -// Open state for the dropdown -// --------------------------- -.open { - // IE7's z-index only goes to the nearest positioned ancestor, which would - // make the menu appear below buttons that appeared later on the page - *z-index: @zindexDropdown; - - & > .dropdown-menu { - display: block; - } -} - -// Right aligned dropdowns -// --------------------------- -.pull-right > .dropdown-menu { - right: 0; - left: auto; -} - -// Allow for dropdowns to go bottom up (aka, dropup-menu) -// ------------------------------------------------------ -// Just add .dropup after the standard .dropdown class and you're set, bro. -// TODO: abstract this so that the navbar fixed styles are not placed here? -.dropup, -.navbar-fixed-bottom .dropdown { - // Reverse the caret - .caret { - border-top: 0; - border-bottom: 4px solid @black; - content: ""; - } - // Different positioning for bottom up menu - .dropdown-menu { - top: auto; - bottom: 100%; - margin-bottom: 1px; - } -} - -// Sub menus -// --------------------------- -.dropdown-submenu { - position: relative; -} -// Default dropdowns -.dropdown-submenu > .dropdown-menu { - top: 0; - left: 100%; - margin-top: -6px; - margin-left: -1px; - .border-radius(0 6px 6px 6px); -} -.dropdown-submenu:hover > .dropdown-menu { - display: block; -} - -// Dropups -.dropup .dropdown-submenu > .dropdown-menu { - top: auto; - bottom: 0; - margin-top: 0; - margin-bottom: -2px; - .border-radius(5px 5px 5px 0); -} - -// Caret to indicate there is a submenu -.dropdown-submenu > a:after { - display: block; - content: " "; - float: right; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - border-width: 5px 0 5px 5px; - border-left-color: darken(@dropdownBackground, 20%); - margin-top: 5px; - margin-right: -10px; -} -.dropdown-submenu:hover > a:after { - border-left-color: @dropdownLinkColorHover; -} - -// Left aligned submenus -.dropdown-submenu.pull-left { - // Undo the float - // Yes, this is awkward since .pull-left adds a float, but it sticks to our conventions elsewhere. - float: none; - - // Positioning the submenu - > .dropdown-menu { - left: -100%; - margin-left: 10px; - .border-radius(6px 0 6px 6px); - } -} - -// Tweak nav headers -// ----------------- -// Increase padding from 15px to 20px on sides -.dropdown .dropdown-menu .nav-header { - padding-left: 20px; - padding-right: 20px; -} - -// Typeahead -// --------- -.typeahead { - z-index: 1051; - margin-top: 2px; // give it some space to breathe - .border-radius(@baseBorderRadius); -} +// +// Dropdown menus +// -------------------------------------------------- + + +// Use the .menu class on any
  • element within the topbar or ul.tabs and you'll get some superfancy dropdowns +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle { + // The caret makes the toggle a bit too tall in IE7 + *margin-bottom: -3px; +} +.dropdown-toggle:active, +.open .dropdown-toggle { + outline: 0; +} + +// Dropdown arrow/caret +// -------------------- +.caret { + display: inline-block; + width: 0; + height: 0; + vertical-align: top; + border-top: 4px solid @black; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + content: ""; +} + +// Place the caret +.dropdown .caret { + margin-top: 8px; + margin-left: 2px; +} + +// The dropdown menu (ul) +// ---------------------- +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: @zindexDropdown; + display: none; // none by default, but block on "open" of the menu + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; // override default ul + list-style: none; + background-color: @dropdownBackground; + border: 1px solid #ccc; // Fallback for IE7-8 + border: 1px solid @dropdownBorder; + *border-right-width: 2px; + *border-bottom-width: 2px; + .border-radius(6px); + .box-shadow(0 5px 10px rgba(0,0,0,.2)); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + + // Aligns the dropdown menu to right + &.pull-right { + right: 0; + left: auto; + } + + // Dividers (basically an hr) within the dropdown + .divider { + .nav-divider(@dropdownDividerTop, @dropdownDividerBottom); + } + + // Links within the dropdown menu + > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: @baseLineHeight; + color: @dropdownLinkColor; + white-space: nowrap; + } +} + +// Hover/Focus state +// ----------- +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus, +.dropdown-submenu:hover > a, +.dropdown-submenu:focus > a { + text-decoration: none; + color: @dropdownLinkColorHover; + #gradient > .vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%)); +} + +// Active state +// ------------ +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: @dropdownLinkColorActive; + text-decoration: none; + outline: 0; + #gradient > .vertical(@dropdownLinkBackgroundActive, darken(@dropdownLinkBackgroundActive, 5%)); +} + +// Disabled state +// -------------- +// Gray out text and ensure the hover/focus state remains gray +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: @grayLight; +} +// Nuke hover/focus effects +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + background-image: none; // Remove CSS gradient + .reset-filter(); + cursor: default; +} + +// Open state for the dropdown +// --------------------------- +.open { + // IE7's z-index only goes to the nearest positioned ancestor, which would + // make the menu appear below buttons that appeared later on the page + *z-index: @zindexDropdown; + + & > .dropdown-menu { + display: block; + } +} + +// Right aligned dropdowns +// --------------------------- +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// ------------------------------------------------------ +// Just add .dropup after the standard .dropdown class and you're set, bro. +// TODO: abstract this so that the navbar fixed styles are not placed here? +.dropup, +.navbar-fixed-bottom .dropdown { + // Reverse the caret + .caret { + border-top: 0; + border-bottom: 4px solid @black; + content: ""; + } + // Different positioning for bottom up menu + .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; + } +} + +// Sub menus +// --------------------------- +.dropdown-submenu { + position: relative; +} +// Default dropdowns +.dropdown-submenu > .dropdown-menu { + top: 0; + left: 100%; + margin-top: -6px; + margin-left: -1px; + .border-radius(0 6px 6px 6px); +} +.dropdown-submenu:hover > .dropdown-menu { + display: block; +} + +// Dropups +.dropup .dropdown-submenu > .dropdown-menu { + top: auto; + bottom: 0; + margin-top: 0; + margin-bottom: -2px; + .border-radius(5px 5px 5px 0); +} + +// Caret to indicate there is a submenu +.dropdown-submenu > a:after { + display: block; + content: " "; + float: right; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + border-width: 5px 0 5px 5px; + border-left-color: darken(@dropdownBackground, 20%); + margin-top: 5px; + margin-right: -10px; +} +.dropdown-submenu:hover > a:after { + border-left-color: @dropdownLinkColorHover; +} + +// Left aligned submenus +.dropdown-submenu.pull-left { + // Undo the float + // Yes, this is awkward since .pull-left adds a float, but it sticks to our conventions elsewhere. + float: none; + + // Positioning the submenu + > .dropdown-menu { + left: -100%; + margin-left: 10px; + .border-radius(6px 0 6px 6px); + } +} + +// Tweak nav headers +// ----------------- +// Increase padding from 15px to 20px on sides +.dropdown .dropdown-menu .nav-header { + padding-left: 20px; + padding-right: 20px; +} + +// Typeahead +// --------- +.typeahead { + z-index: 1051; + margin-top: 2px; // give it some space to breathe + .border-radius(@baseBorderRadius); +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/forms.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/forms.less index 2bfb88e7a0..9f4ef4b8f9 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/forms.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/forms.less @@ -1,690 +1,690 @@ -// -// Forms -// -------------------------------------------------- - - -// GENERAL STYLES -// -------------- - -// Make all forms have space below them -form { - margin: 0 0 @baseLineHeight; -} - -fieldset { - padding: 0; - margin: 0; - border: 0; -} - -// Groups of fields with labels on top (legends) -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: @baseLineHeight; - font-size: @baseFontSize * 1.5; - line-height: @baseLineHeight * 2; - color: @grayDark; - border: 0; - border-bottom: 1px solid #e5e5e5; - - // Small - small { - font-size: @baseLineHeight * .75; - color: @grayLight; - } -} - -// Set font for forms -label, -input, -button, -select, -textarea { - #font > .shorthand(@baseFontSize,normal,@baseLineHeight); // Set size, weight, line-height here -} -input, -button, -select, -textarea { - font-family: @baseFontFamily; // And only set font-family here for those that need it (note the missing label element) -} - -// Identify controls by their labels -label { - display: block; - margin-bottom: 5px; -} - -// Form controls -// ------------------------- - -// Shared size and type resets -select, -textarea, -input[type="text"], -input[type="password"], -input[type="datetime"], -input[type="datetime-local"], -input[type="date"], -input[type="month"], -input[type="time"], -input[type="week"], -input[type="number"], -input[type="email"], -input[type="url"], -input[type="search"], -input[type="tel"], -input[type="color"], -.uneditable-input { - display: inline-block; - height: @baseLineHeight; - padding: 4px 6px; - margin-bottom: @baseLineHeight / 2; - font-size: @baseFontSize; - line-height: @baseLineHeight; - color: @gray; - .border-radius(@inputBorderRadius); - vertical-align: middle; -} - -// Reset appearance properties for textual inputs and textarea -// Declare width for legacy (can't be on input[type=*] selectors or it's too specific) -input, -textarea, -.uneditable-input { - width: 206px; // plus 12px padding and 2px border -} -// Reset height since textareas have rows -textarea { - height: auto; -} -// Everything else -textarea, -input[type="text"], -input[type="password"], -input[type="datetime"], -input[type="datetime-local"], -input[type="date"], -input[type="month"], -input[type="time"], -input[type="week"], -input[type="number"], -input[type="email"], -input[type="url"], -input[type="search"], -input[type="tel"], -input[type="color"], -.uneditable-input { - background-color: @inputBackground; - border: 1px solid @inputBorder; - .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); - .transition(~"border linear .2s, box-shadow linear .2s"); - - // Focus state - &:focus { - border-color: rgba(82,168,236,.8); - outline: 0; - outline: thin dotted \9; /* IE6-9 */ - .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6)"); - } -} - -// Position radios and checkboxes better -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - *margin-top: 0; /* IE7 */ - margin-top: 1px \9; /* IE8-9 */ - line-height: normal; -} - -// Reset width of input images, buttons, radios, checkboxes -input[type="file"], -input[type="image"], -input[type="submit"], -input[type="reset"], -input[type="button"], -input[type="radio"], -input[type="checkbox"] { - width: auto; // Override of generic input selector -} - -// Set the height of select and file controls to match text inputs -select, -input[type="file"] { - height: @inputHeight; /* In IE7, the height of the select element cannot be changed by height, only font-size */ - *margin-top: 4px; /* For IE7, add top margin to align select with labels */ - line-height: @inputHeight; -} - -// Make select elements obey height by applying a border -select { - width: 220px; // default input width + 10px of padding that doesn't get applied - border: 1px solid @inputBorder; - background-color: @inputBackground; // Chrome on Linux and Mobile Safari need background-color -} - -// Make multiple select elements height not fixed -select[multiple], -select[size] { - height: auto; -} - -// Focus for select, file, radio, and checkbox -select:focus, -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - .tab-focus(); -} - - -// Uneditable inputs -// ------------------------- - -// Make uneditable inputs look inactive -.uneditable-input, -.uneditable-textarea { - color: @grayLight; - background-color: darken(@inputBackground, 1%); - border-color: @inputBorder; - .box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); - cursor: not-allowed; -} - -// For text that needs to appear as an input but should not be an input -.uneditable-input { - overflow: hidden; // prevent text from wrapping, but still cut it off like an input does - white-space: nowrap; -} - -// Make uneditable textareas behave like a textarea -.uneditable-textarea { - width: auto; - height: auto; -} - - -// Placeholder -// ------------------------- - -// Placeholder text gets special styles because when browsers invalidate entire lines if it doesn't understand a selector -input, -textarea { - .placeholder(); -} - - -// CHECKBOXES & RADIOS -// ------------------- - -// Indent the labels to position radios/checkboxes as hanging -.radio, -.checkbox { - min-height: @baseLineHeight; // clear the floating input if there is no label text - padding-left: 20px; -} -.radio input[type="radio"], -.checkbox input[type="checkbox"] { - float: left; - margin-left: 0px; -} - -// Move the options list down to align with labels -.controls > .radio:first-child, -.controls > .checkbox:first-child { - padding-top: 5px; // has to be padding because margin collaspes -} - -// Radios and checkboxes on same line -// TODO v3: Convert .inline to .control-inline -.radio.inline, -.checkbox.inline { - display: inline-block; - padding-top: 5px; - margin-bottom: 0; - vertical-align: middle; -} -.radio.inline + .radio.inline, -.checkbox.inline + .checkbox.inline { - margin-left: 10px; // space out consecutive inline controls -} - - - -// INPUT SIZES -// ----------- - -// General classes for quick sizes -.input-mini { width: 60px; } -.input-small { width: 90px; } -.input-medium { width: 150px; } -.input-large { width: 210px; } -.input-xlarge { width: 270px; } -.input-xxlarge { width: 530px; } - -// Grid style input sizes -input[class*="span"], -select[class*="span"], -textarea[class*="span"], -.uneditable-input[class*="span"], -// Redeclare since the fluid row class is more specific -.row-fluid input[class*="span"], -.row-fluid select[class*="span"], -.row-fluid textarea[class*="span"], -.row-fluid .uneditable-input[class*="span"] { - float: none; - margin-left: 0; -} -// Ensure input-prepend/append never wraps -.input-append input[class*="span"], -.input-append .uneditable-input[class*="span"], -.input-prepend input[class*="span"], -.input-prepend .uneditable-input[class*="span"], -.row-fluid input[class*="span"], -.row-fluid select[class*="span"], -.row-fluid textarea[class*="span"], -.row-fluid .uneditable-input[class*="span"], -.row-fluid .input-prepend [class*="span"], -.row-fluid .input-append [class*="span"] { - display: inline-block; -} - - - -// GRID SIZING FOR INPUTS -// ---------------------- - -// Grid sizes -#grid > .input(@gridColumnWidth, @gridGutterWidth); - -// Control row for multiple inputs per line -.controls-row { - .clearfix(); // Clear the float from controls -} - -// Float to collapse white-space for proper grid alignment -.controls-row [class*="span"], -// Redeclare the fluid grid collapse since we undo the float for inputs -.row-fluid .controls-row [class*="span"] { - float: left; -} -// Explicity set top padding on all checkboxes/radios, not just first-child -.controls-row .checkbox[class*="span"], -.controls-row .radio[class*="span"] { - padding-top: 5px; -} - - - - -// DISABLED STATE -// -------------- - -// Disabled and read-only inputs -input[disabled], -select[disabled], -textarea[disabled], -input[readonly], -select[readonly], -textarea[readonly] { - cursor: not-allowed; - background-color: @inputDisabledBackground; -} -// Explicitly reset the colors here -input[type="radio"][disabled], -input[type="checkbox"][disabled], -input[type="radio"][readonly], -input[type="checkbox"][readonly] { - background-color: transparent; -} - - - - -// FORM FIELD FEEDBACK STATES -// -------------------------- - -// Warning -.control-group.warning { - .formFieldState(@warningText, @warningText, @warningBackground); -} -// Error -.control-group.error { - .formFieldState(@errorText, @errorText, @errorBackground); -} -// Success -.control-group.success { - .formFieldState(@successText, @successText, @successBackground); -} -// Success -.control-group.info { - .formFieldState(@infoText, @infoText, @infoBackground); -} - -// HTML5 invalid states -// Shares styles with the .control-group.error above -input:focus:invalid, -textarea:focus:invalid, -select:focus:invalid { - color: #b94a48; - border-color: #ee5f5b; - &:focus { - border-color: darken(#ee5f5b, 10%); - @shadow: 0 0 6px lighten(#ee5f5b, 20%); - .box-shadow(@shadow); - } -} - - - -// FORM ACTIONS -// ------------ - -.form-actions { - padding: (@baseLineHeight - 1) 20px @baseLineHeight; - margin-top: @baseLineHeight; - margin-bottom: @baseLineHeight; - background-color: @formActionsBackground; - border-top: 1px solid #e5e5e5; - .clearfix(); // Adding clearfix to allow for .pull-right button containers -} - - - -// HELP TEXT -// --------- - -.help-block, -.help-inline { - color: lighten(@textColor, 15%); // lighten the text some for contrast -} - -.help-block { - display: block; // account for any element using help-block - margin-bottom: @baseLineHeight / 2; -} - -.help-inline { - display: inline-block; - .ie7-inline-block(); - vertical-align: middle; - padding-left: 5px; -} - - - -// INPUT GROUPS -// ------------ - -// Allow us to put symbols and text within the input field for a cleaner look -.input-append, -.input-prepend { - display: inline-block; - margin-bottom: @baseLineHeight / 2; - vertical-align: middle; - font-size: 0; // white space collapse hack - white-space: nowrap; // Prevent span and input from separating - - // Reset the white space collapse hack - input, - select, - .uneditable-input, - .dropdown-menu, - .popover { - font-size: @baseFontSize; - } - - input, - select, - .uneditable-input { - position: relative; // placed here by default so that on :focus we can place the input above the .add-on for full border and box-shadow goodness - margin-bottom: 0; // prevent bottom margin from screwing up alignment in stacked forms - *margin-left: 0; - vertical-align: top; - .border-radius(0 @inputBorderRadius @inputBorderRadius 0); - // Make input on top when focused so blue border and shadow always show - &:focus { - z-index: 2; - } - } - .add-on { - display: inline-block; - width: auto; - height: @baseLineHeight; - min-width: 16px; - padding: 4px 5px; - font-size: @baseFontSize; - font-weight: normal; - line-height: @baseLineHeight; - text-align: center; - text-shadow: 0 1px 0 @white; - background-color: @grayLighter; - border: 1px solid #ccc; - } - .add-on, - .btn, - .btn-group > .dropdown-toggle { - vertical-align: top; - .border-radius(0); - } - .active { - background-color: lighten(@green, 30); - border-color: @green; - } -} - -.input-prepend { - .add-on, - .btn { - margin-right: -1px; - } - .add-on:first-child, - .btn:first-child { - // FYI, `.btn:first-child` accounts for a button group that's prepended - .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); - } -} - -.input-append { - input, - select, - .uneditable-input { - .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); - + .btn-group .btn:last-child { - .border-radius(0 @inputBorderRadius @inputBorderRadius 0); - } - } - .add-on, - .btn, - .btn-group { - margin-left: -1px; - } - .add-on:last-child, - .btn:last-child, - .btn-group:last-child > .dropdown-toggle { - .border-radius(0 @inputBorderRadius @inputBorderRadius 0); - } -} - -// Remove all border-radius for inputs with both prepend and append -.input-prepend.input-append { - input, - select, - .uneditable-input { - .border-radius(0); - + .btn-group .btn { - .border-radius(0 @inputBorderRadius @inputBorderRadius 0); - } - } - .add-on:first-child, - .btn:first-child { - margin-right: -1px; - .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); - } - .add-on:last-child, - .btn:last-child { - margin-left: -1px; - .border-radius(0 @inputBorderRadius @inputBorderRadius 0); - } - .btn-group:first-child { - margin-left: 0; - } -} - - - - -// SEARCH FORM -// ----------- - -input.search-query { - padding-right: 14px; - padding-right: 4px \9; - padding-left: 14px; - padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */ - margin-bottom: 0; // Remove the default margin on all inputs - .border-radius(15px); -} - -/* Allow for input prepend/append in search forms */ -.form-search .input-append .search-query, -.form-search .input-prepend .search-query { - .border-radius(0); // Override due to specificity -} -.form-search .input-append .search-query { - .border-radius(14px 0 0 14px); -} -.form-search .input-append .btn { - .border-radius(0 14px 14px 0); -} -.form-search .input-prepend .search-query { - .border-radius(0 14px 14px 0); -} -.form-search .input-prepend .btn { - .border-radius(14px 0 0 14px); -} - - - - -// HORIZONTAL & VERTICAL FORMS -// --------------------------- - -// Common properties -// ----------------- - -.form-search, -.form-inline, -.form-horizontal { - input, - textarea, - select, - .help-inline, - .uneditable-input, - .input-prepend, - .input-append { - display: inline-block; - .ie7-inline-block(); - margin-bottom: 0; - vertical-align: middle; - } - // Re-hide hidden elements due to specifity - .hide { - display: none; - } -} -.form-search label, -.form-inline label, -.form-search .btn-group, -.form-inline .btn-group { - display: inline-block; -} -// Remove margin for input-prepend/-append -.form-search .input-append, -.form-inline .input-append, -.form-search .input-prepend, -.form-inline .input-prepend { - margin-bottom: 0; -} -// Inline checkbox/radio labels (remove padding on left) -.form-search .radio, -.form-search .checkbox, -.form-inline .radio, -.form-inline .checkbox { - padding-left: 0; - margin-bottom: 0; - vertical-align: middle; -} -// Remove float and margin, set to inline-block -.form-search .radio input[type="radio"], -.form-search .checkbox input[type="checkbox"], -.form-inline .radio input[type="radio"], -.form-inline .checkbox input[type="checkbox"] { - float: left; - margin-right: 3px; - margin-left: 0; -} - - -// Margin to space out fieldsets -.control-group { - margin-bottom: @baseLineHeight / 2; -} - -// Legend collapses margin, so next element is responsible for spacing -legend + .control-group { - margin-top: @baseLineHeight; - -webkit-margin-top-collapse: separate; -} - -// Horizontal-specific styles -// -------------------------- - -.form-horizontal { - // Increase spacing between groups - .control-group { - margin-bottom: @baseLineHeight; - .clearfix(); - } - // Float the labels left - .control-label { - float: left; - width: @horizontalComponentOffset - 20; - padding-top: 5px; - text-align: right; - } - // Move over all input controls and content - .controls { - // Super jank IE7 fix to ensure the inputs in .input-append and input-prepend - // don't inherit the margin of the parent, in this case .controls - *display: inline-block; - *padding-left: 20px; - margin-left: @horizontalComponentOffset; - *margin-left: 0; - &:first-child { - *padding-left: @horizontalComponentOffset; - } - } - // Remove bottom margin on block level help text since that's accounted for on .control-group - .help-block { - margin-bottom: 0; - } - // And apply it only to .help-block instances that follow a form control - input, - select, - textarea, - .uneditable-input, - .input-prepend, - .input-append { - + .help-block { - margin-top: @baseLineHeight / 2; - } - } - // Move over buttons in .form-actions to align with .controls - .form-actions { - padding-left: @horizontalComponentOffset; - } -} +// +// Forms +// -------------------------------------------------- + + +// GENERAL STYLES +// -------------- + +// Make all forms have space below them +form { + margin: 0 0 @baseLineHeight; +} + +fieldset { + padding: 0; + margin: 0; + border: 0; +} + +// Groups of fields with labels on top (legends) +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: @baseLineHeight; + font-size: @baseFontSize * 1.5; + line-height: @baseLineHeight * 2; + color: @grayDark; + border: 0; + border-bottom: 1px solid #e5e5e5; + + // Small + small { + font-size: @baseLineHeight * .75; + color: @grayLight; + } +} + +// Set font for forms +label, +input, +button, +select, +textarea { + #font > .shorthand(@baseFontSize,normal,@baseLineHeight); // Set size, weight, line-height here +} +input, +button, +select, +textarea { + font-family: @baseFontFamily; // And only set font-family here for those that need it (note the missing label element) +} + +// Identify controls by their labels +label { + display: block; + margin-bottom: 5px; +} + +// Form controls +// ------------------------- + +// Shared size and type resets +select, +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + display: inline-block; + height: @baseLineHeight; + padding: 4px 6px; + margin-bottom: @baseLineHeight / 2; + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @gray; + .border-radius(@inputBorderRadius); + vertical-align: middle; +} + +// Reset appearance properties for textual inputs and textarea +// Declare width for legacy (can't be on input[type=*] selectors or it's too specific) +input, +textarea, +.uneditable-input { + width: 206px; // plus 12px padding and 2px border +} +// Reset height since textareas have rows +textarea { + height: auto; +} +// Everything else +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + background-color: @inputBackground; + border: 1px solid @inputBorder; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); + .transition(~"border linear .2s, box-shadow linear .2s"); + + // Focus state + &:focus { + border-color: rgba(82,168,236,.8); + outline: 0; + outline: thin dotted \9; /* IE6-9 */ + .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6)"); + } +} + +// Position radios and checkboxes better +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + *margin-top: 0; /* IE7 */ + margin-top: 1px \9; /* IE8-9 */ + line-height: normal; +} + +// Reset width of input images, buttons, radios, checkboxes +input[type="file"], +input[type="image"], +input[type="submit"], +input[type="reset"], +input[type="button"], +input[type="radio"], +input[type="checkbox"] { + width: auto; // Override of generic input selector +} + +// Set the height of select and file controls to match text inputs +select, +input[type="file"] { + height: @inputHeight; /* In IE7, the height of the select element cannot be changed by height, only font-size */ + *margin-top: 4px; /* For IE7, add top margin to align select with labels */ + line-height: @inputHeight; +} + +// Make select elements obey height by applying a border +select { + width: 220px; // default input width + 10px of padding that doesn't get applied + border: 1px solid @inputBorder; + background-color: @inputBackground; // Chrome on Linux and Mobile Safari need background-color +} + +// Make multiple select elements height not fixed +select[multiple], +select[size] { + height: auto; +} + +// Focus for select, file, radio, and checkbox +select:focus, +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + .tab-focus(); +} + + +// Uneditable inputs +// ------------------------- + +// Make uneditable inputs look inactive +.uneditable-input, +.uneditable-textarea { + color: @grayLight; + background-color: darken(@inputBackground, 1%); + border-color: @inputBorder; + .box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); + cursor: not-allowed; +} + +// For text that needs to appear as an input but should not be an input +.uneditable-input { + overflow: hidden; // prevent text from wrapping, but still cut it off like an input does + white-space: nowrap; +} + +// Make uneditable textareas behave like a textarea +.uneditable-textarea { + width: auto; + height: auto; +} + + +// Placeholder +// ------------------------- + +// Placeholder text gets special styles because when browsers invalidate entire lines if it doesn't understand a selector +input, +textarea { + .placeholder(); +} + + +// CHECKBOXES & RADIOS +// ------------------- + +// Indent the labels to position radios/checkboxes as hanging +.radio, +.checkbox { + min-height: @baseLineHeight; // clear the floating input if there is no label text + padding-left: 20px; +} +.radio input[type="radio"], +.checkbox input[type="checkbox"] { + float: left; + margin-left: 0px; +} + +// Move the options list down to align with labels +.controls > .radio:first-child, +.controls > .checkbox:first-child { + padding-top: 5px; // has to be padding because margin collaspes +} + +// Radios and checkboxes on same line +// TODO v3: Convert .inline to .control-inline +.radio.inline, +.checkbox.inline { + display: inline-block; + padding-top: 5px; + margin-bottom: 0; + vertical-align: middle; +} +.radio.inline + .radio.inline, +.checkbox.inline + .checkbox.inline { + margin-left: 10px; // space out consecutive inline controls +} + + + +// INPUT SIZES +// ----------- + +// General classes for quick sizes +.input-mini { width: 60px; } +.input-small { width: 90px; } +.input-medium { width: 150px; } +.input-large { width: 210px; } +.input-xlarge { width: 270px; } +.input-xxlarge { width: 530px; } + +// Grid style input sizes +input[class*="span"], +select[class*="span"], +textarea[class*="span"], +.uneditable-input[class*="span"], +// Redeclare since the fluid row class is more specific +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"] { + float: none; + margin-left: 0; +} +// Ensure input-prepend/append never wraps +.input-append input[class*="span"], +.input-append .uneditable-input[class*="span"], +.input-prepend input[class*="span"], +.input-prepend .uneditable-input[class*="span"], +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"], +.row-fluid .input-prepend [class*="span"], +.row-fluid .input-append [class*="span"] { + display: inline-block; +} + + + +// GRID SIZING FOR INPUTS +// ---------------------- + +// Grid sizes +#grid > .input(@gridColumnWidth, @gridGutterWidth); + +// Control row for multiple inputs per line +.controls-row { + .clearfix(); // Clear the float from controls +} + +// Float to collapse white-space for proper grid alignment +.controls-row [class*="span"], +// Redeclare the fluid grid collapse since we undo the float for inputs +.row-fluid .controls-row [class*="span"] { + float: left; +} +// Explicity set top padding on all checkboxes/radios, not just first-child +.controls-row .checkbox[class*="span"], +.controls-row .radio[class*="span"] { + padding-top: 5px; +} + + + + +// DISABLED STATE +// -------------- + +// Disabled and read-only inputs +input[disabled], +select[disabled], +textarea[disabled], +input[readonly], +select[readonly], +textarea[readonly] { + cursor: not-allowed; + background-color: @inputDisabledBackground; +} +// Explicitly reset the colors here +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"][readonly], +input[type="checkbox"][readonly] { + background-color: transparent; +} + + + + +// FORM FIELD FEEDBACK STATES +// -------------------------- + +// Warning +.control-group.warning { + .formFieldState(@warningText, @warningText, @warningBackground); +} +// Error +.control-group.error { + .formFieldState(@errorText, @errorText, @errorBackground); +} +// Success +.control-group.success { + .formFieldState(@successText, @successText, @successBackground); +} +// Success +.control-group.info { + .formFieldState(@infoText, @infoText, @infoBackground); +} + +// HTML5 invalid states +// Shares styles with the .control-group.error above +input:focus:invalid, +textarea:focus:invalid, +select:focus:invalid { + color: #b94a48; + border-color: #ee5f5b; + &:focus { + border-color: darken(#ee5f5b, 10%); + @shadow: 0 0 6px lighten(#ee5f5b, 20%); + .box-shadow(@shadow); + } +} + + + +// FORM ACTIONS +// ------------ + +.form-actions { + padding: (@baseLineHeight - 1) 20px @baseLineHeight; + margin-top: @baseLineHeight; + margin-bottom: @baseLineHeight; + background-color: @formActionsBackground; + border-top: 1px solid #e5e5e5; + .clearfix(); // Adding clearfix to allow for .pull-right button containers +} + + + +// HELP TEXT +// --------- + +.help-block, +.help-inline { + color: lighten(@textColor, 15%); // lighten the text some for contrast +} + +.help-block { + display: block; // account for any element using help-block + margin-bottom: @baseLineHeight / 2; +} + +.help-inline { + display: inline-block; + .ie7-inline-block(); + vertical-align: middle; + padding-left: 5px; +} + + + +// INPUT GROUPS +// ------------ + +// Allow us to put symbols and text within the input field for a cleaner look +.input-append, +.input-prepend { + display: inline-block; + margin-bottom: @baseLineHeight / 2; + vertical-align: middle; + font-size: 0; // white space collapse hack + white-space: nowrap; // Prevent span and input from separating + + // Reset the white space collapse hack + input, + select, + .uneditable-input, + .dropdown-menu, + .popover { + font-size: @baseFontSize; + } + + input, + select, + .uneditable-input { + position: relative; // placed here by default so that on :focus we can place the input above the .add-on for full border and box-shadow goodness + margin-bottom: 0; // prevent bottom margin from screwing up alignment in stacked forms + *margin-left: 0; + vertical-align: top; + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + // Make input on top when focused so blue border and shadow always show + &:focus { + z-index: 2; + } + } + .add-on { + display: inline-block; + width: auto; + height: @baseLineHeight; + min-width: 16px; + padding: 4px 5px; + font-size: @baseFontSize; + font-weight: normal; + line-height: @baseLineHeight; + text-align: center; + text-shadow: 0 1px 0 @white; + background-color: @grayLighter; + border: 1px solid #ccc; + } + .add-on, + .btn, + .btn-group > .dropdown-toggle { + vertical-align: top; + .border-radius(0); + } + .active { + background-color: lighten(@green, 30); + border-color: @green; + } +} + +.input-prepend { + .add-on, + .btn { + margin-right: -1px; + } + .add-on:first-child, + .btn:first-child { + // FYI, `.btn:first-child` accounts for a button group that's prepended + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + } +} + +.input-append { + input, + select, + .uneditable-input { + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + + .btn-group .btn:last-child { + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } + } + .add-on, + .btn, + .btn-group { + margin-left: -1px; + } + .add-on:last-child, + .btn:last-child, + .btn-group:last-child > .dropdown-toggle { + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } +} + +// Remove all border-radius for inputs with both prepend and append +.input-prepend.input-append { + input, + select, + .uneditable-input { + .border-radius(0); + + .btn-group .btn { + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } + } + .add-on:first-child, + .btn:first-child { + margin-right: -1px; + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + } + .add-on:last-child, + .btn:last-child { + margin-left: -1px; + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } + .btn-group:first-child { + margin-left: 0; + } +} + + + + +// SEARCH FORM +// ----------- + +input.search-query { + padding-right: 14px; + padding-right: 4px \9; + padding-left: 14px; + padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */ + margin-bottom: 0; // Remove the default margin on all inputs + .border-radius(15px); +} + +/* Allow for input prepend/append in search forms */ +.form-search .input-append .search-query, +.form-search .input-prepend .search-query { + .border-radius(0); // Override due to specificity +} +.form-search .input-append .search-query { + .border-radius(14px 0 0 14px); +} +.form-search .input-append .btn { + .border-radius(0 14px 14px 0); +} +.form-search .input-prepend .search-query { + .border-radius(0 14px 14px 0); +} +.form-search .input-prepend .btn { + .border-radius(14px 0 0 14px); +} + + + + +// HORIZONTAL & VERTICAL FORMS +// --------------------------- + +// Common properties +// ----------------- + +.form-search, +.form-inline, +.form-horizontal { + input, + textarea, + select, + .help-inline, + .uneditable-input, + .input-prepend, + .input-append { + display: inline-block; + .ie7-inline-block(); + margin-bottom: 0; + vertical-align: middle; + } + // Re-hide hidden elements due to specifity + .hide { + display: none; + } +} +.form-search label, +.form-inline label, +.form-search .btn-group, +.form-inline .btn-group { + display: inline-block; +} +// Remove margin for input-prepend/-append +.form-search .input-append, +.form-inline .input-append, +.form-search .input-prepend, +.form-inline .input-prepend { + margin-bottom: 0; +} +// Inline checkbox/radio labels (remove padding on left) +.form-search .radio, +.form-search .checkbox, +.form-inline .radio, +.form-inline .checkbox { + padding-left: 0; + margin-bottom: 0; + vertical-align: middle; +} +// Remove float and margin, set to inline-block +.form-search .radio input[type="radio"], +.form-search .checkbox input[type="checkbox"], +.form-inline .radio input[type="radio"], +.form-inline .checkbox input[type="checkbox"] { + float: left; + margin-right: 3px; + margin-left: 0; +} + + +// Margin to space out fieldsets +.control-group { + margin-bottom: @baseLineHeight / 2; +} + +// Legend collapses margin, so next element is responsible for spacing +legend + .control-group { + margin-top: @baseLineHeight; + -webkit-margin-top-collapse: separate; +} + +// Horizontal-specific styles +// -------------------------- + +.form-horizontal { + // Increase spacing between groups + .control-group { + margin-bottom: @baseLineHeight; + .clearfix(); + } + // Float the labels left + .control-label { + float: left; + width: @horizontalComponentOffset - 20; + padding-top: 5px; + text-align: right; + } + // Move over all input controls and content + .controls { + // Super jank IE7 fix to ensure the inputs in .input-append and input-prepend + // don't inherit the margin of the parent, in this case .controls + *display: inline-block; + *padding-left: 20px; + margin-left: @horizontalComponentOffset; + *margin-left: 0; + &:first-child { + *padding-left: @horizontalComponentOffset; + } + } + // Remove bottom margin on block level help text since that's accounted for on .control-group + .help-block { + margin-bottom: 0; + } + // And apply it only to .help-block instances that follow a form control + input, + select, + textarea, + .uneditable-input, + .input-prepend, + .input-append { + + .help-block { + margin-top: @baseLineHeight / 2; + } + } + // Move over buttons in .form-actions to align with .controls + .form-actions { + padding-left: @horizontalComponentOffset; + } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/grid.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/grid.less index 2d93e674fd..750d203514 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/grid.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/grid.less @@ -1,21 +1,21 @@ -// -// Grid system -// -------------------------------------------------- - - -// Fixed (940px) -#grid > .core(@gridColumnWidth, @gridGutterWidth); - -// Fluid (940px) -#grid > .fluid(@fluidGridColumnWidth, @fluidGridGutterWidth); - -// Reset utility classes due to specificity -[class*="span"].hide, -.row-fluid [class*="span"].hide { - display: none; -} - -[class*="span"].pull-right, -.row-fluid [class*="span"].pull-right { - float: right; -} +// +// Grid system +// -------------------------------------------------- + + +// Fixed (940px) +#grid > .core(@gridColumnWidth, @gridGutterWidth); + +// Fluid (940px) +#grid > .fluid(@fluidGridColumnWidth, @fluidGridGutterWidth); + +// Reset utility classes due to specificity +[class*="span"].hide, +.row-fluid [class*="span"].hide { + display: none; +} + +[class*="span"].pull-right, +.row-fluid [class*="span"].pull-right { + float: right; +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/hero-unit.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/hero-unit.less index 596f558822..763d86aeee 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/hero-unit.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/hero-unit.less @@ -1,25 +1,25 @@ -// -// Hero unit -// -------------------------------------------------- - - -.hero-unit { - padding: 60px; - margin-bottom: 30px; - font-size: 18px; - font-weight: 200; - line-height: @baseLineHeight * 1.5; - color: @heroUnitLeadColor; - background-color: @heroUnitBackground; - .border-radius(6px); - h1 { - margin-bottom: 0; - font-size: 60px; - line-height: 1; - color: @heroUnitHeadingColor; - letter-spacing: -1px; - } - li { - line-height: @baseLineHeight * 1.5; // Reset since we specify in type.less - } -} +// +// Hero unit +// -------------------------------------------------- + + +.hero-unit { + padding: 60px; + margin-bottom: 30px; + font-size: 18px; + font-weight: 200; + line-height: @baseLineHeight * 1.5; + color: @heroUnitLeadColor; + background-color: @heroUnitBackground; + .border-radius(6px); + h1 { + margin-bottom: 0; + font-size: 60px; + line-height: 1; + color: @heroUnitHeadingColor; + letter-spacing: -1px; + } + li { + line-height: @baseLineHeight * 1.5; // Reset since we specify in type.less + } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/labels-badges.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/labels-badges.less index 0c63b4ac6d..bc321fe5c1 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/labels-badges.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/labels-badges.less @@ -1,84 +1,84 @@ -// -// Labels and badges -// -------------------------------------------------- - - -// Base classes -.label, -.badge { - display: inline-block; - padding: 2px 4px; - font-size: @baseFontSize * .846; - font-weight: bold; - line-height: 14px; // ensure proper line-height if floated - color: @white; - vertical-align: baseline; - white-space: nowrap; - text-shadow: 0 -1px 0 rgba(0,0,0,.25); - background-color: @grayLight; -} -// Set unique padding and border-radii -.label { - .border-radius(3px); -} -.badge { - padding-left: 9px; - padding-right: 9px; - .border-radius(9px); -} - -// Empty labels/badges collapse -.label, -.badge { - &:empty { - display: none; - } -} - -// Hover/focus state, but only for links -a { - &.label:hover, - &.label:focus, - &.badge:hover, - &.badge:focus { - color: @white; - text-decoration: none; - cursor: pointer; - } -} - -// Colors -// Only give background-color difference to links (and to simplify, we don't qualifty with `a` but [href] attribute) -.label, -.badge { - // Important (red) - &-important { background-color: @errorText; } - &-important[href] { background-color: darken(@errorText, 10%); } - // Warnings (orange) - &-warning { background-color: @orange; } - &-warning[href] { background-color: darken(@orange, 10%); } - // Success (green) - &-success { background-color: @successText; } - &-success[href] { background-color: darken(@successText, 10%); } - // Info (turquoise) - &-info { background-color: @infoText; } - &-info[href] { background-color: darken(@infoText, 10%); } - // Inverse (black) - &-inverse { background-color: @grayDark; } - &-inverse[href] { background-color: darken(@grayDark, 10%); } -} - -// Quick fix for labels/badges in buttons -.btn { - .label, - .badge { - position: relative; - top: -1px; - } -} -.btn-mini { - .label, - .badge { - top: 0; - } -} +// +// Labels and badges +// -------------------------------------------------- + + +// Base classes +.label, +.badge { + display: inline-block; + padding: 2px 4px; + font-size: @baseFontSize * .846; + font-weight: bold; + line-height: 14px; // ensure proper line-height if floated + color: @white; + vertical-align: baseline; + white-space: nowrap; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + background-color: @grayLight; +} +// Set unique padding and border-radii +.label { + .border-radius(3px); +} +.badge { + padding-left: 9px; + padding-right: 9px; + .border-radius(9px); +} + +// Empty labels/badges collapse +.label, +.badge { + &:empty { + display: none; + } +} + +// Hover/focus state, but only for links +a { + &.label:hover, + &.label:focus, + &.badge:hover, + &.badge:focus { + color: @white; + text-decoration: none; + cursor: pointer; + } +} + +// Colors +// Only give background-color difference to links (and to simplify, we don't qualifty with `a` but [href] attribute) +.label, +.badge { + // Important (red) + &-important { background-color: @errorText; } + &-important[href] { background-color: darken(@errorText, 10%); } + // Warnings (orange) + &-warning { background-color: @orange; } + &-warning[href] { background-color: darken(@orange, 10%); } + // Success (green) + &-success { background-color: @successText; } + &-success[href] { background-color: darken(@successText, 10%); } + // Info (turquoise) + &-info { background-color: @infoText; } + &-info[href] { background-color: darken(@infoText, 10%); } + // Inverse (black) + &-inverse { background-color: @grayDark; } + &-inverse[href] { background-color: darken(@grayDark, 10%); } +} + +// Quick fix for labels/badges in buttons +.btn { + .label, + .badge { + position: relative; + top: -1px; + } +} +.btn-mini { + .label, + .badge { + top: 0; + } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/layouts.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/layouts.less index ff5a253e72..24a2062117 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/layouts.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/layouts.less @@ -1,16 +1,16 @@ -// -// Layouts -// -------------------------------------------------- - - -// Container (centered, fixed-width layouts) -.container { - .container-fixed(); -} - -// Fluid layouts (left aligned, with sidebar, min- & max-width content) -.container-fluid { - padding-right: @gridGutterWidth; - padding-left: @gridGutterWidth; - .clearfix(); +// +// Layouts +// -------------------------------------------------- + + +// Container (centered, fixed-width layouts) +.container { + .container-fixed(); +} + +// Fluid layouts (left aligned, with sidebar, min- & max-width content) +.container-fluid { + padding-right: @gridGutterWidth; + padding-left: @gridGutterWidth; + .clearfix(); } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/media.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/media.less index dedbfd37a4..e461e446d2 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/media.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/media.less @@ -1,55 +1,55 @@ -// Media objects -// Source: http://stubbornella.org/content/?p=497 -// -------------------------------------------------- - - -// Common styles -// ------------------------- - -// Clear the floats -.media, -.media-body { - overflow: hidden; - *overflow: visible; - zoom: 1; -} - -// Proper spacing between instances of .media -.media, -.media .media { - margin-top: 15px; -} -.media:first-child { - margin-top: 0; -} - -// For images and videos, set to block -.media-object { - display: block; -} - -// Reset margins on headings for tighter default spacing -.media-heading { - margin: 0 0 5px; -} - - -// Media image alignment -// ------------------------- - -.media > .pull-left { - margin-right: 10px; -} -.media > .pull-right { - margin-left: 10px; -} - - -// Media list variation -// ------------------------- - -// Undo default ul/ol styles -.media-list { - margin-left: 0; - list-style: none; -} +// Media objects +// Source: http://stubbornella.org/content/?p=497 +// -------------------------------------------------- + + +// Common styles +// ------------------------- + +// Clear the floats +.media, +.media-body { + overflow: hidden; + *overflow: visible; + zoom: 1; +} + +// Proper spacing between instances of .media +.media, +.media .media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} + +// For images and videos, set to block +.media-object { + display: block; +} + +// Reset margins on headings for tighter default spacing +.media-heading { + margin: 0 0 5px; +} + + +// Media image alignment +// ------------------------- + +.media > .pull-left { + margin-right: 10px; +} +.media > .pull-right { + margin-left: 10px; +} + + +// Media list variation +// ------------------------- + +// Undo default ul/ol styles +.media-list { + margin-left: 0; + list-style: none; +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/mixins.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/mixins.less index 2741551e9a..79d889219f 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/mixins.less @@ -1,702 +1,702 @@ -// -// Mixins -// -------------------------------------------------- - - -// UTILITY MIXINS -// -------------------------------------------------- - -// Clearfix -// -------- -// For clearing floats like a boss h5bp.com/q -.clearfix { - *zoom: 1; - &:before, - &:after { - display: table; - content: ""; - // Fixes Opera/contenteditable bug: - // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 - line-height: 0; - } - &:after { - clear: both; - } -} - -// Webkit-style focus -// ------------------ -.tab-focus() { - // Default - outline: thin dotted #333; - // Webkit - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -// Center-align a block level element -// ---------------------------------- -.center-block() { - display: block; - margin-left: auto; - margin-right: auto; -} - -// IE7 inline-block -// ---------------- -.ie7-inline-block() { - *display: inline; /* IE7 inline-block hack */ - *zoom: 1; -} - -// IE7 likes to collapse whitespace on either side of the inline-block elements. -// Ems because we're attempting to match the width of a space character. Left -// version is for form buttons, which typically come after other elements, and -// right version is for icons, which come before. Applying both is ok, but it will -// mean that space between those elements will be .6em (~2 space characters) in IE7, -// instead of the 1 space in other browsers. -.ie7-restore-left-whitespace() { - *margin-left: .3em; - - &:first-child { - *margin-left: 0; - } -} - -.ie7-restore-right-whitespace() { - *margin-right: .3em; -} - -// Sizing shortcuts -// ------------------------- -.size(@height, @width) { - width: @width; - height: @height; -} -.square(@size) { - .size(@size, @size); -} - -// Placeholder text -// ------------------------- -.placeholder(@color: @placeholderText) { - &:-moz-placeholder { - color: @color; - } - &:-ms-input-placeholder { - color: @color; - } - &::-webkit-input-placeholder { - color: @color; - } -} - -// Text overflow -// ------------------------- -// Requires inline-block or block for proper styling -.text-overflow() { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -// CSS image replacement -// ------------------------- -// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 -.hide-text { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} - - -// FONTS -// -------------------------------------------------- - -#font { - #family { - .serif() { - font-family: @serifFontFamily; - } - .sans-serif() { - font-family: @sansFontFamily; - } - .monospace() { - font-family: @monoFontFamily; - } - } - .shorthand(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { - font-size: @size; - font-weight: @weight; - line-height: @lineHeight; - } - .serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { - #font > #family > .serif; - #font > .shorthand(@size, @weight, @lineHeight); - } - .sans-serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { - #font > #family > .sans-serif; - #font > .shorthand(@size, @weight, @lineHeight); - } - .monospace(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { - #font > #family > .monospace; - #font > .shorthand(@size, @weight, @lineHeight); - } -} - - -// FORMS -// -------------------------------------------------- - -// Block level inputs -.input-block-level { - display: block; - width: 100%; - min-height: @inputHeight; // Make inputs at least the height of their button counterpart (base line-height + padding + border) - .box-sizing(border-box); // Makes inputs behave like true block-level elements -} - - - -// Mixin for form field states -.formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) { - // Set the text color - .control-label, - .help-block, - .help-inline { - color: @textColor; - } - // Style inputs accordingly - .checkbox, - .radio, - input, - select, - textarea { - color: @textColor; - } - input, - select, - textarea { - border-color: @borderColor; - .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work - &:focus { - border-color: darken(@borderColor, 10%); - @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@borderColor, 20%); - .box-shadow(@shadow); - } - } - // Give a small background color for input-prepend/-append - .input-prepend .add-on, - .input-append .add-on { - color: @textColor; - background-color: @backgroundColor; - border-color: @textColor; - } -} - - - -// CSS3 PROPERTIES -// -------------------------------------------------- - -// Border Radius -.border-radius(@radius) { - -webkit-border-radius: @radius; - -moz-border-radius: @radius; - border-radius: @radius; -} - -// Single Corner Border Radius -.border-top-left-radius(@radius) { - -webkit-border-top-left-radius: @radius; - -moz-border-radius-topleft: @radius; - border-top-left-radius: @radius; -} -.border-top-right-radius(@radius) { - -webkit-border-top-right-radius: @radius; - -moz-border-radius-topright: @radius; - border-top-right-radius: @radius; -} -.border-bottom-right-radius(@radius) { - -webkit-border-bottom-right-radius: @radius; - -moz-border-radius-bottomright: @radius; - border-bottom-right-radius: @radius; -} -.border-bottom-left-radius(@radius) { - -webkit-border-bottom-left-radius: @radius; - -moz-border-radius-bottomleft: @radius; - border-bottom-left-radius: @radius; -} - -// Single Side Border Radius -.border-top-radius(@radius) { - .border-top-right-radius(@radius); - .border-top-left-radius(@radius); -} -.border-right-radius(@radius) { - .border-top-right-radius(@radius); - .border-bottom-right-radius(@radius); -} -.border-bottom-radius(@radius) { - .border-bottom-right-radius(@radius); - .border-bottom-left-radius(@radius); -} -.border-left-radius(@radius) { - .border-top-left-radius(@radius); - .border-bottom-left-radius(@radius); -} - -// Drop shadows -.box-shadow(@shadow) { - -webkit-box-shadow: @shadow; - -moz-box-shadow: @shadow; - box-shadow: @shadow; -} - -// Transitions -.transition(@transition) { - -webkit-transition: @transition; - -moz-transition: @transition; - -o-transition: @transition; - transition: @transition; -} -.transition-delay(@transition-delay) { - -webkit-transition-delay: @transition-delay; - -moz-transition-delay: @transition-delay; - -o-transition-delay: @transition-delay; - transition-delay: @transition-delay; -} -.transition-duration(@transition-duration) { - -webkit-transition-duration: @transition-duration; - -moz-transition-duration: @transition-duration; - -o-transition-duration: @transition-duration; - transition-duration: @transition-duration; -} - -// Transformations -.rotate(@degrees) { - -webkit-transform: rotate(@degrees); - -moz-transform: rotate(@degrees); - -ms-transform: rotate(@degrees); - -o-transform: rotate(@degrees); - transform: rotate(@degrees); -} -.scale(@ratio) { - -webkit-transform: scale(@ratio); - -moz-transform: scale(@ratio); - -ms-transform: scale(@ratio); - -o-transform: scale(@ratio); - transform: scale(@ratio); -} -.translate(@x, @y) { - -webkit-transform: translate(@x, @y); - -moz-transform: translate(@x, @y); - -ms-transform: translate(@x, @y); - -o-transform: translate(@x, @y); - transform: translate(@x, @y); -} -.skew(@x, @y) { - -webkit-transform: skew(@x, @y); - -moz-transform: skew(@x, @y); - -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twitter/bootstrap/issues/4885 - -o-transform: skew(@x, @y); - transform: skew(@x, @y); - -webkit-backface-visibility: hidden; // See https://github.com/twitter/bootstrap/issues/5319 -} -.translate3d(@x, @y, @z) { - -webkit-transform: translate3d(@x, @y, @z); - -moz-transform: translate3d(@x, @y, @z); - -o-transform: translate3d(@x, @y, @z); - transform: translate3d(@x, @y, @z); -} - -// Backface visibility -// Prevent browsers from flickering when using CSS 3D transforms. -// Default value is `visible`, but can be changed to `hidden -// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples -.backface-visibility(@visibility){ - -webkit-backface-visibility: @visibility; - -moz-backface-visibility: @visibility; - backface-visibility: @visibility; -} - -// Background clipping -// Heads up: FF 3.6 and under need "padding" instead of "padding-box" -.background-clip(@clip) { - -webkit-background-clip: @clip; - -moz-background-clip: @clip; - background-clip: @clip; -} - -// Background sizing -.background-size(@size) { - -webkit-background-size: @size; - -moz-background-size: @size; - -o-background-size: @size; - background-size: @size; -} - - -// Box sizing -.box-sizing(@boxmodel) { - -webkit-box-sizing: @boxmodel; - -moz-box-sizing: @boxmodel; - box-sizing: @boxmodel; -} - -// User select -// For selecting text on the page -.user-select(@select) { - -webkit-user-select: @select; - -moz-user-select: @select; - -ms-user-select: @select; - -o-user-select: @select; - user-select: @select; -} - -// Resize anything -.resizable(@direction) { - resize: @direction; // Options: horizontal, vertical, both - overflow: auto; // Safari fix -} - -// CSS3 Content Columns -.content-columns(@columnCount, @columnGap: @gridGutterWidth) { - -webkit-column-count: @columnCount; - -moz-column-count: @columnCount; - column-count: @columnCount; - -webkit-column-gap: @columnGap; - -moz-column-gap: @columnGap; - column-gap: @columnGap; -} - -// Optional hyphenation -.hyphens(@mode: auto) { - word-wrap: break-word; - -webkit-hyphens: @mode; - -moz-hyphens: @mode; - -ms-hyphens: @mode; - -o-hyphens: @mode; - hyphens: @mode; -} - -// Opacity -.opacity(@opacity) { - opacity: @opacity / 100; - filter: ~"alpha(opacity=@{opacity})"; -} - - - -// BACKGROUNDS -// -------------------------------------------------- - -// Add an alphatransparency value to any background or border color (via Elyse Holladay) -#translucent { - .background(@color: @white, @alpha: 1) { - background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); - } - .border(@color: @white, @alpha: 1) { - border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); - .background-clip(padding-box); - } -} - -// Gradient Bar Colors for buttons and alerts -.gradientBar(@primaryColor, @secondaryColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) { - color: @textColor; - text-shadow: @textShadow; - #gradient > .vertical(@primaryColor, @secondaryColor); - border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); - border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); -} - -// Gradients -#gradient { - .horizontal(@startColor: #555, @endColor: #333) { - background-color: @endColor; - background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ - background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ - background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ - background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 - background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 - background-repeat: repeat-x; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down - } - .vertical(@startColor: #555, @endColor: #333) { - background-color: mix(@startColor, @endColor, 60%); - background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ - background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ - background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 - background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 - background-repeat: repeat-x; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down - } - .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { - background-color: @endColor; - background-repeat: repeat-x; - background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ - background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ - background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 - background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10 - } - .horizontal-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { - background-color: mix(@midColor, @endColor, 80%); - background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); - background-image: -webkit-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); - background-image: -moz-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); - background-image: -o-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); - background-image: linear-gradient(to right, @startColor, @midColor @colorStop, @endColor); - background-repeat: no-repeat; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback - } - - .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { - background-color: mix(@midColor, @endColor, 80%); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); - background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); - background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor); - background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); - background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); - background-repeat: no-repeat; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback - } - .radial(@innerColor: #555, @outerColor: #333) { - background-color: @outerColor; - background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor)); - background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor); - background-image: -moz-radial-gradient(circle, @innerColor, @outerColor); - background-image: -o-radial-gradient(circle, @innerColor, @outerColor); - background-repeat: no-repeat; - } - .striped(@color: #555, @angle: 45deg) { - background-color: @color; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); - } -} -// Reset filters for IE -.reset-filter() { - filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); -} - - - -// COMPONENT MIXINS -// -------------------------------------------------- - -// Horizontal dividers -// ------------------------- -// Dividers (basically an hr) within dropdowns and nav lists -.nav-divider(@top: #e5e5e5, @bottom: @white) { - // IE7 needs a set width since we gave a height. Restricting just - // to IE7 to keep the 1px left/right space in other browsers. - // It is unclear where IE is getting the extra space that we need - // to negative-margin away, but so it goes. - *width: 100%; - height: 1px; - margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px - *margin: -5px 0 5px; - overflow: hidden; - background-color: @top; - border-bottom: 1px solid @bottom; -} - -// Button backgrounds -// ------------------ -.buttonBackground(@startColor, @endColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) { - // gradientBar will set the background to a pleasing blend of these, to support IE<=9 - .gradientBar(@startColor, @endColor, @textColor, @textShadow); - *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - .reset-filter(); - - // in these cases the gradient won't cover the background, so we override - &:hover, &:focus, &:active, &.active, &.disabled, &[disabled] { - color: @textColor; - background-color: @endColor; - *background-color: darken(@endColor, 5%); - } - - // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves - &:active, - &.active { - background-color: darken(@endColor, 10%) e("\9"); - } -} - -// Navbar vertical align -// ------------------------- -// Vertically center elements in the navbar. -// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin. -.navbarVerticalAlign(@elementHeight) { - margin-top: (@navbarHeight - @elementHeight) / 2; -} - - - -// Grid System -// ----------- - -// Centered container element -.container-fixed() { - margin-right: auto; - margin-left: auto; - .clearfix(); -} - -// Table columns -.tableColumns(@columnSpan: 1) { - float: none; // undo default grid column styles - width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells - margin-left: 0; // undo default grid column styles -} - -// Make a Grid -// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior -.makeRow() { - margin-left: @gridGutterWidth * -1; - .clearfix(); -} -.makeColumn(@columns: 1, @offset: 0) { - float: left; - margin-left: (@gridColumnWidth * @offset) + (@gridGutterWidth * (@offset - 1)) + (@gridGutterWidth * 2); - width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); -} - -// The Grid -#grid { - - .core (@gridColumnWidth, @gridGutterWidth) { - - .spanX (@index) when (@index > 0) { - .span@{index} { .span(@index); } - .spanX(@index - 1); - } - .spanX (0) {} - - .offsetX (@index) when (@index > 0) { - .offset@{index} { .offset(@index); } - .offsetX(@index - 1); - } - .offsetX (0) {} - - .offset (@columns) { - margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1)); - } - - .span (@columns) { - width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); - } - - .row { - margin-left: @gridGutterWidth * -1; - .clearfix(); - } - - [class*="span"] { - float: left; - min-height: 1px; // prevent collapsing columns - margin-left: @gridGutterWidth; - } - - // Set the container width, and override it for fixed navbars in media queries - .container, - .navbar-static-top .container, - .navbar-fixed-top .container, - .navbar-fixed-bottom .container { .span(@gridColumns); } - - // generate .spanX and .offsetX - .spanX (@gridColumns); - .offsetX (@gridColumns); - - } - - .fluid (@fluidGridColumnWidth, @fluidGridGutterWidth) { - - .spanX (@index) when (@index > 0) { - .span@{index} { .span(@index); } - .spanX(@index - 1); - } - .spanX (0) {} - - .offsetX (@index) when (@index > 0) { - .offset@{index} { .offset(@index); } - .offset@{index}:first-child { .offsetFirstChild(@index); } - .offsetX(@index - 1); - } - .offsetX (0) {} - - .offset (@columns) { - margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth*2); - *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + (@fluidGridGutterWidth*2) - (.5 / @gridRowWidth * 100 * 1%); - } - - .offsetFirstChild (@columns) { - margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth); - *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); - } - - .span (@columns) { - width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)); - *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%); - } - - .row-fluid { - width: 100%; - .clearfix(); - [class*="span"] { - .input-block-level(); - float: left; - margin-left: @fluidGridGutterWidth; - *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); - } - [class*="span"]:first-child { - margin-left: 0; - } - - // Space grid-sized controls properly if multiple per line - .controls-row [class*="span"] + [class*="span"] { - margin-left: @fluidGridGutterWidth; - } - - // generate .spanX and .offsetX - .spanX (@gridColumns); - .offsetX (@gridColumns); - } - - } - - .input(@gridColumnWidth, @gridGutterWidth) { - - .spanX (@index) when (@index > 0) { - input.span@{index}, textarea.span@{index}, .uneditable-input.span@{index} { .span(@index); } - .spanX(@index - 1); - } - .spanX (0) {} - - .span(@columns) { - width: ((@gridColumnWidth) * @columns) + (@gridGutterWidth * (@columns - 1)) - 14; - } - - input, - textarea, - .uneditable-input { - margin-left: 0; // override margin-left from core grid system - } - - // Space grid-sized controls properly if multiple per line - .controls-row [class*="span"] + [class*="span"] { - margin-left: @gridGutterWidth; - } - - // generate .spanX - .spanX (@gridColumns); - - } -} +// +// Mixins +// -------------------------------------------------- + + +// UTILITY MIXINS +// -------------------------------------------------- + +// Clearfix +// -------- +// For clearing floats like a boss h5bp.com/q +.clearfix { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + // Fixes Opera/contenteditable bug: + // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 + line-height: 0; + } + &:after { + clear: both; + } +} + +// Webkit-style focus +// ------------------ +.tab-focus() { + // Default + outline: thin dotted #333; + // Webkit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Center-align a block level element +// ---------------------------------- +.center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// IE7 inline-block +// ---------------- +.ie7-inline-block() { + *display: inline; /* IE7 inline-block hack */ + *zoom: 1; +} + +// IE7 likes to collapse whitespace on either side of the inline-block elements. +// Ems because we're attempting to match the width of a space character. Left +// version is for form buttons, which typically come after other elements, and +// right version is for icons, which come before. Applying both is ok, but it will +// mean that space between those elements will be .6em (~2 space characters) in IE7, +// instead of the 1 space in other browsers. +.ie7-restore-left-whitespace() { + *margin-left: .3em; + + &:first-child { + *margin-left: 0; + } +} + +.ie7-restore-right-whitespace() { + *margin-right: .3em; +} + +// Sizing shortcuts +// ------------------------- +.size(@height, @width) { + width: @width; + height: @height; +} +.square(@size) { + .size(@size, @size); +} + +// Placeholder text +// ------------------------- +.placeholder(@color: @placeholderText) { + &:-moz-placeholder { + color: @color; + } + &:-ms-input-placeholder { + color: @color; + } + &::-webkit-input-placeholder { + color: @color; + } +} + +// Text overflow +// ------------------------- +// Requires inline-block or block for proper styling +.text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// CSS image replacement +// ------------------------- +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + + +// FONTS +// -------------------------------------------------- + +#font { + #family { + .serif() { + font-family: @serifFontFamily; + } + .sans-serif() { + font-family: @sansFontFamily; + } + .monospace() { + font-family: @monoFontFamily; + } + } + .shorthand(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + font-size: @size; + font-weight: @weight; + line-height: @lineHeight; + } + .serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .sans-serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .sans-serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .monospace(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .monospace; + #font > .shorthand(@size, @weight, @lineHeight); + } +} + + +// FORMS +// -------------------------------------------------- + +// Block level inputs +.input-block-level { + display: block; + width: 100%; + min-height: @inputHeight; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + .box-sizing(border-box); // Makes inputs behave like true block-level elements +} + + + +// Mixin for form field states +.formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) { + // Set the text color + .control-label, + .help-block, + .help-inline { + color: @textColor; + } + // Style inputs accordingly + .checkbox, + .radio, + input, + select, + textarea { + color: @textColor; + } + input, + select, + textarea { + border-color: @borderColor; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work + &:focus { + border-color: darken(@borderColor, 10%); + @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@borderColor, 20%); + .box-shadow(@shadow); + } + } + // Give a small background color for input-prepend/-append + .input-prepend .add-on, + .input-append .add-on { + color: @textColor; + background-color: @backgroundColor; + border-color: @textColor; + } +} + + + +// CSS3 PROPERTIES +// -------------------------------------------------- + +// Border Radius +.border-radius(@radius) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} + +// Single Corner Border Radius +.border-top-left-radius(@radius) { + -webkit-border-top-left-radius: @radius; + -moz-border-radius-topleft: @radius; + border-top-left-radius: @radius; +} +.border-top-right-radius(@radius) { + -webkit-border-top-right-radius: @radius; + -moz-border-radius-topright: @radius; + border-top-right-radius: @radius; +} +.border-bottom-right-radius(@radius) { + -webkit-border-bottom-right-radius: @radius; + -moz-border-radius-bottomright: @radius; + border-bottom-right-radius: @radius; +} +.border-bottom-left-radius(@radius) { + -webkit-border-bottom-left-radius: @radius; + -moz-border-radius-bottomleft: @radius; + border-bottom-left-radius: @radius; +} + +// Single Side Border Radius +.border-top-radius(@radius) { + .border-top-right-radius(@radius); + .border-top-left-radius(@radius); +} +.border-right-radius(@radius) { + .border-top-right-radius(@radius); + .border-bottom-right-radius(@radius); +} +.border-bottom-radius(@radius) { + .border-bottom-right-radius(@radius); + .border-bottom-left-radius(@radius); +} +.border-left-radius(@radius) { + .border-top-left-radius(@radius); + .border-bottom-left-radius(@radius); +} + +// Drop shadows +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; + -moz-box-shadow: @shadow; + box-shadow: @shadow; +} + +// Transitions +.transition(@transition) { + -webkit-transition: @transition; + -moz-transition: @transition; + -o-transition: @transition; + transition: @transition; +} +.transition-delay(@transition-delay) { + -webkit-transition-delay: @transition-delay; + -moz-transition-delay: @transition-delay; + -o-transition-delay: @transition-delay; + transition-delay: @transition-delay; +} +.transition-duration(@transition-duration) { + -webkit-transition-duration: @transition-duration; + -moz-transition-duration: @transition-duration; + -o-transition-duration: @transition-duration; + transition-duration: @transition-duration; +} + +// Transformations +.rotate(@degrees) { + -webkit-transform: rotate(@degrees); + -moz-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); + -o-transform: rotate(@degrees); + transform: rotate(@degrees); +} +.scale(@ratio) { + -webkit-transform: scale(@ratio); + -moz-transform: scale(@ratio); + -ms-transform: scale(@ratio); + -o-transform: scale(@ratio); + transform: scale(@ratio); +} +.translate(@x, @y) { + -webkit-transform: translate(@x, @y); + -moz-transform: translate(@x, @y); + -ms-transform: translate(@x, @y); + -o-transform: translate(@x, @y); + transform: translate(@x, @y); +} +.skew(@x, @y) { + -webkit-transform: skew(@x, @y); + -moz-transform: skew(@x, @y); + -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twitter/bootstrap/issues/4885 + -o-transform: skew(@x, @y); + transform: skew(@x, @y); + -webkit-backface-visibility: hidden; // See https://github.com/twitter/bootstrap/issues/5319 +} +.translate3d(@x, @y, @z) { + -webkit-transform: translate3d(@x, @y, @z); + -moz-transform: translate3d(@x, @y, @z); + -o-transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden +// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples +.backface-visibility(@visibility){ + -webkit-backface-visibility: @visibility; + -moz-backface-visibility: @visibility; + backface-visibility: @visibility; +} + +// Background clipping +// Heads up: FF 3.6 and under need "padding" instead of "padding-box" +.background-clip(@clip) { + -webkit-background-clip: @clip; + -moz-background-clip: @clip; + background-clip: @clip; +} + +// Background sizing +.background-size(@size) { + -webkit-background-size: @size; + -moz-background-size: @size; + -o-background-size: @size; + background-size: @size; +} + + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + box-sizing: @boxmodel; +} + +// User select +// For selecting text on the page +.user-select(@select) { + -webkit-user-select: @select; + -moz-user-select: @select; + -ms-user-select: @select; + -o-user-select: @select; + user-select: @select; +} + +// Resize anything +.resizable(@direction) { + resize: @direction; // Options: horizontal, vertical, both + overflow: auto; // Safari fix +} + +// CSS3 Content Columns +.content-columns(@columnCount, @columnGap: @gridGutterWidth) { + -webkit-column-count: @columnCount; + -moz-column-count: @columnCount; + column-count: @columnCount; + -webkit-column-gap: @columnGap; + -moz-column-gap: @columnGap; + column-gap: @columnGap; +} + +// Optional hyphenation +.hyphens(@mode: auto) { + word-wrap: break-word; + -webkit-hyphens: @mode; + -moz-hyphens: @mode; + -ms-hyphens: @mode; + -o-hyphens: @mode; + hyphens: @mode; +} + +// Opacity +.opacity(@opacity) { + opacity: @opacity / 100; + filter: ~"alpha(opacity=@{opacity})"; +} + + + +// BACKGROUNDS +// -------------------------------------------------- + +// Add an alphatransparency value to any background or border color (via Elyse Holladay) +#translucent { + .background(@color: @white, @alpha: 1) { + background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + } + .border(@color: @white, @alpha: 1) { + border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + .background-clip(padding-box); + } +} + +// Gradient Bar Colors for buttons and alerts +.gradientBar(@primaryColor, @secondaryColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) { + color: @textColor; + text-shadow: @textShadow; + #gradient > .vertical(@primaryColor, @secondaryColor); + border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); +} + +// Gradients +#gradient { + .horizontal(@startColor: #555, @endColor: #333) { + background-color: @endColor; + background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down + } + .vertical(@startColor: #555, @endColor: #333) { + background-color: mix(@startColor, @endColor, 60%); + background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down + } + .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { + background-color: @endColor; + background-repeat: repeat-x; + background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10 + } + .horizontal-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + background-color: mix(@midColor, @endColor, 80%); + background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); + background-image: -webkit-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: -moz-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: -o-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: linear-gradient(to right, @startColor, @midColor @colorStop, @endColor); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback + } + + .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + background-color: mix(@midColor, @endColor, 80%); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); + background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor); + background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback + } + .radial(@innerColor: #555, @outerColor: #333) { + background-color: @outerColor; + background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor)); + background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor); + background-image: -moz-radial-gradient(circle, @innerColor, @outerColor); + background-image: -o-radial-gradient(circle, @innerColor, @outerColor); + background-repeat: no-repeat; + } + .striped(@color: #555, @angle: 45deg) { + background-color: @color; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + } +} +// Reset filters for IE +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} + + + +// COMPONENT MIXINS +// -------------------------------------------------- + +// Horizontal dividers +// ------------------------- +// Dividers (basically an hr) within dropdowns and nav lists +.nav-divider(@top: #e5e5e5, @bottom: @white) { + // IE7 needs a set width since we gave a height. Restricting just + // to IE7 to keep the 1px left/right space in other browsers. + // It is unclear where IE is getting the extra space that we need + // to negative-margin away, but so it goes. + *width: 100%; + height: 1px; + margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px + *margin: -5px 0 5px; + overflow: hidden; + background-color: @top; + border-bottom: 1px solid @bottom; +} + +// Button backgrounds +// ------------------ +.buttonBackground(@startColor, @endColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) { + // gradientBar will set the background to a pleasing blend of these, to support IE<=9 + .gradientBar(@startColor, @endColor, @textColor, @textShadow); + *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + .reset-filter(); + + // in these cases the gradient won't cover the background, so we override + &:hover, &:focus, &:active, &.active, &.disabled, &[disabled] { + color: @textColor; + background-color: @endColor; + *background-color: darken(@endColor, 5%); + } + + // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves + &:active, + &.active { + background-color: darken(@endColor, 10%) e("\9"); + } +} + +// Navbar vertical align +// ------------------------- +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin. +.navbarVerticalAlign(@elementHeight) { + margin-top: (@navbarHeight - @elementHeight) / 2; +} + + + +// Grid System +// ----------- + +// Centered container element +.container-fixed() { + margin-right: auto; + margin-left: auto; + .clearfix(); +} + +// Table columns +.tableColumns(@columnSpan: 1) { + float: none; // undo default grid column styles + width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells + margin-left: 0; // undo default grid column styles +} + +// Make a Grid +// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior +.makeRow() { + margin-left: @gridGutterWidth * -1; + .clearfix(); +} +.makeColumn(@columns: 1, @offset: 0) { + float: left; + margin-left: (@gridColumnWidth * @offset) + (@gridGutterWidth * (@offset - 1)) + (@gridGutterWidth * 2); + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); +} + +// The Grid +#grid { + + .core (@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + .span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + .offset@{index} { .offset(@index); } + .offsetX(@index - 1); + } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1)); + } + + .span (@columns) { + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); + } + + .row { + margin-left: @gridGutterWidth * -1; + .clearfix(); + } + + [class*="span"] { + float: left; + min-height: 1px; // prevent collapsing columns + margin-left: @gridGutterWidth; + } + + // Set the container width, and override it for fixed navbars in media queries + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { .span(@gridColumns); } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + + } + + .fluid (@fluidGridColumnWidth, @fluidGridGutterWidth) { + + .spanX (@index) when (@index > 0) { + .span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + .offset@{index} { .offset(@index); } + .offset@{index}:first-child { .offsetFirstChild(@index); } + .offsetX(@index - 1); + } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth*2); + *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + (@fluidGridGutterWidth*2) - (.5 / @gridRowWidth * 100 * 1%); + } + + .offsetFirstChild (@columns) { + margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth); + *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + + .span (@columns) { + width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)); + *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%); + } + + .row-fluid { + width: 100%; + .clearfix(); + [class*="span"] { + .input-block-level(); + float: left; + margin-left: @fluidGridGutterWidth; + *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + [class*="span"]:first-child { + margin-left: 0; + } + + // Space grid-sized controls properly if multiple per line + .controls-row [class*="span"] + [class*="span"] { + margin-left: @fluidGridGutterWidth; + } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + } + + } + + .input(@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + input.span@{index}, textarea.span@{index}, .uneditable-input.span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .span(@columns) { + width: ((@gridColumnWidth) * @columns) + (@gridGutterWidth * (@columns - 1)) - 14; + } + + input, + textarea, + .uneditable-input { + margin-left: 0; // override margin-left from core grid system + } + + // Space grid-sized controls properly if multiple per line + .controls-row [class*="span"] + [class*="span"] { + margin-left: @gridGutterWidth; + } + + // generate .spanX + .spanX (@gridColumns); + + } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/modals.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/modals.less index ad6a3dbf58..8e272d409f 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/modals.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/modals.less @@ -1,95 +1,95 @@ -// -// Modals -// -------------------------------------------------- - -// Background -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: @zindexModalBackdrop; - background-color: @black; - // Fade for backdrop - &.fade { opacity: 0; } -} - -.modal-backdrop, -.modal-backdrop.fade.in { - .opacity(80); -} - -// Base modal -.modal { - position: fixed; - top: 10%; - left: 50%; - z-index: @zindexModal; - width: 560px; - margin-left: -280px; - background-color: @white; - border: 1px solid #999; - border: 1px solid rgba(0,0,0,.3); - *border: 1px solid #999; /* IE6-7 */ - .border-radius(6px); - .box-shadow(0 3px 7px rgba(0,0,0,0.3)); - .background-clip(padding-box); - // Remove focus outline from opened modal - outline: none; - - &.fade { - .transition(e('opacity .3s linear, top .3s ease-out')); - top: -25%; - } - &.fade.in { top: 10%; } -} -.modal-header { - padding: 9px 15px; - border-bottom: 1px solid #eee; - // Close icon - .close { margin-top: 2px; } - // Heading - h3 { - margin: 0; - line-height: 30px; - } -} - -// Body (where all modal content resides) -.modal-body { - position: relative; - overflow-y: auto; - max-height: 400px; - padding: 15px; -} -// Remove bottom margin if need be -.modal-form { - margin-bottom: 0; -} - -// Footer (for actions) -.modal-footer { - padding: 14px 15px 15px; - margin-bottom: 0; - text-align: right; // right align buttons - background-color: #f5f5f5; - border-top: 1px solid #ddd; - .border-radius(0 0 6px 6px); - .box-shadow(inset 0 1px 0 @white); - .clearfix(); // clear it in case folks use .pull-* classes on buttons - - // Properly space out buttons - .btn + .btn { - margin-left: 5px; - margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs - } - // but override that for button groups - .btn-group .btn + .btn { - margin-left: -1px; - } - // and override it for block buttons as well - .btn-block + .btn-block { - margin-left: 0; - } -} +// +// Modals +// -------------------------------------------------- + +// Background +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: @zindexModalBackdrop; + background-color: @black; + // Fade for backdrop + &.fade { opacity: 0; } +} + +.modal-backdrop, +.modal-backdrop.fade.in { + .opacity(80); +} + +// Base modal +.modal { + position: fixed; + top: 10%; + left: 50%; + z-index: @zindexModal; + width: 560px; + margin-left: -280px; + background-color: @white; + border: 1px solid #999; + border: 1px solid rgba(0,0,0,.3); + *border: 1px solid #999; /* IE6-7 */ + .border-radius(6px); + .box-shadow(0 3px 7px rgba(0,0,0,0.3)); + .background-clip(padding-box); + // Remove focus outline from opened modal + outline: none; + + &.fade { + .transition(e('opacity .3s linear, top .3s ease-out')); + top: -25%; + } + &.fade.in { top: 10%; } +} +.modal-header { + padding: 9px 15px; + border-bottom: 1px solid #eee; + // Close icon + .close { margin-top: 2px; } + // Heading + h3 { + margin: 0; + line-height: 30px; + } +} + +// Body (where all modal content resides) +.modal-body { + position: relative; + overflow-y: auto; + max-height: 400px; + padding: 15px; +} +// Remove bottom margin if need be +.modal-form { + margin-bottom: 0; +} + +// Footer (for actions) +.modal-footer { + padding: 14px 15px 15px; + margin-bottom: 0; + text-align: right; // right align buttons + background-color: #f5f5f5; + border-top: 1px solid #ddd; + .border-radius(0 0 6px 6px); + .box-shadow(inset 0 1px 0 @white); + .clearfix(); // clear it in case folks use .pull-* classes on buttons + + // Properly space out buttons + .btn + .btn { + margin-left: 5px; + margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs + } + // but override that for button groups + .btn-group .btn + .btn { + margin-left: -1px; + } + // and override it for block buttons as well + .btn-block + .btn-block { + margin-left: 0; + } +} 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 b4e1edf65f..540e7973bb 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navbar.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navbar.less @@ -1,497 +1,497 @@ -// -// Navbars (Redux) -// -------------------------------------------------- - - -// COMMON STYLES -// ------------- - -// Base class and wrapper -.navbar { - overflow: visible; - margin-bottom: @baseLineHeight; - - // Fix for IE7's bad z-indexing so dropdowns don't appear below content that follows the navbar - *position: relative; - *z-index: 2; -} - -// Inner for background effects -// Gradient is applied to its own element because overflow visible is not honored by IE when filter is present -.navbar-inner { - min-height: @navbarHeight; - padding-left: 20px; - padding-right: 20px; - #gradient > .vertical(@navbarBackgroundHighlight, @navbarBackground); - border: 1px solid @navbarBorder; - .border-radius(@baseBorderRadius); - .box-shadow(0 1px 4px rgba(0,0,0,.065)); - - // Prevent floats from breaking the navbar - .clearfix(); -} - -// Set width to auto for default container -// We then reset it for fixed navbars in the #gridSystem mixin -.navbar .container { - width: auto; -} - -// Override the default collapsed state -.nav-collapse.collapse { - height: auto; - overflow: visible; -} - - -// Brand: website or project name -// ------------------------- -.navbar .brand { - float: left; - display: block; - // Vertically center the text given @navbarHeight - padding: ((@navbarHeight - @baseLineHeight) / 2) 20px ((@navbarHeight - @baseLineHeight) / 2); - margin-left: -20px; // negative indent to left-align the text down the page - font-size: 20px; - font-weight: 200; - color: @navbarBrandColor; - text-shadow: 0 1px 0 @navbarBackgroundHighlight; - &:hover, - &:focus { - text-decoration: none; - } -} - -// Plain text in topbar -// ------------------------- -.navbar-text { - margin-bottom: 0; - line-height: @navbarHeight; - color: @navbarText; -} - -// Janky solution for now to account for links outside the .nav -// ------------------------- -.navbar-link { - color: @navbarLinkColor; - &:hover, - &:focus { - color: @navbarLinkColorHover; - } -} - -// Dividers in navbar -// ------------------------- -.navbar .divider-vertical { - height: @navbarHeight; - margin: 0 9px; - border-left: 1px solid @navbarBackground; - border-right: 1px solid @navbarBackgroundHighlight; -} - -// Buttons in navbar -// ------------------------- -.navbar .btn, -.navbar .btn-group { - .navbarVerticalAlign(30px); // Vertically center in navbar -} -.navbar .btn-group .btn, -.navbar .input-prepend .btn, -.navbar .input-append .btn, -.navbar .input-prepend .btn-group, -.navbar .input-append .btn-group { - margin-top: 0; // then undo the margin here so we don't accidentally double it -} - -// Navbar forms -// ------------------------- -.navbar-form { - margin-bottom: 0; // remove default bottom margin - .clearfix(); - input, - select, - .radio, - .checkbox { - .navbarVerticalAlign(30px); // Vertically center in navbar - } - input, - select, - .btn { - display: inline-block; - margin-bottom: 0; - } - input[type="image"], - input[type="checkbox"], - input[type="radio"] { - margin-top: 3px; - } - .input-append, - .input-prepend { - margin-top: 5px; - white-space: nowrap; // preven two items from separating within a .navbar-form that has .pull-left - input { - margin-top: 0; // remove the margin on top since it's on the parent - } - } -} - -// Navbar search -// ------------------------- -.navbar-search { - position: relative; - float: left; - .navbarVerticalAlign(30px); // Vertically center in navbar - margin-bottom: 0; - .search-query { - margin-bottom: 0; - padding: 4px 14px; - #font > .sans-serif(13px, normal, 1); - .border-radius(15px); // redeclare because of specificity of the type attribute - } -} - - - -// Static navbar -// ------------------------- - -.navbar-static-top { - position: static; - margin-bottom: 0; // remove 18px margin for default navbar - .navbar-inner { - .border-radius(0); - } -} - - - -// Fixed navbar -// ------------------------- - -// Shared (top/bottom) styles -.navbar-fixed-top, -.navbar-fixed-bottom { - position: fixed; - right: 0; - left: 0; - z-index: @zindexFixedNavbar; - margin-bottom: 0; // remove 18px margin for default navbar -} -.navbar-fixed-top .navbar-inner, -.navbar-static-top .navbar-inner { - border-width: 0 0 1px; -} -.navbar-fixed-bottom .navbar-inner { - border-width: 1px 0 0; -} -.navbar-fixed-top .navbar-inner, -.navbar-fixed-bottom .navbar-inner { - padding-left: 0; - padding-right: 0; - .border-radius(0); -} - -// 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-fixed-top .container, -.navbar-fixed-bottom .container { -width: (@gridColumnWidth * @gridColumns) + (@gridGutterWidth * (@gridColumns - 1)); -} - -// Fixed to top -.navbar-fixed-top { - top: 0; -} -.navbar-fixed-top, -.navbar-static-top { - .navbar-inner { - .box-shadow(~"0 1px 10px rgba(0,0,0,.1)"); - } -} - -// Fixed to bottom -.navbar-fixed-bottom { - bottom: 0; - .navbar-inner { - .box-shadow(~"0 -1px 10px rgba(0,0,0,.1)"); - } -} - - - -// NAVIGATION -// ---------- - -.navbar .nav { - position: relative; - left: 0; - display: block; - float: left; - margin: 0 10px 0 0; -} -.navbar .nav.pull-right { - float: right; // redeclare due to specificity - margin-right: 0; // remove margin on float right nav -} -.navbar .nav > li { - float: left; -} - -// Links -.navbar .nav > li > a { - float: none; - // Vertically center the text given @navbarHeight - padding: ((@navbarHeight - @baseLineHeight) / 2) 15px ((@navbarHeight - @baseLineHeight) / 2); - color: @navbarLinkColor; - text-decoration: none; - text-shadow: 0 1px 0 @navbarBackgroundHighlight; -} -.navbar .nav .dropdown-toggle .caret { - margin-top: 8px; -} - -// Hover/focus -.navbar .nav > li > a:focus, -.navbar .nav > li > a:hover { - background-color: @navbarLinkBackgroundHover; // "transparent" is default to differentiate :hover/:focus from .active - color: @navbarLinkColorHover; - text-decoration: none; -} - -// Active nav items -.navbar .nav > .active > a, -.navbar .nav > .active > a:hover, -.navbar .nav > .active > a:focus { - color: @navbarLinkColorActive; - text-decoration: none; - background-color: @navbarLinkBackgroundActive; - .box-shadow(inset 0 3px 8px rgba(0,0,0,.125)); -} - -// Navbar button for toggling navbar items in responsive layouts -// These definitions need to come after '.navbar .btn' -.navbar .btn-navbar { - display: none; - float: right; - padding: 7px 10px; - margin-left: 5px; - margin-right: 5px; - .buttonBackground(darken(@navbarBackgroundHighlight, 5%), darken(@navbarBackground, 5%)); - .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075)"); -} -.navbar .btn-navbar .icon-bar { - display: block; - width: 18px; - height: 2px; - background-color: #f5f5f5; - .border-radius(1px); - .box-shadow(0 1px 0 rgba(0,0,0,.25)); -} -.btn-navbar .icon-bar + .icon-bar { - margin-top: 3px; -} - - - -// Dropdown menus -// -------------- - -// Menu position and menu carets -.navbar .nav > li > .dropdown-menu { - &:before { - content: ''; - display: inline-block; - border-left: 7px solid transparent; - border-right: 7px solid transparent; - border-bottom: 7px solid #ccc; - border-bottom-color: @dropdownBorder; - position: absolute; - top: -7px; - left: 9px; - } - &:after { - content: ''; - display: inline-block; - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-bottom: 6px solid @dropdownBackground; - position: absolute; - top: -6px; - left: 10px; - } -} -// Menu position and menu caret support for dropups via extra dropup class -.navbar-fixed-bottom .nav > li > .dropdown-menu { - &:before { - border-top: 7px solid #ccc; - border-top-color: @dropdownBorder; - border-bottom: 0; - bottom: -7px; - top: auto; - } - &:after { - border-top: 6px solid @dropdownBackground; - border-bottom: 0; - bottom: -6px; - top: auto; - } -} - -// Caret should match text color on hover/focus -.navbar .nav li.dropdown > a:hover .caret, -.navbar .nav li.dropdown > a:focus .caret { - border-top-color: @navbarLinkColorHover; - border-bottom-color: @navbarLinkColorHover; -} - -// Remove background color from open dropdown -.navbar .nav li.dropdown.open > .dropdown-toggle, -.navbar .nav li.dropdown.active > .dropdown-toggle, -.navbar .nav li.dropdown.open.active > .dropdown-toggle { - background-color: @navbarLinkBackgroundActive; - color: @navbarLinkColorActive; -} -.navbar .nav li.dropdown > .dropdown-toggle .caret { - border-top-color: @navbarLinkColor; - border-bottom-color: @navbarLinkColor; -} -.navbar .nav li.dropdown.open > .dropdown-toggle .caret, -.navbar .nav li.dropdown.active > .dropdown-toggle .caret, -.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret { - border-top-color: @navbarLinkColorActive; - border-bottom-color: @navbarLinkColorActive; -} - -// Right aligned menus need alt position -.navbar .pull-right > li > .dropdown-menu, -.navbar .nav > li > .dropdown-menu.pull-right { - left: auto; - right: 0; - &:before { - left: auto; - right: 12px; - } - &:after { - left: auto; - right: 13px; - } - .dropdown-menu { - left: auto; - right: 100%; - margin-left: 0; - margin-right: -1px; - .border-radius(6px 0 6px 6px); - } -} - - -// Inverted navbar -// ------------------------- - -.navbar-inverse { - - .navbar-inner { - #gradient > .vertical(@navbarInverseBackgroundHighlight, @navbarInverseBackground); - border-color: @navbarInverseBorder; - } - - .brand, - .nav > li > a { - color: @navbarInverseLinkColor; - text-shadow: 0 -1px 0 rgba(0,0,0,.25); - &:hover, - &:focus { - color: @navbarInverseLinkColorHover; - } - } - - .brand { - color: @navbarInverseBrandColor; - } - - .navbar-text { - color: @navbarInverseText; - } - - .nav > li > a:focus, - .nav > li > a:hover { - background-color: @navbarInverseLinkBackgroundHover; - color: @navbarInverseLinkColorHover; - } - - .nav .active > a, - .nav .active > a:hover, - .nav .active > a:focus { - color: @navbarInverseLinkColorActive; - background-color: @navbarInverseLinkBackgroundActive; - } - - // Inline text links - .navbar-link { - color: @navbarInverseLinkColor; - &:hover, - &:focus { - color: @navbarInverseLinkColorHover; - } - } - - // Dividers in navbar - .divider-vertical { - border-left-color: @navbarInverseBackground; - border-right-color: @navbarInverseBackgroundHighlight; - } - - // Dropdowns - .nav li.dropdown.open > .dropdown-toggle, - .nav li.dropdown.active > .dropdown-toggle, - .nav li.dropdown.open.active > .dropdown-toggle { - background-color: @navbarInverseLinkBackgroundActive; - color: @navbarInverseLinkColorActive; - } - .nav li.dropdown > a:hover .caret, - .nav li.dropdown > a:focus .caret { - border-top-color: @navbarInverseLinkColorActive; - border-bottom-color: @navbarInverseLinkColorActive; - } - .nav li.dropdown > .dropdown-toggle .caret { - border-top-color: @navbarInverseLinkColor; - border-bottom-color: @navbarInverseLinkColor; - } - .nav li.dropdown.open > .dropdown-toggle .caret, - .nav li.dropdown.active > .dropdown-toggle .caret, - .nav li.dropdown.open.active > .dropdown-toggle .caret { - border-top-color: @navbarInverseLinkColorActive; - border-bottom-color: @navbarInverseLinkColorActive; - } - - // Navbar search - .navbar-search { - .search-query { - color: @white; - background-color: @navbarInverseSearchBackground; - border-color: @navbarInverseSearchBorder; - .box-shadow(~"inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15)"); - .transition(none); - .placeholder(@navbarInverseSearchPlaceholderColor); - - // Focus states (we use .focused since IE7-8 and down doesn't support :focus) - &:focus, - &.focused { - padding: 5px 15px; - color: @grayDark; - text-shadow: 0 1px 0 @white; - background-color: @navbarInverseSearchBackgroundFocus; - border: 0; - .box-shadow(0 0 3px rgba(0,0,0,.15)); - outline: 0; - } - } - } - - // Navbar collapse button - .btn-navbar { - .buttonBackground(darken(@navbarInverseBackgroundHighlight, 5%), darken(@navbarInverseBackground, 5%)); - } - -} +// +// Navbars (Redux) +// -------------------------------------------------- + + +// COMMON STYLES +// ------------- + +// Base class and wrapper +.navbar { + overflow: visible; + margin-bottom: @baseLineHeight; + + // Fix for IE7's bad z-indexing so dropdowns don't appear below content that follows the navbar + *position: relative; + *z-index: 2; +} + +// Inner for background effects +// Gradient is applied to its own element because overflow visible is not honored by IE when filter is present +.navbar-inner { + min-height: @navbarHeight; + padding-left: 20px; + padding-right: 20px; + #gradient > .vertical(@navbarBackgroundHighlight, @navbarBackground); + border: 1px solid @navbarBorder; + .border-radius(@baseBorderRadius); + .box-shadow(0 1px 4px rgba(0,0,0,.065)); + + // Prevent floats from breaking the navbar + .clearfix(); +} + +// Set width to auto for default container +// We then reset it for fixed navbars in the #gridSystem mixin +.navbar .container { + width: auto; +} + +// Override the default collapsed state +.nav-collapse.collapse { + height: auto; + overflow: visible; +} + + +// Brand: website or project name +// ------------------------- +.navbar .brand { + float: left; + display: block; + // Vertically center the text given @navbarHeight + padding: ((@navbarHeight - @baseLineHeight) / 2) 20px ((@navbarHeight - @baseLineHeight) / 2); + margin-left: -20px; // negative indent to left-align the text down the page + font-size: 20px; + font-weight: 200; + color: @navbarBrandColor; + text-shadow: 0 1px 0 @navbarBackgroundHighlight; + &:hover, + &:focus { + text-decoration: none; + } +} + +// Plain text in topbar +// ------------------------- +.navbar-text { + margin-bottom: 0; + line-height: @navbarHeight; + color: @navbarText; +} + +// Janky solution for now to account for links outside the .nav +// ------------------------- +.navbar-link { + color: @navbarLinkColor; + &:hover, + &:focus { + color: @navbarLinkColorHover; + } +} + +// Dividers in navbar +// ------------------------- +.navbar .divider-vertical { + height: @navbarHeight; + margin: 0 9px; + border-left: 1px solid @navbarBackground; + border-right: 1px solid @navbarBackgroundHighlight; +} + +// Buttons in navbar +// ------------------------- +.navbar .btn, +.navbar .btn-group { + .navbarVerticalAlign(30px); // Vertically center in navbar +} +.navbar .btn-group .btn, +.navbar .input-prepend .btn, +.navbar .input-append .btn, +.navbar .input-prepend .btn-group, +.navbar .input-append .btn-group { + margin-top: 0; // then undo the margin here so we don't accidentally double it +} + +// Navbar forms +// ------------------------- +.navbar-form { + margin-bottom: 0; // remove default bottom margin + .clearfix(); + input, + select, + .radio, + .checkbox { + .navbarVerticalAlign(30px); // Vertically center in navbar + } + input, + select, + .btn { + display: inline-block; + margin-bottom: 0; + } + input[type="image"], + input[type="checkbox"], + input[type="radio"] { + margin-top: 3px; + } + .input-append, + .input-prepend { + margin-top: 5px; + white-space: nowrap; // preven two items from separating within a .navbar-form that has .pull-left + input { + margin-top: 0; // remove the margin on top since it's on the parent + } + } +} + +// Navbar search +// ------------------------- +.navbar-search { + position: relative; + float: left; + .navbarVerticalAlign(30px); // Vertically center in navbar + margin-bottom: 0; + .search-query { + margin-bottom: 0; + padding: 4px 14px; + #font > .sans-serif(13px, normal, 1); + .border-radius(15px); // redeclare because of specificity of the type attribute + } +} + + + +// Static navbar +// ------------------------- + +.navbar-static-top { + position: static; + margin-bottom: 0; // remove 18px margin for default navbar + .navbar-inner { + .border-radius(0); + } +} + + + +// Fixed navbar +// ------------------------- + +// Shared (top/bottom) styles +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: @zindexFixedNavbar; + margin-bottom: 0; // remove 18px margin for default navbar +} +.navbar-fixed-top .navbar-inner, +.navbar-static-top .navbar-inner { + border-width: 0 0 1px; +} +.navbar-fixed-bottom .navbar-inner { + border-width: 1px 0 0; +} +.navbar-fixed-top .navbar-inner, +.navbar-fixed-bottom .navbar-inner { + padding-left: 0; + padding-right: 0; + .border-radius(0); +} + +// 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-fixed-top .container, +.navbar-fixed-bottom .container { +width: (@gridColumnWidth * @gridColumns) + (@gridGutterWidth * (@gridColumns - 1)); +} + +// Fixed to top +.navbar-fixed-top { + top: 0; +} +.navbar-fixed-top, +.navbar-static-top { + .navbar-inner { + .box-shadow(~"0 1px 10px rgba(0,0,0,.1)"); + } +} + +// Fixed to bottom +.navbar-fixed-bottom { + bottom: 0; + .navbar-inner { + .box-shadow(~"0 -1px 10px rgba(0,0,0,.1)"); + } +} + + + +// NAVIGATION +// ---------- + +.navbar .nav { + position: relative; + left: 0; + display: block; + float: left; + margin: 0 10px 0 0; +} +.navbar .nav.pull-right { + float: right; // redeclare due to specificity + margin-right: 0; // remove margin on float right nav +} +.navbar .nav > li { + float: left; +} + +// Links +.navbar .nav > li > a { + float: none; + // Vertically center the text given @navbarHeight + padding: ((@navbarHeight - @baseLineHeight) / 2) 15px ((@navbarHeight - @baseLineHeight) / 2); + color: @navbarLinkColor; + text-decoration: none; + text-shadow: 0 1px 0 @navbarBackgroundHighlight; +} +.navbar .nav .dropdown-toggle .caret { + margin-top: 8px; +} + +// Hover/focus +.navbar .nav > li > a:focus, +.navbar .nav > li > a:hover { + background-color: @navbarLinkBackgroundHover; // "transparent" is default to differentiate :hover/:focus from .active + color: @navbarLinkColorHover; + text-decoration: none; +} + +// Active nav items +.navbar .nav > .active > a, +.navbar .nav > .active > a:hover, +.navbar .nav > .active > a:focus { + color: @navbarLinkColorActive; + text-decoration: none; + background-color: @navbarLinkBackgroundActive; + .box-shadow(inset 0 3px 8px rgba(0,0,0,.125)); +} + +// Navbar button for toggling navbar items in responsive layouts +// These definitions need to come after '.navbar .btn' +.navbar .btn-navbar { + display: none; + float: right; + padding: 7px 10px; + margin-left: 5px; + margin-right: 5px; + .buttonBackground(darken(@navbarBackgroundHighlight, 5%), darken(@navbarBackground, 5%)); + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075)"); +} +.navbar .btn-navbar .icon-bar { + display: block; + width: 18px; + height: 2px; + background-color: #f5f5f5; + .border-radius(1px); + .box-shadow(0 1px 0 rgba(0,0,0,.25)); +} +.btn-navbar .icon-bar + .icon-bar { + margin-top: 3px; +} + + + +// Dropdown menus +// -------------- + +// Menu position and menu carets +.navbar .nav > li > .dropdown-menu { + &:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: @dropdownBorder; + position: absolute; + top: -7px; + left: 9px; + } + &:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid @dropdownBackground; + position: absolute; + top: -6px; + left: 10px; + } +} +// Menu position and menu caret support for dropups via extra dropup class +.navbar-fixed-bottom .nav > li > .dropdown-menu { + &:before { + border-top: 7px solid #ccc; + border-top-color: @dropdownBorder; + border-bottom: 0; + bottom: -7px; + top: auto; + } + &:after { + border-top: 6px solid @dropdownBackground; + border-bottom: 0; + bottom: -6px; + top: auto; + } +} + +// Caret should match text color on hover/focus +.navbar .nav li.dropdown > a:hover .caret, +.navbar .nav li.dropdown > a:focus .caret { + border-top-color: @navbarLinkColorHover; + border-bottom-color: @navbarLinkColorHover; +} + +// Remove background color from open dropdown +.navbar .nav li.dropdown.open > .dropdown-toggle, +.navbar .nav li.dropdown.active > .dropdown-toggle, +.navbar .nav li.dropdown.open.active > .dropdown-toggle { + background-color: @navbarLinkBackgroundActive; + color: @navbarLinkColorActive; +} +.navbar .nav li.dropdown > .dropdown-toggle .caret { + border-top-color: @navbarLinkColor; + border-bottom-color: @navbarLinkColor; +} +.navbar .nav li.dropdown.open > .dropdown-toggle .caret, +.navbar .nav li.dropdown.active > .dropdown-toggle .caret, +.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret { + border-top-color: @navbarLinkColorActive; + border-bottom-color: @navbarLinkColorActive; +} + +// Right aligned menus need alt position +.navbar .pull-right > li > .dropdown-menu, +.navbar .nav > li > .dropdown-menu.pull-right { + left: auto; + right: 0; + &:before { + left: auto; + right: 12px; + } + &:after { + left: auto; + right: 13px; + } + .dropdown-menu { + left: auto; + right: 100%; + margin-left: 0; + margin-right: -1px; + .border-radius(6px 0 6px 6px); + } +} + + +// Inverted navbar +// ------------------------- + +.navbar-inverse { + + .navbar-inner { + #gradient > .vertical(@navbarInverseBackgroundHighlight, @navbarInverseBackground); + border-color: @navbarInverseBorder; + } + + .brand, + .nav > li > a { + color: @navbarInverseLinkColor; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + &:hover, + &:focus { + color: @navbarInverseLinkColorHover; + } + } + + .brand { + color: @navbarInverseBrandColor; + } + + .navbar-text { + color: @navbarInverseText; + } + + .nav > li > a:focus, + .nav > li > a:hover { + background-color: @navbarInverseLinkBackgroundHover; + color: @navbarInverseLinkColorHover; + } + + .nav .active > a, + .nav .active > a:hover, + .nav .active > a:focus { + color: @navbarInverseLinkColorActive; + background-color: @navbarInverseLinkBackgroundActive; + } + + // Inline text links + .navbar-link { + color: @navbarInverseLinkColor; + &:hover, + &:focus { + color: @navbarInverseLinkColorHover; + } + } + + // Dividers in navbar + .divider-vertical { + border-left-color: @navbarInverseBackground; + border-right-color: @navbarInverseBackgroundHighlight; + } + + // Dropdowns + .nav li.dropdown.open > .dropdown-toggle, + .nav li.dropdown.active > .dropdown-toggle, + .nav li.dropdown.open.active > .dropdown-toggle { + background-color: @navbarInverseLinkBackgroundActive; + color: @navbarInverseLinkColorActive; + } + .nav li.dropdown > a:hover .caret, + .nav li.dropdown > a:focus .caret { + border-top-color: @navbarInverseLinkColorActive; + border-bottom-color: @navbarInverseLinkColorActive; + } + .nav li.dropdown > .dropdown-toggle .caret { + border-top-color: @navbarInverseLinkColor; + border-bottom-color: @navbarInverseLinkColor; + } + .nav li.dropdown.open > .dropdown-toggle .caret, + .nav li.dropdown.active > .dropdown-toggle .caret, + .nav li.dropdown.open.active > .dropdown-toggle .caret { + border-top-color: @navbarInverseLinkColorActive; + border-bottom-color: @navbarInverseLinkColorActive; + } + + // Navbar search + .navbar-search { + .search-query { + color: @white; + background-color: @navbarInverseSearchBackground; + border-color: @navbarInverseSearchBorder; + .box-shadow(~"inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15)"); + .transition(none); + .placeholder(@navbarInverseSearchPlaceholderColor); + + // Focus states (we use .focused since IE7-8 and down doesn't support :focus) + &:focus, + &.focused { + padding: 5px 15px; + color: @grayDark; + text-shadow: 0 1px 0 @white; + background-color: @navbarInverseSearchBackgroundFocus; + border: 0; + .box-shadow(0 0 3px rgba(0,0,0,.15)); + outline: 0; + } + } + } + + // Navbar collapse button + .btn-navbar { + .buttonBackground(darken(@navbarInverseBackgroundHighlight, 5%), darken(@navbarInverseBackground, 5%)); + } + +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navs.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navs.less index 71ada2003a..01cd805bde 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navs.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/navs.less @@ -1,409 +1,409 @@ -// -// Navs -// -------------------------------------------------- - - -// BASE CLASS -// ---------- - -.nav { - margin-left: 0; - margin-bottom: @baseLineHeight; - list-style: none; -} - -// Make links block level -.nav > li > a { - display: block; -} -.nav > li > a:hover, -.nav > li > a:focus { - text-decoration: none; - background-color: @grayLighter; -} - -// Prevent IE8 from misplacing imgs -// See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989 -.nav > li > a > img { - max-width: none; -} - -// Redeclare pull classes because of specifity -.nav > .pull-right { - float: right; -} - -// Nav headers (for dropdowns and lists) -.nav-header { - display: block; - padding: 3px 15px; - font-size: 11px; - font-weight: bold; - line-height: @baseLineHeight; - color: @grayLight; - text-shadow: 0 1px 0 rgba(255,255,255,.5); - text-transform: uppercase; -} -// Space them out when they follow another list item (link) -.nav li + .nav-header { - margin-top: 9px; -} - - - -// NAV LIST -// -------- - -.nav-list { - padding-left: 15px; - padding-right: 15px; - margin-bottom: 0; -} -.nav-list > li > a, -.nav-list .nav-header { - margin-left: -15px; - margin-right: -15px; - text-shadow: 0 1px 0 rgba(255,255,255,.5); -} -.nav-list > li > a { - padding: 3px 15px; -} -.nav-list > .active > a, -.nav-list > .active > a:hover, -.nav-list > .active > a:focus { - color: @white; - text-shadow: 0 -1px 0 rgba(0,0,0,.2); - background-color: @linkColor; -} -.nav-list [class^="icon-"], -.nav-list [class*=" icon-"] { - margin-right: 2px; -} -// Dividers (basically an hr) within the dropdown -.nav-list .divider { - .nav-divider(); -} - - - -// TABS AND PILLS -// ------------- - -// Common styles -.nav-tabs, -.nav-pills { - .clearfix(); -} -.nav-tabs > li, -.nav-pills > li { - float: left; -} -.nav-tabs > li > a, -.nav-pills > li > a { - padding-right: 12px; - padding-left: 12px; - margin-right: 2px; - line-height: 14px; // keeps the overall height an even number -} - -// TABS -// ---- - -// Give the tabs something to sit on -.nav-tabs { - border-bottom: 1px solid #ddd; -} -// Make the list-items overlay the bottom border -.nav-tabs > li { - margin-bottom: -1px; -} -// Actual tabs (as links) -.nav-tabs > li > a { - padding-top: 8px; - padding-bottom: 8px; - line-height: @baseLineHeight; - border: 1px solid transparent; - .border-radius(4px 4px 0 0); - &:hover, - &:focus { - border-color: @grayLighter @grayLighter #ddd; - } -} -// Active state, and it's :hover/:focus to override normal :hover/:focus -.nav-tabs > .active > a, -.nav-tabs > .active > a:hover, -.nav-tabs > .active > a:focus { - color: @gray; - background-color: @bodyBackground; - border: 1px solid #ddd; - border-bottom-color: transparent; - cursor: default; -} - - -// PILLS -// ----- - -// Links rendered as pills -.nav-pills > li > a { - padding-top: 8px; - padding-bottom: 8px; - margin-top: 2px; - margin-bottom: 2px; - .border-radius(5px); -} - -// Active state -.nav-pills > .active > a, -.nav-pills > .active > a:hover, -.nav-pills > .active > a:focus { - color: @white; - background-color: @linkColor; -} - - - -// STACKED NAV -// ----------- - -// Stacked tabs and pills -.nav-stacked > li { - float: none; -} -.nav-stacked > li > a { - margin-right: 0; // no need for the gap between nav items -} - -// Tabs -.nav-tabs.nav-stacked { - border-bottom: 0; -} -.nav-tabs.nav-stacked > li > a { - border: 1px solid #ddd; - .border-radius(0); -} -.nav-tabs.nav-stacked > li:first-child > a { - .border-top-radius(4px); -} -.nav-tabs.nav-stacked > li:last-child > a { - .border-bottom-radius(4px); -} -.nav-tabs.nav-stacked > li > a:hover, -.nav-tabs.nav-stacked > li > a:focus { - border-color: #ddd; - z-index: 2; -} - -// Pills -.nav-pills.nav-stacked > li > a { - margin-bottom: 3px; -} -.nav-pills.nav-stacked > li:last-child > a { - margin-bottom: 1px; // decrease margin to match sizing of stacked tabs -} - - - -// DROPDOWNS -// --------- - -.nav-tabs .dropdown-menu { - .border-radius(0 0 6px 6px); // remove the top rounded corners here since there is a hard edge above the menu -} -.nav-pills .dropdown-menu { - .border-radius(6px); // make rounded corners match the pills -} - -// Default dropdown links -// ------------------------- -// Make carets use linkColor to start -.nav .dropdown-toggle .caret { - border-top-color: @linkColor; - border-bottom-color: @linkColor; - margin-top: 6px; -} -.nav .dropdown-toggle:hover .caret, -.nav .dropdown-toggle:focus .caret { - border-top-color: @linkColorHover; - border-bottom-color: @linkColorHover; -} -/* move down carets for tabs */ -.nav-tabs .dropdown-toggle .caret { - margin-top: 8px; -} - -// Active dropdown links -// ------------------------- -.nav .active .dropdown-toggle .caret { - border-top-color: #fff; - border-bottom-color: #fff; -} -.nav-tabs .active .dropdown-toggle .caret { - border-top-color: @gray; - border-bottom-color: @gray; -} - -// Active:hover/:focus dropdown links -// ------------------------- -.nav > .dropdown.active > a:hover, -.nav > .dropdown.active > a:focus { - cursor: pointer; -} - -// Open dropdowns -// ------------------------- -.nav-tabs .open .dropdown-toggle, -.nav-pills .open .dropdown-toggle, -.nav > li.dropdown.open.active > a:hover, -.nav > li.dropdown.open.active > a:focus { - color: @white; - background-color: @grayLight; - border-color: @grayLight; -} -.nav li.dropdown.open .caret, -.nav li.dropdown.open.active .caret, -.nav li.dropdown.open a:hover .caret, -.nav li.dropdown.open a:focus .caret { - border-top-color: @white; - border-bottom-color: @white; - .opacity(100); -} - -// Dropdowns in stacked tabs -.tabs-stacked .open > a:hover, -.tabs-stacked .open > a:focus { - border-color: @grayLight; -} - - - -// TABBABLE -// -------- - - -// COMMON STYLES -// ------------- - -// Clear any floats -.tabbable { - .clearfix(); -} -.tab-content { - overflow: auto; // prevent content from running below tabs -} - -// Remove border on bottom, left, right -.tabs-below > .nav-tabs, -.tabs-right > .nav-tabs, -.tabs-left > .nav-tabs { - border-bottom: 0; -} - -// Show/hide tabbable areas -.tab-content > .tab-pane, -.pill-content > .pill-pane { - display: none; -} -.tab-content > .active, -.pill-content > .active { - display: block; -} - - -// BOTTOM -// ------ - -.tabs-below > .nav-tabs { - border-top: 1px solid #ddd; -} -.tabs-below > .nav-tabs > li { - margin-top: -1px; - margin-bottom: 0; -} -.tabs-below > .nav-tabs > li > a { - .border-radius(0 0 4px 4px); - &:hover, - &:focus { - border-bottom-color: transparent; - border-top-color: #ddd; - } -} -.tabs-below > .nav-tabs > .active > a, -.tabs-below > .nav-tabs > .active > a:hover, -.tabs-below > .nav-tabs > .active > a:focus { - border-color: transparent #ddd #ddd #ddd; -} - -// LEFT & RIGHT -// ------------ - -// Common styles -.tabs-left > .nav-tabs > li, -.tabs-right > .nav-tabs > li { - float: none; -} -.tabs-left > .nav-tabs > li > a, -.tabs-right > .nav-tabs > li > a { - min-width: 74px; - margin-right: 0; - margin-bottom: 3px; -} - -// Tabs on the left -.tabs-left > .nav-tabs { - float: left; - margin-right: 19px; - border-right: 1px solid #ddd; -} -.tabs-left > .nav-tabs > li > a { - margin-right: -1px; - .border-radius(4px 0 0 4px); -} -.tabs-left > .nav-tabs > li > a:hover, -.tabs-left > .nav-tabs > li > a:focus { - border-color: @grayLighter #ddd @grayLighter @grayLighter; -} -.tabs-left > .nav-tabs .active > a, -.tabs-left > .nav-tabs .active > a:hover, -.tabs-left > .nav-tabs .active > a:focus { - border-color: #ddd transparent #ddd #ddd; - *border-right-color: @white; -} - -// Tabs on the right -.tabs-right > .nav-tabs { - float: right; - margin-left: 19px; - border-left: 1px solid #ddd; -} -.tabs-right > .nav-tabs > li > a { - margin-left: -1px; - .border-radius(0 4px 4px 0); -} -.tabs-right > .nav-tabs > li > a:hover, -.tabs-right > .nav-tabs > li > a:focus { - border-color: @grayLighter @grayLighter @grayLighter #ddd; -} -.tabs-right > .nav-tabs .active > a, -.tabs-right > .nav-tabs .active > a:hover, -.tabs-right > .nav-tabs .active > a:focus { - border-color: #ddd #ddd #ddd transparent; - *border-left-color: @white; -} - - - -// DISABLED STATES -// --------------- - -// Gray out text -.nav > .disabled > a { - color: @grayLight; -} -// Nuke hover/focus effects -.nav > .disabled > a:hover, -.nav > .disabled > a:focus { - text-decoration: none; - background-color: transparent; - cursor: default; -} +// +// Navs +// -------------------------------------------------- + + +// BASE CLASS +// ---------- + +.nav { + margin-left: 0; + margin-bottom: @baseLineHeight; + list-style: none; +} + +// Make links block level +.nav > li > a { + display: block; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: @grayLighter; +} + +// Prevent IE8 from misplacing imgs +// See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989 +.nav > li > a > img { + max-width: none; +} + +// Redeclare pull classes because of specifity +.nav > .pull-right { + float: right; +} + +// Nav headers (for dropdowns and lists) +.nav-header { + display: block; + padding: 3px 15px; + font-size: 11px; + font-weight: bold; + line-height: @baseLineHeight; + color: @grayLight; + text-shadow: 0 1px 0 rgba(255,255,255,.5); + text-transform: uppercase; +} +// Space them out when they follow another list item (link) +.nav li + .nav-header { + margin-top: 9px; +} + + + +// NAV LIST +// -------- + +.nav-list { + padding-left: 15px; + padding-right: 15px; + margin-bottom: 0; +} +.nav-list > li > a, +.nav-list .nav-header { + margin-left: -15px; + margin-right: -15px; + text-shadow: 0 1px 0 rgba(255,255,255,.5); +} +.nav-list > li > a { + padding: 3px 15px; +} +.nav-list > .active > a, +.nav-list > .active > a:hover, +.nav-list > .active > a:focus { + color: @white; + text-shadow: 0 -1px 0 rgba(0,0,0,.2); + background-color: @linkColor; +} +.nav-list [class^="icon-"], +.nav-list [class*=" icon-"] { + margin-right: 2px; +} +// Dividers (basically an hr) within the dropdown +.nav-list .divider { + .nav-divider(); +} + + + +// TABS AND PILLS +// ------------- + +// Common styles +.nav-tabs, +.nav-pills { + .clearfix(); +} +.nav-tabs > li, +.nav-pills > li { + float: left; +} +.nav-tabs > li > a, +.nav-pills > li > a { + padding-right: 12px; + padding-left: 12px; + margin-right: 2px; + line-height: 14px; // keeps the overall height an even number +} + +// TABS +// ---- + +// Give the tabs something to sit on +.nav-tabs { + border-bottom: 1px solid #ddd; +} +// Make the list-items overlay the bottom border +.nav-tabs > li { + margin-bottom: -1px; +} +// Actual tabs (as links) +.nav-tabs > li > a { + padding-top: 8px; + padding-bottom: 8px; + line-height: @baseLineHeight; + border: 1px solid transparent; + .border-radius(4px 4px 0 0); + &:hover, + &:focus { + border-color: @grayLighter @grayLighter #ddd; + } +} +// Active state, and it's :hover/:focus to override normal :hover/:focus +.nav-tabs > .active > a, +.nav-tabs > .active > a:hover, +.nav-tabs > .active > a:focus { + color: @gray; + background-color: @bodyBackground; + border: 1px solid #ddd; + border-bottom-color: transparent; + cursor: default; +} + + +// PILLS +// ----- + +// Links rendered as pills +.nav-pills > li > a { + padding-top: 8px; + padding-bottom: 8px; + margin-top: 2px; + margin-bottom: 2px; + .border-radius(5px); +} + +// Active state +.nav-pills > .active > a, +.nav-pills > .active > a:hover, +.nav-pills > .active > a:focus { + color: @white; + background-color: @linkColor; +} + + + +// STACKED NAV +// ----------- + +// Stacked tabs and pills +.nav-stacked > li { + float: none; +} +.nav-stacked > li > a { + margin-right: 0; // no need for the gap between nav items +} + +// Tabs +.nav-tabs.nav-stacked { + border-bottom: 0; +} +.nav-tabs.nav-stacked > li > a { + border: 1px solid #ddd; + .border-radius(0); +} +.nav-tabs.nav-stacked > li:first-child > a { + .border-top-radius(4px); +} +.nav-tabs.nav-stacked > li:last-child > a { + .border-bottom-radius(4px); +} +.nav-tabs.nav-stacked > li > a:hover, +.nav-tabs.nav-stacked > li > a:focus { + border-color: #ddd; + z-index: 2; +} + +// Pills +.nav-pills.nav-stacked > li > a { + margin-bottom: 3px; +} +.nav-pills.nav-stacked > li:last-child > a { + margin-bottom: 1px; // decrease margin to match sizing of stacked tabs +} + + + +// DROPDOWNS +// --------- + +.nav-tabs .dropdown-menu { + .border-radius(0 0 6px 6px); // remove the top rounded corners here since there is a hard edge above the menu +} +.nav-pills .dropdown-menu { + .border-radius(6px); // make rounded corners match the pills +} + +// Default dropdown links +// ------------------------- +// Make carets use linkColor to start +.nav .dropdown-toggle .caret { + border-top-color: @linkColor; + border-bottom-color: @linkColor; + margin-top: 6px; +} +.nav .dropdown-toggle:hover .caret, +.nav .dropdown-toggle:focus .caret { + border-top-color: @linkColorHover; + border-bottom-color: @linkColorHover; +} +/* move down carets for tabs */ +.nav-tabs .dropdown-toggle .caret { + margin-top: 8px; +} + +// Active dropdown links +// ------------------------- +.nav .active .dropdown-toggle .caret { + border-top-color: #fff; + border-bottom-color: #fff; +} +.nav-tabs .active .dropdown-toggle .caret { + border-top-color: @gray; + border-bottom-color: @gray; +} + +// Active:hover/:focus dropdown links +// ------------------------- +.nav > .dropdown.active > a:hover, +.nav > .dropdown.active > a:focus { + cursor: pointer; +} + +// Open dropdowns +// ------------------------- +.nav-tabs .open .dropdown-toggle, +.nav-pills .open .dropdown-toggle, +.nav > li.dropdown.open.active > a:hover, +.nav > li.dropdown.open.active > a:focus { + color: @white; + background-color: @grayLight; + border-color: @grayLight; +} +.nav li.dropdown.open .caret, +.nav li.dropdown.open.active .caret, +.nav li.dropdown.open a:hover .caret, +.nav li.dropdown.open a:focus .caret { + border-top-color: @white; + border-bottom-color: @white; + .opacity(100); +} + +// Dropdowns in stacked tabs +.tabs-stacked .open > a:hover, +.tabs-stacked .open > a:focus { + border-color: @grayLight; +} + + + +// TABBABLE +// -------- + + +// COMMON STYLES +// ------------- + +// Clear any floats +.tabbable { + .clearfix(); +} +.tab-content { + overflow: auto; // prevent content from running below tabs +} + +// Remove border on bottom, left, right +.tabs-below > .nav-tabs, +.tabs-right > .nav-tabs, +.tabs-left > .nav-tabs { + border-bottom: 0; +} + +// Show/hide tabbable areas +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} +.tab-content > .active, +.pill-content > .active { + display: block; +} + + +// BOTTOM +// ------ + +.tabs-below > .nav-tabs { + border-top: 1px solid #ddd; +} +.tabs-below > .nav-tabs > li { + margin-top: -1px; + margin-bottom: 0; +} +.tabs-below > .nav-tabs > li > a { + .border-radius(0 0 4px 4px); + &:hover, + &:focus { + border-bottom-color: transparent; + border-top-color: #ddd; + } +} +.tabs-below > .nav-tabs > .active > a, +.tabs-below > .nav-tabs > .active > a:hover, +.tabs-below > .nav-tabs > .active > a:focus { + border-color: transparent #ddd #ddd #ddd; +} + +// LEFT & RIGHT +// ------------ + +// Common styles +.tabs-left > .nav-tabs > li, +.tabs-right > .nav-tabs > li { + float: none; +} +.tabs-left > .nav-tabs > li > a, +.tabs-right > .nav-tabs > li > a { + min-width: 74px; + margin-right: 0; + margin-bottom: 3px; +} + +// Tabs on the left +.tabs-left > .nav-tabs { + float: left; + margin-right: 19px; + border-right: 1px solid #ddd; +} +.tabs-left > .nav-tabs > li > a { + margin-right: -1px; + .border-radius(4px 0 0 4px); +} +.tabs-left > .nav-tabs > li > a:hover, +.tabs-left > .nav-tabs > li > a:focus { + border-color: @grayLighter #ddd @grayLighter @grayLighter; +} +.tabs-left > .nav-tabs .active > a, +.tabs-left > .nav-tabs .active > a:hover, +.tabs-left > .nav-tabs .active > a:focus { + border-color: #ddd transparent #ddd #ddd; + *border-right-color: @white; +} + +// Tabs on the right +.tabs-right > .nav-tabs { + float: right; + margin-left: 19px; + border-left: 1px solid #ddd; +} +.tabs-right > .nav-tabs > li > a { + margin-left: -1px; + .border-radius(0 4px 4px 0); +} +.tabs-right > .nav-tabs > li > a:hover, +.tabs-right > .nav-tabs > li > a:focus { + border-color: @grayLighter @grayLighter @grayLighter #ddd; +} +.tabs-right > .nav-tabs .active > a, +.tabs-right > .nav-tabs .active > a:hover, +.tabs-right > .nav-tabs .active > a:focus { + border-color: #ddd #ddd #ddd transparent; + *border-left-color: @white; +} + + + +// DISABLED STATES +// --------------- + +// Gray out text +.nav > .disabled > a { + color: @grayLight; +} +// Nuke hover/focus effects +.nav > .disabled > a:hover, +.nav > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + cursor: default; +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pager.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pager.less index 1401c0e482..1476188297 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pager.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pager.less @@ -1,43 +1,43 @@ -// -// Pager pagination -// -------------------------------------------------- - - -.pager { - margin: @baseLineHeight 0; - list-style: none; - text-align: center; - .clearfix(); -} -.pager li { - display: inline; -} -.pager li > a, -.pager li > span { - display: inline-block; - padding: 5px 14px; - background-color: #fff; - border: 1px solid #ddd; - .border-radius(15px); -} -.pager li > a:hover, -.pager li > a:focus { - text-decoration: none; - background-color: #f5f5f5; -} -.pager .next > a, -.pager .next > span { - float: right; -} -.pager .previous > a, -.pager .previous > span { - float: left; -} -.pager .disabled > a, -.pager .disabled > a:hover, -.pager .disabled > a:focus, -.pager .disabled > span { - color: @grayLight; - background-color: #fff; - cursor: default; +// +// Pager pagination +// -------------------------------------------------- + + +.pager { + margin: @baseLineHeight 0; + list-style: none; + text-align: center; + .clearfix(); +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + .border-radius(15px); +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #f5f5f5; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: @grayLight; + background-color: #fff; + cursor: default; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less index 510d16f705..a789db2d28 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less @@ -1,123 +1,123 @@ -// -// Pagination (multiple pages) -// -------------------------------------------------- - -// Space out pagination from surrounding content -.pagination { - margin: @baseLineHeight 0; -} - -.pagination ul { - // Allow for text-based alignment - display: inline-block; - .ie7-inline-block(); - // Reset default ul styles - margin-left: 0; - margin-bottom: 0; - // Visuals - .border-radius(@baseBorderRadius); - .box-shadow(0 1px 2px rgba(0,0,0,.05)); -} -.pagination ul > li { - display: inline; // Remove list-style and block-level defaults -} -.pagination ul > li > a, -.pagination ul > li > span { - float: left; // Collapse white-space - padding: 4px 12px; - line-height: @baseLineHeight; - text-decoration: none; - background-color: @paginationBackground; - border: 1px solid @paginationBorder; - border-left-width: 0; -} -.pagination ul > li > a:hover, -.pagination ul > li > a:focus, -.pagination ul > .active > a, -.pagination ul > .active > span { - background-color: @paginationActiveBackground; -} -.pagination ul > .active > a, -.pagination ul > .active > span { - color: @grayLight; - cursor: default; -} -.pagination ul > .disabled > span, -.pagination ul > .disabled > a, -.pagination ul > .disabled > a:hover, -.pagination ul > .disabled > a:focus { - color: @grayLight; - background-color: transparent; - cursor: default; -} -.pagination ul > li:first-child > a, -.pagination ul > li:first-child > span { - border-left-width: 1px; - .border-left-radius(@baseBorderRadius); -} -.pagination ul > li:last-child > a, -.pagination ul > li:last-child > span { - .border-right-radius(@baseBorderRadius); -} - - -// Alignment -// -------------------------------------------------- - -.pagination-centered { - text-align: center; -} -.pagination-right { - text-align: right; -} - - -// Sizing -// -------------------------------------------------- - -// Large -.pagination-large { - ul > li > a, - ul > li > span { - padding: @paddingLarge; - font-size: @fontSizeLarge; - } - ul > li:first-child > a, - ul > li:first-child > span { - .border-left-radius(@borderRadiusLarge); - } - ul > li:last-child > a, - ul > li:last-child > span { - .border-right-radius(@borderRadiusLarge); - } -} - -// Small and mini -.pagination-mini, -.pagination-small { - ul > li:first-child > a, - ul > li:first-child > span { - .border-left-radius(@borderRadiusSmall); - } - ul > li:last-child > a, - ul > li:last-child > span { - .border-right-radius(@borderRadiusSmall); - } -} - -// Small -.pagination-small { - ul > li > a, - ul > li > span { - padding: @paddingSmall; - font-size: @fontSizeSmall; - } -} -// Mini -.pagination-mini { - ul > li > a, - ul > li > span { - padding: @paddingMini; - font-size: @fontSizeMini; - } -} +// +// Pagination (multiple pages) +// -------------------------------------------------- + +// Space out pagination from surrounding content +.pagination { + margin: @baseLineHeight 0; +} + +.pagination ul { + // Allow for text-based alignment + display: inline-block; + .ie7-inline-block(); + // Reset default ul styles + margin-left: 0; + margin-bottom: 0; + // Visuals + .border-radius(@baseBorderRadius); + .box-shadow(0 1px 2px rgba(0,0,0,.05)); +} +.pagination ul > li { + display: inline; // Remove list-style and block-level defaults +} +.pagination ul > li > a, +.pagination ul > li > span { + float: left; // Collapse white-space + padding: 4px 12px; + line-height: @baseLineHeight; + text-decoration: none; + background-color: @paginationBackground; + border: 1px solid @paginationBorder; + border-left-width: 0; +} +.pagination ul > li > a:hover, +.pagination ul > li > a:focus, +.pagination ul > .active > a, +.pagination ul > .active > span { + background-color: @paginationActiveBackground; +} +.pagination ul > .active > a, +.pagination ul > .active > span { + color: @grayLight; + cursor: default; +} +.pagination ul > .disabled > span, +.pagination ul > .disabled > a, +.pagination ul > .disabled > a:hover, +.pagination ul > .disabled > a:focus { + color: @grayLight; + background-color: transparent; + cursor: default; +} +.pagination ul > li:first-child > a, +.pagination ul > li:first-child > span { + border-left-width: 1px; + .border-left-radius(@baseBorderRadius); +} +.pagination ul > li:last-child > a, +.pagination ul > li:last-child > span { + .border-right-radius(@baseBorderRadius); +} + + +// Alignment +// -------------------------------------------------- + +.pagination-centered { + text-align: center; +} +.pagination-right { + text-align: right; +} + + +// Sizing +// -------------------------------------------------- + +// Large +.pagination-large { + ul > li > a, + ul > li > span { + padding: @paddingLarge; + font-size: @fontSizeLarge; + } + ul > li:first-child > a, + ul > li:first-child > span { + .border-left-radius(@borderRadiusLarge); + } + ul > li:last-child > a, + ul > li:last-child > span { + .border-right-radius(@borderRadiusLarge); + } +} + +// Small and mini +.pagination-mini, +.pagination-small { + ul > li:first-child > a, + ul > li:first-child > span { + .border-left-radius(@borderRadiusSmall); + } + ul > li:last-child > a, + ul > li:last-child > span { + .border-right-radius(@borderRadiusSmall); + } +} + +// Small +.pagination-small { + ul > li > a, + ul > li > span { + padding: @paddingSmall; + font-size: @fontSizeSmall; + } +} +// Mini +.pagination-mini { + ul > li > a, + ul > li > span { + padding: @paddingMini; + font-size: @fontSizeMini; + } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/popovers.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/popovers.less index 4dbe3bfaee..aae35c8cd5 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/popovers.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/popovers.less @@ -1,133 +1,133 @@ -// -// Popovers -// -------------------------------------------------- - - -.popover { - position: absolute; - top: 0; - left: 0; - z-index: @zindexPopover; - display: none; - max-width: 276px; - padding: 1px; - text-align: left; // Reset given new insertion method - background-color: @popoverBackground; - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0,0,0,.2); - .border-radius(6px); - .box-shadow(0 5px 10px rgba(0,0,0,.2)); - - // Overrides for proper insertion - white-space: normal; - - // Offset the popover to account for the popover arrow - &.top { margin-top: -10px; } - &.right { margin-left: 10px; } - &.bottom { margin-top: 10px; } - &.left { margin-left: -10px; } -} - -.popover-title { - margin: 0; // reset heading margin - padding: 8px 14px; - font-size: 14px; - font-weight: normal; - line-height: 18px; - background-color: @popoverTitleBackground; - border-bottom: 1px solid darken(@popoverTitleBackground, 5%); - .border-radius(5px 5px 0 0); - - &:empty { - display: none; - } -} - -.popover-content { - padding: 9px 14px; -} - -// Arrows -// -// .arrow is outer, .arrow:after is inner - -.popover .arrow, -.popover .arrow:after { - position: absolute; - display: block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} -.popover .arrow { - border-width: @popoverArrowOuterWidth; -} -.popover .arrow:after { - border-width: @popoverArrowWidth; - content: ""; -} - -.popover { - &.top .arrow { - left: 50%; - margin-left: -@popoverArrowOuterWidth; - border-bottom-width: 0; - border-top-color: #999; // IE8 fallback - border-top-color: @popoverArrowOuterColor; - bottom: -@popoverArrowOuterWidth; - &:after { - bottom: 1px; - margin-left: -@popoverArrowWidth; - border-bottom-width: 0; - border-top-color: @popoverArrowColor; - } - } - &.right .arrow { - top: 50%; - left: -@popoverArrowOuterWidth; - margin-top: -@popoverArrowOuterWidth; - border-left-width: 0; - border-right-color: #999; // IE8 fallback - border-right-color: @popoverArrowOuterColor; - &:after { - left: 1px; - bottom: -@popoverArrowWidth; - border-left-width: 0; - border-right-color: @popoverArrowColor; - } - } - &.bottom .arrow { - left: 50%; - margin-left: -@popoverArrowOuterWidth; - border-top-width: 0; - border-bottom-color: #999; // IE8 fallback - border-bottom-color: @popoverArrowOuterColor; - top: -@popoverArrowOuterWidth; - &:after { - top: 1px; - margin-left: -@popoverArrowWidth; - border-top-width: 0; - border-bottom-color: @popoverArrowColor; - } - } - - &.left .arrow { - top: 50%; - right: -@popoverArrowOuterWidth; - margin-top: -@popoverArrowOuterWidth; - border-right-width: 0; - border-left-color: #999; // IE8 fallback - border-left-color: @popoverArrowOuterColor; - &:after { - right: 1px; - border-right-width: 0; - border-left-color: @popoverArrowColor; - bottom: -@popoverArrowWidth; - } - } - -} +// +// Popovers +// -------------------------------------------------- + + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: @zindexPopover; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; // Reset given new insertion method + background-color: @popoverBackground; + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0,0,0,.2); + .border-radius(6px); + .box-shadow(0 5px 10px rgba(0,0,0,.2)); + + // Overrides for proper insertion + white-space: normal; + + // Offset the popover to account for the popover arrow + &.top { margin-top: -10px; } + &.right { margin-left: 10px; } + &.bottom { margin-top: 10px; } + &.left { margin-left: -10px; } +} + +.popover-title { + margin: 0; // reset heading margin + padding: 8px 14px; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: @popoverTitleBackground; + border-bottom: 1px solid darken(@popoverTitleBackground, 5%); + .border-radius(5px 5px 0 0); + + &:empty { + display: none; + } +} + +.popover-content { + padding: 9px 14px; +} + +// Arrows +// +// .arrow is outer, .arrow:after is inner + +.popover .arrow, +.popover .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover .arrow { + border-width: @popoverArrowOuterWidth; +} +.popover .arrow:after { + border-width: @popoverArrowWidth; + content: ""; +} + +.popover { + &.top .arrow { + left: 50%; + margin-left: -@popoverArrowOuterWidth; + border-bottom-width: 0; + border-top-color: #999; // IE8 fallback + border-top-color: @popoverArrowOuterColor; + bottom: -@popoverArrowOuterWidth; + &:after { + bottom: 1px; + margin-left: -@popoverArrowWidth; + border-bottom-width: 0; + border-top-color: @popoverArrowColor; + } + } + &.right .arrow { + top: 50%; + left: -@popoverArrowOuterWidth; + margin-top: -@popoverArrowOuterWidth; + border-left-width: 0; + border-right-color: #999; // IE8 fallback + border-right-color: @popoverArrowOuterColor; + &:after { + left: 1px; + bottom: -@popoverArrowWidth; + border-left-width: 0; + border-right-color: @popoverArrowColor; + } + } + &.bottom .arrow { + left: 50%; + margin-left: -@popoverArrowOuterWidth; + border-top-width: 0; + border-bottom-color: #999; // IE8 fallback + border-bottom-color: @popoverArrowOuterColor; + top: -@popoverArrowOuterWidth; + &:after { + top: 1px; + margin-left: -@popoverArrowWidth; + border-top-width: 0; + border-bottom-color: @popoverArrowColor; + } + } + + &.left .arrow { + top: 50%; + right: -@popoverArrowOuterWidth; + margin-top: -@popoverArrowOuterWidth; + border-right-width: 0; + border-left-color: #999; // IE8 fallback + border-left-color: @popoverArrowOuterColor; + &:after { + right: 1px; + border-right-width: 0; + border-left-color: @popoverArrowColor; + bottom: -@popoverArrowWidth; + } + } + +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/progress-bars.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/progress-bars.less index e918474fe9..5e0c3dda01 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/progress-bars.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/progress-bars.less @@ -1,122 +1,122 @@ -// -// Progress bars -// -------------------------------------------------- - - -// ANIMATIONS -// ---------- - -// Webkit -@-webkit-keyframes progress-bar-stripes { - from { background-position: 40px 0; } - to { background-position: 0 0; } -} - -// Firefox -@-moz-keyframes progress-bar-stripes { - from { background-position: 40px 0; } - to { background-position: 0 0; } -} - -// IE9 -@-ms-keyframes progress-bar-stripes { - from { background-position: 40px 0; } - to { background-position: 0 0; } -} - -// Opera -@-o-keyframes progress-bar-stripes { - from { background-position: 0 0; } - to { background-position: 40px 0; } -} - -// Spec -@keyframes progress-bar-stripes { - from { background-position: 40px 0; } - to { background-position: 0 0; } -} - - - -// THE BARS -// -------- - -// Outer container -.progress { - overflow: hidden; - height: @baseLineHeight; - margin-bottom: @baseLineHeight; - #gradient > .vertical(#f5f5f5, #f9f9f9); - .box-shadow(inset 0 1px 2px rgba(0,0,0,.1)); - .border-radius(@baseBorderRadius); -} - -// Bar of progress -.progress .bar { - width: 0%; - height: 100%; - color: @white; - float: left; - font-size: 12px; - text-align: center; - text-shadow: 0 -1px 0 rgba(0,0,0,.25); - #gradient > .vertical(#149bdf, #0480be); - .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15)); - .box-sizing(border-box); - .transition(width .6s ease); -} -.progress .bar + .bar { - .box-shadow(~"inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15)"); -} - -// Striped bars -.progress-striped .bar { - #gradient > .striped(#149bdf); - .background-size(40px 40px); -} - -// Call animation for the active one -.progress.active .bar { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -moz-animation: progress-bar-stripes 2s linear infinite; - -ms-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite; -} - - - -// COLORS -// ------ - -// Danger (red) -.progress-danger .bar, .progress .bar-danger { - #gradient > .vertical(#ee5f5b, #c43c35); -} -.progress-danger.progress-striped .bar, .progress-striped .bar-danger { - #gradient > .striped(#ee5f5b); -} - -// Success (green) -.progress-success .bar, .progress .bar-success { - #gradient > .vertical(#62c462, #57a957); -} -.progress-success.progress-striped .bar, .progress-striped .bar-success { - #gradient > .striped(#62c462); -} - -// Info (teal) -.progress-info .bar, .progress .bar-info { - #gradient > .vertical(#5bc0de, #339bb9); -} -.progress-info.progress-striped .bar, .progress-striped .bar-info { - #gradient > .striped(#5bc0de); -} - -// Warning (orange) -.progress-warning .bar, .progress .bar-warning { - #gradient > .vertical(lighten(@orange, 15%), @orange); -} -.progress-warning.progress-striped .bar, .progress-striped .bar-warning { - #gradient > .striped(lighten(@orange, 15%)); -} +// +// Progress bars +// -------------------------------------------------- + + +// ANIMATIONS +// ---------- + +// Webkit +@-webkit-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// Firefox +@-moz-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// IE9 +@-ms-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// Opera +@-o-keyframes progress-bar-stripes { + from { background-position: 0 0; } + to { background-position: 40px 0; } +} + +// Spec +@keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + + + +// THE BARS +// -------- + +// Outer container +.progress { + overflow: hidden; + height: @baseLineHeight; + margin-bottom: @baseLineHeight; + #gradient > .vertical(#f5f5f5, #f9f9f9); + .box-shadow(inset 0 1px 2px rgba(0,0,0,.1)); + .border-radius(@baseBorderRadius); +} + +// Bar of progress +.progress .bar { + width: 0%; + height: 100%; + color: @white; + float: left; + font-size: 12px; + text-align: center; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + #gradient > .vertical(#149bdf, #0480be); + .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15)); + .box-sizing(border-box); + .transition(width .6s ease); +} +.progress .bar + .bar { + .box-shadow(~"inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15)"); +} + +// Striped bars +.progress-striped .bar { + #gradient > .striped(#149bdf); + .background-size(40px 40px); +} + +// Call animation for the active one +.progress.active .bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -moz-animation: progress-bar-stripes 2s linear infinite; + -ms-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} + + + +// COLORS +// ------ + +// Danger (red) +.progress-danger .bar, .progress .bar-danger { + #gradient > .vertical(#ee5f5b, #c43c35); +} +.progress-danger.progress-striped .bar, .progress-striped .bar-danger { + #gradient > .striped(#ee5f5b); +} + +// Success (green) +.progress-success .bar, .progress .bar-success { + #gradient > .vertical(#62c462, #57a957); +} +.progress-success.progress-striped .bar, .progress-striped .bar-success { + #gradient > .striped(#62c462); +} + +// Info (teal) +.progress-info .bar, .progress .bar-info { + #gradient > .vertical(#5bc0de, #339bb9); +} +.progress-info.progress-striped .bar, .progress-striped .bar-info { + #gradient > .striped(#5bc0de); +} + +// Warning (orange) +.progress-warning .bar, .progress .bar-warning { + #gradient > .vertical(lighten(@orange, 15%), @orange); +} +.progress-warning.progress-striped .bar, .progress-striped .bar-warning { + #gradient > .striped(lighten(@orange, 15%)); +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/reset.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/reset.less index 5063c7d7ad..4806bd5e59 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/reset.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/reset.less @@ -1,216 +1,216 @@ -// -// Reset CSS -// Adapted from http://github.com/necolas/normalize.css -// -------------------------------------------------- - - -// Display in IE6-9 and FF3 -// ------------------------- - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -nav, -section { - display: block; -} - -// Display block in IE6-9 and FF3 -// ------------------------- - -audio, -canvas, -video { - display: inline-block; - *display: inline; - *zoom: 1; -} - -// Prevents modern browsers from displaying 'audio' without controls -// ------------------------- - -audio:not([controls]) { - display: none; -} - -// Base settings -// ------------------------- - -html { - font-size: 100%; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} -// Focus states -a:focus { - .tab-focus(); -} -// Hover & Active -a:hover, -a:active { - outline: 0; -} - -// Prevents sub and sup affecting line-height in all browsers -// ------------------------- - -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} -sup { - top: -0.5em; -} -sub { - bottom: -0.25em; -} - -// Img border in a's and image quality -// ------------------------- - -img { - /* Responsive images (ensure images don't scale beyond their parents) */ - max-width: 100%; /* Part 1: Set a maxium relative to the parent */ - width: auto\9; /* IE7-8 need help adjusting responsive images */ - height: auto; /* Part 2: Scale the height according to the width, otherwise you get stretching */ - - vertical-align: middle; - border: 0; - -ms-interpolation-mode: bicubic; -} - -// Prevent max-width from affecting Google Maps -#map_canvas img, -.google-maps img { - max-width: none; -} - -// Forms -// ------------------------- - -// Font size in all browsers, margin changes, misc consistency -button, -input, -select, -textarea { - margin: 0; - font-size: 100%; - vertical-align: middle; -} -button, -input { - *overflow: visible; // Inner spacing ie IE6/7 - line-height: normal; // FF3/4 have !important on line-height in UA stylesheet -} -button::-moz-focus-inner, -input::-moz-focus-inner { // Inner padding and border oddities in FF3/4 - padding: 0; - border: 0; -} -button, -html input[type="button"], // Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; // Corrects inability to style clickable `input` types in iOS. - cursor: pointer; // Improves usability and consistency of cursor style between image-type `input` and others. -} -label, -select, -button, -input[type="button"], -input[type="reset"], -input[type="submit"], -input[type="radio"], -input[type="checkbox"] { - cursor: pointer; // Improves usability and consistency of cursor style between image-type `input` and others. -} -input[type="search"] { // Appearance in Safari/Chrome - .box-sizing(content-box); - -webkit-appearance: textfield; -} -input[type="search"]::-webkit-search-decoration, -input[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5 -} -textarea { - overflow: auto; // Remove vertical scrollbar in IE6-9 - vertical-align: top; // Readability and alignment cross-browser -} - - -// Printing -// ------------------------- -// Source: https://github.com/h5bp/html5-boilerplate/blob/master/css/main.css - -@media print { - - * { - text-shadow: none !important; - color: #000 !important; // Black prints faster: h5bp.com/s - background: transparent !important; - box-shadow: none !important; - } - - a, - a:visited { - text-decoration: underline; - } - - a[href]:after { - content: " (" attr(href) ")"; - } - - abbr[title]:after { - content: " (" attr(title) ")"; - } - - // Don't show links for images, or javascript/internal links - .ir a:after, - a[href^="javascript:"]:after, - a[href^="#"]:after { - content: ""; - } - - pre, - blockquote { - border: 1px solid #999; - page-break-inside: avoid; - } - - thead { - display: table-header-group; // h5bp.com/t - } - - tr, - img { - page-break-inside: avoid; - } - - img { - max-width: 100% !important; - } - - @page { - margin: 0.5cm; - } - - p, - h2, - h3 { - orphans: 3; - widows: 3; - } - - h2, - h3 { - page-break-after: avoid; - } -} +// +// Reset CSS +// Adapted from http://github.com/necolas/normalize.css +// -------------------------------------------------- + + +// Display in IE6-9 and FF3 +// ------------------------- + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section { + display: block; +} + +// Display block in IE6-9 and FF3 +// ------------------------- + +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +// Prevents modern browsers from displaying 'audio' without controls +// ------------------------- + +audio:not([controls]) { + display: none; +} + +// Base settings +// ------------------------- + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +// Focus states +a:focus { + .tab-focus(); +} +// Hover & Active +a:hover, +a:active { + outline: 0; +} + +// Prevents sub and sup affecting line-height in all browsers +// ------------------------- + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} + +// Img border in a's and image quality +// ------------------------- + +img { + /* Responsive images (ensure images don't scale beyond their parents) */ + max-width: 100%; /* Part 1: Set a maxium relative to the parent */ + width: auto\9; /* IE7-8 need help adjusting responsive images */ + height: auto; /* Part 2: Scale the height according to the width, otherwise you get stretching */ + + vertical-align: middle; + border: 0; + -ms-interpolation-mode: bicubic; +} + +// Prevent max-width from affecting Google Maps +#map_canvas img, +.google-maps img { + max-width: none; +} + +// Forms +// ------------------------- + +// Font size in all browsers, margin changes, misc consistency +button, +input, +select, +textarea { + margin: 0; + font-size: 100%; + vertical-align: middle; +} +button, +input { + *overflow: visible; // Inner spacing ie IE6/7 + line-height: normal; // FF3/4 have !important on line-height in UA stylesheet +} +button::-moz-focus-inner, +input::-moz-focus-inner { // Inner padding and border oddities in FF3/4 + padding: 0; + border: 0; +} +button, +html input[type="button"], // Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; // Corrects inability to style clickable `input` types in iOS. + cursor: pointer; // Improves usability and consistency of cursor style between image-type `input` and others. +} +label, +select, +button, +input[type="button"], +input[type="reset"], +input[type="submit"], +input[type="radio"], +input[type="checkbox"] { + cursor: pointer; // Improves usability and consistency of cursor style between image-type `input` and others. +} +input[type="search"] { // Appearance in Safari/Chrome + .box-sizing(content-box); + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-decoration, +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5 +} +textarea { + overflow: auto; // Remove vertical scrollbar in IE6-9 + vertical-align: top; // Readability and alignment cross-browser +} + + +// Printing +// ------------------------- +// Source: https://github.com/h5bp/html5-boilerplate/blob/master/css/main.css + +@media print { + + * { + text-shadow: none !important; + color: #000 !important; // Black prints faster: h5bp.com/s + background: transparent !important; + box-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } + + // Don't show links for images, or javascript/internal links + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; // h5bp.com/t + } + + tr, + img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + @page { + margin: 0.5cm; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-1200px-min.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-1200px-min.less index 7a24dbcd01..4f35ba6ca2 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-1200px-min.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-1200px-min.less @@ -1,28 +1,28 @@ -// -// Responsive: Large desktop and up -// -------------------------------------------------- - - -@media (min-width: 1200px) { - - // Fixed grid - #grid > .core(@gridColumnWidth1200, @gridGutterWidth1200); - - // Fluid grid - #grid > .fluid(@fluidGridColumnWidth1200, @fluidGridGutterWidth1200); - - // Input grid - #grid > .input(@gridColumnWidth1200, @gridGutterWidth1200); - - // Thumbnails - .thumbnails { - margin-left: -@gridGutterWidth1200; - } - .thumbnails > li { - margin-left: @gridGutterWidth1200; - } - .row-fluid .thumbnails { - margin-left: 0; - } - -} +// +// Responsive: Large desktop and up +// -------------------------------------------------- + + +@media (min-width: 1200px) { + + // Fixed grid + #grid > .core(@gridColumnWidth1200, @gridGutterWidth1200); + + // Fluid grid + #grid > .fluid(@fluidGridColumnWidth1200, @fluidGridGutterWidth1200); + + // Input grid + #grid > .input(@gridColumnWidth1200, @gridGutterWidth1200); + + // Thumbnails + .thumbnails { + margin-left: -@gridGutterWidth1200; + } + .thumbnails > li { + margin-left: @gridGutterWidth1200; + } + .row-fluid .thumbnails { + margin-left: 0; + } + +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-767px-max.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-767px-max.less index 8b255e50e0..128f4ce30d 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-767px-max.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-767px-max.less @@ -1,193 +1,193 @@ -// -// Responsive: Landscape phone to desktop/tablet -// -------------------------------------------------- - - -@media (max-width: 767px) { - - // Padding to set content in a bit - body { - padding-left: 20px; - padding-right: 20px; - } - // Negative indent the now static "fixed" navbar - .navbar-fixed-top, - .navbar-fixed-bottom, - .navbar-static-top { - margin-left: -20px; - margin-right: -20px; - } - // Remove padding on container given explicit padding set on body - .container-fluid { - padding: 0; - } - - // TYPOGRAPHY - // ---------- - // Reset horizontal dl - .dl-horizontal { - dt { - float: none; - clear: none; - width: auto; - text-align: left; - } - dd { - margin-left: 0; - } - } - - // GRID & CONTAINERS - // ----------------- - // Remove width from containers - .container { - width: auto; - } - // Fluid rows - .row-fluid { - width: 100%; - } - // Undo negative margin on rows and thumbnails - .row, - .thumbnails { - margin-left: 0; - } - .thumbnails > li { - float: none; - margin-left: 0; // Reset the default margin for all li elements when no .span* classes are present - } - // Make all grid-sized elements block level again - [class*="span"], - .uneditable-input[class*="span"], // Makes uneditable inputs full-width when using grid sizing - .row-fluid [class*="span"] { - float: none; - display: block; - width: 100%; - margin-left: 0; - .box-sizing(border-box); - } - .span12, - .row-fluid .span12 { - width: 100%; - .box-sizing(border-box); - } - .row-fluid [class*="offset"]:first-child { - margin-left: 0; - } - - // FORM FIELDS - // ----------- - // Make span* classes full width - .input-large, - .input-xlarge, - .input-xxlarge, - input[class*="span"], - select[class*="span"], - textarea[class*="span"], - .uneditable-input { - .input-block-level(); - } - // But don't let it screw up prepend/append inputs - .input-prepend input, - .input-append input, - .input-prepend input[class*="span"], - .input-append input[class*="span"] { - display: inline-block; // redeclare so they don't wrap to new lines - width: auto; - } - .controls-row [class*="span"] + [class*="span"] { - margin-left: 0; - } - - // Modals - .modal { - position: fixed; - top: 20px; - left: 20px; - right: 20px; - width: auto; - margin: 0; - &.fade { top: -100px; } - &.fade.in { top: 20px; } - } - -} - - - -// UP TO LANDSCAPE PHONE -// --------------------- - -@media (max-width: 480px) { - - // Smooth out the collapsing/expanding nav - .nav-collapse { - -webkit-transform: translate3d(0, 0, 0); // activate the GPU - } - - // Block level the page header small tag for readability - .page-header h1 small { - display: block; - line-height: @baseLineHeight; - } - - // Update checkboxes for iOS - input[type="checkbox"], - input[type="radio"] { - border: 1px solid #ccc; - } - - // Remove the horizontal form styles - .form-horizontal { - .control-label { - float: none; - width: auto; - padding-top: 0; - text-align: left; - } - // Move over all input controls and content - .controls { - margin-left: 0; - } - // Move the options list down to align with labels - .control-list { - padding-top: 0; // has to be padding because margin collaspes - } - // Move over buttons in .form-actions to align with .controls - .form-actions { - padding-left: 10px; - padding-right: 10px; - } - } - - // Medias - // Reset float and spacing to stack - .media .pull-left, - .media .pull-right { - float: none; - display: block; - margin-bottom: 10px; - } - // Remove side margins since we stack instead of indent - .media-object { - margin-right: 0; - margin-left: 0; - } - - // Modals - .modal { - top: 10px; - left: 10px; - right: 10px; - } - .modal-header .close { - padding: 10px; - margin: -10px; - } - - // Carousel - .carousel-caption { - position: static; - } - -} +// +// Responsive: Landscape phone to desktop/tablet +// -------------------------------------------------- + + +@media (max-width: 767px) { + + // Padding to set content in a bit + body { + padding-left: 20px; + padding-right: 20px; + } + // Negative indent the now static "fixed" navbar + .navbar-fixed-top, + .navbar-fixed-bottom, + .navbar-static-top { + margin-left: -20px; + margin-right: -20px; + } + // Remove padding on container given explicit padding set on body + .container-fluid { + padding: 0; + } + + // TYPOGRAPHY + // ---------- + // Reset horizontal dl + .dl-horizontal { + dt { + float: none; + clear: none; + width: auto; + text-align: left; + } + dd { + margin-left: 0; + } + } + + // GRID & CONTAINERS + // ----------------- + // Remove width from containers + .container { + width: auto; + } + // Fluid rows + .row-fluid { + width: 100%; + } + // Undo negative margin on rows and thumbnails + .row, + .thumbnails { + margin-left: 0; + } + .thumbnails > li { + float: none; + margin-left: 0; // Reset the default margin for all li elements when no .span* classes are present + } + // Make all grid-sized elements block level again + [class*="span"], + .uneditable-input[class*="span"], // Makes uneditable inputs full-width when using grid sizing + .row-fluid [class*="span"] { + float: none; + display: block; + width: 100%; + margin-left: 0; + .box-sizing(border-box); + } + .span12, + .row-fluid .span12 { + width: 100%; + .box-sizing(border-box); + } + .row-fluid [class*="offset"]:first-child { + margin-left: 0; + } + + // FORM FIELDS + // ----------- + // Make span* classes full width + .input-large, + .input-xlarge, + .input-xxlarge, + input[class*="span"], + select[class*="span"], + textarea[class*="span"], + .uneditable-input { + .input-block-level(); + } + // But don't let it screw up prepend/append inputs + .input-prepend input, + .input-append input, + .input-prepend input[class*="span"], + .input-append input[class*="span"] { + display: inline-block; // redeclare so they don't wrap to new lines + width: auto; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 0; + } + + // Modals + .modal { + position: fixed; + top: 20px; + left: 20px; + right: 20px; + width: auto; + margin: 0; + &.fade { top: -100px; } + &.fade.in { top: 20px; } + } + +} + + + +// UP TO LANDSCAPE PHONE +// --------------------- + +@media (max-width: 480px) { + + // Smooth out the collapsing/expanding nav + .nav-collapse { + -webkit-transform: translate3d(0, 0, 0); // activate the GPU + } + + // Block level the page header small tag for readability + .page-header h1 small { + display: block; + line-height: @baseLineHeight; + } + + // Update checkboxes for iOS + input[type="checkbox"], + input[type="radio"] { + border: 1px solid #ccc; + } + + // Remove the horizontal form styles + .form-horizontal { + .control-label { + float: none; + width: auto; + padding-top: 0; + text-align: left; + } + // Move over all input controls and content + .controls { + margin-left: 0; + } + // Move the options list down to align with labels + .control-list { + padding-top: 0; // has to be padding because margin collaspes + } + // Move over buttons in .form-actions to align with .controls + .form-actions { + padding-left: 10px; + padding-right: 10px; + } + } + + // Medias + // Reset float and spacing to stack + .media .pull-left, + .media .pull-right { + float: none; + display: block; + margin-bottom: 10px; + } + // Remove side margins since we stack instead of indent + .media-object { + margin-right: 0; + margin-left: 0; + } + + // Modals + .modal { + top: 10px; + left: 10px; + right: 10px; + } + .modal-header .close { + padding: 10px; + margin: -10px; + } + + // Carousel + .carousel-caption { + position: static; + } + +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-768px-979px.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-768px-979px.less index b24124e18e..8e8c486a06 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-768px-979px.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-768px-979px.less @@ -1,19 +1,19 @@ -// -// Responsive: Tablet to desktop -// -------------------------------------------------- - - -@media (min-width: 768px) and (max-width: 979px) { - - // Fixed grid - #grid > .core(@gridColumnWidth768, @gridGutterWidth768); - - // Fluid grid - #grid > .fluid(@fluidGridColumnWidth768, @fluidGridGutterWidth768); - - // Input grid - #grid > .input(@gridColumnWidth768, @gridGutterWidth768); - - // No need to reset .thumbnails here since it's the same @gridGutterWidth - -} +// +// Responsive: Tablet to desktop +// -------------------------------------------------- + + +@media (min-width: 768px) and (max-width: 979px) { + + // Fixed grid + #grid > .core(@gridColumnWidth768, @gridGutterWidth768); + + // Fluid grid + #grid > .fluid(@fluidGridColumnWidth768, @fluidGridGutterWidth768); + + // Input grid + #grid > .input(@gridColumnWidth768, @gridGutterWidth768); + + // No need to reset .thumbnails here since it's the same @gridGutterWidth + +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-navbar.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-navbar.less index 0778e5883a..21cd3ba671 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-navbar.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-navbar.less @@ -1,189 +1,189 @@ -// -// Responsive: Navbar -// -------------------------------------------------- - - -// TABLETS AND BELOW -// ----------------- -@media (max-width: @navbarCollapseWidth) { - - // UNFIX THE TOPBAR - // ---------------- - // Remove any padding from the body - body { - padding-top: 0; - } - // Unfix the navbars - .navbar-fixed-top, - .navbar-fixed-bottom { - position: static; - } - .navbar-fixed-top { - margin-bottom: @baseLineHeight; - } - .navbar-fixed-bottom { - margin-top: @baseLineHeight; - } - .navbar-fixed-top .navbar-inner, - .navbar-fixed-bottom .navbar-inner { - padding: 5px; - } - .navbar .container { - width: auto; - padding: 0; - } - // Account for brand name - .navbar .brand { - padding-left: 10px; - padding-right: 10px; - margin: 0 0 0 -5px; - } - - // COLLAPSIBLE NAVBAR - // ------------------ - // Nav collapse clears brand - .nav-collapse { - clear: both; - } - // Block-level the nav - .nav-collapse .nav { - float: none; - margin: 0 0 (@baseLineHeight / 2); - } - .nav-collapse .nav > li { - float: none; - } - .nav-collapse .nav > li > a { - margin-bottom: 2px; - } - .nav-collapse .nav > .divider-vertical { - display: none; - } - .nav-collapse .nav .nav-header { - color: @navbarText; - text-shadow: none; - } - // Nav and dropdown links in navbar - .nav-collapse .nav > li > a, - .nav-collapse .dropdown-menu a { - padding: 9px 15px; - font-weight: bold; - color: @navbarLinkColor; - .border-radius(3px); - } - // Buttons - .nav-collapse .btn { - padding: 4px 10px 4px; - font-weight: normal; - .border-radius(@baseBorderRadius); - } - .nav-collapse .dropdown-menu li + li a { - margin-bottom: 2px; - } - .nav-collapse .nav > li > a:hover, - .nav-collapse .nav > li > a:focus, - .nav-collapse .dropdown-menu a:hover, - .nav-collapse .dropdown-menu a:focus { - background-color: @navbarBackground; - } - .navbar-inverse .nav-collapse .nav > li > a, - .navbar-inverse .nav-collapse .dropdown-menu a { - color: @navbarInverseLinkColor; - } - .navbar-inverse .nav-collapse .nav > li > a:hover, - .navbar-inverse .nav-collapse .nav > li > a:focus, - .navbar-inverse .nav-collapse .dropdown-menu a:hover, - .navbar-inverse .nav-collapse .dropdown-menu a:focus { - background-color: @navbarInverseBackground; - } - // Buttons in the navbar - .nav-collapse.in .btn-group { - margin-top: 5px; - padding: 0; - } - // Dropdowns in the navbar - .nav-collapse .dropdown-menu { - position: static; - top: auto; - left: auto; - float: none; - display: none; - max-width: none; - margin: 0 15px; - padding: 0; - background-color: transparent; - border: none; - .border-radius(0); - .box-shadow(none); - } - .nav-collapse .open > .dropdown-menu { - display: block; - } - - .nav-collapse .dropdown-menu:before, - .nav-collapse .dropdown-menu:after { - display: none; - } - .nav-collapse .dropdown-menu .divider { - display: none; - } - .nav-collapse .nav > li > .dropdown-menu { - &:before, - &:after { - display: none; - } - } - // Forms in navbar - .nav-collapse .navbar-form, - .nav-collapse .navbar-search { - float: none; - padding: (@baseLineHeight / 2) 15px; - margin: (@baseLineHeight / 2) 0; - border-top: 1px solid @navbarBackground; - border-bottom: 1px solid @navbarBackground; - .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1)"); - } - .navbar-inverse .nav-collapse .navbar-form, - .navbar-inverse .nav-collapse .navbar-search { - border-top-color: @navbarInverseBackground; - border-bottom-color: @navbarInverseBackground; - } - // Pull right (secondary) nav content - .navbar .nav-collapse .nav.pull-right { - float: none; - margin-left: 0; - } - // Hide everything in the navbar save .brand and toggle button */ - .nav-collapse, - .nav-collapse.collapse { - overflow: hidden; - height: 0; - } - // Navbar button - .navbar .btn-navbar { - display: block; - } - - // STATIC NAVBAR - // ------------- - .navbar-static .navbar-inner { - padding-left: 10px; - padding-right: 10px; - } - - -} - - -// DEFAULT DESKTOP -// --------------- - -@media (min-width: @navbarCollapseDesktopWidth) { - - // Required to make the collapsing navbar work on regular desktops - .nav-collapse.collapse { - height: auto !important; - overflow: visible !important; - } - -} +// +// Responsive: Navbar +// -------------------------------------------------- + + +// TABLETS AND BELOW +// ----------------- +@media (max-width: @navbarCollapseWidth) { + + // UNFIX THE TOPBAR + // ---------------- + // Remove any padding from the body + body { + padding-top: 0; + } + // Unfix the navbars + .navbar-fixed-top, + .navbar-fixed-bottom { + position: static; + } + .navbar-fixed-top { + margin-bottom: @baseLineHeight; + } + .navbar-fixed-bottom { + margin-top: @baseLineHeight; + } + .navbar-fixed-top .navbar-inner, + .navbar-fixed-bottom .navbar-inner { + padding: 5px; + } + .navbar .container { + width: auto; + padding: 0; + } + // Account for brand name + .navbar .brand { + padding-left: 10px; + padding-right: 10px; + margin: 0 0 0 -5px; + } + + // COLLAPSIBLE NAVBAR + // ------------------ + // Nav collapse clears brand + .nav-collapse { + clear: both; + } + // Block-level the nav + .nav-collapse .nav { + float: none; + margin: 0 0 (@baseLineHeight / 2); + } + .nav-collapse .nav > li { + float: none; + } + .nav-collapse .nav > li > a { + margin-bottom: 2px; + } + .nav-collapse .nav > .divider-vertical { + display: none; + } + .nav-collapse .nav .nav-header { + color: @navbarText; + text-shadow: none; + } + // Nav and dropdown links in navbar + .nav-collapse .nav > li > a, + .nav-collapse .dropdown-menu a { + padding: 9px 15px; + font-weight: bold; + color: @navbarLinkColor; + .border-radius(3px); + } + // Buttons + .nav-collapse .btn { + padding: 4px 10px 4px; + font-weight: normal; + .border-radius(@baseBorderRadius); + } + .nav-collapse .dropdown-menu li + li a { + margin-bottom: 2px; + } + .nav-collapse .nav > li > a:hover, + .nav-collapse .nav > li > a:focus, + .nav-collapse .dropdown-menu a:hover, + .nav-collapse .dropdown-menu a:focus { + background-color: @navbarBackground; + } + .navbar-inverse .nav-collapse .nav > li > a, + .navbar-inverse .nav-collapse .dropdown-menu a { + color: @navbarInverseLinkColor; + } + .navbar-inverse .nav-collapse .nav > li > a:hover, + .navbar-inverse .nav-collapse .nav > li > a:focus, + .navbar-inverse .nav-collapse .dropdown-menu a:hover, + .navbar-inverse .nav-collapse .dropdown-menu a:focus { + background-color: @navbarInverseBackground; + } + // Buttons in the navbar + .nav-collapse.in .btn-group { + margin-top: 5px; + padding: 0; + } + // Dropdowns in the navbar + .nav-collapse .dropdown-menu { + position: static; + top: auto; + left: auto; + float: none; + display: none; + max-width: none; + margin: 0 15px; + padding: 0; + background-color: transparent; + border: none; + .border-radius(0); + .box-shadow(none); + } + .nav-collapse .open > .dropdown-menu { + display: block; + } + + .nav-collapse .dropdown-menu:before, + .nav-collapse .dropdown-menu:after { + display: none; + } + .nav-collapse .dropdown-menu .divider { + display: none; + } + .nav-collapse .nav > li > .dropdown-menu { + &:before, + &:after { + display: none; + } + } + // Forms in navbar + .nav-collapse .navbar-form, + .nav-collapse .navbar-search { + float: none; + padding: (@baseLineHeight / 2) 15px; + margin: (@baseLineHeight / 2) 0; + border-top: 1px solid @navbarBackground; + border-bottom: 1px solid @navbarBackground; + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1)"); + } + .navbar-inverse .nav-collapse .navbar-form, + .navbar-inverse .nav-collapse .navbar-search { + border-top-color: @navbarInverseBackground; + border-bottom-color: @navbarInverseBackground; + } + // Pull right (secondary) nav content + .navbar .nav-collapse .nav.pull-right { + float: none; + margin-left: 0; + } + // Hide everything in the navbar save .brand and toggle button */ + .nav-collapse, + .nav-collapse.collapse { + overflow: hidden; + height: 0; + } + // Navbar button + .navbar .btn-navbar { + display: block; + } + + // STATIC NAVBAR + // ------------- + .navbar-static .navbar-inner { + padding-left: 10px; + padding-right: 10px; + } + + +} + + +// DEFAULT DESKTOP +// --------------- + +@media (min-width: @navbarCollapseDesktopWidth) { + + // Required to make the collapsing navbar work on regular desktops + .nav-collapse.collapse { + height: auto !important; + overflow: visible !important; + } + +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-utilities.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-utilities.less index 0f85f849c5..bf43e8ef73 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-utilities.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive-utilities.less @@ -1,59 +1,59 @@ -// -// Responsive: Utility classes -// -------------------------------------------------- - - -// IE10 Metro responsive -// Required for Windows 8 Metro split-screen snapping with IE10 -// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ -@-ms-viewport{ - width: device-width; -} - -// Hide from screenreaders and browsers -// Credit: HTML5 Boilerplate -.hidden { - display: none; - visibility: hidden; -} - -// Visibility utilities - -// For desktops -.visible-phone { display: none !important; } -.visible-tablet { display: none !important; } -.hidden-phone { } -.hidden-tablet { } -.hidden-desktop { display: none !important; } -.visible-desktop { display: inherit !important; } - -// Tablets & small desktops only -@media (min-width: 768px) and (max-width: 979px) { - // Hide everything else - .hidden-desktop { display: inherit !important; } - .visible-desktop { display: none !important ; } - // Show - .visible-tablet { display: inherit !important; } - // Hide - .hidden-tablet { display: none !important; } -} - -// Phones only -@media (max-width: 767px) { - // Hide everything else - .hidden-desktop { display: inherit !important; } - .visible-desktop { display: none !important; } - // Show - .visible-phone { display: inherit !important; } // Use inherit to restore previous behavior - // Hide - .hidden-phone { display: none !important; } -} - -// Print utilities -.visible-print { display: none !important; } -.hidden-print { } - -@media print { - .visible-print { display: inherit !important; } - .hidden-print { display: none !important; } -} +// +// Responsive: Utility classes +// -------------------------------------------------- + + +// IE10 Metro responsive +// Required for Windows 8 Metro split-screen snapping with IE10 +// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ +@-ms-viewport{ + width: device-width; +} + +// Hide from screenreaders and browsers +// Credit: HTML5 Boilerplate +.hidden { + display: none; + visibility: hidden; +} + +// Visibility utilities + +// For desktops +.visible-phone { display: none !important; } +.visible-tablet { display: none !important; } +.hidden-phone { } +.hidden-tablet { } +.hidden-desktop { display: none !important; } +.visible-desktop { display: inherit !important; } + +// Tablets & small desktops only +@media (min-width: 768px) and (max-width: 979px) { + // Hide everything else + .hidden-desktop { display: inherit !important; } + .visible-desktop { display: none !important ; } + // Show + .visible-tablet { display: inherit !important; } + // Hide + .hidden-tablet { display: none !important; } +} + +// Phones only +@media (max-width: 767px) { + // Hide everything else + .hidden-desktop { display: inherit !important; } + .visible-desktop { display: none !important; } + // Show + .visible-phone { display: inherit !important; } // Use inherit to restore previous behavior + // Hide + .hidden-phone { display: none !important; } +} + +// Print utilities +.visible-print { display: none !important; } +.hidden-print { } + +@media print { + .visible-print { display: inherit !important; } + .hidden-print { display: none !important; } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive.less index c36a056997..b8366defbd 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/responsive.less @@ -1,48 +1,48 @@ -/*! - * Bootstrap Responsive v2.3.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */ - - -// Responsive.less -// For phone and tablet devices -// ------------------------------------------------------------- - - -// REPEAT VARIABLES & MIXINS -// ------------------------- -// Required since we compile the responsive stuff separately - -@import "variables.less"; // Modify this for custom colors, font-sizes, etc -@import "mixins.less"; - - -// RESPONSIVE CLASSES -// ------------------ - -@import "responsive-utilities.less"; - - -// MEDIA QUERIES -// ------------------ - -// Large desktops -@import "responsive-1200px-min.less"; - -// Tablets to regular desktops -@import "responsive-768px-979px.less"; - -// Phones to portrait tablets and narrow desktops -@import "responsive-767px-max.less"; - - -// RESPONSIVE NAVBAR -// ------------------ - -// From 979px and below, show a button to toggle navbar contents -@import "responsive-navbar.less"; +/*! + * Bootstrap Responsive v2.3.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + + +// Responsive.less +// For phone and tablet devices +// ------------------------------------------------------------- + + +// REPEAT VARIABLES & MIXINS +// ------------------------- +// Required since we compile the responsive stuff separately + +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "mixins.less"; + + +// RESPONSIVE CLASSES +// ------------------ + +@import "responsive-utilities.less"; + + +// MEDIA QUERIES +// ------------------ + +// Large desktops +@import "responsive-1200px-min.less"; + +// Tablets to regular desktops +@import "responsive-768px-979px.less"; + +// Phones to portrait tablets and narrow desktops +@import "responsive-767px-max.less"; + + +// RESPONSIVE NAVBAR +// ------------------ + +// From 979px and below, show a button to toggle navbar contents +@import "responsive-navbar.less"; diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less index 25a8257d11..f17e8cadb4 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less @@ -1,53 +1,53 @@ -// -// Scaffolding -// -------------------------------------------------- - - -// Body reset -// ------------------------- - -body { - margin: 0; - font-family: @baseFontFamily; - font-size: @baseFontSize; - line-height: @baseLineHeight; - color: @textColor; - background-color: @bodyBackground; -} - - -// Links -// ------------------------- - -a { - color: @linkColor; - text-decoration: none; -} -a:hover, -a:focus { - color: @linkColorHover; - text-decoration: underline; -} - - -// Images -// ------------------------- - -// Rounded corners -.img-rounded { - .border-radius(6px); -} - -// Add polaroid-esque trim -.img-polaroid { - padding: 4px; - background-color: #fff; - border: 1px solid #ccc; - border: 1px solid rgba(0,0,0,.2); - .box-shadow(0 1px 3px rgba(0,0,0,.1)); -} - -// Perfect circle -.img-circle { - .border-radius(500px); // crank the border-radius so it works with most reasonably sized images -} +// +// Scaffolding +// -------------------------------------------------- + + +// Body reset +// ------------------------- + +body { + margin: 0; + font-family: @baseFontFamily; + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @textColor; + background-color: @bodyBackground; +} + + +// Links +// ------------------------- + +a { + color: @linkColor; + text-decoration: none; +} +a:hover, +a:focus { + color: @linkColorHover; + text-decoration: underline; +} + + +// Images +// ------------------------- + +// Rounded corners +.img-rounded { + .border-radius(6px); +} + +// Add polaroid-esque trim +.img-polaroid { + padding: 4px; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0,0,0,.2); + .box-shadow(0 1px 3px rgba(0,0,0,.1)); +} + +// Perfect circle +.img-circle { + .border-radius(500px); // crank the border-radius so it works with most reasonably sized images +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less index 19194d7384..1812bf71ac 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less @@ -1,197 +1,197 @@ -// -// Sprites -// -------------------------------------------------- - - -// ICONS -// ----- - -// All icons receive the styles of the tag with a base class -// of .i and are then given a unique class to add width, height, -// and background-position. Your resulting HTML will look like -// . - -// For the white version of the icons, just add the .icon-white class: -// - -[class^="icon-"], -[class*=" icon-"] { - display: inline-block; - width: 14px; - height: 14px; - .ie7-restore-right-whitespace(); - line-height: 14px; - vertical-align: text-top; - background-image: url("@{iconSpritePath}"); - background-position: 14px 14px; - background-repeat: no-repeat; - margin-top: 1px; -} - -/* White icons with optional class, or on hover/focus/active states of certain elements */ -.icon-white, -.nav-pills > .active > a > [class^="icon-"], -.nav-pills > .active > a > [class*=" icon-"], -.nav-list > .active > a > [class^="icon-"], -.nav-list > .active > a > [class*=" icon-"], -.navbar-inverse .nav > .active > a > [class^="icon-"], -.navbar-inverse .nav > .active > a > [class*=" icon-"], -.dropdown-menu > li > a:hover > [class^="icon-"], -.dropdown-menu > li > a:focus > [class^="icon-"], -.dropdown-menu > li > a:hover > [class*=" icon-"], -.dropdown-menu > li > a:focus > [class*=" icon-"], -.dropdown-menu > .active > a > [class^="icon-"], -.dropdown-menu > .active > a > [class*=" icon-"], -.dropdown-submenu:hover > a > [class^="icon-"], -.dropdown-submenu:focus > a > [class^="icon-"], -.dropdown-submenu:hover > a > [class*=" icon-"], -.dropdown-submenu:focus > a > [class*=" icon-"] { - background-image: url("@{iconWhiteSpritePath}"); -} - -.icon-glass { background-position: 0 0; } -.icon-music { background-position: -24px 0; } -.icon-search { background-position: -48px 0; } -.icon-envelope { background-position: -72px 0; } -.icon-heart { background-position: -96px 0; } -.icon-star { background-position: -120px 0; } -.icon-star-empty { background-position: -144px 0; } -.icon-user { background-position: -168px 0; } -.icon-film { background-position: -192px 0; } -.icon-th-large { background-position: -216px 0; } -.icon-th { background-position: -240px 0; } -.icon-th-list { background-position: -264px 0; } -.icon-ok { background-position: -288px 0; } -.icon-remove { background-position: -312px 0; } -.icon-zoom-in { background-position: -336px 0; } -.icon-zoom-out { background-position: -360px 0; } -.icon-off { background-position: -384px 0; } -.icon-signal { background-position: -408px 0; } -.icon-cog { background-position: -432px 0; } -.icon-trash { background-position: -456px 0; } - -.icon-home { background-position: 0 -24px; } -.icon-file { background-position: -24px -24px; } -.icon-time { background-position: -48px -24px; } -.icon-road { background-position: -72px -24px; } -.icon-download-alt { background-position: -96px -24px; } -.icon-download { background-position: -120px -24px; } -.icon-upload { background-position: -144px -24px; } -.icon-inbox { background-position: -168px -24px; } -.icon-play-circle { background-position: -192px -24px; } -.icon-repeat { background-position: -216px -24px; } -.icon-refresh { background-position: -240px -24px; } -.icon-list-alt { background-position: -264px -24px; } -.icon-lock { background-position: -287px -24px; } // 1px off -.icon-flag { background-position: -312px -24px; } -.icon-headphones { background-position: -336px -24px; } -.icon-volume-off { background-position: -360px -24px; } -.icon-volume-down { background-position: -384px -24px; } -.icon-volume-up { background-position: -408px -24px; } -.icon-qrcode { background-position: -432px -24px; } -.icon-barcode { background-position: -456px -24px; } - -.icon-tag { background-position: 0 -48px; } -.icon-tags { background-position: -25px -48px; } // 1px off -.icon-book { background-position: -48px -48px; } -.icon-bookmark { background-position: -72px -48px; } -.icon-print { background-position: -96px -48px; } -.icon-camera { background-position: -120px -48px; } -.icon-font { background-position: -144px -48px; } -.icon-bold { background-position: -167px -48px; } // 1px off -.icon-italic { background-position: -192px -48px; } -.icon-text-height { background-position: -216px -48px; } -.icon-text-width { background-position: -240px -48px; } -.icon-align-left { background-position: -264px -48px; } -.icon-align-center { background-position: -288px -48px; } -.icon-align-right { background-position: -312px -48px; } -.icon-align-justify { background-position: -336px -48px; } -.icon-list { background-position: -360px -48px; } -.icon-indent-left { background-position: -384px -48px; } -.icon-indent-right { background-position: -408px -48px; } -.icon-facetime-video { background-position: -432px -48px; } -.icon-picture { background-position: -456px -48px; } - -.icon-pencil { background-position: 0 -72px; } -.icon-map-marker { background-position: -24px -72px; } -.icon-adjust { background-position: -48px -72px; } -.icon-tint { background-position: -72px -72px; } -.icon-edit { background-position: -96px -72px; } -.icon-share { background-position: -120px -72px; } -.icon-check { background-position: -144px -72px; } -.icon-move { background-position: -168px -72px; } -.icon-step-backward { background-position: -192px -72px; } -.icon-fast-backward { background-position: -216px -72px; } -.icon-backward { background-position: -240px -72px; } -.icon-play { background-position: -264px -72px; } -.icon-pause { background-position: -288px -72px; } -.icon-stop { background-position: -312px -72px; } -.icon-forward { background-position: -336px -72px; } -.icon-fast-forward { background-position: -360px -72px; } -.icon-step-forward { background-position: -384px -72px; } -.icon-eject { background-position: -408px -72px; } -.icon-chevron-left { background-position: -432px -72px; } -.icon-chevron-right { background-position: -456px -72px; } - -.icon-plus-sign { background-position: 0 -96px; } -.icon-minus-sign { background-position: -24px -96px; } -.icon-remove-sign { background-position: -48px -96px; } -.icon-ok-sign { background-position: -72px -96px; } -.icon-question-sign { background-position: -96px -96px; } -.icon-info-sign { background-position: -120px -96px; } -.icon-screenshot { background-position: -144px -96px; } -.icon-remove-circle { background-position: -168px -96px; } -.icon-ok-circle { background-position: -192px -96px; } -.icon-ban-circle { background-position: -216px -96px; } -.icon-arrow-left { background-position: -240px -96px; } -.icon-arrow-right { background-position: -264px -96px; } -.icon-arrow-up { background-position: -289px -96px; } // 1px off -.icon-arrow-down { background-position: -312px -96px; } -.icon-share-alt { background-position: -336px -96px; } -.icon-resize-full { background-position: -360px -96px; } -.icon-resize-small { background-position: -384px -96px; } -.icon-plus { background-position: -408px -96px; } -.icon-minus { background-position: -433px -96px; } -.icon-asterisk { background-position: -456px -96px; } - -.icon-exclamation-sign { background-position: 0 -120px; } -.icon-gift { background-position: -24px -120px; } -.icon-leaf { background-position: -48px -120px; } -.icon-fire { background-position: -72px -120px; } -.icon-eye-open { background-position: -96px -120px; } -.icon-eye-close { background-position: -120px -120px; } -.icon-warning-sign { background-position: -144px -120px; } -.icon-plane { background-position: -168px -120px; } -.icon-calendar { background-position: -192px -120px; } -.icon-random { background-position: -216px -120px; width: 16px; } -.icon-comment { background-position: -240px -120px; } -.icon-magnet { background-position: -264px -120px; } -.icon-chevron-up { background-position: -288px -120px; } -.icon-chevron-down { background-position: -313px -119px; } // 1px, 1px off -.icon-retweet { background-position: -336px -120px; } -.icon-shopping-cart { background-position: -360px -120px; } -.icon-folder-close { background-position: -384px -120px; width: 16px; } -.icon-folder-open { background-position: -408px -120px; width: 16px; } -.icon-resize-vertical { background-position: -432px -119px; } // 1px, 1px off -.icon-resize-horizontal { background-position: -456px -118px; } // 1px, 2px off - -.icon-hdd { background-position: 0 -144px; } -.icon-bullhorn { background-position: -24px -144px; } -.icon-bell { background-position: -48px -144px; } -.icon-certificate { background-position: -72px -144px; } -.icon-thumbs-up { background-position: -96px -144px; } -.icon-thumbs-down { background-position: -120px -144px; } -.icon-hand-right { background-position: -144px -144px; } -.icon-hand-left { background-position: -168px -144px; } -.icon-hand-up { background-position: -192px -144px; } -.icon-hand-down { background-position: -216px -144px; } -.icon-circle-arrow-right { background-position: -240px -144px; } -.icon-circle-arrow-left { background-position: -264px -144px; } -.icon-circle-arrow-up { background-position: -288px -144px; } -.icon-circle-arrow-down { background-position: -312px -144px; } -.icon-globe { background-position: -336px -144px; } -.icon-wrench { background-position: -360px -144px; } -.icon-tasks { background-position: -384px -144px; } -.icon-filter { background-position: -408px -144px; } -.icon-briefcase { background-position: -432px -144px; } -.icon-fullscreen { background-position: -456px -144px; } +// +// Sprites +// -------------------------------------------------- + + +// ICONS +// ----- + +// All icons receive the styles of the tag with a base class +// of .i and are then given a unique class to add width, height, +// and background-position. Your resulting HTML will look like +// . + +// For the white version of the icons, just add the .icon-white class: +// + +[class^="icon-"], +[class*=" icon-"] { + display: inline-block; + width: 14px; + height: 14px; + .ie7-restore-right-whitespace(); + line-height: 14px; + vertical-align: text-top; + background-image: url("@{iconSpritePath}"); + background-position: 14px 14px; + background-repeat: no-repeat; + margin-top: 1px; +} + +/* White icons with optional class, or on hover/focus/active states of certain elements */ +.icon-white, +.nav-pills > .active > a > [class^="icon-"], +.nav-pills > .active > a > [class*=" icon-"], +.nav-list > .active > a > [class^="icon-"], +.nav-list > .active > a > [class*=" icon-"], +.navbar-inverse .nav > .active > a > [class^="icon-"], +.navbar-inverse .nav > .active > a > [class*=" icon-"], +.dropdown-menu > li > a:hover > [class^="icon-"], +.dropdown-menu > li > a:focus > [class^="icon-"], +.dropdown-menu > li > a:hover > [class*=" icon-"], +.dropdown-menu > li > a:focus > [class*=" icon-"], +.dropdown-menu > .active > a > [class^="icon-"], +.dropdown-menu > .active > a > [class*=" icon-"], +.dropdown-submenu:hover > a > [class^="icon-"], +.dropdown-submenu:focus > a > [class^="icon-"], +.dropdown-submenu:hover > a > [class*=" icon-"], +.dropdown-submenu:focus > a > [class*=" icon-"] { + background-image: url("@{iconWhiteSpritePath}"); +} + +.icon-glass { background-position: 0 0; } +.icon-music { background-position: -24px 0; } +.icon-search { background-position: -48px 0; } +.icon-envelope { background-position: -72px 0; } +.icon-heart { background-position: -96px 0; } +.icon-star { background-position: -120px 0; } +.icon-star-empty { background-position: -144px 0; } +.icon-user { background-position: -168px 0; } +.icon-film { background-position: -192px 0; } +.icon-th-large { background-position: -216px 0; } +.icon-th { background-position: -240px 0; } +.icon-th-list { background-position: -264px 0; } +.icon-ok { background-position: -288px 0; } +.icon-remove { background-position: -312px 0; } +.icon-zoom-in { background-position: -336px 0; } +.icon-zoom-out { background-position: -360px 0; } +.icon-off { background-position: -384px 0; } +.icon-signal { background-position: -408px 0; } +.icon-cog { background-position: -432px 0; } +.icon-trash { background-position: -456px 0; } + +.icon-home { background-position: 0 -24px; } +.icon-file { background-position: -24px -24px; } +.icon-time { background-position: -48px -24px; } +.icon-road { background-position: -72px -24px; } +.icon-download-alt { background-position: -96px -24px; } +.icon-download { background-position: -120px -24px; } +.icon-upload { background-position: -144px -24px; } +.icon-inbox { background-position: -168px -24px; } +.icon-play-circle { background-position: -192px -24px; } +.icon-repeat { background-position: -216px -24px; } +.icon-refresh { background-position: -240px -24px; } +.icon-list-alt { background-position: -264px -24px; } +.icon-lock { background-position: -287px -24px; } // 1px off +.icon-flag { background-position: -312px -24px; } +.icon-headphones { background-position: -336px -24px; } +.icon-volume-off { background-position: -360px -24px; } +.icon-volume-down { background-position: -384px -24px; } +.icon-volume-up { background-position: -408px -24px; } +.icon-qrcode { background-position: -432px -24px; } +.icon-barcode { background-position: -456px -24px; } + +.icon-tag { background-position: 0 -48px; } +.icon-tags { background-position: -25px -48px; } // 1px off +.icon-book { background-position: -48px -48px; } +.icon-bookmark { background-position: -72px -48px; } +.icon-print { background-position: -96px -48px; } +.icon-camera { background-position: -120px -48px; } +.icon-font { background-position: -144px -48px; } +.icon-bold { background-position: -167px -48px; } // 1px off +.icon-italic { background-position: -192px -48px; } +.icon-text-height { background-position: -216px -48px; } +.icon-text-width { background-position: -240px -48px; } +.icon-align-left { background-position: -264px -48px; } +.icon-align-center { background-position: -288px -48px; } +.icon-align-right { background-position: -312px -48px; } +.icon-align-justify { background-position: -336px -48px; } +.icon-list { background-position: -360px -48px; } +.icon-indent-left { background-position: -384px -48px; } +.icon-indent-right { background-position: -408px -48px; } +.icon-facetime-video { background-position: -432px -48px; } +.icon-picture { background-position: -456px -48px; } + +.icon-pencil { background-position: 0 -72px; } +.icon-map-marker { background-position: -24px -72px; } +.icon-adjust { background-position: -48px -72px; } +.icon-tint { background-position: -72px -72px; } +.icon-edit { background-position: -96px -72px; } +.icon-share { background-position: -120px -72px; } +.icon-check { background-position: -144px -72px; } +.icon-move { background-position: -168px -72px; } +.icon-step-backward { background-position: -192px -72px; } +.icon-fast-backward { background-position: -216px -72px; } +.icon-backward { background-position: -240px -72px; } +.icon-play { background-position: -264px -72px; } +.icon-pause { background-position: -288px -72px; } +.icon-stop { background-position: -312px -72px; } +.icon-forward { background-position: -336px -72px; } +.icon-fast-forward { background-position: -360px -72px; } +.icon-step-forward { background-position: -384px -72px; } +.icon-eject { background-position: -408px -72px; } +.icon-chevron-left { background-position: -432px -72px; } +.icon-chevron-right { background-position: -456px -72px; } + +.icon-plus-sign { background-position: 0 -96px; } +.icon-minus-sign { background-position: -24px -96px; } +.icon-remove-sign { background-position: -48px -96px; } +.icon-ok-sign { background-position: -72px -96px; } +.icon-question-sign { background-position: -96px -96px; } +.icon-info-sign { background-position: -120px -96px; } +.icon-screenshot { background-position: -144px -96px; } +.icon-remove-circle { background-position: -168px -96px; } +.icon-ok-circle { background-position: -192px -96px; } +.icon-ban-circle { background-position: -216px -96px; } +.icon-arrow-left { background-position: -240px -96px; } +.icon-arrow-right { background-position: -264px -96px; } +.icon-arrow-up { background-position: -289px -96px; } // 1px off +.icon-arrow-down { background-position: -312px -96px; } +.icon-share-alt { background-position: -336px -96px; } +.icon-resize-full { background-position: -360px -96px; } +.icon-resize-small { background-position: -384px -96px; } +.icon-plus { background-position: -408px -96px; } +.icon-minus { background-position: -433px -96px; } +.icon-asterisk { background-position: -456px -96px; } + +.icon-exclamation-sign { background-position: 0 -120px; } +.icon-gift { background-position: -24px -120px; } +.icon-leaf { background-position: -48px -120px; } +.icon-fire { background-position: -72px -120px; } +.icon-eye-open { background-position: -96px -120px; } +.icon-eye-close { background-position: -120px -120px; } +.icon-warning-sign { background-position: -144px -120px; } +.icon-plane { background-position: -168px -120px; } +.icon-calendar { background-position: -192px -120px; } +.icon-random { background-position: -216px -120px; width: 16px; } +.icon-comment { background-position: -240px -120px; } +.icon-magnet { background-position: -264px -120px; } +.icon-chevron-up { background-position: -288px -120px; } +.icon-chevron-down { background-position: -313px -119px; } // 1px, 1px off +.icon-retweet { background-position: -336px -120px; } +.icon-shopping-cart { background-position: -360px -120px; } +.icon-folder-close { background-position: -384px -120px; width: 16px; } +.icon-folder-open { background-position: -408px -120px; width: 16px; } +.icon-resize-vertical { background-position: -432px -119px; } // 1px, 1px off +.icon-resize-horizontal { background-position: -456px -118px; } // 1px, 2px off + +.icon-hdd { background-position: 0 -144px; } +.icon-bullhorn { background-position: -24px -144px; } +.icon-bell { background-position: -48px -144px; } +.icon-certificate { background-position: -72px -144px; } +.icon-thumbs-up { background-position: -96px -144px; } +.icon-thumbs-down { background-position: -120px -144px; } +.icon-hand-right { background-position: -144px -144px; } +.icon-hand-left { background-position: -168px -144px; } +.icon-hand-up { background-position: -192px -144px; } +.icon-hand-down { background-position: -216px -144px; } +.icon-circle-arrow-right { background-position: -240px -144px; } +.icon-circle-arrow-left { background-position: -264px -144px; } +.icon-circle-arrow-up { background-position: -288px -144px; } +.icon-circle-arrow-down { background-position: -312px -144px; } +.icon-globe { background-position: -336px -144px; } +.icon-wrench { background-position: -360px -144px; } +.icon-tasks { background-position: -384px -144px; } +.icon-filter { background-position: -408px -144px; } +.icon-briefcase { background-position: -432px -144px; } +.icon-fullscreen { background-position: -456px -144px; } diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less index 1899173a81..0e35271e11 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less @@ -1,244 +1,244 @@ -// -// Tables -// -------------------------------------------------- - - -// BASE TABLES -// ----------------- - -table { - max-width: 100%; - background-color: @tableBackground; - border-collapse: collapse; - border-spacing: 0; -} - -// BASELINE STYLES -// --------------- - -.table { - width: 100%; - margin-bottom: @baseLineHeight; - // Cells - th, - td { - padding: 8px; - line-height: @baseLineHeight; - text-align: left; - vertical-align: top; - border-top: 1px solid @tableBorder; - } - th { - font-weight: bold; - } - // Bottom align for column headings - thead th { - vertical-align: bottom; - } - // Remove top border from thead by default - caption + thead tr:first-child th, - caption + thead tr:first-child td, - colgroup + thead tr:first-child th, - colgroup + thead tr:first-child td, - thead:first-child tr:first-child th, - thead:first-child tr:first-child td { - border-top: 0; - } - // Account for multiple tbody instances - tbody + tbody { - border-top: 2px solid @tableBorder; - } - - // Nesting - .table { - background-color: @bodyBackground; - } -} - - - -// CONDENSED TABLE W/ HALF PADDING -// ------------------------------- - -.table-condensed { - th, - td { - padding: 4px 5px; - } -} - - -// BORDERED VERSION -// ---------------- - -.table-bordered { - border: 1px solid @tableBorder; - border-collapse: separate; // Done so we can round those corners! - *border-collapse: collapse; // IE7 can't round corners anyway - border-left: 0; - .border-radius(@baseBorderRadius); - th, - td { - border-left: 1px solid @tableBorder; - } - // Prevent a double border - caption + thead tr:first-child th, - caption + tbody tr:first-child th, - caption + tbody tr:first-child td, - colgroup + thead tr:first-child th, - colgroup + tbody tr:first-child th, - colgroup + tbody tr:first-child td, - thead:first-child tr:first-child th, - tbody:first-child tr:first-child th, - tbody:first-child tr:first-child td { - border-top: 0; - } - // For first th/td in the first row in the first thead or tbody - thead:first-child tr:first-child > th:first-child, - tbody:first-child tr:first-child > td:first-child, - tbody:first-child tr:first-child > th:first-child { - .border-top-left-radius(@baseBorderRadius); - } - // For last th/td in the first row in the first thead or tbody - thead:first-child tr:first-child > th:last-child, - tbody:first-child tr:first-child > td:last-child, - tbody:first-child tr:first-child > th:last-child { - .border-top-right-radius(@baseBorderRadius); - } - // For first th/td (can be either) in the last row in the last thead, tbody, and tfoot - thead:last-child tr:last-child > th:first-child, - tbody:last-child tr:last-child > td:first-child, - tbody:last-child tr:last-child > th:first-child, - tfoot:last-child tr:last-child > td:first-child, - tfoot:last-child tr:last-child > th:first-child { - .border-bottom-left-radius(@baseBorderRadius); - } - // For last th/td (can be either) in the last row in the last thead, tbody, and tfoot - thead:last-child tr:last-child > th:last-child, - tbody:last-child tr:last-child > td:last-child, - tbody:last-child tr:last-child > th:last-child, - tfoot:last-child tr:last-child > td:last-child, - tfoot:last-child tr:last-child > th:last-child { - .border-bottom-right-radius(@baseBorderRadius); - } - - // Clear border-radius for first and last td in the last row in the last tbody for table with tfoot - tfoot + tbody:last-child tr:last-child td:first-child { - .border-bottom-left-radius(0); - } - tfoot + tbody:last-child tr:last-child td:last-child { - .border-bottom-right-radius(0); - } - - // Special fixes to round the left border on the first td/th - caption + thead tr:first-child th:first-child, - caption + tbody tr:first-child td:first-child, - colgroup + thead tr:first-child th:first-child, - colgroup + tbody tr:first-child td:first-child { - .border-top-left-radius(@baseBorderRadius); - } - caption + thead tr:first-child th:last-child, - caption + tbody tr:first-child td:last-child, - colgroup + thead tr:first-child th:last-child, - colgroup + tbody tr:first-child td:last-child { - .border-top-right-radius(@baseBorderRadius); - } - -} - - - - -// ZEBRA-STRIPING -// -------------- - -// Default zebra-stripe styles (alternating gray and transparent backgrounds) -.table-striped { - tbody { - > tr:nth-child(odd) > td, - > tr:nth-child(odd) > th { - background-color: @tableBackgroundAccent; - } - } -} - - -// HOVER EFFECT -// ------------ -// Placed here since it has to come after the potential zebra striping -.table-hover { - tbody { - tr:hover > td, - tr:hover > th { - background-color: @tableBackgroundHover; - } - } -} - - -// TABLE CELL SIZING -// ----------------- - -// Reset default grid behavior -table td[class*="span"], -table th[class*="span"], -.row-fluid table td[class*="span"], -.row-fluid table th[class*="span"] { - display: table-cell; - float: none; // undo default grid column styles - margin-left: 0; // undo default grid column styles -} - -// Change the column widths to account for td/th padding -.table td, -.table th { - &.span1 { .tableColumns(1); } - &.span2 { .tableColumns(2); } - &.span3 { .tableColumns(3); } - &.span4 { .tableColumns(4); } - &.span5 { .tableColumns(5); } - &.span6 { .tableColumns(6); } - &.span7 { .tableColumns(7); } - &.span8 { .tableColumns(8); } - &.span9 { .tableColumns(9); } - &.span10 { .tableColumns(10); } - &.span11 { .tableColumns(11); } - &.span12 { .tableColumns(12); } -} - - - -// TABLE BACKGROUNDS -// ----------------- -// Exact selectors below required to override .table-striped - -.table tbody tr { - &.success > td { - background-color: @successBackground; - } - &.error > td { - background-color: @errorBackground; - } - &.warning > td { - background-color: @warningBackground; - } - &.info > td { - background-color: @infoBackground; - } -} - -// Hover states for .table-hover -.table-hover tbody tr { - &.success:hover > td { - background-color: darken(@successBackground, 5%); - } - &.error:hover > td { - background-color: darken(@errorBackground, 5%); - } - &.warning:hover > td { - background-color: darken(@warningBackground, 5%); - } - &.info:hover > td { - background-color: darken(@infoBackground, 5%); - } -} +// +// Tables +// -------------------------------------------------- + + +// BASE TABLES +// ----------------- + +table { + max-width: 100%; + background-color: @tableBackground; + border-collapse: collapse; + border-spacing: 0; +} + +// BASELINE STYLES +// --------------- + +.table { + width: 100%; + margin-bottom: @baseLineHeight; + // Cells + th, + td { + padding: 8px; + line-height: @baseLineHeight; + text-align: left; + vertical-align: top; + border-top: 1px solid @tableBorder; + } + th { + font-weight: bold; + } + // Bottom align for column headings + thead th { + vertical-align: bottom; + } + // Remove top border from thead by default + caption + thead tr:first-child th, + caption + thead tr:first-child td, + colgroup + thead tr:first-child th, + colgroup + thead tr:first-child td, + thead:first-child tr:first-child th, + thead:first-child tr:first-child td { + border-top: 0; + } + // Account for multiple tbody instances + tbody + tbody { + border-top: 2px solid @tableBorder; + } + + // Nesting + .table { + background-color: @bodyBackground; + } +} + + + +// CONDENSED TABLE W/ HALF PADDING +// ------------------------------- + +.table-condensed { + th, + td { + padding: 4px 5px; + } +} + + +// BORDERED VERSION +// ---------------- + +.table-bordered { + border: 1px solid @tableBorder; + border-collapse: separate; // Done so we can round those corners! + *border-collapse: collapse; // IE7 can't round corners anyway + border-left: 0; + .border-radius(@baseBorderRadius); + th, + td { + border-left: 1px solid @tableBorder; + } + // Prevent a double border + caption + thead tr:first-child th, + caption + tbody tr:first-child th, + caption + tbody tr:first-child td, + colgroup + thead tr:first-child th, + colgroup + tbody tr:first-child th, + colgroup + tbody tr:first-child td, + thead:first-child tr:first-child th, + tbody:first-child tr:first-child th, + tbody:first-child tr:first-child td { + border-top: 0; + } + // For first th/td in the first row in the first thead or tbody + thead:first-child tr:first-child > th:first-child, + tbody:first-child tr:first-child > td:first-child, + tbody:first-child tr:first-child > th:first-child { + .border-top-left-radius(@baseBorderRadius); + } + // For last th/td in the first row in the first thead or tbody + thead:first-child tr:first-child > th:last-child, + tbody:first-child tr:first-child > td:last-child, + tbody:first-child tr:first-child > th:last-child { + .border-top-right-radius(@baseBorderRadius); + } + // For first th/td (can be either) in the last row in the last thead, tbody, and tfoot + thead:last-child tr:last-child > th:first-child, + tbody:last-child tr:last-child > td:first-child, + tbody:last-child tr:last-child > th:first-child, + tfoot:last-child tr:last-child > td:first-child, + tfoot:last-child tr:last-child > th:first-child { + .border-bottom-left-radius(@baseBorderRadius); + } + // For last th/td (can be either) in the last row in the last thead, tbody, and tfoot + thead:last-child tr:last-child > th:last-child, + tbody:last-child tr:last-child > td:last-child, + tbody:last-child tr:last-child > th:last-child, + tfoot:last-child tr:last-child > td:last-child, + tfoot:last-child tr:last-child > th:last-child { + .border-bottom-right-radius(@baseBorderRadius); + } + + // Clear border-radius for first and last td in the last row in the last tbody for table with tfoot + tfoot + tbody:last-child tr:last-child td:first-child { + .border-bottom-left-radius(0); + } + tfoot + tbody:last-child tr:last-child td:last-child { + .border-bottom-right-radius(0); + } + + // Special fixes to round the left border on the first td/th + caption + thead tr:first-child th:first-child, + caption + tbody tr:first-child td:first-child, + colgroup + thead tr:first-child th:first-child, + colgroup + tbody tr:first-child td:first-child { + .border-top-left-radius(@baseBorderRadius); + } + caption + thead tr:first-child th:last-child, + caption + tbody tr:first-child td:last-child, + colgroup + thead tr:first-child th:last-child, + colgroup + tbody tr:first-child td:last-child { + .border-top-right-radius(@baseBorderRadius); + } + +} + + + + +// ZEBRA-STRIPING +// -------------- + +// Default zebra-stripe styles (alternating gray and transparent backgrounds) +.table-striped { + tbody { + > tr:nth-child(odd) > td, + > tr:nth-child(odd) > th { + background-color: @tableBackgroundAccent; + } + } +} + + +// HOVER EFFECT +// ------------ +// Placed here since it has to come after the potential zebra striping +.table-hover { + tbody { + tr:hover > td, + tr:hover > th { + background-color: @tableBackgroundHover; + } + } +} + + +// TABLE CELL SIZING +// ----------------- + +// Reset default grid behavior +table td[class*="span"], +table th[class*="span"], +.row-fluid table td[class*="span"], +.row-fluid table th[class*="span"] { + display: table-cell; + float: none; // undo default grid column styles + margin-left: 0; // undo default grid column styles +} + +// Change the column widths to account for td/th padding +.table td, +.table th { + &.span1 { .tableColumns(1); } + &.span2 { .tableColumns(2); } + &.span3 { .tableColumns(3); } + &.span4 { .tableColumns(4); } + &.span5 { .tableColumns(5); } + &.span6 { .tableColumns(6); } + &.span7 { .tableColumns(7); } + &.span8 { .tableColumns(8); } + &.span9 { .tableColumns(9); } + &.span10 { .tableColumns(10); } + &.span11 { .tableColumns(11); } + &.span12 { .tableColumns(12); } +} + + + +// TABLE BACKGROUNDS +// ----------------- +// Exact selectors below required to override .table-striped + +.table tbody tr { + &.success > td { + background-color: @successBackground; + } + &.error > td { + background-color: @errorBackground; + } + &.warning > td { + background-color: @warningBackground; + } + &.info > td { + background-color: @infoBackground; + } +} + +// Hover states for .table-hover +.table-hover tbody tr { + &.success:hover > td { + background-color: darken(@successBackground, 5%); + } + &.error:hover > td { + background-color: darken(@errorBackground, 5%); + } + &.warning:hover > td { + background-color: darken(@warningBackground, 5%); + } + &.info:hover > td { + background-color: darken(@infoBackground, 5%); + } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/buttons.html b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/buttons.html index 821b797056..9b3c2c572c 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/buttons.html +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/buttons.html @@ -1,139 +1,139 @@ - - - - - Buttons · Bootstrap - - - - - - - - - - - - - - - - - - - - - - -
    - -

    Dropups

    - - - -
    - - - - - - - - - - - - - - - - - - - + + + + + Buttons · Bootstrap + + + + + + + + + + + + + + + + + + + + + + +
    + +

    Dropups

    + + + +
    + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.css b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.css index 3514c7c6ec..0f5604ee68 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.css +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.css @@ -1,150 +1,150 @@ -/*! - * Bootstrap CSS Tests - */ - - -/* Remove background image */ -body { - background-image: none; -} - -/* Space out subhead */ -.subhead { - margin-bottom: 36px; -} -/*h4 { - margin-bottom: 5px; -} -*/ - -.type-test { - margin-bottom: 20px; - padding: 0 20px 20px; - background: url(../../docs/assets/img/grid-baseline-20px.png); -} -.type-test h1, -.type-test h2, -.type-test h3, -.type-test h4, -.type-test h5, -.type-test h6 { - background-color: rgba(255,0,0,.2); -} - - -/* colgroup tests */ -.col1 { - background-color: rgba(255,0,0,.1); -} -.col2 { - background-color: rgba(0,255,0,.1); -} -.col3 { - background-color: rgba(0,0,255,.1); -} - - -/* Fluid row inputs */ -#rowInputs .row > [class*=span], -#fluidRowInputs .row-fluid > [class*=span] { - background-color: rgba(255,0,0,.1); -} - - -/* Fluid grid */ -.fluid-grid { - margin-bottom: 45px; -} -.fluid-grid .row { - height: 40px; - padding-top: 10px; - margin-top: 10px; - color: #ddd; - text-align: center; -} -.fluid-grid .span1 { - background-color: #999; -} - - -/* Gradients */ - -[class^="gradient-"] { - width: 100%; - height: 400px; - margin: 20px 0; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; -} - -.gradient-horizontal { - background-color: #333333; - background-image: -moz-linear-gradient(left, #555555, #333333); - background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#555555), to(#333333)); - background-image: -webkit-linear-gradient(left, #555555, #333333); - background-image: -o-linear-gradient(left, #555555, #333333); - background-image: linear-gradient(to right, #555555, #333333); - background-repeat: repeat-x; - filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff555555', endColorstr='#ff333333', GradientType=1); -} - -.gradient-vertical { - background-color: #474747; - background-image: -moz-linear-gradient(top, #555555, #333333); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#333333)); - background-image: -webkit-linear-gradient(top, #555555, #333333); - background-image: -o-linear-gradient(top, #555555, #333333); - background-image: linear-gradient(to bottom, #555555, #333333); - background-repeat: repeat-x; - filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff555555', endColorstr='#ff333333', GradientType=0); -} - -.gradient-directional { - background-color: #333333; - background-image: -moz-linear-gradient(45deg, #555555, #333333); - background-image: -webkit-linear-gradient(45deg, #555555, #333333); - background-image: -o-linear-gradient(45deg, #555555, #333333); - background-image: linear-gradient(45deg, #555555, #333333); - background-repeat: repeat-x; -} - -.gradient-vertical-three { - background-color: #8940a5; - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#00b3ee), color-stop(50%, #7a43b6), to(#c3325f)); - background-image: -webkit-linear-gradient(#00b3ee, #7a43b6 50%, #c3325f); - background-image: -moz-linear-gradient(top, #00b3ee, #7a43b6 50%, #c3325f); - background-image: -o-linear-gradient(#00b3ee, #7a43b6 50%, #c3325f); - background-image: linear-gradient(#00b3ee, #7a43b6 50%, #c3325f); - background-repeat: no-repeat; - filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff00b3ee', endColorstr='#ffc3325f', GradientType=0); -} - -.gradient-radial { - background-color: #333333; - background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(#555555), to(#333333)); - background-image: -webkit-radial-gradient(circle, #555555, #333333); - background-image: -moz-radial-gradient(circle, #555555, #333333); - background-image: -o-radial-gradient(circle, #555555, #333333); - background-repeat: no-repeat; -} - -.gradient-striped { - background-color: #555555; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.gradient-horizontal-three { - background-color: #00b3ee; - background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(#00b3ee), color-stop(50%, #7a43b6), to(#c3325f)); - background-image: -webkit-linear-gradient(left, #00b3ee, #7a43b6 50%, #c3325f); - background-image: -moz-linear-gradient(left, #00b3ee, #7a43b6 50%, #c3325f); - background-image: -o-linear-gradient(left, #00b3ee, #7a43b6 50%, #c3325f); - background-image: linear-gradient(to right, #00b3ee, #7a43b6 50%, #c3325f); - background-repeat: no-repeat; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00b3ee', endColorstr='#c3325f', GradientType=0); -} +/*! + * Bootstrap CSS Tests + */ + + +/* Remove background image */ +body { + background-image: none; +} + +/* Space out subhead */ +.subhead { + margin-bottom: 36px; +} +/*h4 { + margin-bottom: 5px; +} +*/ + +.type-test { + margin-bottom: 20px; + padding: 0 20px 20px; + background: url(../../docs/assets/img/grid-baseline-20px.png); +} +.type-test h1, +.type-test h2, +.type-test h3, +.type-test h4, +.type-test h5, +.type-test h6 { + background-color: rgba(255,0,0,.2); +} + + +/* colgroup tests */ +.col1 { + background-color: rgba(255,0,0,.1); +} +.col2 { + background-color: rgba(0,255,0,.1); +} +.col3 { + background-color: rgba(0,0,255,.1); +} + + +/* Fluid row inputs */ +#rowInputs .row > [class*=span], +#fluidRowInputs .row-fluid > [class*=span] { + background-color: rgba(255,0,0,.1); +} + + +/* Fluid grid */ +.fluid-grid { + margin-bottom: 45px; +} +.fluid-grid .row { + height: 40px; + padding-top: 10px; + margin-top: 10px; + color: #ddd; + text-align: center; +} +.fluid-grid .span1 { + background-color: #999; +} + + +/* Gradients */ + +[class^="gradient-"] { + width: 100%; + height: 400px; + margin: 20px 0; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +.gradient-horizontal { + background-color: #333333; + background-image: -moz-linear-gradient(left, #555555, #333333); + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#555555), to(#333333)); + background-image: -webkit-linear-gradient(left, #555555, #333333); + background-image: -o-linear-gradient(left, #555555, #333333); + background-image: linear-gradient(to right, #555555, #333333); + background-repeat: repeat-x; + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff555555', endColorstr='#ff333333', GradientType=1); +} + +.gradient-vertical { + background-color: #474747; + background-image: -moz-linear-gradient(top, #555555, #333333); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#333333)); + background-image: -webkit-linear-gradient(top, #555555, #333333); + background-image: -o-linear-gradient(top, #555555, #333333); + background-image: linear-gradient(to bottom, #555555, #333333); + background-repeat: repeat-x; + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff555555', endColorstr='#ff333333', GradientType=0); +} + +.gradient-directional { + background-color: #333333; + background-image: -moz-linear-gradient(45deg, #555555, #333333); + background-image: -webkit-linear-gradient(45deg, #555555, #333333); + background-image: -o-linear-gradient(45deg, #555555, #333333); + background-image: linear-gradient(45deg, #555555, #333333); + background-repeat: repeat-x; +} + +.gradient-vertical-three { + background-color: #8940a5; + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#00b3ee), color-stop(50%, #7a43b6), to(#c3325f)); + background-image: -webkit-linear-gradient(#00b3ee, #7a43b6 50%, #c3325f); + background-image: -moz-linear-gradient(top, #00b3ee, #7a43b6 50%, #c3325f); + background-image: -o-linear-gradient(#00b3ee, #7a43b6 50%, #c3325f); + background-image: linear-gradient(#00b3ee, #7a43b6 50%, #c3325f); + background-repeat: no-repeat; + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff00b3ee', endColorstr='#ffc3325f', GradientType=0); +} + +.gradient-radial { + background-color: #333333; + background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(#555555), to(#333333)); + background-image: -webkit-radial-gradient(circle, #555555, #333333); + background-image: -moz-radial-gradient(circle, #555555, #333333); + background-image: -o-radial-gradient(circle, #555555, #333333); + background-repeat: no-repeat; +} + +.gradient-striped { + background-color: #555555; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.gradient-horizontal-three { + background-color: #00b3ee; + background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(#00b3ee), color-stop(50%, #7a43b6), to(#c3325f)); + background-image: -webkit-linear-gradient(left, #00b3ee, #7a43b6 50%, #c3325f); + background-image: -moz-linear-gradient(left, #00b3ee, #7a43b6 50%, #c3325f); + background-image: -o-linear-gradient(left, #00b3ee, #7a43b6 50%, #c3325f); + background-image: linear-gradient(to right, #00b3ee, #7a43b6 50%, #c3325f); + background-repeat: no-repeat; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00b3ee', endColorstr='#c3325f', GradientType=0); +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.html b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.html index a9a5034fd6..c69688c763 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.html +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/css-tests.html @@ -1,1399 +1,1399 @@ - - - - - CSS Tests · Twitter Bootstrap - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -

    CSS Tests

    -

    One stop shop for quick debugging and edge-case tests of CSS.

    -
    -
    - - -
    - -
    - - - - - - - -
    -
    -
    -

    h1. Heading 1

    -

    h2. Heading 2

    -

    h3. Heading 3

    -

    h4. Heading 4

    -
    h5. Heading 5
    -
    h6. Heading 6
    -

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    -
    -
    -
    -
    -

    h1. Heading 1

    -

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    -

    h2. Heading 2

    -

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    -

    h3. Heading 3

    -

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    -

    h4. Heading 4

    -

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    -
    h5. Heading 5
    -

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    -
    h6. Heading 6
    -

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    -
    -
    -
    - - - - - - - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -

    - - - - - - - - -
    -
    -
    12 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    -
    -
    11 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    1 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    -
    -
    10 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    2 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    -
    -
    9 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    3 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    -
    -
    8 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    4 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    -
    -
    7 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    5 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    -
    -
    6 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    6 -
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    1
    -
    -
    -
    -
    - - - - - - - -
    -
    -

    Bordered without thead

    - - - - - - - - - - - - - - - - - - -
    123
    123
    123
    -

    Bordered without thead, with caption

    - - - - - - - - - - - - - - - - - - - -
    Table caption
    123
    123
    123
    -

    Bordered without thead, with colgroup

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    123
    123
    123
    369
    -

    Bordered with thead, with colgroup

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    ABC
    123
    123
    123
    369
    -
    -
    -

    Bordered with thead and caption

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Table caption
    123
    123
    123
    123
    369
    -

    Bordered with rowspan and colspan

    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    123
    1 and 23
    123
    13
    2 and 3
    -
    -
    - - -

    Grid sizing

    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    123
    1 and 23
    123
    13
    2 and 3
    -
    -
    - -

    Nesting and striping

    - - - - - - - - - - - -
    Test
    - - - - - - - - - - - - - - - - - - - - - -
    TestTest
    - test - - test -
    - test - - test -
    - test - - test -
    -
    - -

    Fluid grid sizing

    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    123
    1 and 23
    123
    13
    2 and 3
    -
    -
    - - - - - - - -

    Buttons and button groups

    -
    - - - -
    - -

    Horizontal form errors

    -
    -
    - -
    - - Please correct the error -
    -
    -
    - -
    -
    -

    Prepend and append on inputs

    -
    -
    -
    - @ - -
    -
    -
    -
    - - @ -
    -
    -
    -
    - $ - - .00 -
    -
    -
    -
    -
    -

    Prepend and append with uneditable

    -
    -
    - $ - Some value here -
    -
    - Some value here - .00 -
    -
    - $ - Some value here - .00 -
    -
    -
    -
    -

    Prepend with type="submit"

    - -
    - - -
    -
    - - - -
    -
    -
    - -

    Fluid prepended and appended inputs

    -
    -
    -
    -
    -
    - @ -
    -
    -
    -
    - @ -
    -
    -
    -
    - $.00 -
    -
    -
    -
    -
    - -

    Fixed row with inputs

    -

    Inputs should not extend past the light red background, set on their parent, a .span* column.

    - -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -

    Fluid row with inputs

    -

    Inputs should not extend past the light red background, set on their parent, a .span* column.

    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    - -
    - -

    Inline form in fluid row

    - -
    -
    -
    - - - - -
    -
    -
    - - -
    - - -

    Fluid textarea at .span12

    -
    -
    - -
    -
    - - -
    - - -

    Selects

    -
    - -
    - - -
    - - - - - - - - -

    Dropdown link with hash URL

    - - -

    Dropdown link with custom URL and data-target

    - - -

    Dropdown on a button

    - - -
    - - - - - - -

    Default thumbnails (no grid sizing)

    -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    - - - -

    Standard grid sizing

    - - -

    Fluid thumbnails

    -
    -
    - -
    -
    - - - - - - - -
    - -
    -
    -

    I'm in Section 1.

    - -
    - -
    -
    -

    I'm in Section 1.1.

    -
    -
    -

    I'm in Section 1.2.

    -
    -
    -

    I'm in Section 1.3.

    -
    -
    -
    -
    -
    -

    Howdy, I'm in Section 2.

    -
    -
    -

    What up girl, this is Section 3.

    -
    -
    -
    - -
    - - - - - - -
    -
    -

    Inline label

    -

    Cras justo odio, dapibus ac facilisis in, egestas eget quam. Maecenas sed diam Label name eget risus varius blandit sit amet non magna. Fusce .class-name dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

    -
    -
    -
    - - Hey! Read this. -
    -
    -
    - - -
    -
    - -
    - - - - - - - - - - - - - -
    - Maecenas faucibus mollis interdum. Nulla vitae elit libero, a pharetra augue. Donec ullamcorper nulla non metus auctor fringilla. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. - -
    - - - - -
    -
    - -

    Mini buttons: text and icon

    -
    - - -
    - -
    - - - - - - - -

    Visible on...

    -
      -
    • Phone✔ Phone
    • -
    • Tablet✔ Tablet
    • -
    • Desktop✔ Desktop
    • -
    -
      -
    • Phone + Tablet✔ Phone + Tablet
    • -
    • Tablet + Desktop✔ Tablet + Desktop
    • -
    • All✔ All
    • -
    - -

    Hidden on...

    -
      -
    • Phone✔ Phone
    • -
    • Tablet✔ Tablet
    • -
    • Desktop✔ Desktop
    • -
    -
      -
    • Phone + Tablet✔ Phone + Tablet
    • -
    • Tablet + Desktop✔ Tablet + Desktop
    • -
    • All✔ All
    • -
    - - - - - - - -

    Horizontal

    -
    - -

    Vertical

    -
    - -

    Directional

    -
    - -

    Three colors

    -
    - -

    Radial

    -
    - -

    Striped

    -
    - -

    Horizontal three colors

    -
    - - - - - -

    Alert default

    -
    - - Alert! Best check yourself, you're not looking too good. -
    -
    - -

    Alert! Best check yourself, you're not looking too good.

    -
    - -

    Success

    -
    - - Success! Best check yourself, you're not looking too good. -
    -
    - -

    Success! Best check yourself, you're not looking too good.

    -
    - -

    Info

    -
    - - Info! Best check yourself, you're not looking too good. -
    -
    - -

    Info! Best check yourself, you're not looking too good.

    -
    - -

    Warning

    -
    - - Warning! Best check yourself, you're not looking too good. -
    -
    - -

    Warning! Best check yourself, you're not looking too good.

    -
    - -

    Error

    -
    - - Error! Best check yourself, you're not looking too good. -
    -
    - -

    Error! Best check yourself, you're not looking too good.

    -
    - - -
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - + + + + + CSS Tests · Twitter Bootstrap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +

    CSS Tests

    +

    One stop shop for quick debugging and edge-case tests of CSS.

    +
    +
    + + +
    + +
    + + + + + + + +
    +
    +
    +

    h1. Heading 1

    +

    h2. Heading 2

    +

    h3. Heading 3

    +

    h4. Heading 4

    +
    h5. Heading 5
    +
    h6. Heading 6
    +

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    +
    +
    +
    +
    +

    h1. Heading 1

    +

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    +

    h2. Heading 2

    +

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    +

    h3. Heading 3

    +

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    +

    h4. Heading 4

    +

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    +
    h5. Heading 5
    +

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    +
    h6. Heading 6
    +

    Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    +
    +
    +
    + + + + + + + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +

    + + + + + + + + +
    +
    +
    12 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    11 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    1 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    10 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    2 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    9 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    3 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    8 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    4 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    7 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    5 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    6 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    6 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    + + + + + + + +
    +
    +

    Bordered without thead

    + + + + + + + + + + + + + + + + + + +
    123
    123
    123
    +

    Bordered without thead, with caption

    + + + + + + + + + + + + + + + + + + + +
    Table caption
    123
    123
    123
    +

    Bordered without thead, with colgroup

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    123
    123
    123
    369
    +

    Bordered with thead, with colgroup

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ABC
    123
    123
    123
    369
    +
    +
    +

    Bordered with thead and caption

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Table caption
    123
    123
    123
    123
    369
    +

    Bordered with rowspan and colspan

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    123
    1 and 23
    123
    13
    2 and 3
    +
    +
    + + +

    Grid sizing

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    123
    1 and 23
    123
    13
    2 and 3
    +
    +
    + +

    Nesting and striping

    + + + + + + + + + + + +
    Test
    + + + + + + + + + + + + + + + + + + + + + +
    TestTest
    + test + + test +
    + test + + test +
    + test + + test +
    +
    + +

    Fluid grid sizing

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    123
    1 and 23
    123
    13
    2 and 3
    +
    +
    + + + + + + + +

    Buttons and button groups

    +
    + + + +
    + +

    Horizontal form errors

    +
    +
    + +
    + + Please correct the error +
    +
    +
    + +
    +
    +

    Prepend and append on inputs

    +
    +
    +
    + @ + +
    +
    +
    +
    + + @ +
    +
    +
    +
    + $ + + .00 +
    +
    +
    +
    +
    +

    Prepend and append with uneditable

    +
    +
    + $ + Some value here +
    +
    + Some value here + .00 +
    +
    + $ + Some value here + .00 +
    +
    +
    +
    +

    Prepend with type="submit"

    + +
    + + +
    +
    + + + +
    +
    +
    + +

    Fluid prepended and appended inputs

    +
    +
    +
    +
    +
    + @ +
    +
    +
    +
    + @ +
    +
    +
    +
    + $.00 +
    +
    +
    +
    +
    + +

    Fixed row with inputs

    +

    Inputs should not extend past the light red background, set on their parent, a .span* column.

    + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +

    Fluid row with inputs

    +

    Inputs should not extend past the light red background, set on their parent, a .span* column.

    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    + +

    Inline form in fluid row

    + +
    +
    +
    + + + + +
    +
    +
    + + +
    + + +

    Fluid textarea at .span12

    +
    +
    + +
    +
    + + +
    + + +

    Selects

    +
    + +
    + + +
    + + + + + + + + +

    Dropdown link with hash URL

    + + +

    Dropdown link with custom URL and data-target

    + + +

    Dropdown on a button

    + + +
    + + + + + + +

    Default thumbnails (no grid sizing)

    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    + + + +

    Standard grid sizing

    + + +

    Fluid thumbnails

    +
    +
    + +
    +
    + + + + + + + +
    + +
    +
    +

    I'm in Section 1.

    + +
    + +
    +
    +

    I'm in Section 1.1.

    +
    +
    +

    I'm in Section 1.2.

    +
    +
    +

    I'm in Section 1.3.

    +
    +
    +
    +
    +
    +

    Howdy, I'm in Section 2.

    +
    +
    +

    What up girl, this is Section 3.

    +
    +
    +
    + +
    + + + + + + +
    +
    +

    Inline label

    +

    Cras justo odio, dapibus ac facilisis in, egestas eget quam. Maecenas sed diam Label name eget risus varius blandit sit amet non magna. Fusce .class-name dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

    +
    +
    +
    + + Hey! Read this. +
    +
    +
    + + +
    +
    + +
    + + + + + + + + + + + + + +
    + Maecenas faucibus mollis interdum. Nulla vitae elit libero, a pharetra augue. Donec ullamcorper nulla non metus auctor fringilla. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. + +
    + + + + +
    +
    + +

    Mini buttons: text and icon

    +
    + + +
    + +
    + + + + + + + +

    Visible on...

    +
      +
    • Phone✔ Phone
    • +
    • Tablet✔ Tablet
    • +
    • Desktop✔ Desktop
    • +
    +
      +
    • Phone + Tablet✔ Phone + Tablet
    • +
    • Tablet + Desktop✔ Tablet + Desktop
    • +
    • All✔ All
    • +
    + +

    Hidden on...

    +
      +
    • Phone✔ Phone
    • +
    • Tablet✔ Tablet
    • +
    • Desktop✔ Desktop
    • +
    +
      +
    • Phone + Tablet✔ Phone + Tablet
    • +
    • Tablet + Desktop✔ Tablet + Desktop
    • +
    • All✔ All
    • +
    + + + + + + + +

    Horizontal

    +
    + +

    Vertical

    +
    + +

    Directional

    +
    + +

    Three colors

    +
    + +

    Radial

    +
    + +

    Striped

    +
    + +

    Horizontal three colors

    +
    + + + + + +

    Alert default

    +
    + + Alert! Best check yourself, you're not looking too good. +
    +
    + +

    Alert! Best check yourself, you're not looking too good.

    +
    + +

    Success

    +
    + + Success! Best check yourself, you're not looking too good. +
    +
    + +

    Success! Best check yourself, you're not looking too good.

    +
    + +

    Info

    +
    + + Info! Best check yourself, you're not looking too good. +
    +
    + +

    Info! Best check yourself, you're not looking too good.

    +
    + +

    Warning

    +
    + + Warning! Best check yourself, you're not looking too good. +
    +
    + +

    Warning! Best check yourself, you're not looking too good.

    +
    + +

    Error

    +
    + + Error! Best check yourself, you're not looking too good. +
    +
    + +

    Error! Best check yourself, you're not looking too good.

    +
    + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/forms-responsive.html b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/forms-responsive.html index f0d00fcae7..c3e208d021 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/forms-responsive.html +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/forms-responsive.html @@ -1,71 +1,71 @@ - - - - - Bootstrap, from Twitter - - - - - - - - - - - - - - - - - - - - - - -
    - - - -

    Vertical alignment

    - - - span1 - -

    Width across elements

    -
    - -
    -
    - -
    -
    - span2 -
    - - - - -
    - - - span1 -
    - -
    - - - + + + + + Bootstrap, from Twitter + + + + + + + + + + + + + + + + + + + + + + +
    + + + +

    Vertical alignment

    + + + span1 + +

    Width across elements

    +
    + +
    +
    + +
    +
    + span2 +
    + + + + +
    + + + span1 +
    + +
    + + + diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/forms.html b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/forms.html index cccc44401a..a63d728a00 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/forms.html +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/forms.html @@ -1,179 +1,179 @@ - - - - - Bootstrap, from Twitter - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    - - - - -
    -
    - -
    - - - + + + + + Bootstrap, from Twitter + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    +
    + +
    + + + diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar-fixed-top.html b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar-fixed-top.html index 027dceb08b..2d9a7a718c 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar-fixed-top.html +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar-fixed-top.html @@ -1,104 +1,104 @@ - - - - - Bootstrap, from Twitter - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - -
    -

    Navbar example

    -

    This example is a quick exercise to illustrate how the default, static navbar and fixed to top navbar work. It includes the responsive CSS and HTML, so it also adapts to your viewport and device.

    -

    - View navbar docs » -

    -
    - -
    - - - - - - - - - - - - - - - - - - - + + + + + Bootstrap, from Twitter + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +

    Navbar example

    +

    This example is a quick exercise to illustrate how the default, static navbar and fixed to top navbar work. It includes the responsive CSS and HTML, so it also adapts to your viewport and device.

    +

    + View navbar docs » +

    +
    + +
    + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar-static-top.html b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar-static-top.html index cf10f8984d..4bead8ec67 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar-static-top.html +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar-static-top.html @@ -1,107 +1,107 @@ - - - - - Bootstrap, from Twitter - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - -
    -

    Navbar example

    -

    This example is a quick exercise to illustrate how the default, static navbar and fixed to top navbar work. It includes the responsive CSS and HTML, so it also adapts to your viewport and device.

    -

    - View navbar docs » -

    -
    - -
    - - - - - - - - - - - - - - - - - - - + + + + + Bootstrap, from Twitter + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +

    Navbar example

    +

    This example is a quick exercise to illustrate how the default, static navbar and fixed to top navbar work. It includes the responsive CSS and HTML, so it also adapts to your viewport and device.

    +

    + View navbar docs » +

    +
    + +
    + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar.html b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar.html index a7750194e9..d5ad4784ef 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar.html +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tests/navbar.html @@ -1,107 +1,107 @@ - - - - - Bootstrap, from Twitter - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - -
    -

    Navbar example

    -

    This example is a quick exercise to illustrate how the default, static navbar and fixed to top navbar work. It includes the responsive CSS and HTML, so it also adapts to your viewport and device.

    -

    - View navbar docs » -

    -
    - -
    - - - - - - - - - - - - - - - - - - - + + + + + Bootstrap, from Twitter + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Navbar example

    +

    This example is a quick exercise to illustrate how the default, static navbar and fixed to top navbar work. It includes the responsive CSS and HTML, so it also adapts to your viewport and device.

    +

    + View navbar docs » +

    +
    + +
    + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/thumbnails.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/thumbnails.less index c81b6398ed..d86f4ed7a9 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/thumbnails.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/thumbnails.less @@ -1,58 +1,58 @@ -// -// Thumbnails -// -------------------------------------------------- - - -// Note: `.thumbnails` and `.thumbnails > li` are overriden in responsive files - -// Make wrapper ul behave like the grid -.thumbnails { - margin-left: -@gridGutterWidth; - list-style: none; - .clearfix(); -} -// Fluid rows have no left margin -.row-fluid .thumbnails { - margin-left: 0; -} - -// Float li to make thumbnails appear in a row -.thumbnails > li { - float: left; // Explicity set the float since we don't require .span* classes - margin-bottom: @baseLineHeight; - margin-left: @gridGutterWidth; - a:hover{ - text-decoration:none; - } -} - -// The actual thumbnail (can be `a` or `div`) -.thumbnail { - display: block; - padding: 4px; - line-height: @baseLineHeight; - border: 1px solid @gray-8; - .border-radius(@baseBorderRadius); - .box-shadow(0 1px 3px rgba(0,0,0,.055)); - .transition(all .2s ease-in-out); -} -// Add a hover/focus state for linked versions only. -a.thumbnail:hover, -a.thumbnail:focus, -a div.thumbnail:hover, -a div.thumbnail:focus { - border-color: @turquoise; - .box-shadow(0 1px 4px rgba(0,105,214,.25)); -} - -// Images and captions -.thumbnail > img { - display: block; - max-width: 100%; - margin-left: auto; - margin-right: auto; -} -.thumbnail .caption { - padding: 9px; - color: @gray; -} +// +// Thumbnails +// -------------------------------------------------- + + +// Note: `.thumbnails` and `.thumbnails > li` are overriden in responsive files + +// Make wrapper ul behave like the grid +.thumbnails { + margin-left: -@gridGutterWidth; + list-style: none; + .clearfix(); +} +// Fluid rows have no left margin +.row-fluid .thumbnails { + margin-left: 0; +} + +// Float li to make thumbnails appear in a row +.thumbnails > li { + float: left; // Explicity set the float since we don't require .span* classes + margin-bottom: @baseLineHeight; + margin-left: @gridGutterWidth; + a:hover{ + text-decoration:none; + } +} + +// The actual thumbnail (can be `a` or `div`) +.thumbnail { + display: block; + padding: 4px; + line-height: @baseLineHeight; + border: 1px solid @gray-8; + .border-radius(@baseBorderRadius); + .box-shadow(0 1px 3px rgba(0,0,0,.055)); + .transition(all .2s ease-in-out); +} +// Add a hover/focus state for linked versions only. +a.thumbnail:hover, +a.thumbnail:focus, +a div.thumbnail:hover, +a div.thumbnail:focus { + border-color: @turquoise; + .box-shadow(0 1px 4px rgba(0,105,214,.25)); +} + +// Images and captions +.thumbnail > img { + display: block; + max-width: 100%; + margin-left: auto; + margin-right: auto; +} +.thumbnail .caption { + padding: 9px; + color: @gray; +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tooltip.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tooltip.less index 360dc24ae1..83d5f2bd76 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tooltip.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tooltip.less @@ -1,70 +1,70 @@ -// -// Tooltips -// -------------------------------------------------- - - -// Base class -.tooltip { - position: absolute; - z-index: @zindexTooltip; - display: block; - visibility: visible; - font-size: 11px; - line-height: 1.4; - .opacity(0); - &.in { .opacity(80); } - &.top { margin-top: -3px; padding: 5px 0; } - &.right { margin-left: 3px; padding: 0 5px; } - &.bottom { margin-top: 3px; padding: 5px 0; } - &.left { margin-left: -3px; padding: 0 5px; } -} - -// Wrapper for the tooltip content -.tooltip-inner { - max-width: 200px; - padding: 8px; - color: @tooltipColor; - text-align: center; - text-decoration: none; - background-color: @tooltipBackground; - .border-radius(@baseBorderRadius); -} - -// Arrows -.tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} -.tooltip { - &.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -@tooltipArrowWidth; - border-width: @tooltipArrowWidth @tooltipArrowWidth 0; - border-top-color: @tooltipArrowColor; - } - &.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -@tooltipArrowWidth; - border-width: @tooltipArrowWidth @tooltipArrowWidth @tooltipArrowWidth 0; - border-right-color: @tooltipArrowColor; - } - &.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -@tooltipArrowWidth; - border-width: @tooltipArrowWidth 0 @tooltipArrowWidth @tooltipArrowWidth; - border-left-color: @tooltipArrowColor; - } - &.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -@tooltipArrowWidth; - border-width: 0 @tooltipArrowWidth @tooltipArrowWidth; - border-bottom-color: @tooltipArrowColor; - } -} +// +// Tooltips +// -------------------------------------------------- + + +// Base class +.tooltip { + position: absolute; + z-index: @zindexTooltip; + display: block; + visibility: visible; + font-size: 11px; + line-height: 1.4; + .opacity(0); + &.in { .opacity(80); } + &.top { margin-top: -3px; padding: 5px 0; } + &.right { margin-left: 3px; padding: 0 5px; } + &.bottom { margin-top: 3px; padding: 5px 0; } + &.left { margin-left: -3px; padding: 0 5px; } +} + +// Wrapper for the tooltip content +.tooltip-inner { + max-width: 200px; + padding: 8px; + color: @tooltipColor; + text-align: center; + text-decoration: none; + background-color: @tooltipBackground; + .border-radius(@baseBorderRadius); +} + +// Arrows +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip { + &.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -@tooltipArrowWidth; + border-width: @tooltipArrowWidth @tooltipArrowWidth 0; + border-top-color: @tooltipArrowColor; + } + &.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -@tooltipArrowWidth; + border-width: @tooltipArrowWidth @tooltipArrowWidth @tooltipArrowWidth 0; + border-right-color: @tooltipArrowColor; + } + &.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -@tooltipArrowWidth; + border-width: @tooltipArrowWidth 0 @tooltipArrowWidth @tooltipArrowWidth; + border-left-color: @tooltipArrowColor; + } + &.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -@tooltipArrowWidth; + border-width: 0 @tooltipArrowWidth @tooltipArrowWidth; + border-bottom-color: @tooltipArrowColor; + } +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less index cfe8ec6f80..337138ac8e 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less @@ -1,247 +1,247 @@ -// -// Typography -// -------------------------------------------------- - - -// Body text -// ------------------------- - -p { - margin: 0 0 @baseLineHeight / 2; -} -.lead { - margin-bottom: @baseLineHeight; - font-size: @baseFontSize * 1.5; - font-weight: 200; - line-height: @baseLineHeight * 1.5; -} - - -// Emphasis & misc -// ------------------------- - -// Ex: 14px base font * 85% = about 12px -small { font-size: 85%; } - -strong { font-weight: bold; } -em { font-style: italic; } -cite { font-style: normal; } - -// Utility classes -.muted { color: @grayLight; } -a.muted:hover, -a.muted:focus { color: darken(@grayLight, 10%); } - -.text-warning { color: @warningText; } -a.text-warning:hover, -a.text-warning:focus { color: darken(@warningText, 10%); } - -.text-error { color: @errorText; } -a.text-error:hover, -a.text-error:focus { color: darken(@errorText, 10%); } - -.text-info { color: @infoText; } -a.text-info:hover, -a.text-info:focus { color: darken(@infoText, 10%); } - -.text-success { color: @successText; } -a.text-success:hover, -a.text-success:focus { color: darken(@successText, 10%); } - -.text-left { text-align: left; } -.text-right { text-align: right; } -.text-center { text-align: center; } - - -// Headings -// ------------------------- - -h1, h2, h3, h4, h5, h6 { - margin: (@baseLineHeight / 2) 0; - font-family: @headingsFontFamily; - font-weight: @headingsFontWeight; - line-height: @baseLineHeight; - color: @headingsColor; - text-rendering: optimizelegibility; // Fix the character spacing for headings - small { - font-weight: normal; - line-height: 1; - color: @grayLight; - } -} - -h1, -h2, -h3 { line-height: @baseLineHeight * 2; } - -h1 { font-size: @baseFontSize * 2.75; } // ~38px -h2 { font-size: @baseFontSize * 2.25; } // ~32px -h3 { font-size: @baseFontSize * 1.75; } // ~24px -h4 { font-size: @baseFontSize * 1.25; } // ~18px -h5 { font-size: @baseFontSize; } -h6 { font-size: @baseFontSize * 0.85; } // ~12px - -h1 small { font-size: @baseFontSize * 1.75; } // ~24px -h2 small { font-size: @baseFontSize * 1.25; } // ~18px -h3 small { font-size: @baseFontSize; } -h4 small { font-size: @baseFontSize; } - - -// Page header -// ------------------------- - -.page-header { - padding-bottom: (@baseLineHeight / 2) - 1; - margin: @baseLineHeight 0 (@baseLineHeight * 1.5); - border-bottom: 1px solid @grayLighter; -} - - - -// Lists -// -------------------------------------------------- - -// Unordered and Ordered lists -ul, ol { - padding: 0; - margin: 0 0 @baseLineHeight / 2 25px; -} -ul ul, -ul ol, -ol ol, -ol ul { - margin-bottom: 0; -} -li { - line-height: @baseLineHeight; -} - -// Remove default list styles -ul.unstyled, -ol.unstyled { - margin-left: 0; - list-style: none; -} - -// Single-line list items -ul.inline, -ol.inline { - margin-left: 0; - list-style: none; - > li { - display: inline-block; - .ie7-inline-block(); - padding-left: 5px; - padding-right: 5px; - } -} - -// Description Lists -dl { - margin-bottom: @baseLineHeight; -} -dt, -dd { - line-height: @baseLineHeight; -} -dt { - font-weight: bold; -} -dd { - margin-left: @baseLineHeight / 2; -} -// Horizontal layout (like forms) -.dl-horizontal { - .clearfix(); // Ensure dl clears floats if empty dd elements present - dt { - float: left; - width: @horizontalComponentOffset - 20; - clear: left; - text-align: right; - .text-overflow(); - } - dd { - margin-left: @horizontalComponentOffset; - } -} - -// MISC -// ---- - -// Horizontal rules -hr { - margin: @baseLineHeight 0; - border: 0; - border-top: 1px solid @hrBorder; - border-bottom: 1px solid @white; -} - -// Abbreviations and acronyms -abbr[title], -// Added data-* attribute to help out our tooltip plugin, per https://github.com/twitter/bootstrap/issues/5257 -abbr[data-original-title] { - cursor: help; - border-bottom: 1px dotted @grayLight; -} -abbr.initialism { - font-size: 90%; - text-transform: uppercase; -} - -// Blockquotes -blockquote { - padding: 0 0 0 15px; - margin: 0 0 @baseLineHeight; - border-left: 5px solid @grayLighter; - p { - margin-bottom: 0; - font-size: @baseFontSize * 1.25; - font-weight: 300; - line-height: 1.25; - } - small { - display: block; - line-height: @baseLineHeight; - color: @grayLight; - &:before { - content: '\2014 \00A0'; - } - } - - // Float right with text-align: right - &.pull-right { - float: right; - padding-right: 15px; - padding-left: 0; - border-right: 5px solid @grayLighter; - border-left: 0; - p, - small { - text-align: right; - } - small { - &:before { - content: ''; - } - &:after { - content: '\00A0 \2014'; - } - } - } -} - -// Quotes -q:before, -q:after, -blockquote:before, -blockquote:after { - content: ""; -} - -// Addresses -address { - display: block; - margin-bottom: @baseLineHeight; - font-style: normal; - line-height: @baseLineHeight; -} +// +// Typography +// -------------------------------------------------- + + +// Body text +// ------------------------- + +p { + margin: 0 0 @baseLineHeight / 2; +} +.lead { + margin-bottom: @baseLineHeight; + font-size: @baseFontSize * 1.5; + font-weight: 200; + line-height: @baseLineHeight * 1.5; +} + + +// Emphasis & misc +// ------------------------- + +// Ex: 14px base font * 85% = about 12px +small { font-size: 85%; } + +strong { font-weight: bold; } +em { font-style: italic; } +cite { font-style: normal; } + +// Utility classes +.muted { color: @grayLight; } +a.muted:hover, +a.muted:focus { color: darken(@grayLight, 10%); } + +.text-warning { color: @warningText; } +a.text-warning:hover, +a.text-warning:focus { color: darken(@warningText, 10%); } + +.text-error { color: @errorText; } +a.text-error:hover, +a.text-error:focus { color: darken(@errorText, 10%); } + +.text-info { color: @infoText; } +a.text-info:hover, +a.text-info:focus { color: darken(@infoText, 10%); } + +.text-success { color: @successText; } +a.text-success:hover, +a.text-success:focus { color: darken(@successText, 10%); } + +.text-left { text-align: left; } +.text-right { text-align: right; } +.text-center { text-align: center; } + + +// Headings +// ------------------------- + +h1, h2, h3, h4, h5, h6 { + margin: (@baseLineHeight / 2) 0; + font-family: @headingsFontFamily; + font-weight: @headingsFontWeight; + line-height: @baseLineHeight; + color: @headingsColor; + text-rendering: optimizelegibility; // Fix the character spacing for headings + small { + font-weight: normal; + line-height: 1; + color: @grayLight; + } +} + +h1, +h2, +h3 { line-height: @baseLineHeight * 2; } + +h1 { font-size: @baseFontSize * 2.75; } // ~38px +h2 { font-size: @baseFontSize * 2.25; } // ~32px +h3 { font-size: @baseFontSize * 1.75; } // ~24px +h4 { font-size: @baseFontSize * 1.25; } // ~18px +h5 { font-size: @baseFontSize; } +h6 { font-size: @baseFontSize * 0.85; } // ~12px + +h1 small { font-size: @baseFontSize * 1.75; } // ~24px +h2 small { font-size: @baseFontSize * 1.25; } // ~18px +h3 small { font-size: @baseFontSize; } +h4 small { font-size: @baseFontSize; } + + +// Page header +// ------------------------- + +.page-header { + padding-bottom: (@baseLineHeight / 2) - 1; + margin: @baseLineHeight 0 (@baseLineHeight * 1.5); + border-bottom: 1px solid @grayLighter; +} + + + +// Lists +// -------------------------------------------------- + +// Unordered and Ordered lists +ul, ol { + padding: 0; + margin: 0 0 @baseLineHeight / 2 25px; +} +ul ul, +ul ol, +ol ol, +ol ul { + margin-bottom: 0; +} +li { + line-height: @baseLineHeight; +} + +// Remove default list styles +ul.unstyled, +ol.unstyled { + margin-left: 0; + list-style: none; +} + +// Single-line list items +ul.inline, +ol.inline { + margin-left: 0; + list-style: none; + > li { + display: inline-block; + .ie7-inline-block(); + padding-left: 5px; + padding-right: 5px; + } +} + +// Description Lists +dl { + margin-bottom: @baseLineHeight; +} +dt, +dd { + line-height: @baseLineHeight; +} +dt { + font-weight: bold; +} +dd { + margin-left: @baseLineHeight / 2; +} +// Horizontal layout (like forms) +.dl-horizontal { + .clearfix(); // Ensure dl clears floats if empty dd elements present + dt { + float: left; + width: @horizontalComponentOffset - 20; + clear: left; + text-align: right; + .text-overflow(); + } + dd { + margin-left: @horizontalComponentOffset; + } +} + +// MISC +// ---- + +// Horizontal rules +hr { + margin: @baseLineHeight 0; + border: 0; + border-top: 1px solid @hrBorder; + border-bottom: 1px solid @white; +} + +// Abbreviations and acronyms +abbr[title], +// Added data-* attribute to help out our tooltip plugin, per https://github.com/twitter/bootstrap/issues/5257 +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted @grayLight; +} +abbr.initialism { + font-size: 90%; + text-transform: uppercase; +} + +// Blockquotes +blockquote { + padding: 0 0 0 15px; + margin: 0 0 @baseLineHeight; + border-left: 5px solid @grayLighter; + p { + margin-bottom: 0; + font-size: @baseFontSize * 1.25; + font-weight: 300; + line-height: 1.25; + } + small { + display: block; + line-height: @baseLineHeight; + color: @grayLight; + &:before { + content: '\2014 \00A0'; + } + } + + // Float right with text-align: right + &.pull-right { + float: right; + padding-right: 15px; + padding-left: 0; + border-right: 5px solid @grayLighter; + border-left: 0; + p, + small { + text-align: right; + } + small { + &:before { + content: ''; + } + &:after { + content: '\00A0 \2014'; + } + } + } +} + +// Quotes +q:before, +q:after, +blockquote:before, +blockquote:after { + content: ""; +} + +// Addresses +address { + display: block; + margin-bottom: @baseLineHeight; + font-style: normal; + line-height: @baseLineHeight; +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/utilities.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/utilities.less index 7fbd118c02..314b4ffdb4 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/utilities.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/utilities.less @@ -1,30 +1,30 @@ -// -// Utility classes -// -------------------------------------------------- - - -// Quick floats -.pull-right { - float: right; -} -.pull-left { - float: left; -} - -// Toggling content -.hide { - display: none; -} -.show { - display: block; -} - -// Visibility -.invisible { - visibility: hidden; -} - -// For Affix plugin -.affix { - position: fixed; -} +// +// Utility classes +// -------------------------------------------------- + + +// Quick floats +.pull-right { + float: right; +} +.pull-left { + float: left; +} + +// Toggling content +.hide { + display: none; +} +.show { + display: block; +} + +// Visibility +.invisible { + visibility: hidden; +} + +// For Affix plugin +.affix { + position: fixed; +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/variables.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/variables.less index cb5d4d3152..31c131b1e2 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/variables.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/variables.less @@ -1,301 +1,301 @@ -// -// Variables -// -------------------------------------------------- - - -// Global values -// -------------------------------------------------- - - -// Grays -// ------------------------- -@black: #000; -@grayDarker: #222; -@grayDark: #333; -@gray: #555; -@grayLight: #999; -@grayLighter: #eee; -@white: #fff; - - -// Accent colors -// ------------------------- -@blue: #049cdb; -@blueDark: #0064cd; -@green: #46a546; -@red: #9d261d; -@yellow: #ffc40d; -@orange: #f89406; -@pink: #c3325f; -@purple: #7a43b6; - - -// Scaffolding -// ------------------------- -@bodyBackground: @white; -@textColor: @grayDark; - - -// Links -// ------------------------- -@linkColor: #08c; -@linkColorHover: darken(@linkColor, 15%); - - -// Typography -// ------------------------- -@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif; -@serifFontFamily: Georgia, "Times New Roman", Times, serif; -@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace; - -@baseFontSize: 14px; -@baseFontFamily: @sansFontFamily; -@baseLineHeight: 20px; -@altFontFamily: @serifFontFamily; - -@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily -@headingsFontWeight: bold; // instead of browser default, bold -@headingsColor: inherit; // empty to use BS default, @textColor - - -// Component sizing -// ------------------------- -// Based on 14px font-size and 20px line-height - -@fontSizeLarge: @baseFontSize * 1.25; // ~18px -@fontSizeSmall: @baseFontSize * 0.85; // ~12px -@fontSizeMini: @baseFontSize * 0.75; // ~11px - -@paddingLarge: 11px 19px; // 44px -@paddingSmall: 2px 10px; // 26px -@paddingMini: 0 6px; // 22px - -@baseBorderRadius: 4px; -@borderRadiusLarge: 6px; -@borderRadiusSmall: 3px; - - -// Tables -// ------------------------- -@tableBackground: transparent; // overall background-color -@tableBackgroundAccent: #f9f9f9; // for striping -@tableBackgroundHover: #f5f5f5; // for hover -@tableBorder: #ddd; // table and cell border - -// Buttons -// ------------------------- -@btnBackground: @white; -@btnBackgroundHighlight: darken(@white, 10%); -@btnBorder: #ccc; - -@btnPrimaryBackground: @linkColor; -@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 20%); - -@btnInfoBackground: #5bc0de; -@btnInfoBackgroundHighlight: #2f96b4; - -@btnSuccessBackground: #62c462; -@btnSuccessBackgroundHighlight: #51a351; - -@btnWarningBackground: lighten(@orange, 15%); -@btnWarningBackgroundHighlight: @orange; - -@btnDangerBackground: #ee5f5b; -@btnDangerBackgroundHighlight: #bd362f; - -@btnInverseBackground: #444; -@btnInverseBackgroundHighlight: @grayDarker; - - -// Forms -// ------------------------- -@inputBackground: @white; -@inputBorder: #ccc; -@inputBorderRadius: @baseBorderRadius; -@inputDisabledBackground: @grayLighter; -@formActionsBackground: #f5f5f5; -@inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border - - -// Dropdowns -// ------------------------- -@dropdownBackground: @white; -@dropdownBorder: rgba(0,0,0,.2); -@dropdownDividerTop: #e5e5e5; -@dropdownDividerBottom: @white; - -@dropdownLinkColor: @grayDark; -@dropdownLinkColorHover: @white; -@dropdownLinkColorActive: @white; - -@dropdownLinkBackgroundActive: @linkColor; -@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive; - - - -// COMPONENT VARIABLES -// -------------------------------------------------- - - -// Z-index master list -// ------------------------- -// Used for a bird's eye view of components dependent on the z-axis -// Try to avoid customizing these :) -@zindexDropdown: 1000; -@zindexPopover: 1010; -@zindexTooltip: 1030; -@zindexFixedNavbar: 1030; -@zindexModalBackdrop: 1040; -@zindexModal: 1050; - - -// Sprite icons path -// ------------------------- -@iconSpritePath: "../img/glyphicons-halflings.png"; -@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png"; - - -// Input placeholder text color -// ------------------------- -@placeholderText: @grayLight; - - -// Hr border color -// ------------------------- -@hrBorder: @grayLighter; - - -// Horizontal forms & lists -// ------------------------- -@horizontalComponentOffset: 180px; - - -// Wells -// ------------------------- -@wellBackground: #f5f5f5; - - -// Navbar -// ------------------------- -@navbarCollapseWidth: 979px; -@navbarCollapseDesktopWidth: @navbarCollapseWidth + 1; - -@navbarHeight: 40px; -@navbarBackgroundHighlight: #ffffff; -@navbarBackground: darken(@navbarBackgroundHighlight, 5%); -@navbarBorder: darken(@navbarBackground, 12%); - -@navbarText: #777; -@navbarLinkColor: #777; -@navbarLinkColorHover: @grayDark; -@navbarLinkColorActive: @gray; -@navbarLinkBackgroundHover: transparent; -@navbarLinkBackgroundActive: darken(@navbarBackground, 5%); - -@navbarBrandColor: @navbarLinkColor; - -// Inverted navbar -@navbarInverseBackground: #111111; -@navbarInverseBackgroundHighlight: #222222; -@navbarInverseBorder: #252525; - -@navbarInverseText: @grayLight; -@navbarInverseLinkColor: @grayLight; -@navbarInverseLinkColorHover: @white; -@navbarInverseLinkColorActive: @navbarInverseLinkColorHover; -@navbarInverseLinkBackgroundHover: transparent; -@navbarInverseLinkBackgroundActive: @navbarInverseBackground; - -@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%); -@navbarInverseSearchBackgroundFocus: @white; -@navbarInverseSearchBorder: @navbarInverseBackground; -@navbarInverseSearchPlaceholderColor: #ccc; - -@navbarInverseBrandColor: @navbarInverseLinkColor; - - -// Pagination -// ------------------------- -@paginationBackground: #fff; -@paginationBorder: #ddd; -@paginationActiveBackground: #f5f5f5; - - -// Hero unit -// ------------------------- -@heroUnitBackground: @grayLighter; -@heroUnitHeadingColor: inherit; -@heroUnitLeadColor: inherit; - - -// Form states and alerts -// ------------------------- -@warningText: #c09853; -@warningBackground: #fcf8e3; -@warningBorder: darken(spin(@warningBackground, -10), 3%); - -@errorText: #b94a48; -@errorBackground: #f2dede; -@errorBorder: darken(spin(@errorBackground, -10), 3%); - -@successText: #468847; -@successBackground: #dff0d8; -@successBorder: darken(spin(@successBackground, -10), 5%); - -@infoText: #3a87ad; -@infoBackground: #d9edf7; -@infoBorder: darken(spin(@infoBackground, -10), 7%); - - -// Tooltips and popovers -// ------------------------- -@tooltipColor: #fff; -@tooltipBackground: #000; -@tooltipArrowWidth: 5px; -@tooltipArrowColor: @tooltipBackground; - -@popoverBackground: #fff; -@popoverArrowWidth: 10px; -@popoverArrowColor: #fff; -@popoverTitleBackground: darken(@popoverBackground, 3%); - -// Special enhancement for popovers -@popoverArrowOuterWidth: @popoverArrowWidth + 1; -@popoverArrowOuterColor: rgba(0,0,0,.25); - - - -// GRID -// -------------------------------------------------- - - -// Default 940px grid -// ------------------------- -@gridColumns: 12; -@gridColumnWidth: 60px; -@gridGutterWidth: 20px; -@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); - -// 1200px min -@gridColumnWidth1200: 70px; -@gridGutterWidth1200: 30px; -@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1)); - -// 768px-979px -@gridColumnWidth768: 42px; -@gridGutterWidth768: 20px; -@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1)); - - -// Fluid grid -// ------------------------- -@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth); -@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth); - -// 1200px min -@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200); -@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200); - -// 768px-979px -@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768); -@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768); +// +// Variables +// -------------------------------------------------- + + +// Global values +// -------------------------------------------------- + + +// Grays +// ------------------------- +@black: #000; +@grayDarker: #222; +@grayDark: #333; +@gray: #555; +@grayLight: #999; +@grayLighter: #eee; +@white: #fff; + + +// Accent colors +// ------------------------- +@blue: #049cdb; +@blueDark: #0064cd; +@green: #46a546; +@red: #9d261d; +@yellow: #ffc40d; +@orange: #f89406; +@pink: #c3325f; +@purple: #7a43b6; + + +// Scaffolding +// ------------------------- +@bodyBackground: @white; +@textColor: @grayDark; + + +// Links +// ------------------------- +@linkColor: #08c; +@linkColorHover: darken(@linkColor, 15%); + + +// Typography +// ------------------------- +@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif; +@serifFontFamily: Georgia, "Times New Roman", Times, serif; +@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace; + +@baseFontSize: 14px; +@baseFontFamily: @sansFontFamily; +@baseLineHeight: 20px; +@altFontFamily: @serifFontFamily; + +@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily +@headingsFontWeight: bold; // instead of browser default, bold +@headingsColor: inherit; // empty to use BS default, @textColor + + +// Component sizing +// ------------------------- +// Based on 14px font-size and 20px line-height + +@fontSizeLarge: @baseFontSize * 1.25; // ~18px +@fontSizeSmall: @baseFontSize * 0.85; // ~12px +@fontSizeMini: @baseFontSize * 0.75; // ~11px + +@paddingLarge: 11px 19px; // 44px +@paddingSmall: 2px 10px; // 26px +@paddingMini: 0 6px; // 22px + +@baseBorderRadius: 4px; +@borderRadiusLarge: 6px; +@borderRadiusSmall: 3px; + + +// Tables +// ------------------------- +@tableBackground: transparent; // overall background-color +@tableBackgroundAccent: #f9f9f9; // for striping +@tableBackgroundHover: #f5f5f5; // for hover +@tableBorder: #ddd; // table and cell border + +// Buttons +// ------------------------- +@btnBackground: @white; +@btnBackgroundHighlight: darken(@white, 10%); +@btnBorder: #ccc; + +@btnPrimaryBackground: @linkColor; +@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 20%); + +@btnInfoBackground: #5bc0de; +@btnInfoBackgroundHighlight: #2f96b4; + +@btnSuccessBackground: #62c462; +@btnSuccessBackgroundHighlight: #51a351; + +@btnWarningBackground: lighten(@orange, 15%); +@btnWarningBackgroundHighlight: @orange; + +@btnDangerBackground: #ee5f5b; +@btnDangerBackgroundHighlight: #bd362f; + +@btnInverseBackground: #444; +@btnInverseBackgroundHighlight: @grayDarker; + + +// Forms +// ------------------------- +@inputBackground: @white; +@inputBorder: #ccc; +@inputBorderRadius: @baseBorderRadius; +@inputDisabledBackground: @grayLighter; +@formActionsBackground: #f5f5f5; +@inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border + + +// Dropdowns +// ------------------------- +@dropdownBackground: @white; +@dropdownBorder: rgba(0,0,0,.2); +@dropdownDividerTop: #e5e5e5; +@dropdownDividerBottom: @white; + +@dropdownLinkColor: @grayDark; +@dropdownLinkColorHover: @white; +@dropdownLinkColorActive: @white; + +@dropdownLinkBackgroundActive: @linkColor; +@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive; + + + +// COMPONENT VARIABLES +// -------------------------------------------------- + + +// Z-index master list +// ------------------------- +// Used for a bird's eye view of components dependent on the z-axis +// Try to avoid customizing these :) +@zindexDropdown: 1000; +@zindexPopover: 1010; +@zindexTooltip: 1030; +@zindexFixedNavbar: 1030; +@zindexModalBackdrop: 1040; +@zindexModal: 1050; + + +// Sprite icons path +// ------------------------- +@iconSpritePath: "../img/glyphicons-halflings.png"; +@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png"; + + +// Input placeholder text color +// ------------------------- +@placeholderText: @grayLight; + + +// Hr border color +// ------------------------- +@hrBorder: @grayLighter; + + +// Horizontal forms & lists +// ------------------------- +@horizontalComponentOffset: 180px; + + +// Wells +// ------------------------- +@wellBackground: #f5f5f5; + + +// Navbar +// ------------------------- +@navbarCollapseWidth: 979px; +@navbarCollapseDesktopWidth: @navbarCollapseWidth + 1; + +@navbarHeight: 40px; +@navbarBackgroundHighlight: #ffffff; +@navbarBackground: darken(@navbarBackgroundHighlight, 5%); +@navbarBorder: darken(@navbarBackground, 12%); + +@navbarText: #777; +@navbarLinkColor: #777; +@navbarLinkColorHover: @grayDark; +@navbarLinkColorActive: @gray; +@navbarLinkBackgroundHover: transparent; +@navbarLinkBackgroundActive: darken(@navbarBackground, 5%); + +@navbarBrandColor: @navbarLinkColor; + +// Inverted navbar +@navbarInverseBackground: #111111; +@navbarInverseBackgroundHighlight: #222222; +@navbarInverseBorder: #252525; + +@navbarInverseText: @grayLight; +@navbarInverseLinkColor: @grayLight; +@navbarInverseLinkColorHover: @white; +@navbarInverseLinkColorActive: @navbarInverseLinkColorHover; +@navbarInverseLinkBackgroundHover: transparent; +@navbarInverseLinkBackgroundActive: @navbarInverseBackground; + +@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%); +@navbarInverseSearchBackgroundFocus: @white; +@navbarInverseSearchBorder: @navbarInverseBackground; +@navbarInverseSearchPlaceholderColor: #ccc; + +@navbarInverseBrandColor: @navbarInverseLinkColor; + + +// Pagination +// ------------------------- +@paginationBackground: #fff; +@paginationBorder: #ddd; +@paginationActiveBackground: #f5f5f5; + + +// Hero unit +// ------------------------- +@heroUnitBackground: @grayLighter; +@heroUnitHeadingColor: inherit; +@heroUnitLeadColor: inherit; + + +// Form states and alerts +// ------------------------- +@warningText: #c09853; +@warningBackground: #fcf8e3; +@warningBorder: darken(spin(@warningBackground, -10), 3%); + +@errorText: #b94a48; +@errorBackground: #f2dede; +@errorBorder: darken(spin(@errorBackground, -10), 3%); + +@successText: #468847; +@successBackground: #dff0d8; +@successBorder: darken(spin(@successBackground, -10), 5%); + +@infoText: #3a87ad; +@infoBackground: #d9edf7; +@infoBorder: darken(spin(@infoBackground, -10), 7%); + + +// Tooltips and popovers +// ------------------------- +@tooltipColor: #fff; +@tooltipBackground: #000; +@tooltipArrowWidth: 5px; +@tooltipArrowColor: @tooltipBackground; + +@popoverBackground: #fff; +@popoverArrowWidth: 10px; +@popoverArrowColor: #fff; +@popoverTitleBackground: darken(@popoverBackground, 3%); + +// Special enhancement for popovers +@popoverArrowOuterWidth: @popoverArrowWidth + 1; +@popoverArrowOuterColor: rgba(0,0,0,.25); + + + +// GRID +// -------------------------------------------------- + + +// Default 940px grid +// ------------------------- +@gridColumns: 12; +@gridColumnWidth: 60px; +@gridGutterWidth: 20px; +@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); + +// 1200px min +@gridColumnWidth1200: 70px; +@gridGutterWidth1200: 30px; +@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1)); + +// 768px-979px +@gridColumnWidth768: 42px; +@gridGutterWidth768: 20px; +@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1)); + + +// Fluid grid +// ------------------------- +@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth); +@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth); + +// 1200px min +@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200); +@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200); + +// 768px-979px +@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768); +@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768); diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/wells.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/wells.less index 2fd2c4fab6..84a744b1c5 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/wells.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/wells.less @@ -1,29 +1,29 @@ -// -// Wells -// -------------------------------------------------- - - -// Base class -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: @wellBackground; - border: 1px solid darken(@wellBackground, 7%); - .border-radius(@baseBorderRadius); - .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); - blockquote { - border-color: #ddd; - border-color: rgba(0,0,0,.15); - } -} - -// Sizes -.well-large { - padding: 24px; - .border-radius(@borderRadiusLarge); -} -.well-small { - padding: 9px; - .border-radius(@borderRadiusSmall); -} +// +// Wells +// -------------------------------------------------- + + +// Base class +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: @wellBackground; + border: 1px solid darken(@wellBackground, 7%); + .border-radius(@baseBorderRadius); + .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); + blockquote { + border-color: #ddd; + border-color: rgba(0,0,0,.15); + } +} + +// Sizes +.well-large { + padding: 24px; + .border-radius(@borderRadiusLarge); +} +.well-small { + padding: 9px; + .border-radius(@borderRadiusSmall); +} diff --git a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.converter.js b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.converter.js index 5b940dd8ee..8e1af69a68 100644 --- a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.converter.js +++ b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.converter.js @@ -1,1344 +1,1344 @@ -var Markdown; - -if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module - Markdown = exports; -else - Markdown = {}; - -// The following text is included for historical reasons, but should -// be taken with a pinch of salt; it's not all true anymore. - -// -// Wherever possible, Showdown is a straight, line-by-line port -// of the Perl version of Markdown. -// -// This is not a normal parser design; it's basically just a -// series of string substitutions. It's hard to read and -// maintain this way, but keeping Showdown close to the original -// design makes it easier to port new features. -// -// More importantly, Showdown behaves like markdown.pl in most -// edge cases. So web applications can do client-side preview -// in Javascript, and then build identical HTML on the server. -// -// This port needs the new RegExp functionality of ECMA 262, -// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers -// should do fine. Even with the new regular expression features, -// We do a lot of work to emulate Perl's regex functionality. -// The tricky changes in this file mostly have the "attacklab:" -// label. Major or self-explanatory changes don't. -// -// Smart diff tools like Araxis Merge will be able to match up -// this file with markdown.pl in a useful way. A little tweaking -// helps: in a copy of markdown.pl, replace "#" with "//" and -// replace "$text" with "text". Be sure to ignore whitespace -// and line endings. -// - - -// -// Usage: -// -// var text = "Markdown *rocks*."; -// -// var converter = new Markdown.Converter(); -// var html = converter.makeHtml(text); -// -// alert(html); -// -// Note: move the sample code to the bottom of this -// file before uncommenting it. -// - -(function () { - - function identity(x) { return x; } - function returnFalse(x) { return false; } - - function HookCollection() { } - - HookCollection.prototype = { - - chain: function (hookname, func) { - var original = this[hookname]; - if (!original) - throw new Error("unknown hook " + hookname); - - if (original === identity) - this[hookname] = func; - else - this[hookname] = function (x) { return func(original(x)); } - }, - set: function (hookname, func) { - if (!this[hookname]) - throw new Error("unknown hook " + hookname); - this[hookname] = func; - }, - addNoop: function (hookname) { - this[hookname] = identity; - }, - addFalse: function (hookname) { - this[hookname] = returnFalse; - } - }; - - Markdown.HookCollection = HookCollection; - - // g_urls and g_titles allow arbitrary user-entered strings as keys. This - // caused an exception (and hence stopped the rendering) when the user entered - // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this - // (since no builtin property starts with "s_"). See - // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug - // (granted, switching from Array() to Object() alone would have left only __proto__ - // to be a problem) - function SaveHash() { } - SaveHash.prototype = { - set: function (key, value) { - this["s_" + key] = value; - }, - get: function (key) { - return this["s_" + key]; - } - }; - - Markdown.Converter = function () { - var pluginHooks = this.hooks = new HookCollection(); - pluginHooks.addNoop("plainLinkText"); // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link - pluginHooks.addNoop("preConversion"); // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked - pluginHooks.addNoop("postConversion"); // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml - - // - // Private state of the converter instance: - // - - // Global hashes, used by various utility routines - var g_urls; - var g_titles; - var g_html_blocks; - - // Used to track when we're inside an ordered or unordered list - // (see _ProcessListItems() for details): - var g_list_level; - - this.makeHtml = function (text) { - - // - // Main function. The order in which other subs are called here is - // essential. Link and image substitutions need to happen before - // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the - // and tags get encoded. - // - - // This will only happen if makeHtml on the same converter instance is called from a plugin hook. - // Don't do that. - if (g_urls) - throw new Error("Recursive call to converter.makeHtml"); - - // Create the private state objects. - g_urls = new SaveHash(); - g_titles = new SaveHash(); - g_html_blocks = []; - g_list_level = 0; - - text = pluginHooks.preConversion(text); - - // attacklab: Replace ~ with ~T - // This lets us use tilde as an escape char to avoid md5 hashes - // The choice of character is arbitray; anything that isn't - // magic in Markdown will work. - text = text.replace(/~/g, "~T"); - - // attacklab: Replace $ with ~D - // RegExp interprets $ as a special character - // when it's in a replacement string - text = text.replace(/\$/g, "~D"); - - // Standardize line endings - text = text.replace(/\r\n/g, "\n"); // DOS to Unix - text = text.replace(/\r/g, "\n"); // Mac to Unix - - // Make sure text begins and ends with a couple of newlines: - text = "\n\n" + text + "\n\n"; - - // Convert all tabs to spaces. - text = _Detab(text); - - // Strip any lines consisting only of spaces and tabs. - // This makes subsequent regexen easier to write, because we can - // match consecutive blank lines with /\n+/ instead of something - // contorted like /[ \t]*\n+/ . - text = text.replace(/^[ \t]+$/mg, ""); - - // Turn block-level HTML blocks into hash entries - text = _HashHTMLBlocks(text); - - // Strip link definitions, store in hashes. - text = _StripLinkDefinitions(text); - - text = _RunBlockGamut(text); - - text = _UnescapeSpecialChars(text); - - // attacklab: Restore dollar signs - text = text.replace(/~D/g, "$$"); - - // attacklab: Restore tildes - text = text.replace(/~T/g, "~"); - - text = pluginHooks.postConversion(text); - - g_html_blocks = g_titles = g_urls = null; - - return text; - }; - - function _StripLinkDefinitions(text) { - // - // Strips link definitions from text, stores the URLs and titles in - // hash references. - // - - // Link defs are in the form: ^[id]: url "optional title" - - /* - text = text.replace(/ - ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 - [ \t]* - \n? // maybe *one* newline - [ \t]* - ? // url = $2 - (?=\s|$) // lookahead for whitespace instead of the lookbehind removed below - [ \t]* - \n? // maybe one newline - [ \t]* - ( // (potential) title = $3 - (\n*) // any lines skipped = $4 attacklab: lookbehind removed - [ \t]+ - ["(] - (.+?) // title = $5 - [")] - [ \t]* - )? // title is optional - (?:\n+|$) - /gm, function(){...}); - */ - - text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, - function (wholeMatch, m1, m2, m3, m4, m5) { - m1 = m1.toLowerCase(); - g_urls.set(m1, _EncodeAmpsAndAngles(m2)); // Link IDs are case-insensitive - if (m4) { - // Oops, found blank lines, so it's not a title. - // Put back the parenthetical statement we stole. - return m3; - } else if (m5) { - g_titles.set(m1, m5.replace(/"/g, """)); - } - - // Completely remove the definition from the text - return ""; - } - ); - - return text; - } - - function _HashHTMLBlocks(text) { - - // Hashify HTML blocks: - // We only want to do this for block-level HTML tags, such as headers, - // lists, and tables. That's because we still want to wrap

    s around - // "paragraphs" that are wrapped in non-block-level tags, such as anchors, - // phrase emphasis, and spans. The list of tags we're looking for is - // hard-coded: - var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" - var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" - - // First, look for nested blocks, e.g.: - //

    - //
    - // tags for inner block must be indented. - //
    - //
    - // - // The outermost tags must start at the left margin for this to match, and - // the inner nested divs must be indented. - // We need to do this before the next, more liberal match, because the next - // match will start at the first `
    ` and stop at the first `
    `. - - // attacklab: This regex can be expensive when it fails. - - /* - text = text.replace(/ - ( // save in $1 - ^ // start of line (with /m) - <($block_tags_a) // start tag = $2 - \b // word break - // attacklab: hack around khtml/pcre bug... - [^\r]*?\n // any number of lines, minimally matching - // the matching end tag - [ \t]* // trailing spaces/tabs - (?=\n+) // followed by a newline - ) // attacklab: there are sentinel newlines at end of document - /gm,function(){...}}; - */ - text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement); - - // - // Now match more liberally, simply from `\n` to `\n` - // - - /* - text = text.replace(/ - ( // save in $1 - ^ // start of line (with /m) - <($block_tags_b) // start tag = $2 - \b // word break - // attacklab: hack around khtml/pcre bug... - [^\r]*? // any number of lines, minimally matching - .* // the matching end tag - [ \t]* // trailing spaces/tabs - (?=\n+) // followed by a newline - ) // attacklab: there are sentinel newlines at end of document - /gm,function(){...}}; - */ - text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement); - - // Special case just for
    . It was easier to make a special case than - // to make the other regex more complicated. - - /* - text = text.replace(/ - \n // Starting after a blank line - [ ]{0,3} - ( // save in $1 - (<(hr) // start tag = $2 - \b // word break - ([^<>])*? - \/?>) // the matching end tag - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement); - - // Special case for standalone HTML comments: - - /* - text = text.replace(/ - \n\n // Starting after a blank line - [ ]{0,3} // attacklab: g_tab_width - 1 - ( // save in $1 - -]|-[^>])(?:[^-]|-[^-])*)--) // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256 - > - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/\n\n[ ]{0,3}(-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement); - - // PHP and ASP-style processor instructions ( and <%...%>) - - /* - text = text.replace(/ - (?: - \n\n // Starting after a blank line - ) - ( // save in $1 - [ ]{0,3} // attacklab: g_tab_width - 1 - (?: - <([?%]) // $2 - [^\r]*? - \2> - ) - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement); - - return text; - } - - function hashElement(wholeMatch, m1) { - var blockText = m1; - - // Undo double lines - blockText = blockText.replace(/^\n+/, ""); - - // strip trailing blank lines - blockText = blockText.replace(/\n+$/g, ""); - - // Replace the element text with a marker ("~KxK" where x is its key) - blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n"; - - return blockText; - } - - function _RunBlockGamut(text, doNotUnhash) { - // - // These are all the transformations that form block-level - // tags like paragraphs, headers, and list items. - // - text = _DoHeaders(text); - - // Do Horizontal Rules: - var replacement = "
    \n"; - text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement); - text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement); - text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement); - - text = _DoLists(text); - text = _DoCodeBlocks(text); - text = _DoBlockQuotes(text); - - // We already ran _HashHTMLBlocks() before, in Markdown(), but that - // was to escape raw HTML in the original Markdown source. This time, - // we're escaping the markup we've just created, so that we don't wrap - //

    tags around block-level tags. - text = _HashHTMLBlocks(text); - text = _FormParagraphs(text, doNotUnhash); - - return text; - } - - function _RunSpanGamut(text) { - // - // These are all the transformations that occur *within* block-level - // tags like paragraphs, headers, and list items. - // - - text = _DoCodeSpans(text); - text = _EscapeSpecialCharsWithinTagAttributes(text); - text = _EncodeBackslashEscapes(text); - - // Process anchor and image tags. Images must come first, - // because ![foo][f] looks like an anchor. - text = _DoImages(text); - text = _DoAnchors(text); - - // Make links out of things like `` - // Must come after _DoAnchors(), because you can use < and > - // delimiters in inline links like [this](). - text = _DoAutoLinks(text); - - text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now - - text = _EncodeAmpsAndAngles(text); - text = _DoItalicsAndBold(text); - - // Do hard breaks: - text = text.replace(/ +\n/g, "
    \n"); - - return text; - } - - function _EscapeSpecialCharsWithinTagAttributes(text) { - // - // Within tags -- meaning between < and > -- encode [\ ` * _] so they - // don't conflict with their use in Markdown for code, italics and strong. - // - - // Build a regex to find HTML tags and comments. See Friedl's - // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. - - // SE: changed the comment part of the regex - - var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi; - - text = text.replace(regex, function (wholeMatch) { - var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`"); - tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987 - return tag; - }); - - return text; - } - - function _DoAnchors(text) { - // - // Turn Markdown link shortcuts into XHTML
    tags. - // - // - // First, handle reference-style links: [link text] [id] - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ( - (?: - \[[^\]]*\] // allow brackets nested one level - | - [^\[] // or anything else - )* - ) - \] - - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces - - \[ - (.*?) // id = $3 - \] - ) - ()()()() // pad remaining backreferences - /g, writeAnchorTag); - */ - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag); - - // - // Next, inline-style links: [link text](url "optional title") - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ( - (?: - \[[^\]]*\] // allow brackets nested one level - | - [^\[\]] // or anything else - )* - ) - \] - \( // literal paren - [ \t]* - () // no id, so leave $3 empty - ? - [ \t]* - ( // $5 - (['"]) // quote char = $6 - (.*?) // Title = $7 - \6 // matching quote - [ \t]* // ignore any spaces/tabs between closing quote and ) - )? // title is optional - \) - ) - /g, writeAnchorTag); - */ - - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag); - - // - // Last, handle reference-style shortcuts: [link text] - // These must come last in case you've also got [link test][1] - // or [link test](/foo) - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ([^\[\]]+) // link text = $2; can't contain '[' or ']' - \] - ) - ()()()()() // pad rest of backreferences - /g, writeAnchorTag); - */ - text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); - - return text; - } - - function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) { - if (m7 == undefined) m7 = ""; - var whole_match = m1; - var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs - var link_id = m3.toLowerCase(); - var url = m4; - var title = m7; - - if (url == "") { - if (link_id == "") { - // lower-case and turn embedded newlines into spaces - link_id = link_text.toLowerCase().replace(/ ?\n/g, " "); - } - url = "#" + link_id; - - if (g_urls.get(link_id) != undefined) { - url = g_urls.get(link_id); - if (g_titles.get(link_id) != undefined) { - title = g_titles.get(link_id); - } - } - else { - if (whole_match.search(/\(\s*\)$/m) > -1) { - // Special case for explicit empty url - url = ""; - } else { - return whole_match; - } - } - } - url = encodeProblemUrlChars(url); - url = escapeCharacters(url, "*_"); - var result = ""; - - return result; - } - - function _DoImages(text) { - // - // Turn Markdown image shortcuts into tags. - // - - // - // First, handle reference-style labeled images: ![alt text][id] - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - !\[ - (.*?) // alt text = $2 - \] - - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces - - \[ - (.*?) // id = $3 - \] - ) - ()()()() // pad rest of backreferences - /g, writeImageTag); - */ - text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag); - - // - // Next, handle inline images: ![alt text](url "optional title") - // Don't forget: encode * and _ - - /* - text = text.replace(/ - ( // wrap whole match in $1 - !\[ - (.*?) // alt text = $2 - \] - \s? // One optional whitespace character - \( // literal paren - [ \t]* - () // no id, so leave $3 empty - ? // src url = $4 - [ \t]* - ( // $5 - (['"]) // quote char = $6 - (.*?) // title = $7 - \6 // matching quote - [ \t]* - )? // title is optional - \) - ) - /g, writeImageTag); - */ - text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag); - - return text; - } - - function attributeEncode(text) { - // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title) - // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it) - return text.replace(/>/g, ">").replace(/" + _RunSpanGamut(m1) + "\n\n"; } - ); - - text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, - function (matchFound, m1) { return "

    " + _RunSpanGamut(m1) + "

    \n\n"; } - ); - - // atx-style headers: - // # Header 1 - // ## Header 2 - // ## Header 2 with closing hashes ## - // ... - // ###### Header 6 - // - - /* - text = text.replace(/ - ^(\#{1,6}) // $1 = string of #'s - [ \t]* - (.+?) // $2 = Header text - [ \t]* - \#* // optional closing #'s (not counted) - \n+ - /gm, function() {...}); - */ - - text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, - function (wholeMatch, m1, m2) { - var h_level = m1.length; - return "" + _RunSpanGamut(m2) + "\n\n"; - } - ); - - return text; - } - - function _DoLists(text) { - // - // Form HTML ordered (numbered) and unordered (bulleted) lists. - // - - // attacklab: add sentinel to hack around khtml/safari bug: - // http://bugs.webkit.org/show_bug.cgi?id=11231 - text += "~0"; - - // Re-usable pattern to match any entirel ul or ol list: - - /* - var whole_list = / - ( // $1 = whole list - ( // $2 - [ ]{0,3} // attacklab: g_tab_width - 1 - ([*+-]|\d+[.]) // $3 = first list item marker - [ \t]+ - ) - [^\r]+? - ( // $4 - ~0 // sentinel for workaround; should be $ - | - \n{2,} - (?=\S) - (?! // Negative lookahead for another list item marker - [ \t]* - (?:[*+-]|\d+[.])[ \t]+ - ) - ) - ) - /g - */ - var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; - - if (g_list_level) { - text = text.replace(whole_list, function (wholeMatch, m1, m2) { - var list = m1; - var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol"; - - var result = _ProcessListItems(list, list_type); - - // Trim any trailing whitespace, to put the closing `` - // up on the preceding line, to get it past the current stupid - // HTML block parser. This is a hack to work around the terrible - // hack that is the HTML block parser. - result = result.replace(/\s+$/, ""); - result = "<" + list_type + ">" + result + "\n"; - return result; - }); - } else { - whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; - text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) { - var runup = m1; - var list = m2; - - var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol"; - var result = _ProcessListItems(list, list_type); - result = runup + "<" + list_type + ">\n" + result + "\n"; - return result; - }); - } - - // attacklab: strip sentinel - text = text.replace(/~0/, ""); - - return text; - } - - var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" }; - - function _ProcessListItems(list_str, list_type) { - // - // Process the contents of a single ordered or unordered list, splitting it - // into individual list items. - // - // list_type is either "ul" or "ol". - - // The $g_list_level global keeps track of when we're inside a list. - // Each time we enter a list, we increment it; when we leave a list, - // we decrement. If it's zero, we're not in a list anymore. - // - // We do this because when we're not inside a list, we want to treat - // something like this: - // - // I recommend upgrading to version - // 8. Oops, now this line is treated - // as a sub-list. - // - // As a single paragraph, despite the fact that the second line starts - // with a digit-period-space sequence. - // - // Whereas when we're inside a list (or sub-list), that line will be - // treated as the start of a sub-list. What a kludge, huh? This is - // an aspect of Markdown's syntax that's hard to parse perfectly - // without resorting to mind-reading. Perhaps the solution is to - // change the syntax rules such that sub-lists must start with a - // starting cardinal number; e.g. "1." or "a.". - - g_list_level++; - - // trim trailing blank lines: - list_str = list_str.replace(/\n{2,}$/, "\n"); - - // attacklab: add sentinel to emulate \z - list_str += "~0"; - - // In the original attacklab showdown, list_type was not given to this function, and anything - // that matched /[*+-]|\d+[.]/ would just create the next
  • , causing this mismatch: - // - // Markdown rendered by WMD rendered by MarkdownSharp - // ------------------------------------------------------------------ - // 1. first 1. first 1. first - // 2. second 2. second 2. second - // - third 3. third * third - // - // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx, - // with {MARKER} being one of \d+[.] or [*+-], depending on list_type: - - /* - list_str = list_str.replace(/ - (^[ \t]*) // leading whitespace = $1 - ({MARKER}) [ \t]+ // list marker = $2 - ([^\r]+? // list item text = $3 - (\n+) - ) - (?= - (~0 | \2 ({MARKER}) [ \t]+) - ) - /gm, function(){...}); - */ - - var marker = _listItemMarkers[list_type]; - var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm"); - var last_item_had_a_double_newline = false; - list_str = list_str.replace(re, - function (wholeMatch, m1, m2, m3) { - var item = m3; - var leading_space = m1; - var ends_with_double_newline = /\n\n$/.test(item); - var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1; - - if (contains_double_newline || last_item_had_a_double_newline) { - item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true); - } - else { - // Recursion for sub-lists: - item = _DoLists(_Outdent(item)); - item = item.replace(/\n$/, ""); // chomp(item) - item = _RunSpanGamut(item); - } - last_item_had_a_double_newline = ends_with_double_newline; - return "
  • " + item + "
  • \n"; - } - ); - - // attacklab: strip sentinel - list_str = list_str.replace(/~0/g, ""); - - g_list_level--; - return list_str; - } - - function _DoCodeBlocks(text) { - // - // Process Markdown `
    ` blocks.
    -			//  
    -
    -			/*
    -            text = text.replace(/
    -                (?:\n\n|^)
    -                (                               // $1 = the code block -- one or more lines, starting with a space/tab
    -                    (?:
    -                        (?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
    -                        .*\n+
    -                    )+
    -                )
    -                (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
    -            /g ,function(){...});
    -            */
    -
    -			// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
    -			text += "~0";
    -
    -			text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
    -                function (wholeMatch, m1, m2) {
    -                	var codeblock = m1;
    -                	var nextChar = m2;
    -
    -                	codeblock = _EncodeCode(_Outdent(codeblock));
    -                	codeblock = _Detab(codeblock);
    -                	codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
    -                	codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
    -
    -                	codeblock = '
    ' + codeblock + '\n
    '; - - return "\n\n" + codeblock + "\n\n" + nextChar; - } - ); - - // attacklab: strip sentinel - text = text.replace(/~0/, ""); - - return text; - } - - function hashBlock(text) { - text = text.replace(/(^\n+|\n+$)/g, ""); - return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n"; - } - - function _DoCodeSpans(text) { - // - // * Backtick quotes are used for spans. - // - // * You can use multiple backticks as the delimiters if you want to - // include literal backticks in the code span. So, this input: - // - // Just type ``foo `bar` baz`` at the prompt. - // - // Will translate to: - // - //

    Just type foo `bar` baz at the prompt.

    - // - // There's no arbitrary limit to the number of backticks you - // can use as delimters. If you need three consecutive backticks - // in your code, use four for delimiters, etc. - // - // * You can use spaces to get literal backticks at the edges: - // - // ... type `` `bar` `` ... - // - // Turns to: - // - // ... type `bar` ... - // - - /* - text = text.replace(/ - (^|[^\\]) // Character before opening ` can't be a backslash - (`+) // $2 = Opening run of ` - ( // $3 = The code block - [^\r]*? - [^`] // attacklab: work around lack of lookbehind - ) - \2 // Matching closer - (?!`) - /gm, function(){...}); - */ - - text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, - function (wholeMatch, m1, m2, m3, m4) { - var c = m3; - c = c.replace(/^([ \t]*)/g, ""); // leading whitespace - c = c.replace(/[ \t]*$/g, ""); // trailing whitespace - c = _EncodeCode(c); - c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs. - return m1 + "" + c + ""; - } - ); - - return text; - } - - function _EncodeCode(text) { - // - // Encode/escape certain characters inside Markdown code runs. - // The point is that in code, these characters are literals, - // and lose their special Markdown meanings. - // - // Encode all ampersands; HTML entities are not - // entities within a Markdown code span. - text = text.replace(/&/g, "&"); - - // Do the angle bracket song and dance: - text = text.replace(//g, ">"); - - // Now, escape characters that are magic in Markdown: - text = escapeCharacters(text, "\*_{}[]\\", false); - - // jj the line above breaks this: - //--- - - //* Item - - // 1. Subitem - - // special char: * - //--- - - return text; - } - - function _DoItalicsAndBold(text) { - - // must go first: - text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g, - "$1$3$4"); - - text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g, - "$1$3$4"); - - return text; - } - - function _DoBlockQuotes(text) { - - /* - text = text.replace(/ - ( // Wrap whole match in $1 - ( - ^[ \t]*>[ \t]? // '>' at the start of a line - .+\n // rest of the first line - (.+\n)* // subsequent consecutive lines - \n* // blanks - )+ - ) - /gm, function(){...}); - */ - - text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, - function (wholeMatch, m1) { - var bq = m1; - - // attacklab: hack around Konqueror 3.5.4 bug: - // "----------bug".replace(/^-/g,"") == "bug" - - bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting - - // attacklab: clean up hack - bq = bq.replace(/~0/g, ""); - - bq = bq.replace(/^[ \t]+$/gm, ""); // trim whitespace-only lines - bq = _RunBlockGamut(bq); // recurse - - bq = bq.replace(/(^|\n)/g, "$1 "); - // These leading spaces screw with
     content, so we need to fix that:
    -                	bq = bq.replace(
    -                            /(\s*
    [^\r]+?<\/pre>)/gm,
    -                        function (wholeMatch, m1) {
    -                        	var pre = m1;
    -                        	// attacklab: hack around Konqueror 3.5.4 bug:
    -                        	pre = pre.replace(/^  /mg, "~0");
    -                        	pre = pre.replace(/~0/g, "");
    -                        	return pre;
    -                        });
    -
    -                	return hashBlock("
    \n" + bq + "\n
    "); - } - ); - return text; - } - - function _FormParagraphs(text, doNotUnhash) { - // - // Params: - // $text - string to process with html

    tags - // - - // Strip leading and trailing lines: - text = text.replace(/^\n+/g, ""); - text = text.replace(/\n+$/g, ""); - - var grafs = text.split(/\n{2,}/g); - var grafsOut = []; - - var markerRe = /~K(\d+)K/; - - // - // Wrap

    tags. - // - var end = grafs.length; - for (var i = 0; i < end; i++) { - var str = grafs[i]; - - // if this is an HTML marker, copy it - if (markerRe.test(str)) { - grafsOut.push(str); - } - else if (/\S/.test(str)) { - str = _RunSpanGamut(str); - str = str.replace(/^([ \t]*)/g, "

    "); - str += "

    " - grafsOut.push(str); - } - - } - // - // Unhashify HTML blocks - // - if (!doNotUnhash) { - end = grafsOut.length; - for (var i = 0; i < end; i++) { - var foundAny = true; - while (foundAny) { // we may need several runs, since the data may be nested - foundAny = false; - grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) { - foundAny = true; - return g_html_blocks[id]; - }); - } - } - } - return grafsOut.join("\n\n"); - } - - function _EncodeAmpsAndAngles(text) { - // Smart processing for ampersands and angle brackets that need to be encoded. - - // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: - // http://bumppo.net/projects/amputator/ - text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&"); - - // Encode naked <'s - text = text.replace(/<(?![a-z\/?\$!])/gi, "<"); - - return text; - } - - function _EncodeBackslashEscapes(text) { - // - // Parameter: String. - // Returns: The string, with after processing the following backslash - // escape sequences. - // - - // attacklab: The polite way to do this is with the new - // escapeCharacters() function: - // - // text = escapeCharacters(text,"\\",true); - // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); - // - // ...but we're sidestepping its use of the (slow) RegExp constructor - // as an optimization for Firefox. This function gets called a LOT. - - text = text.replace(/\\(\\)/g, escapeCharacters_callback); - text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback); - return text; - } - - function _DoAutoLinks(text) { - - // note that at this point, all other URL in the text are already hyperlinked as
    - // *except* for the case - - // automatically add < and > around unadorned raw hyperlinks - // must be preceded by space/BOF and followed by non-word/EOF character - text = text.replace(/(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi, "$1<$2$3>$4"); - - // autolink anything like - - var replacer = function (wholematch, m1) { return "" + pluginHooks.plainLinkText(m1) + ""; } - text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer); - - // Email addresses: - /* - text = text.replace(/ - < - (?:mailto:)? - ( - [-.\w]+ - \@ - [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ - ) - > - /gi, _DoAutoLinks_callback()); - */ - - var email_replacer = function (wholematch, m1) { - var mailto = 'mailto:' - var link - var email - if (m1.substring(0, mailto.length) != mailto) { - link = mailto + m1; - email = m1; - } else { - link = m1; - email = m1.substring(mailto.length, m1.length); - } - return "" + pluginHooks.plainLinkText(email) + ""; - } - text = text.replace(/<((?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+))>/gi, email_replacer); - - return text; - } - - function _UnescapeSpecialChars(text) { - // - // Swap back in all the special characters we've hidden. - // - text = text.replace(/~E(\d+)E/g, - function (wholeMatch, m1) { - var charCodeToReplace = parseInt(m1); - return String.fromCharCode(charCodeToReplace); - } - ); - return text; - } - - function _Outdent(text) { - // - // Remove one level of line-leading tabs or spaces - // - - // attacklab: hack around Konqueror 3.5.4 bug: - // "----------bug".replace(/^-/g,"") == "bug" - - text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width - - // attacklab: clean up hack - text = text.replace(/~0/g, "") - - return text; - } - - function _Detab(text) { - if (!/\t/.test(text)) - return text; - - var spaces = [" ", " ", " ", " "], - skew = 0, - v; - - return text.replace(/[\n\t]/g, function (match, offset) { - if (match === "\n") { - skew = offset + 1; - return match; - } - v = (offset - skew) % 4; - skew = offset + 1; - return spaces[v]; - }); - } - - // - // attacklab: Utility functions - // - - var _problemUrlChars = /(?:["'*()[\]:]|~D)/g; - - // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems - function encodeProblemUrlChars(url) { - if (!url) - return ""; - - var len = url.length; - - return url.replace(_problemUrlChars, function (match, offset) { - if (match == "~D") // escape for dollar - return "%24"; - if (match == ":") { - if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1))) - return ":"; - if (url.substring(0, 'mailto:'.length) === 'mailto:') - return ":"; - if (url.substring(0, 'magnet:'.length) === 'magnet:') - return ":"; - } - return "%" + match.charCodeAt(0).toString(16); - }); - } - - - function escapeCharacters(text, charsToEscape, afterBackslash) { - // First we have to escape the escape characters so that - // we can build a character class out of them - var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])"; - - if (afterBackslash) { - regexString = "\\\\" + regexString; - } - - var regex = new RegExp(regexString, "g"); - text = text.replace(regex, escapeCharacters_callback); - - return text; - } - - - function escapeCharacters_callback(wholeMatch, m1) { - var charCodeToEscape = m1.charCodeAt(0); - return "~E" + charCodeToEscape + "E"; - } - - }; // end of the Markdown.Converter constructor - +var Markdown; + +if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module + Markdown = exports; +else + Markdown = {}; + +// The following text is included for historical reasons, but should +// be taken with a pinch of salt; it's not all true anymore. + +// +// Wherever possible, Showdown is a straight, line-by-line port +// of the Perl version of Markdown. +// +// This is not a normal parser design; it's basically just a +// series of string substitutions. It's hard to read and +// maintain this way, but keeping Showdown close to the original +// design makes it easier to port new features. +// +// More importantly, Showdown behaves like markdown.pl in most +// edge cases. So web applications can do client-side preview +// in Javascript, and then build identical HTML on the server. +// +// This port needs the new RegExp functionality of ECMA 262, +// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers +// should do fine. Even with the new regular expression features, +// We do a lot of work to emulate Perl's regex functionality. +// The tricky changes in this file mostly have the "attacklab:" +// label. Major or self-explanatory changes don't. +// +// Smart diff tools like Araxis Merge will be able to match up +// this file with markdown.pl in a useful way. A little tweaking +// helps: in a copy of markdown.pl, replace "#" with "//" and +// replace "$text" with "text". Be sure to ignore whitespace +// and line endings. +// + + +// +// Usage: +// +// var text = "Markdown *rocks*."; +// +// var converter = new Markdown.Converter(); +// var html = converter.makeHtml(text); +// +// alert(html); +// +// Note: move the sample code to the bottom of this +// file before uncommenting it. +// + +(function () { + + function identity(x) { return x; } + function returnFalse(x) { return false; } + + function HookCollection() { } + + HookCollection.prototype = { + + chain: function (hookname, func) { + var original = this[hookname]; + if (!original) + throw new Error("unknown hook " + hookname); + + if (original === identity) + this[hookname] = func; + else + this[hookname] = function (x) { return func(original(x)); } + }, + set: function (hookname, func) { + if (!this[hookname]) + throw new Error("unknown hook " + hookname); + this[hookname] = func; + }, + addNoop: function (hookname) { + this[hookname] = identity; + }, + addFalse: function (hookname) { + this[hookname] = returnFalse; + } + }; + + Markdown.HookCollection = HookCollection; + + // g_urls and g_titles allow arbitrary user-entered strings as keys. This + // caused an exception (and hence stopped the rendering) when the user entered + // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this + // (since no builtin property starts with "s_"). See + // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug + // (granted, switching from Array() to Object() alone would have left only __proto__ + // to be a problem) + function SaveHash() { } + SaveHash.prototype = { + set: function (key, value) { + this["s_" + key] = value; + }, + get: function (key) { + return this["s_" + key]; + } + }; + + Markdown.Converter = function () { + var pluginHooks = this.hooks = new HookCollection(); + pluginHooks.addNoop("plainLinkText"); // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link + pluginHooks.addNoop("preConversion"); // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked + pluginHooks.addNoop("postConversion"); // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml + + // + // Private state of the converter instance: + // + + // Global hashes, used by various utility routines + var g_urls; + var g_titles; + var g_html_blocks; + + // Used to track when we're inside an ordered or unordered list + // (see _ProcessListItems() for details): + var g_list_level; + + this.makeHtml = function (text) { + + // + // Main function. The order in which other subs are called here is + // essential. Link and image substitutions need to happen before + // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the + // and tags get encoded. + // + + // This will only happen if makeHtml on the same converter instance is called from a plugin hook. + // Don't do that. + if (g_urls) + throw new Error("Recursive call to converter.makeHtml"); + + // Create the private state objects. + g_urls = new SaveHash(); + g_titles = new SaveHash(); + g_html_blocks = []; + g_list_level = 0; + + text = pluginHooks.preConversion(text); + + // attacklab: Replace ~ with ~T + // This lets us use tilde as an escape char to avoid md5 hashes + // The choice of character is arbitray; anything that isn't + // magic in Markdown will work. + text = text.replace(/~/g, "~T"); + + // attacklab: Replace $ with ~D + // RegExp interprets $ as a special character + // when it's in a replacement string + text = text.replace(/\$/g, "~D"); + + // Standardize line endings + text = text.replace(/\r\n/g, "\n"); // DOS to Unix + text = text.replace(/\r/g, "\n"); // Mac to Unix + + // Make sure text begins and ends with a couple of newlines: + text = "\n\n" + text + "\n\n"; + + // Convert all tabs to spaces. + text = _Detab(text); + + // Strip any lines consisting only of spaces and tabs. + // This makes subsequent regexen easier to write, because we can + // match consecutive blank lines with /\n+/ instead of something + // contorted like /[ \t]*\n+/ . + text = text.replace(/^[ \t]+$/mg, ""); + + // Turn block-level HTML blocks into hash entries + text = _HashHTMLBlocks(text); + + // Strip link definitions, store in hashes. + text = _StripLinkDefinitions(text); + + text = _RunBlockGamut(text); + + text = _UnescapeSpecialChars(text); + + // attacklab: Restore dollar signs + text = text.replace(/~D/g, "$$"); + + // attacklab: Restore tildes + text = text.replace(/~T/g, "~"); + + text = pluginHooks.postConversion(text); + + g_html_blocks = g_titles = g_urls = null; + + return text; + }; + + function _StripLinkDefinitions(text) { + // + // Strips link definitions from text, stores the URLs and titles in + // hash references. + // + + // Link defs are in the form: ^[id]: url "optional title" + + /* + text = text.replace(/ + ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 + [ \t]* + \n? // maybe *one* newline + [ \t]* + ? // url = $2 + (?=\s|$) // lookahead for whitespace instead of the lookbehind removed below + [ \t]* + \n? // maybe one newline + [ \t]* + ( // (potential) title = $3 + (\n*) // any lines skipped = $4 attacklab: lookbehind removed + [ \t]+ + ["(] + (.+?) // title = $5 + [")] + [ \t]* + )? // title is optional + (?:\n+|$) + /gm, function(){...}); + */ + + text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, + function (wholeMatch, m1, m2, m3, m4, m5) { + m1 = m1.toLowerCase(); + g_urls.set(m1, _EncodeAmpsAndAngles(m2)); // Link IDs are case-insensitive + if (m4) { + // Oops, found blank lines, so it's not a title. + // Put back the parenthetical statement we stole. + return m3; + } else if (m5) { + g_titles.set(m1, m5.replace(/"/g, """)); + } + + // Completely remove the definition from the text + return ""; + } + ); + + return text; + } + + function _HashHTMLBlocks(text) { + + // Hashify HTML blocks: + // We only want to do this for block-level HTML tags, such as headers, + // lists, and tables. That's because we still want to wrap

    s around + // "paragraphs" that are wrapped in non-block-level tags, such as anchors, + // phrase emphasis, and spans. The list of tags we're looking for is + // hard-coded: + var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" + var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" + + // First, look for nested blocks, e.g.: + //

    + //
    + // tags for inner block must be indented. + //
    + //
    + // + // The outermost tags must start at the left margin for this to match, and + // the inner nested divs must be indented. + // We need to do this before the next, more liberal match, because the next + // match will start at the first `
    ` and stop at the first `
    `. + + // attacklab: This regex can be expensive when it fails. + + /* + text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_a) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*?\n // any number of lines, minimally matching + // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement); + + // + // Now match more liberally, simply from `\n` to `\n` + // + + /* + text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_b) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*? // any number of lines, minimally matching + .* // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement); + + // Special case just for
    . It was easier to make a special case than + // to make the other regex more complicated. + + /* + text = text.replace(/ + \n // Starting after a blank line + [ ]{0,3} + ( // save in $1 + (<(hr) // start tag = $2 + \b // word break + ([^<>])*? + \/?>) // the matching end tag + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement); + + // Special case for standalone HTML comments: + + /* + text = text.replace(/ + \n\n // Starting after a blank line + [ ]{0,3} // attacklab: g_tab_width - 1 + ( // save in $1 + -]|-[^>])(?:[^-]|-[^-])*)--) // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256 + > + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/\n\n[ ]{0,3}(-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement); + + // PHP and ASP-style processor instructions ( and <%...%>) + + /* + text = text.replace(/ + (?: + \n\n // Starting after a blank line + ) + ( // save in $1 + [ ]{0,3} // attacklab: g_tab_width - 1 + (?: + <([?%]) // $2 + [^\r]*? + \2> + ) + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement); + + return text; + } + + function hashElement(wholeMatch, m1) { + var blockText = m1; + + // Undo double lines + blockText = blockText.replace(/^\n+/, ""); + + // strip trailing blank lines + blockText = blockText.replace(/\n+$/g, ""); + + // Replace the element text with a marker ("~KxK" where x is its key) + blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n"; + + return blockText; + } + + function _RunBlockGamut(text, doNotUnhash) { + // + // These are all the transformations that form block-level + // tags like paragraphs, headers, and list items. + // + text = _DoHeaders(text); + + // Do Horizontal Rules: + var replacement = "
    \n"; + text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement); + text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement); + text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement); + + text = _DoLists(text); + text = _DoCodeBlocks(text); + text = _DoBlockQuotes(text); + + // We already ran _HashHTMLBlocks() before, in Markdown(), but that + // was to escape raw HTML in the original Markdown source. This time, + // we're escaping the markup we've just created, so that we don't wrap + //

    tags around block-level tags. + text = _HashHTMLBlocks(text); + text = _FormParagraphs(text, doNotUnhash); + + return text; + } + + function _RunSpanGamut(text) { + // + // These are all the transformations that occur *within* block-level + // tags like paragraphs, headers, and list items. + // + + text = _DoCodeSpans(text); + text = _EscapeSpecialCharsWithinTagAttributes(text); + text = _EncodeBackslashEscapes(text); + + // Process anchor and image tags. Images must come first, + // because ![foo][f] looks like an anchor. + text = _DoImages(text); + text = _DoAnchors(text); + + // Make links out of things like `` + // Must come after _DoAnchors(), because you can use < and > + // delimiters in inline links like [this](). + text = _DoAutoLinks(text); + + text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now + + text = _EncodeAmpsAndAngles(text); + text = _DoItalicsAndBold(text); + + // Do hard breaks: + text = text.replace(/ +\n/g, "
    \n"); + + return text; + } + + function _EscapeSpecialCharsWithinTagAttributes(text) { + // + // Within tags -- meaning between < and > -- encode [\ ` * _] so they + // don't conflict with their use in Markdown for code, italics and strong. + // + + // Build a regex to find HTML tags and comments. See Friedl's + // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. + + // SE: changed the comment part of the regex + + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi; + + text = text.replace(regex, function (wholeMatch) { + var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`"); + tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987 + return tag; + }); + + return text; + } + + function _DoAnchors(text) { + // + // Turn Markdown link shortcuts into XHTML
    tags. + // + // + // First, handle reference-style links: [link text] [id] + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[] // or anything else + )* + ) + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + ) + ()()()() // pad remaining backreferences + /g, writeAnchorTag); + */ + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag); + + // + // Next, inline-style links: [link text](url "optional title") + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[\]] // or anything else + )* + ) + \] + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + ? + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // Title = $7 + \6 // matching quote + [ \t]* // ignore any spaces/tabs between closing quote and ) + )? // title is optional + \) + ) + /g, writeAnchorTag); + */ + + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag); + + // + // Last, handle reference-style shortcuts: [link text] + // These must come last in case you've also got [link test][1] + // or [link test](/foo) + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ([^\[\]]+) // link text = $2; can't contain '[' or ']' + \] + ) + ()()()()() // pad rest of backreferences + /g, writeAnchorTag); + */ + text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); + + return text; + } + + function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) { + if (m7 == undefined) m7 = ""; + var whole_match = m1; + var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs + var link_id = m3.toLowerCase(); + var url = m4; + var title = m7; + + if (url == "") { + if (link_id == "") { + // lower-case and turn embedded newlines into spaces + link_id = link_text.toLowerCase().replace(/ ?\n/g, " "); + } + url = "#" + link_id; + + if (g_urls.get(link_id) != undefined) { + url = g_urls.get(link_id); + if (g_titles.get(link_id) != undefined) { + title = g_titles.get(link_id); + } + } + else { + if (whole_match.search(/\(\s*\)$/m) > -1) { + // Special case for explicit empty url + url = ""; + } else { + return whole_match; + } + } + } + url = encodeProblemUrlChars(url); + url = escapeCharacters(url, "*_"); + var result = ""; + + return result; + } + + function _DoImages(text) { + // + // Turn Markdown image shortcuts into tags. + // + + // + // First, handle reference-style labeled images: ![alt text][id] + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + ) + ()()()() // pad rest of backreferences + /g, writeImageTag); + */ + text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag); + + // + // Next, handle inline images: ![alt text](url "optional title") + // Don't forget: encode * and _ + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + \s? // One optional whitespace character + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + ? // src url = $4 + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // title = $7 + \6 // matching quote + [ \t]* + )? // title is optional + \) + ) + /g, writeImageTag); + */ + text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag); + + return text; + } + + function attributeEncode(text) { + // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title) + // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it) + return text.replace(/>/g, ">").replace(/" + _RunSpanGamut(m1) + "\n\n"; } + ); + + text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, + function (matchFound, m1) { return "

    " + _RunSpanGamut(m1) + "

    \n\n"; } + ); + + // atx-style headers: + // # Header 1 + // ## Header 2 + // ## Header 2 with closing hashes ## + // ... + // ###### Header 6 + // + + /* + text = text.replace(/ + ^(\#{1,6}) // $1 = string of #'s + [ \t]* + (.+?) // $2 = Header text + [ \t]* + \#* // optional closing #'s (not counted) + \n+ + /gm, function() {...}); + */ + + text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, + function (wholeMatch, m1, m2) { + var h_level = m1.length; + return "" + _RunSpanGamut(m2) + "\n\n"; + } + ); + + return text; + } + + function _DoLists(text) { + // + // Form HTML ordered (numbered) and unordered (bulleted) lists. + // + + // attacklab: add sentinel to hack around khtml/safari bug: + // http://bugs.webkit.org/show_bug.cgi?id=11231 + text += "~0"; + + // Re-usable pattern to match any entirel ul or ol list: + + /* + var whole_list = / + ( // $1 = whole list + ( // $2 + [ ]{0,3} // attacklab: g_tab_width - 1 + ([*+-]|\d+[.]) // $3 = first list item marker + [ \t]+ + ) + [^\r]+? + ( // $4 + ~0 // sentinel for workaround; should be $ + | + \n{2,} + (?=\S) + (?! // Negative lookahead for another list item marker + [ \t]* + (?:[*+-]|\d+[.])[ \t]+ + ) + ) + ) + /g + */ + var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; + + if (g_list_level) { + text = text.replace(whole_list, function (wholeMatch, m1, m2) { + var list = m1; + var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol"; + + var result = _ProcessListItems(list, list_type); + + // Trim any trailing whitespace, to put the closing `` + // up on the preceding line, to get it past the current stupid + // HTML block parser. This is a hack to work around the terrible + // hack that is the HTML block parser. + result = result.replace(/\s+$/, ""); + result = "<" + list_type + ">" + result + "\n"; + return result; + }); + } else { + whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; + text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) { + var runup = m1; + var list = m2; + + var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol"; + var result = _ProcessListItems(list, list_type); + result = runup + "<" + list_type + ">\n" + result + "\n"; + return result; + }); + } + + // attacklab: strip sentinel + text = text.replace(/~0/, ""); + + return text; + } + + var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" }; + + function _ProcessListItems(list_str, list_type) { + // + // Process the contents of a single ordered or unordered list, splitting it + // into individual list items. + // + // list_type is either "ul" or "ol". + + // The $g_list_level global keeps track of when we're inside a list. + // Each time we enter a list, we increment it; when we leave a list, + // we decrement. If it's zero, we're not in a list anymore. + // + // We do this because when we're not inside a list, we want to treat + // something like this: + // + // I recommend upgrading to version + // 8. Oops, now this line is treated + // as a sub-list. + // + // As a single paragraph, despite the fact that the second line starts + // with a digit-period-space sequence. + // + // Whereas when we're inside a list (or sub-list), that line will be + // treated as the start of a sub-list. What a kludge, huh? This is + // an aspect of Markdown's syntax that's hard to parse perfectly + // without resorting to mind-reading. Perhaps the solution is to + // change the syntax rules such that sub-lists must start with a + // starting cardinal number; e.g. "1." or "a.". + + g_list_level++; + + // trim trailing blank lines: + list_str = list_str.replace(/\n{2,}$/, "\n"); + + // attacklab: add sentinel to emulate \z + list_str += "~0"; + + // In the original attacklab showdown, list_type was not given to this function, and anything + // that matched /[*+-]|\d+[.]/ would just create the next
  • , causing this mismatch: + // + // Markdown rendered by WMD rendered by MarkdownSharp + // ------------------------------------------------------------------ + // 1. first 1. first 1. first + // 2. second 2. second 2. second + // - third 3. third * third + // + // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx, + // with {MARKER} being one of \d+[.] or [*+-], depending on list_type: + + /* + list_str = list_str.replace(/ + (^[ \t]*) // leading whitespace = $1 + ({MARKER}) [ \t]+ // list marker = $2 + ([^\r]+? // list item text = $3 + (\n+) + ) + (?= + (~0 | \2 ({MARKER}) [ \t]+) + ) + /gm, function(){...}); + */ + + var marker = _listItemMarkers[list_type]; + var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm"); + var last_item_had_a_double_newline = false; + list_str = list_str.replace(re, + function (wholeMatch, m1, m2, m3) { + var item = m3; + var leading_space = m1; + var ends_with_double_newline = /\n\n$/.test(item); + var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1; + + if (contains_double_newline || last_item_had_a_double_newline) { + item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true); + } + else { + // Recursion for sub-lists: + item = _DoLists(_Outdent(item)); + item = item.replace(/\n$/, ""); // chomp(item) + item = _RunSpanGamut(item); + } + last_item_had_a_double_newline = ends_with_double_newline; + return "
  • " + item + "
  • \n"; + } + ); + + // attacklab: strip sentinel + list_str = list_str.replace(/~0/g, ""); + + g_list_level--; + return list_str; + } + + function _DoCodeBlocks(text) { + // + // Process Markdown `
    ` blocks.
    +			//  
    +
    +			/*
    +            text = text.replace(/
    +                (?:\n\n|^)
    +                (                               // $1 = the code block -- one or more lines, starting with a space/tab
    +                    (?:
    +                        (?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
    +                        .*\n+
    +                    )+
    +                )
    +                (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
    +            /g ,function(){...});
    +            */
    +
    +			// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
    +			text += "~0";
    +
    +			text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
    +                function (wholeMatch, m1, m2) {
    +                	var codeblock = m1;
    +                	var nextChar = m2;
    +
    +                	codeblock = _EncodeCode(_Outdent(codeblock));
    +                	codeblock = _Detab(codeblock);
    +                	codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
    +                	codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
    +
    +                	codeblock = '
    ' + codeblock + '\n
    '; + + return "\n\n" + codeblock + "\n\n" + nextChar; + } + ); + + // attacklab: strip sentinel + text = text.replace(/~0/, ""); + + return text; + } + + function hashBlock(text) { + text = text.replace(/(^\n+|\n+$)/g, ""); + return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n"; + } + + function _DoCodeSpans(text) { + // + // * Backtick quotes are used for spans. + // + // * You can use multiple backticks as the delimiters if you want to + // include literal backticks in the code span. So, this input: + // + // Just type ``foo `bar` baz`` at the prompt. + // + // Will translate to: + // + //

    Just type foo `bar` baz at the prompt.

    + // + // There's no arbitrary limit to the number of backticks you + // can use as delimters. If you need three consecutive backticks + // in your code, use four for delimiters, etc. + // + // * You can use spaces to get literal backticks at the edges: + // + // ... type `` `bar` `` ... + // + // Turns to: + // + // ... type `bar` ... + // + + /* + text = text.replace(/ + (^|[^\\]) // Character before opening ` can't be a backslash + (`+) // $2 = Opening run of ` + ( // $3 = The code block + [^\r]*? + [^`] // attacklab: work around lack of lookbehind + ) + \2 // Matching closer + (?!`) + /gm, function(){...}); + */ + + text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, + function (wholeMatch, m1, m2, m3, m4) { + var c = m3; + c = c.replace(/^([ \t]*)/g, ""); // leading whitespace + c = c.replace(/[ \t]*$/g, ""); // trailing whitespace + c = _EncodeCode(c); + c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs. + return m1 + "" + c + ""; + } + ); + + return text; + } + + function _EncodeCode(text) { + // + // Encode/escape certain characters inside Markdown code runs. + // The point is that in code, these characters are literals, + // and lose their special Markdown meanings. + // + // Encode all ampersands; HTML entities are not + // entities within a Markdown code span. + text = text.replace(/&/g, "&"); + + // Do the angle bracket song and dance: + text = text.replace(//g, ">"); + + // Now, escape characters that are magic in Markdown: + text = escapeCharacters(text, "\*_{}[]\\", false); + + // jj the line above breaks this: + //--- + + //* Item + + // 1. Subitem + + // special char: * + //--- + + return text; + } + + function _DoItalicsAndBold(text) { + + // must go first: + text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g, + "$1$3$4"); + + text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g, + "$1$3$4"); + + return text; + } + + function _DoBlockQuotes(text) { + + /* + text = text.replace(/ + ( // Wrap whole match in $1 + ( + ^[ \t]*>[ \t]? // '>' at the start of a line + .+\n // rest of the first line + (.+\n)* // subsequent consecutive lines + \n* // blanks + )+ + ) + /gm, function(){...}); + */ + + text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, + function (wholeMatch, m1) { + var bq = m1; + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting + + // attacklab: clean up hack + bq = bq.replace(/~0/g, ""); + + bq = bq.replace(/^[ \t]+$/gm, ""); // trim whitespace-only lines + bq = _RunBlockGamut(bq); // recurse + + bq = bq.replace(/(^|\n)/g, "$1 "); + // These leading spaces screw with
     content, so we need to fix that:
    +                	bq = bq.replace(
    +                            /(\s*
    [^\r]+?<\/pre>)/gm,
    +                        function (wholeMatch, m1) {
    +                        	var pre = m1;
    +                        	// attacklab: hack around Konqueror 3.5.4 bug:
    +                        	pre = pre.replace(/^  /mg, "~0");
    +                        	pre = pre.replace(/~0/g, "");
    +                        	return pre;
    +                        });
    +
    +                	return hashBlock("
    \n" + bq + "\n
    "); + } + ); + return text; + } + + function _FormParagraphs(text, doNotUnhash) { + // + // Params: + // $text - string to process with html

    tags + // + + // Strip leading and trailing lines: + text = text.replace(/^\n+/g, ""); + text = text.replace(/\n+$/g, ""); + + var grafs = text.split(/\n{2,}/g); + var grafsOut = []; + + var markerRe = /~K(\d+)K/; + + // + // Wrap

    tags. + // + var end = grafs.length; + for (var i = 0; i < end; i++) { + var str = grafs[i]; + + // if this is an HTML marker, copy it + if (markerRe.test(str)) { + grafsOut.push(str); + } + else if (/\S/.test(str)) { + str = _RunSpanGamut(str); + str = str.replace(/^([ \t]*)/g, "

    "); + str += "

    " + grafsOut.push(str); + } + + } + // + // Unhashify HTML blocks + // + if (!doNotUnhash) { + end = grafsOut.length; + for (var i = 0; i < end; i++) { + var foundAny = true; + while (foundAny) { // we may need several runs, since the data may be nested + foundAny = false; + grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) { + foundAny = true; + return g_html_blocks[id]; + }); + } + } + } + return grafsOut.join("\n\n"); + } + + function _EncodeAmpsAndAngles(text) { + // Smart processing for ampersands and angle brackets that need to be encoded. + + // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: + // http://bumppo.net/projects/amputator/ + text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&"); + + // Encode naked <'s + text = text.replace(/<(?![a-z\/?\$!])/gi, "<"); + + return text; + } + + function _EncodeBackslashEscapes(text) { + // + // Parameter: String. + // Returns: The string, with after processing the following backslash + // escape sequences. + // + + // attacklab: The polite way to do this is with the new + // escapeCharacters() function: + // + // text = escapeCharacters(text,"\\",true); + // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); + // + // ...but we're sidestepping its use of the (slow) RegExp constructor + // as an optimization for Firefox. This function gets called a LOT. + + text = text.replace(/\\(\\)/g, escapeCharacters_callback); + text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback); + return text; + } + + function _DoAutoLinks(text) { + + // note that at this point, all other URL in the text are already hyperlinked as
    + // *except* for the case + + // automatically add < and > around unadorned raw hyperlinks + // must be preceded by space/BOF and followed by non-word/EOF character + text = text.replace(/(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi, "$1<$2$3>$4"); + + // autolink anything like + + var replacer = function (wholematch, m1) { return "" + pluginHooks.plainLinkText(m1) + ""; } + text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer); + + // Email addresses: + /* + text = text.replace(/ + < + (?:mailto:)? + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + /gi, _DoAutoLinks_callback()); + */ + + var email_replacer = function (wholematch, m1) { + var mailto = 'mailto:' + var link + var email + if (m1.substring(0, mailto.length) != mailto) { + link = mailto + m1; + email = m1; + } else { + link = m1; + email = m1.substring(mailto.length, m1.length); + } + return "" + pluginHooks.plainLinkText(email) + ""; + } + text = text.replace(/<((?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+))>/gi, email_replacer); + + return text; + } + + function _UnescapeSpecialChars(text) { + // + // Swap back in all the special characters we've hidden. + // + text = text.replace(/~E(\d+)E/g, + function (wholeMatch, m1) { + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + } + ); + return text; + } + + function _Outdent(text) { + // + // Remove one level of line-leading tabs or spaces + // + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width + + // attacklab: clean up hack + text = text.replace(/~0/g, "") + + return text; + } + + function _Detab(text) { + if (!/\t/.test(text)) + return text; + + var spaces = [" ", " ", " ", " "], + skew = 0, + v; + + return text.replace(/[\n\t]/g, function (match, offset) { + if (match === "\n") { + skew = offset + 1; + return match; + } + v = (offset - skew) % 4; + skew = offset + 1; + return spaces[v]; + }); + } + + // + // attacklab: Utility functions + // + + var _problemUrlChars = /(?:["'*()[\]:]|~D)/g; + + // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems + function encodeProblemUrlChars(url) { + if (!url) + return ""; + + var len = url.length; + + return url.replace(_problemUrlChars, function (match, offset) { + if (match == "~D") // escape for dollar + return "%24"; + if (match == ":") { + if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1))) + return ":"; + if (url.substring(0, 'mailto:'.length) === 'mailto:') + return ":"; + if (url.substring(0, 'magnet:'.length) === 'magnet:') + return ":"; + } + return "%" + match.charCodeAt(0).toString(16); + }); + } + + + function escapeCharacters(text, charsToEscape, afterBackslash) { + // First we have to escape the escape characters so that + // we can build a character class out of them + var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])"; + + if (afterBackslash) { + regexString = "\\\\" + regexString; + } + + var regex = new RegExp(regexString, "g"); + text = text.replace(regex, escapeCharacters_callback); + + return text; + } + + + function escapeCharacters_callback(wholeMatch, m1) { + var charCodeToEscape = m1.charCodeAt(0); + return "~E" + charCodeToEscape + "E"; + } + + }; // end of the Markdown.Converter constructor + })(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.css b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.css index a9e53c65a0..45c9b39c48 100644 --- a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.css +++ b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.css @@ -1,80 +1,80 @@ -.wmd-panel { - width: 100%; -} - -.wmd-input { - height: 300px; - width: 100%; - box-sizing: border-box; - -webkit-box-sizing:border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; -} - -.wmd-preview { - .well; - width: 100%; - box-sizing: border-box; - -webkit-box-sizing:border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; -} - -.wmd-panel .btn-toolbar { - margin-bottom: 0; - padding: 0; - width: 100%; -} - -/* -.icon-link, -.icon-blockquote, -.icon-code, -.icon-bullet-list, -.icon-list, -.icon-header, -.icon-hr-line, -.icon-undo { - background-image: url(Markdown.Editor.Icons.png); -} -.icon-link { background-position: 0 0; } -.icon-blockquote { background-position: -24px 0; } -.icon-code { background-position: -48px 0; } -.icon-bullet-list { background-position: -72px 0; } -.icon-list { background-position: -96px 0; } -.icon-header { background-position: -120px 0; } -.icon-hr-line { background-position: -144px 0; } -.icon-undo { background-position: -168px 0; } - */ - - - - -.wmd-prompt-background -{ - background-color: Black; -} - -.wmd-prompt-dialog -{ - border: 1px solid #999999; - background-color: #F5F5F5; -} - -.wmd-prompt-dialog > div { - font-size: 0.8em; - font-family: arial, helvetica, sans-serif; -} - - -.wmd-prompt-dialog > form > input[type="text"] { - border: 1px solid #999999; - color: black; -} - -.wmd-prompt-dialog > form > input[type="button"]{ - border: 1px solid #888888; - font-family: trebuchet MS, helvetica, sans-serif; - font-size: 0.8em; - font-weight: bold; -} +.wmd-panel { + width: 100%; +} + +.wmd-input { + height: 300px; + width: 100%; + box-sizing: border-box; + -webkit-box-sizing:border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; +} + +.wmd-preview { + .well; + width: 100%; + box-sizing: border-box; + -webkit-box-sizing:border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; +} + +.wmd-panel .btn-toolbar { + margin-bottom: 0; + padding: 0; + width: 100%; +} + +/* +.icon-link, +.icon-blockquote, +.icon-code, +.icon-bullet-list, +.icon-list, +.icon-header, +.icon-hr-line, +.icon-undo { + background-image: url(Markdown.Editor.Icons.png); +} +.icon-link { background-position: 0 0; } +.icon-blockquote { background-position: -24px 0; } +.icon-code { background-position: -48px 0; } +.icon-bullet-list { background-position: -72px 0; } +.icon-list { background-position: -96px 0; } +.icon-header { background-position: -120px 0; } +.icon-hr-line { background-position: -144px 0; } +.icon-undo { background-position: -168px 0; } + */ + + + + +.wmd-prompt-background +{ + background-color: Black; +} + +.wmd-prompt-dialog +{ + border: 1px solid #999999; + background-color: #F5F5F5; +} + +.wmd-prompt-dialog > div { + font-size: 0.8em; + font-family: arial, helvetica, sans-serif; +} + + +.wmd-prompt-dialog > form > input[type="text"] { + border: 1px solid #999999; + color: black; +} + +.wmd-prompt-dialog > form > input[type="button"]{ + border: 1px solid #888888; + font-family: trebuchet MS, helvetica, sans-serif; + font-size: 0.8em; + font-weight: bold; +} diff --git a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js index 14029bfaf6..21ad5d0a8b 100644 --- a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js +++ b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js @@ -1,2110 +1,2110 @@ -// needs Markdown.Converter.js at the moment - -(function () { - - var util = {}, - position = {}, - ui = {}, - doc = window.document, - re = window.RegExp, - nav = window.navigator, - SETTINGS = { lineLength: 72 }, - - // Used to work around some browser bugs where we can't use feature testing. - uaSniffed = { - isIE: /msie/.test(nav.userAgent.toLowerCase()), - isIE_5or6: /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase()), - isOpera: /opera/.test(nav.userAgent.toLowerCase()) - }; - - - // ------------------------------------------------------------------- - // YOUR CHANGES GO HERE - // - // I've tried to localize the things you are likely to change to - // this area. - // ------------------------------------------------------------------- - - // The text that appears on the upper part of the dialog box when - // entering links. - var linkDialogText = "

    http://example.com/ \"optional title\"

    "; - var imageDialogText = "

    http://example.com/images/diagram.jpg \"optional title\"

    "; - - // The default text that appears in the dialog input box when entering - // links. - var imageDefaultText = "http://"; - var linkDefaultText = "http://"; - - var defaultHelpHoverTitle = "Markdown Editing Help"; - - // ------------------------------------------------------------------- - // END OF YOUR CHANGES - // ------------------------------------------------------------------- - - // help, if given, should have a property "handler", the click handler for the help button, - // and can have an optional property "title" for the button's tooltip (defaults to "Markdown Editing Help"). - // If help isn't given, not help button is created. - // - // The constructed editor object has the methods: - // - getConverter() returns the markdown converter object that was passed to the constructor - // - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op. - // - refreshPreview() forces the preview to be updated. This method is only available after run() was called. - Markdown.Editor = function (markdownConverter, idPostfix, help) { - - idPostfix = idPostfix || ""; - - var hooks = this.hooks = new Markdown.HookCollection(); - hooks.addNoop("onPreviewRefresh"); // called with no arguments after the preview has been refreshed - hooks.addNoop("postBlockquoteCreation"); // called with the user's selection *after* the blockquote was created; should return the actual to-be-inserted text - hooks.addFalse("insertImageDialog"); /* called with one parameter: a callback to be called with the URL of the image. If the application creates - * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen - * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used. - */ - - this.getConverter = function () { return markdownConverter; } - - var that = this, - panels; - - this.run = function () { - if (panels) - return; // already initialized - - panels = new PanelCollection(idPostfix); - var commandManager = new CommandManager(hooks); - var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); }); - var undoManager, uiManager; - - if (!/\?noundo/.test(doc.location.href)) { - undoManager = new UndoManager(function () { - previewManager.refresh(); - if (uiManager) // not available on the first call - uiManager.setUndoRedoButtonStates(); - }, panels); - this.textOperation = function (f) { - undoManager.setCommandMode(); - f(); - that.refreshPreview(); - } - } - - uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, help); - uiManager.setUndoRedoButtonStates(); - - var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); }; - - forceRefresh(); - }; - - } - - // before: contains all the text in the input box BEFORE the selection. - // after: contains all the text in the input box AFTER the selection. - function Chunks() { } - - // startRegex: a regular expression to find the start tag - // endRegex: a regular expresssion to find the end tag - Chunks.prototype.findTags = function (startRegex, endRegex) { - - var chunkObj = this; - var regex; - - if (startRegex) { - - regex = util.extendRegExp(startRegex, "", "$"); - - this.before = this.before.replace(regex, - function (match) { - chunkObj.startTag = chunkObj.startTag + match; - return ""; - }); - - regex = util.extendRegExp(startRegex, "^", ""); - - this.selection = this.selection.replace(regex, - function (match) { - chunkObj.startTag = chunkObj.startTag + match; - return ""; - }); - } - - if (endRegex) { - - regex = util.extendRegExp(endRegex, "", "$"); - - this.selection = this.selection.replace(regex, - function (match) { - chunkObj.endTag = match + chunkObj.endTag; - return ""; - }); - - regex = util.extendRegExp(endRegex, "^", ""); - - this.after = this.after.replace(regex, - function (match) { - chunkObj.endTag = match + chunkObj.endTag; - return ""; - }); - } - }; - - // If remove is false, the whitespace is transferred - // to the before/after regions. - // - // If remove is true, the whitespace disappears. - Chunks.prototype.trimWhitespace = function (remove) { - var beforeReplacer, afterReplacer, that = this; - if (remove) { - beforeReplacer = afterReplacer = ""; - } else { - beforeReplacer = function (s) { that.before += s; return ""; } - afterReplacer = function (s) { that.after = s + that.after; return ""; } - } - - this.selection = this.selection.replace(/^(\s*)/, beforeReplacer).replace(/(\s*)$/, afterReplacer); - }; - - - Chunks.prototype.skipLines = function (nLinesBefore, nLinesAfter, findExtraNewlines) { - - if (nLinesBefore === undefined) { - nLinesBefore = 1; - } - - if (nLinesAfter === undefined) { - nLinesAfter = 1; - } - - nLinesBefore++; - nLinesAfter++; - - var regexText; - var replacementText; - - // chrome bug ... documented at: http://meta.stackoverflow.com/questions/63307/blockquote-glitch-in-editor-in-chrome-6-and-7/65985#65985 - if (navigator.userAgent.match(/Chrome/)) { - "X".match(/()./); - } - - this.selection = this.selection.replace(/(^\n*)/, ""); - - this.startTag = this.startTag + re.$1; - - this.selection = this.selection.replace(/(\n*$)/, ""); - this.endTag = this.endTag + re.$1; - this.startTag = this.startTag.replace(/(^\n*)/, ""); - this.before = this.before + re.$1; - this.endTag = this.endTag.replace(/(\n*$)/, ""); - this.after = this.after + re.$1; - - if (this.before) { - - regexText = replacementText = ""; - - while (nLinesBefore--) { - regexText += "\\n?"; - replacementText += "\n"; - } - - if (findExtraNewlines) { - regexText = "\\n*"; - } - this.before = this.before.replace(new re(regexText + "$", ""), replacementText); - } - - if (this.after) { - - regexText = replacementText = ""; - - while (nLinesAfter--) { - regexText += "\\n?"; - replacementText += "\n"; - } - if (findExtraNewlines) { - regexText = "\\n*"; - } - - this.after = this.after.replace(new re(regexText, ""), replacementText); - } - }; - - // end of Chunks - - // A collection of the important regions on the page. - // Cached so we don't have to keep traversing the DOM. - // Also holds ieCachedRange and ieCachedScrollTop, where necessary; working around - // this issue: - // Internet explorer has problems with CSS sprite buttons that use HTML - // lists. When you click on the background image "button", IE will - // select the non-existent link text and discard the selection in the - // textarea. The solution to this is to cache the textarea selection - // on the button's mousedown event and set a flag. In the part of the - // code where we need to grab the selection, we check for the flag - // and, if it's set, use the cached area instead of querying the - // textarea. - // - // This ONLY affects Internet Explorer (tested on versions 6, 7 - // and 8) and ONLY on button clicks. Keyboard shortcuts work - // normally since the focus never leaves the textarea. - function PanelCollection(postfix) { - this.buttonBar = doc.getElementById("wmd-button-bar" + postfix); - this.preview = doc.getElementById("wmd-preview" + postfix); - this.input = doc.getElementById("wmd-input" + postfix); - }; - - // Returns true if the DOM element is visible, false if it's hidden. - // Checks if display is anything other than none. - util.isVisible = function (elem) { - - if (window.getComputedStyle) { - // Most browsers - return window.getComputedStyle(elem, null).getPropertyValue("display") !== "none"; - } - else if (elem.currentStyle) { - // IE - return elem.currentStyle["display"] !== "none"; - } - }; - - - // Adds a listener callback to a DOM element which is fired on a specified - // event. - util.addEvent = function (elem, event, listener) { - if (elem.attachEvent) { - // IE only. The "on" is mandatory. - elem.attachEvent("on" + event, listener); - } - else { - // Other browsers. - elem.addEventListener(event, listener, false); - } - }; - - - // Removes a listener callback from a DOM element which is fired on a specified - // event. - util.removeEvent = function (elem, event, listener) { - if (elem.detachEvent) { - // IE only. The "on" is mandatory. - elem.detachEvent("on" + event, listener); - } - else { - // Other browsers. - elem.removeEventListener(event, listener, false); - } - }; - - // Converts \r\n and \r to \n. - util.fixEolChars = function (text) { - text = text.replace(/\r\n/g, "\n"); - text = text.replace(/\r/g, "\n"); - return text; - }; - - // Extends a regular expression. Returns a new RegExp - // using pre + regex + post as the expression. - // Used in a few functions where we have a base - // expression and we want to pre- or append some - // conditions to it (e.g. adding "$" to the end). - // The flags are unchanged. - // - // regex is a RegExp, pre and post are strings. - util.extendRegExp = function (regex, pre, post) { - - if (pre === null || pre === undefined) { - pre = ""; - } - if (post === null || post === undefined) { - post = ""; - } - - var pattern = regex.toString(); - var flags; - - // Replace the flags with empty space and store them. - pattern = pattern.replace(/\/([gim]*)$/, function (wholeMatch, flagsPart) { - flags = flagsPart; - return ""; - }); - - // Remove the slash delimiters on the regular expression. - pattern = pattern.replace(/(^\/|\/$)/g, ""); - pattern = pre + pattern + post; - - return new re(pattern, flags); - } - - // UNFINISHED - // The assignment in the while loop makes jslint cranky. - // I'll change it to a better loop later. - position.getTop = function (elem, isInner) { - var result = elem.offsetTop; - if (!isInner) { - while (elem = elem.offsetParent) { - result += elem.offsetTop; - } - } - return result; - }; - - position.getHeight = function (elem) { - return elem.offsetHeight || elem.scrollHeight; - }; - - position.getWidth = function (elem) { - return elem.offsetWidth || elem.scrollWidth; - }; - - position.getPageSize = function () { - - var scrollWidth, scrollHeight; - var innerWidth, innerHeight; - - // It's not very clear which blocks work with which browsers. - if (self.innerHeight && self.scrollMaxY) { - scrollWidth = doc.body.scrollWidth; - scrollHeight = self.innerHeight + self.scrollMaxY; - } - else if (doc.body.scrollHeight > doc.body.offsetHeight) { - scrollWidth = doc.body.scrollWidth; - scrollHeight = doc.body.scrollHeight; - } - else { - scrollWidth = doc.body.offsetWidth; - scrollHeight = doc.body.offsetHeight; - } - - if (self.innerHeight) { - // Non-IE browser - innerWidth = self.innerWidth; - innerHeight = self.innerHeight; - } - else if (doc.documentElement && doc.documentElement.clientHeight) { - // Some versions of IE (IE 6 w/ a DOCTYPE declaration) - innerWidth = doc.documentElement.clientWidth; - innerHeight = doc.documentElement.clientHeight; - } - else if (doc.body) { - // Other versions of IE - innerWidth = doc.body.clientWidth; - innerHeight = doc.body.clientHeight; - } - - var maxWidth = Math.max(scrollWidth, innerWidth); - var maxHeight = Math.max(scrollHeight, innerHeight); - return [maxWidth, maxHeight, innerWidth, innerHeight]; - }; - - // Handles pushing and popping TextareaStates for undo/redo commands. - // I should rename the stack variables to list. - function UndoManager(callback, panels) { - - var undoObj = this; - var undoStack = []; // A stack of undo states - var stackPtr = 0; // The index of the current state - var mode = "none"; - var lastState; // The last state - var timer; // The setTimeout handle for cancelling the timer - var inputStateObj; - - // Set the mode for later logic steps. - var setMode = function (newMode, noSave) { - if (mode != newMode) { - mode = newMode; - if (!noSave) { - saveState(); - } - } - - if (!uaSniffed.isIE || mode != "moving") { - timer = setTimeout(refreshState, 1); - } - else { - inputStateObj = null; - } - }; - - var refreshState = function (isInitialState) { - inputStateObj = new TextareaState(panels, isInitialState); - timer = undefined; - }; - - this.setCommandMode = function () { - mode = "command"; - saveState(); - timer = setTimeout(refreshState, 0); - }; - - this.canUndo = function () { - return stackPtr > 1; - }; - - this.canRedo = function () { - if (undoStack[stackPtr + 1]) { - return true; - } - return false; - }; - - // Removes the last state and restores it. - this.undo = function () { - - if (undoObj.canUndo()) { - if (lastState) { - // What about setting state -1 to null or checking for undefined? - lastState.restore(); - lastState = null; - } - else { - undoStack[stackPtr] = new TextareaState(panels); - undoStack[--stackPtr].restore(); - - if (callback) { - callback(); - } - } - } - - mode = "none"; - panels.input.focus(); - refreshState(); - }; - - // Redo an action. - this.redo = function () { - - if (undoObj.canRedo()) { - - undoStack[++stackPtr].restore(); - - if (callback) { - callback(); - } - } - - mode = "none"; - panels.input.focus(); - refreshState(); - }; - - // Push the input area state to the stack. - var saveState = function () { - var currState = inputStateObj || new TextareaState(panels); - - if (!currState) { - return false; - } - if (mode == "moving") { - if (!lastState) { - lastState = currState; - } - return; - } - if (lastState) { - if (undoStack[stackPtr - 1].text != lastState.text) { - undoStack[stackPtr++] = lastState; - } - lastState = null; - } - undoStack[stackPtr++] = currState; - undoStack[stackPtr + 1] = null; - if (callback) { - callback(); - } - }; - - var handleCtrlYZ = function (event) { - - var handled = false; - - if (event.ctrlKey || event.metaKey) { - - // IE and Opera do not support charCode. - var keyCode = event.charCode || event.keyCode; - var keyCodeChar = String.fromCharCode(keyCode); - - switch (keyCodeChar) { - - case "y": - undoObj.redo(); - handled = true; - break; - - case "z": - if (!event.shiftKey) { - undoObj.undo(); - } - else { - undoObj.redo(); - } - handled = true; - break; - } - } - - if (handled) { - if (event.preventDefault) { - event.preventDefault(); - } - if (window.event) { - window.event.returnValue = false; - } - return; - } - }; - - // Set the mode depending on what is going on in the input area. - var handleModeChange = function (event) { - - if (!event.ctrlKey && !event.metaKey) { - - var keyCode = event.keyCode; - - if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) { - // 33 - 40: page up/dn and arrow keys - // 63232 - 63235: page up/dn and arrow keys on safari - setMode("moving"); - } - else if (keyCode == 8 || keyCode == 46 || keyCode == 127) { - // 8: backspace - // 46: delete - // 127: delete - setMode("deleting"); - } - else if (keyCode == 13) { - // 13: Enter - setMode("newlines"); - } - else if (keyCode == 27) { - // 27: escape - setMode("escape"); - } - else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) { - // 16-20 are shift, etc. - // 91: left window key - // I think this might be a little messed up since there are - // a lot of nonprinting keys above 20. - setMode("typing"); - } - } - }; - - var setEventHandlers = function () { - util.addEvent(panels.input, "keypress", function (event) { - // keyCode 89: y - // keyCode 90: z - if ((event.ctrlKey || event.metaKey) && (event.keyCode == 89 || event.keyCode == 90)) { - event.preventDefault(); - } - }); - - var handlePaste = function () { - if (uaSniffed.isIE || (inputStateObj && inputStateObj.text != panels.input.value)) { - if (timer == undefined) { - mode = "paste"; - saveState(); - refreshState(); - } - } - }; - - util.addEvent(panels.input, "keydown", handleCtrlYZ); - util.addEvent(panels.input, "keydown", handleModeChange); - util.addEvent(panels.input, "mousedown", function () { - setMode("moving"); - }); - - panels.input.onpaste = handlePaste; - panels.input.ondrop = handlePaste; - }; - - var init = function () { - setEventHandlers(); - refreshState(true); - saveState(); - }; - - init(); - } - - // end of UndoManager - - // The input textarea state/contents. - // This is used to implement undo/redo by the undo manager. - function TextareaState(panels, isInitialState) { - - // Aliases - var stateObj = this; - var inputArea = panels.input; - this.init = function () { - if (!util.isVisible(inputArea)) { - return; - } - if (!isInitialState && doc.activeElement && doc.activeElement !== inputArea) { // this happens when tabbing out of the input box - return; - } - - this.setInputAreaSelectionStartEnd(); - this.scrollTop = inputArea.scrollTop; - if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) { - this.text = inputArea.value; - } - - } - - // Sets the selected text in the input box after we've performed an - // operation. - this.setInputAreaSelection = function () { - - if (!util.isVisible(inputArea)) { - return; - } - - if (inputArea.selectionStart !== undefined && !uaSniffed.isOpera) { - - inputArea.focus(); - inputArea.selectionStart = stateObj.start; - inputArea.selectionEnd = stateObj.end; - inputArea.scrollTop = stateObj.scrollTop; - } - else if (doc.selection) { - - if (doc.activeElement && doc.activeElement !== inputArea) { - return; - } - - inputArea.focus(); - var range = inputArea.createTextRange(); - range.moveStart("character", -inputArea.value.length); - range.moveEnd("character", -inputArea.value.length); - range.moveEnd("character", stateObj.end); - range.moveStart("character", stateObj.start); - range.select(); - } - }; - - this.setInputAreaSelectionStartEnd = function () { - - if (!panels.ieCachedRange && (inputArea.selectionStart || inputArea.selectionStart === 0)) { - - stateObj.start = inputArea.selectionStart; - stateObj.end = inputArea.selectionEnd; - } - else if (doc.selection) { - - stateObj.text = util.fixEolChars(inputArea.value); - - // IE loses the selection in the textarea when buttons are - // clicked. On IE we cache the selection. Here, if something is cached, - // we take it. - var range = panels.ieCachedRange || doc.selection.createRange(); - - var fixedRange = util.fixEolChars(range.text); - var marker = "\x07"; - var markedRange = marker + fixedRange + marker; - range.text = markedRange; - var inputText = util.fixEolChars(inputArea.value); - - range.moveStart("character", -markedRange.length); - range.text = fixedRange; - - stateObj.start = inputText.indexOf(marker); - stateObj.end = inputText.lastIndexOf(marker) - marker.length; - - var len = stateObj.text.length - util.fixEolChars(inputArea.value).length; - - if (len) { - range.moveStart("character", -fixedRange.length); - while (len--) { - fixedRange += "\n"; - stateObj.end += 1; - } - range.text = fixedRange; - } - - if (panels.ieCachedRange) - stateObj.scrollTop = panels.ieCachedScrollTop; // this is set alongside with ieCachedRange - - panels.ieCachedRange = null; - - this.setInputAreaSelection(); - } - }; - - // Restore this state into the input area. - this.restore = function () { - - if (stateObj.text != undefined && stateObj.text != inputArea.value) { - inputArea.value = stateObj.text; - } - this.setInputAreaSelection(); - inputArea.scrollTop = stateObj.scrollTop; - }; - - // Gets a collection of HTML chunks from the inptut textarea. - this.getChunks = function () { - - var chunk = new Chunks(); - chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start)); - chunk.startTag = ""; - chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end)); - chunk.endTag = ""; - chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end)); - chunk.scrollTop = stateObj.scrollTop; - - return chunk; - }; - - // Sets the TextareaState properties given a chunk of markdown. - this.setChunks = function (chunk) { - - chunk.before = chunk.before + chunk.startTag; - chunk.after = chunk.endTag + chunk.after; - - this.start = chunk.before.length; - this.end = chunk.before.length + chunk.selection.length; - this.text = chunk.before + chunk.selection + chunk.after; - this.scrollTop = chunk.scrollTop; - }; - this.init(); - }; - - function PreviewManager(converter, panels, previewRefreshCallback) { - - var managerObj = this; - var timeout; - var elapsedTime; - var oldInputText; - var maxDelay = 3000; - var startType = "delayed"; // The other legal value is "manual" - - // Adds event listeners to elements - var setupEvents = function (inputElem, listener) { - - util.addEvent(inputElem, "input", listener); - inputElem.onpaste = listener; - inputElem.ondrop = listener; - - util.addEvent(inputElem, "keypress", listener); - util.addEvent(inputElem, "keydown", listener); - }; - - var getDocScrollTop = function () { - - var result = 0; - - if (window.innerHeight) { - result = window.pageYOffset; - } - else - if (doc.documentElement && doc.documentElement.scrollTop) { - result = doc.documentElement.scrollTop; - } - else - if (doc.body) { - result = doc.body.scrollTop; - } - - return result; - }; - - var makePreviewHtml = function () { - - // If there is no registered preview panel - // there is nothing to do. - if (!panels.preview) - return; - - - var text = panels.input.value; - if (text && text == oldInputText) { - return; // Input text hasn't changed. - } - else { - oldInputText = text; - } - - var prevTime = new Date().getTime(); - - text = converter.makeHtml(text); - - // Calculate the processing time of the HTML creation. - // It's used as the delay time in the event listener. - var currTime = new Date().getTime(); - elapsedTime = currTime - prevTime; - - pushPreviewHtml(text); - }; - - // setTimeout is already used. Used as an event listener. - var applyTimeout = function () { - - if (timeout) { - clearTimeout(timeout); - timeout = undefined; - } - - if (startType !== "manual") { - - var delay = 0; - - if (startType === "delayed") { - delay = elapsedTime; - } - - if (delay > maxDelay) { - delay = maxDelay; - } - timeout = setTimeout(makePreviewHtml, delay); - } - }; - - var getScaleFactor = function (panel) { - if (panel.scrollHeight <= panel.clientHeight) { - return 1; - } - return panel.scrollTop / (panel.scrollHeight - panel.clientHeight); - }; - - var setPanelScrollTops = function () { - if (panels.preview) { - panels.preview.scrollTop = (panels.preview.scrollHeight - panels.preview.clientHeight) * getScaleFactor(panels.preview); - } - }; - - this.refresh = function (requiresRefresh) { - - if (requiresRefresh) { - oldInputText = ""; - makePreviewHtml(); - } - else { - applyTimeout(); - } - }; - - this.processingTime = function () { - return elapsedTime; - }; - - var isFirstTimeFilled = true; - - // IE doesn't let you use innerHTML if the element is contained somewhere in a table - // (which is the case for inline editing) -- in that case, detach the element, set the - // value, and reattach. Yes, that *is* ridiculous. - var ieSafePreviewSet = function (text) { - var preview = panels.preview; - var parent = preview.parentNode; - var sibling = preview.nextSibling; - parent.removeChild(preview); - preview.innerHTML = text; - if (!sibling) - parent.appendChild(preview); - else - parent.insertBefore(preview, sibling); - } - - var nonSuckyBrowserPreviewSet = function (text) { - panels.preview.innerHTML = text; - } - - var previewSetter; - - var previewSet = function (text) { - if (previewSetter) - return previewSetter(text); - - try { - nonSuckyBrowserPreviewSet(text); - previewSetter = nonSuckyBrowserPreviewSet; - } catch (e) { - previewSetter = ieSafePreviewSet; - previewSetter(text); - } - }; - - var pushPreviewHtml = function (text) { - - var emptyTop = position.getTop(panels.input) - getDocScrollTop(); - - if (panels.preview) { - previewSet(text); - previewRefreshCallback(); - } - - setPanelScrollTops(); - - if (isFirstTimeFilled) { - isFirstTimeFilled = false; - return; - } - - var fullTop = position.getTop(panels.input) - getDocScrollTop(); - - if (uaSniffed.isIE) { - setTimeout(function () { - window.scrollBy(0, fullTop - emptyTop); - }, 0); - } - else { - window.scrollBy(0, fullTop - emptyTop); - } - }; - - var init = function () { - - setupEvents(panels.input, applyTimeout); - makePreviewHtml(); - - if (panels.preview) { - panels.preview.scrollTop = 0; - } - }; - - init(); - }; - - - // This simulates a modal dialog box and asks for the URL when you - // click the hyperlink or image buttons. - // - // text: The html for the input box. - // defaultInputText: The default value that appears in the input box. - // callback: The function which is executed when the prompt is dismissed, either via OK or Cancel. - // It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel - // was chosen). - ui.prompt = function (title, text, defaultInputText, callback) { - - // These variables need to be declared at this level since they are used - // in multiple functions. - var dialog; // The dialog box. - var input; // The text box where you enter the hyperlink. - - - if (defaultInputText === undefined) { - defaultInputText = ""; - } - - // Used as a keydown event handler. Esc dismisses the prompt. - // Key code 27 is ESC. - var checkEscape = function (key) { - var code = (key.charCode || key.keyCode); - if (code === 27) { - close(true); - } - }; - - // Dismisses the hyperlink input box. - // isCancel is true if we don't care about the input text. - // isCancel is false if we are going to keep the text. - var close = function (isCancel) { - util.removeEvent(doc.body, "keydown", checkEscape); - var text = input.value; - - if (isCancel) { - text = null; - } - else { - // Fixes common pasting errors. - text = text.replace(/^http:\/\/(https?|ftp):\/\//, '$1://'); - if (!/^(?:https?|ftp):\/\//.test(text)) - text = 'http://' + text; - } - - $(dialog).modal('hide'); - - callback(text); - return false; - }; - - - - // Create the text input box form/window. - var createDialog = function () { - // - - // The main dialog box. - dialog = doc.createElement("div"); - dialog.className = "modal hide fade"; - dialog.style.display = "none"; - - // The header. - var header = doc.createElement("div"); - header.className = "modal-header"; - header.innerHTML = '×

    ' + title + '

    '; - dialog.appendChild(header); - - // The body. - var body = doc.createElement("div"); - body.className = "modal-body"; - dialog.appendChild(body); - - // The footer. - var footer = doc.createElement("div"); - footer.className = "modal-footer"; - dialog.appendChild(footer); - - // The dialog text. - var question = doc.createElement("p"); - question.innerHTML = text; - question.style.padding = "5px"; - body.appendChild(question); - - // The web form container for the text box and buttons. - var form = doc.createElement("form"), - style = form.style; - form.onsubmit = function () { return close(false); }; - style.padding = "0"; - style.margin = "0"; - body.appendChild(form); - - // The input text box - input = doc.createElement("input"); - input.type = "text"; - input.value = defaultInputText; - style = input.style; - style.display = "block"; - style.width = "80%"; - style.marginLeft = style.marginRight = "auto"; - form.appendChild(input); - - // The ok button - var okButton = doc.createElement("button"); - okButton.className = "btn btn-primary"; - okButton.type = "button"; - okButton.onclick = function () { return close(false); }; - okButton.innerHTML = "OK"; - - // The cancel button - var cancelButton = doc.createElement("button"); - cancelButton.className = "btn btn-primary"; - cancelButton.type = "button"; - cancelButton.onclick = function () { return close(true); }; - cancelButton.innerHTML = "Cancel"; - - footer.appendChild(okButton); - footer.appendChild(cancelButton); - - util.addEvent(doc.body, "keydown", checkEscape); - - doc.body.appendChild(dialog); - - }; - - // Why is this in a zero-length timeout? - // Is it working around a browser bug? - setTimeout(function () { - - createDialog(); - - var defTextLen = defaultInputText.length; - if (input.selectionStart !== undefined) { - input.selectionStart = 0; - input.selectionEnd = defTextLen; - } - else if (input.createTextRange) { - var range = input.createTextRange(); - range.collapse(false); - range.moveStart("character", -defTextLen); - range.moveEnd("character", defTextLen); - range.select(); - } - - $(dialog).on('shown', function () { - input.focus(); - }) - - $(dialog).on('hidden', function () { - dialog.parentNode.removeChild(dialog); - }) - - $(dialog).modal() - - }, 0); - }; - - function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions) { - - var inputBox = panels.input, - buttons = {}; // buttons.undo, buttons.link, etc. The actual DOM elements. - - makeSpritedButtonRow(); - - var keyEvent = "keydown"; - if (uaSniffed.isOpera) { - keyEvent = "keypress"; - } - - util.addEvent(inputBox, keyEvent, function (key) { - - // Check to see if we have a button key and, if so execute the callback. - if ((key.ctrlKey || key.metaKey) && !key.altKey && !key.shiftKey) { - - var keyCode = key.charCode || key.keyCode; - var keyCodeStr = String.fromCharCode(keyCode).toLowerCase(); - - switch (keyCodeStr) { - case "b": - doClick(buttons.bold); - break; - case "i": - doClick(buttons.italic); - break; - case "l": - doClick(buttons.link); - break; - case "q": - doClick(buttons.quote); - break; - case "k": - doClick(buttons.code); - break; - case "g": - doClick(buttons.image); - break; - case "o": - doClick(buttons.olist); - break; - case "u": - doClick(buttons.ulist); - break; - case "h": - doClick(buttons.heading); - break; - case "r": - doClick(buttons.hr); - break; - case "y": - doClick(buttons.redo); - break; - case "z": - if (key.shiftKey) { - doClick(buttons.redo); - } - else { - doClick(buttons.undo); - } - break; - default: - return; - } - - - if (key.preventDefault) { - key.preventDefault(); - } - - if (window.event) { - window.event.returnValue = false; - } - } - }); - - // Auto-indent on shift-enter - util.addEvent(inputBox, "keyup", function (key) { - if (key.shiftKey && !key.ctrlKey && !key.metaKey) { - var keyCode = key.charCode || key.keyCode; - // Character 13 is Enter - if (keyCode === 13) { - var fakeButton = {}; - fakeButton.textOp = bindCommand("doAutoindent"); - doClick(fakeButton); - } - } - }); - - // special handler because IE clears the context of the textbox on ESC - if (uaSniffed.isIE) { - util.addEvent(inputBox, "keydown", function (key) { - var code = key.keyCode; - if (code === 27) { - return false; - } - }); - } - - - // Perform the button's action. - function doClick(button) { - - inputBox.focus(); - - if (button.textOp) { - - if (undoManager) { - undoManager.setCommandMode(); - } - - var state = new TextareaState(panels); - - if (!state) { - return; - } - - var chunks = state.getChunks(); - - // Some commands launch a "modal" prompt dialog. Javascript - // can't really make a modal dialog box and the WMD code - // will continue to execute while the dialog is displayed. - // This prevents the dialog pattern I'm used to and means - // I can't do something like this: - // - // var link = CreateLinkDialog(); - // makeMarkdownLink(link); - // - // Instead of this straightforward method of handling a - // dialog I have to pass any code which would execute - // after the dialog is dismissed (e.g. link creation) - // in a function parameter. - // - // Yes this is awkward and I think it sucks, but there's - // no real workaround. Only the image and link code - // create dialogs and require the function pointers. - var fixupInputArea = function () { - - inputBox.focus(); - - if (chunks) { - state.setChunks(chunks); - } - - state.restore(); - previewManager.refresh(); - }; - - var noCleanup = button.textOp(chunks, fixupInputArea); - - if (!noCleanup) { - fixupInputArea(); - } - - } - - if (button.execute) { - button.execute(undoManager); - } - }; - - function setupButton(button, isEnabled) { - - if (isEnabled) { - button.disabled = false; - - if (!button.isHelp) { - button.onclick = function () { - if (this.onmouseout) { - this.onmouseout(); - } - doClick(this); - return false; - } - } - } - else { - button.disabled = true; - } - } - - function bindCommand(method) { - if (typeof method === "string") - method = commandManager[method]; - return function () { method.apply(commandManager, arguments); } - } - - function makeSpritedButtonRow() { - - var buttonBar = panels.buttonBar; - var buttonRow = document.createElement("div"); - buttonRow.id = "wmd-button-row" + postfix; - buttonRow.className = 'btn-toolbar'; - buttonRow = buttonBar.appendChild(buttonRow); - - var makeButton = function (id, title, icon, textOp, group) { - var button = document.createElement("button"); - button.className = "btn"; - var buttonImage = document.createElement("i"); - buttonImage.className = icon; - button.id = id + postfix; - button.appendChild(buttonImage); - button.title = title; - $(button).tooltip({ placement: 'bottom' }) - if (textOp) - button.textOp = textOp; - setupButton(button, true); - if (group) { - group.appendChild(button); - } else { - buttonRow.appendChild(button); - } - return button; - }; - var makeGroup = function (num) { - var group = document.createElement("div"); - group.className = "btn-group wmd-button-group" + num; - group.id = "wmd-button-group" + num + postfix; - buttonRow.appendChild(group); - return group - } - - group1 = makeGroup(1); - buttons.bold = makeButton("wmd-bold-button", "Bold - Ctrl+B", "icon-drop", bindCommand("doBold"), group1); - buttons.italic = makeButton("wmd-italic-button", "Italic - Ctrl+I", "icon-font", bindCommand("doItalic"), group1); - - group2 = makeGroup(2); - buttons.link = makeButton("wmd-link-button", "Link - Ctrl+L", "icon-link", bindCommand(function (chunk, postProcessing) { - return this.doLinkOrImage(chunk, postProcessing, false); - }), group2); - buttons.quote = makeButton("wmd-quote-button", "Blockquote - Ctrl+Q", "icon-quote", bindCommand("doBlockquote"), group2); - buttons.code = makeButton("wmd-code-button", "Code Sample - Ctrl+K", "icon-code", bindCommand("doCode"), group2); - buttons.image = makeButton("wmd-image-button", "Image - Ctrl+G", "icon-picture", bindCommand(function (chunk, postProcessing) { - return this.doLinkOrImage(chunk, postProcessing, true); - }), group2); - - group3 = makeGroup(3); - buttons.olist = makeButton("wmd-olist-button", "Numbered List - Ctrl+O", "icon-ordered-list", bindCommand(function (chunk, postProcessing) { - this.doList(chunk, postProcessing, true); - }), group3); - buttons.ulist = makeButton("wmd-ulist-button", "Bulleted List - Ctrl+U", "icon-bulleted-list", bindCommand(function (chunk, postProcessing) { - this.doList(chunk, postProcessing, false); - }), group3); - buttons.heading = makeButton("wmd-heading-button", "Heading - Ctrl+H", "icon-shift", bindCommand("doHeading"), group3); - buttons.hr = makeButton("wmd-hr-button", "Horizontal Rule - Ctrl+R", "icon-remove", bindCommand("doHorizontalRule"), group3); - - group4 = makeGroup(4); - buttons.undo = makeButton("wmd-undo-button", "Undo - Ctrl+Z", "icon-undo", null, group4); - buttons.undo.execute = function (manager) { if (manager) manager.undo(); }; - - var redoTitle = /win/.test(nav.platform.toLowerCase()) ? - "Redo - Ctrl+Y" : - "Redo - Ctrl+Shift+Z"; // mac and other non-Windows platforms - - buttons.redo = makeButton("wmd-redo-button", redoTitle, "icon-share-alt", null, group4); - buttons.redo.execute = function (manager) { if (manager) manager.redo(); }; - - if (helpOptions) { - group5 = makeGroup(5); - group5.className = group5.className + " pull-right"; - var helpButton = document.createElement("button"); - var helpButtonImage = document.createElement("i"); - helpButtonImage.className = "icon-question-sign"; - helpButton.appendChild(helpButtonImage); - helpButton.className = "btn"; - helpButton.id = "wmd-help-button" + postfix; - helpButton.isHelp = true; - helpButton.title = helpOptions.title || defaultHelpHoverTitle; - $(helpButton).tooltip({ placement: 'bottom' }) - helpButton.onclick = helpOptions.handler; - - setupButton(helpButton, true); - group5.appendChild(helpButton); - buttons.help = helpButton; - } - - setUndoRedoButtonStates(); - } - - function setUndoRedoButtonStates() { - if (undoManager) { - setupButton(buttons.undo, undoManager.canUndo()); - setupButton(buttons.redo, undoManager.canRedo()); - } - }; - - this.setUndoRedoButtonStates = setUndoRedoButtonStates; - - } - - function CommandManager(pluginHooks) { - this.hooks = pluginHooks; - } - - var commandProto = CommandManager.prototype; - - // The markdown symbols - 4 spaces = code, > = blockquote, etc. - commandProto.prefixes = "(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)"; - - // Remove markdown symbols from the chunk selection. - commandProto.unwrap = function (chunk) { - var txt = new re("([^\\n])\\n(?!(\\n|" + this.prefixes + "))", "g"); - chunk.selection = chunk.selection.replace(txt, "$1 $2"); - }; - - commandProto.wrap = function (chunk, len) { - this.unwrap(chunk); - var regex = new re("(.{1," + len + "})( +|$\\n?)", "gm"), - that = this; - - chunk.selection = chunk.selection.replace(regex, function (line, marked) { - if (new re("^" + that.prefixes, "").test(line)) { - return line; - } - return marked + "\n"; - }); - - chunk.selection = chunk.selection.replace(/\s+$/, ""); - }; - - commandProto.doBold = function (chunk, postProcessing) { - return this.doBorI(chunk, postProcessing, 2, "strong text"); - }; - - commandProto.doItalic = function (chunk, postProcessing) { - return this.doBorI(chunk, postProcessing, 1, "emphasized text"); - }; - - // chunk: The selected region that will be enclosed with */** - // nStars: 1 for italics, 2 for bold - // insertText: If you just click the button without highlighting text, this gets inserted - commandProto.doBorI = function (chunk, postProcessing, nStars, insertText) { - - // Get rid of whitespace and fixup newlines. - chunk.trimWhitespace(); - chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n"); - - // Look for stars before and after. Is the chunk already marked up? - // note that these regex matches cannot fail - var starsBefore = /(\**$)/.exec(chunk.before)[0]; - var starsAfter = /(^\**)/.exec(chunk.after)[0]; - - var prevStars = Math.min(starsBefore.length, starsAfter.length); - - // Remove stars if we have to since the button acts as a toggle. - if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) { - chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), ""); - chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), ""); - } - else if (!chunk.selection && starsAfter) { - // It's not really clear why this code is necessary. It just moves - // some arbitrary stuff around. - chunk.after = chunk.after.replace(/^([*_]*)/, ""); - chunk.before = chunk.before.replace(/(\s?)$/, ""); - var whitespace = re.$1; - chunk.before = chunk.before + starsAfter + whitespace; - } - else { - - // In most cases, if you don't have any selected text and click the button - // you'll get a selected, marked up region with the default text inserted. - if (!chunk.selection && !starsAfter) { - chunk.selection = insertText; - } - - // Add the true markup. - var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ? - chunk.before = chunk.before + markup; - chunk.after = markup + chunk.after; - } - - return; - }; - - commandProto.stripLinkDefs = function (text, defsToAdd) { - - text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm, - function (totalMatch, id, link, newlines, title) { - defsToAdd[id] = totalMatch.replace(/\s*$/, ""); - if (newlines) { - // Strip the title and return that separately. - defsToAdd[id] = totalMatch.replace(/["(](.+?)[")]$/, ""); - return newlines + title; - } - return ""; - }); - - return text; - }; - - commandProto.addLinkDef = function (chunk, linkDef) { - - var refNumber = 0; // The current reference number - var defsToAdd = {}; // - // Start with a clean slate by removing all previous link definitions. - chunk.before = this.stripLinkDefs(chunk.before, defsToAdd); - chunk.selection = this.stripLinkDefs(chunk.selection, defsToAdd); - chunk.after = this.stripLinkDefs(chunk.after, defsToAdd); - - var defs = ""; - var regex = /(\[)((?:\[[^\]]*\]|[^\[\]])*)(\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g; - - var addDefNumber = function (def) { - refNumber++; - def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, " [" + refNumber + "]:"); - defs += "\n" + def; - }; - - // note that - // a) the recursive call to getLink cannot go infinite, because by definition - // of regex, inner is always a proper substring of wholeMatch, and - // b) more than one level of nesting is neither supported by the regex - // nor making a lot of sense (the only use case for nesting is a linked image) - var getLink = function (wholeMatch, before, inner, afterInner, id, end) { - inner = inner.replace(regex, getLink); - if (defsToAdd[id]) { - addDefNumber(defsToAdd[id]); - return before + inner + afterInner + refNumber + end; - } - return wholeMatch; - }; - - chunk.before = chunk.before.replace(regex, getLink); - - if (linkDef) { - addDefNumber(linkDef); - } - else { - chunk.selection = chunk.selection.replace(regex, getLink); - } - - var refOut = refNumber; - - chunk.after = chunk.after.replace(regex, getLink); - - if (chunk.after) { - chunk.after = chunk.after.replace(/\n*$/, ""); - } - if (!chunk.after) { - chunk.selection = chunk.selection.replace(/\n*$/, ""); - } - - chunk.after += "\n\n" + defs; - - return refOut; - }; - - // takes the line as entered into the add link/as image dialog and makes - // sure the URL and the optinal title are "nice". - function properlyEncoded(linkdef) { - return linkdef.replace(/^\s*(.*?)(?:\s+"(.+)")?\s*$/, function (wholematch, link, title) { - link = link.replace(/\?.*$/, function (querypart) { - return querypart.replace(/\+/g, " "); // in the query string, a plus and a space are identical - }); - link = decodeURIComponent(link); // unencode first, to prevent double encoding - link = encodeURI(link).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29'); - link = link.replace(/\?.*$/, function (querypart) { - return querypart.replace(/\+/g, "%2b"); // since we replaced plus with spaces in the query part, all pluses that now appear where originally encoded - }); - if (title) { - title = title.trim ? title.trim() : title.replace(/^\s*/, "").replace(/\s*$/, ""); - title = $.trim(title).replace(/"/g, "quot;").replace(/\(/g, "(").replace(/\)/g, ")").replace(//g, ">"); - } - return title ? link + ' "' + title + '"' : link; - }); - } - - commandProto.doLinkOrImage = function (chunk, postProcessing, isImage) { - - chunk.trimWhitespace(); - chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/); - var background; - - if (chunk.endTag.length > 1 && chunk.startTag.length > 0) { - - chunk.startTag = chunk.startTag.replace(/!?\[/, ""); - chunk.endTag = ""; - this.addLinkDef(chunk, null); - - } - else { - - // We're moving start and end tag back into the selection, since (as we're in the else block) we're not - // *removing* a link, but *adding* one, so whatever findTags() found is now back to being part of the - // link text. linkEnteredCallback takes care of escaping any brackets. - chunk.selection = chunk.startTag + chunk.selection + chunk.endTag; - chunk.startTag = chunk.endTag = ""; - - if (/\n\n/.test(chunk.selection)) { - this.addLinkDef(chunk, null); - return; - } - var that = this; - // The function to be executed when you enter a link and press OK or Cancel. - // Marks up the link and adds the ref. - var linkEnteredCallback = function (link) { - - if (link !== null) { - // ( $1 - // [^\\] anything that's not a backslash - // (?:\\\\)* an even number (this includes zero) of backslashes - // ) - // (?= followed by - // [[\]] an opening or closing bracket - // ) - // - // In other words, a non-escaped bracket. These have to be escaped now to make sure they - // don't count as the end of the link or similar. - // Note that the actual bracket has to be a lookahead, because (in case of to subsequent brackets), - // the bracket in one match may be the "not a backslash" character in the next match, so it - // should not be consumed by the first match. - // The "prepend a space and finally remove it" steps makes sure there is a "not a backslash" at the - // start of the string, so this also works if the selection begins with a bracket. We cannot solve - // this by anchoring with ^, because in the case that the selection starts with two brackets, this - // would mean a zero-width match at the start. Since zero-width matches advance the string position, - // the first bracket could then not act as the "not a backslash" for the second. - chunk.selection = (" " + chunk.selection).replace(/([^\\](?:\\\\)*)(?=[[\]])/g, "$1\\").substr(1); - - var linkDef = " [999]: " + properlyEncoded(link); - - var num = that.addLinkDef(chunk, linkDef); - chunk.startTag = isImage ? "![" : "["; - chunk.endTag = "][" + num + "]"; - - if (!chunk.selection) { - if (isImage) { - chunk.selection = "enter image description here"; - } - else { - chunk.selection = "enter link description here"; - } - } - } - postProcessing(); - }; - - - if (isImage) { - if (!this.hooks.insertImageDialog(linkEnteredCallback)) - ui.prompt('Insert Image', imageDialogText, imageDefaultText, linkEnteredCallback); - } - else { - ui.prompt('Insert Link', linkDialogText, linkDefaultText, linkEnteredCallback); - } - return true; - } - }; - - // When making a list, hitting shift-enter will put your cursor on the next line - // at the current indent level. - commandProto.doAutoindent = function (chunk, postProcessing) { - - var commandMgr = this, - fakeSelection = false; - - chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n"); - chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n"); - chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n"); - - // There's no selection, end the cursor wasn't at the end of the line: - // The user wants to split the current list item / code line / blockquote line - // (for the latter it doesn't really matter) in two. Temporarily select the - // (rest of the) line to achieve this. - if (!chunk.selection && !/^[ \t]*(?:\n|$)/.test(chunk.after)) { - chunk.after = chunk.after.replace(/^[^\n]*/, function (wholeMatch) { - chunk.selection = wholeMatch; - return ""; - }); - fakeSelection = true; - } - - if (/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(chunk.before)) { - if (commandMgr.doList) { - commandMgr.doList(chunk); - } - } - if (/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)) { - if (commandMgr.doBlockquote) { - commandMgr.doBlockquote(chunk); - } - } - if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) { - if (commandMgr.doCode) { - commandMgr.doCode(chunk); - } - } - - if (fakeSelection) { - chunk.after = chunk.selection + chunk.after; - chunk.selection = ""; - } - }; - - commandProto.doBlockquote = function (chunk, postProcessing) { - - chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/, - function (totalMatch, newlinesBefore, text, newlinesAfter) { - chunk.before += newlinesBefore; - chunk.after = newlinesAfter + chunk.after; - return text; - }); - - chunk.before = chunk.before.replace(/(>[ \t]*)$/, - function (totalMatch, blankLine) { - chunk.selection = blankLine + chunk.selection; - return ""; - }); - - chunk.selection = chunk.selection.replace(/^(\s|>)+$/, ""); - chunk.selection = chunk.selection || "Blockquote"; - - // The original code uses a regular expression to find out how much of the - // text *directly before* the selection already was a blockquote: - - /* - if (chunk.before) { - chunk.before = chunk.before.replace(/\n?$/, "\n"); - } - chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/, - function (totalMatch) { - chunk.startTag = totalMatch; - return ""; - }); - */ - - // This comes down to: - // Go backwards as many lines a possible, such that each line - // a) starts with ">", or - // b) is almost empty, except for whitespace, or - // c) is preceeded by an unbroken chain of non-empty lines - // leading up to a line that starts with ">" and at least one more character - // and in addition - // d) at least one line fulfills a) - // - // Since this is essentially a backwards-moving regex, it's susceptible to - // catstrophic backtracking and can cause the browser to hang; - // see e.g. http://meta.stackoverflow.com/questions/9807. - // - // Hence we replaced this by a simple state machine that just goes through the - // lines and checks for a), b), and c). - - var match = "", - leftOver = "", - line; - if (chunk.before) { - var lines = chunk.before.replace(/\n$/, "").split("\n"); - var inChain = false; - for (var i = 0; i < lines.length; i++) { - var good = false; - line = lines[i]; - inChain = inChain && line.length > 0; // c) any non-empty line continues the chain - if (/^>/.test(line)) { // a) - good = true; - if (!inChain && line.length > 1) // c) any line that starts with ">" and has at least one more character starts the chain - inChain = true; - } else if (/^[ \t]*$/.test(line)) { // b) - good = true; - } else { - good = inChain; // c) the line is not empty and does not start with ">", so it matches if and only if we're in the chain - } - if (good) { - match += line + "\n"; - } else { - leftOver += match + line; - match = "\n"; - } - } - if (!/(^|\n)>/.test(match)) { // d) - leftOver += match; - match = ""; - } - } - - chunk.startTag = match; - chunk.before = leftOver; - - // end of change - - if (chunk.after) { - chunk.after = chunk.after.replace(/^\n?/, "\n"); - } - - chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/, - function (totalMatch) { - chunk.endTag = totalMatch; - return ""; - } - ); - - var replaceBlanksInTags = function (useBracket) { - - var replacement = useBracket ? "> " : ""; - - if (chunk.startTag) { - chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/, - function (totalMatch, markdown) { - return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n"; - }); - } - if (chunk.endTag) { - chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/, - function (totalMatch, markdown) { - return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n"; - }); - } - }; - - if (/^(?![ ]{0,3}>)/m.test(chunk.selection)) { - this.wrap(chunk, SETTINGS.lineLength - 2); - chunk.selection = chunk.selection.replace(/^/gm, "> "); - replaceBlanksInTags(true); - chunk.skipLines(); - } else { - chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, ""); - this.unwrap(chunk); - replaceBlanksInTags(false); - - if (!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag) { - chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n"); - } - - if (!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag) { - chunk.endTag = chunk.endTag.replace(/^\n{0,2}/, "\n\n"); - } - } - - chunk.selection = this.hooks.postBlockquoteCreation(chunk.selection); - - if (!/\n/.test(chunk.selection)) { - chunk.selection = chunk.selection.replace(/^(> *)/, - function (wholeMatch, blanks) { - chunk.startTag += blanks; - return ""; - }); - } - }; - - commandProto.doCode = function (chunk, postProcessing) { - - var hasTextBefore = /\S[ ]*$/.test(chunk.before); - var hasTextAfter = /^[ ]*\S/.test(chunk.after); - - // Use 'four space' markdown if the selection is on its own - // line or is multiline. - if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) { - - chunk.before = chunk.before.replace(/[ ]{4}$/, - function (totalMatch) { - chunk.selection = totalMatch + chunk.selection; - return ""; - }); - - var nLinesBack = 1; - var nLinesForward = 1; - - if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) { - nLinesBack = 0; - } - if (/^\n(\t|[ ]{4,})/.test(chunk.after)) { - nLinesForward = 0; - } - - chunk.skipLines(nLinesBack, nLinesForward); - - if (!chunk.selection) { - chunk.startTag = " "; - chunk.selection = "enter code here"; - } - else { - if (/^[ ]{0,3}\S/m.test(chunk.selection)) { - if (/\n/.test(chunk.selection)) - chunk.selection = chunk.selection.replace(/^/gm, " "); - else // if it's not multiline, do not select the four added spaces; this is more consistent with the doList behavior - chunk.before += " "; - } - else { - chunk.selection = chunk.selection.replace(/^[ ]{4}/gm, ""); - } - } - } - else { - // Use backticks (`) to delimit the code block. - - chunk.trimWhitespace(); - chunk.findTags(/`/, /`/); - - if (!chunk.startTag && !chunk.endTag) { - chunk.startTag = chunk.endTag = "`"; - if (!chunk.selection) { - chunk.selection = "enter code here"; - } - } - else if (chunk.endTag && !chunk.startTag) { - chunk.before += chunk.endTag; - chunk.endTag = ""; - } - else { - chunk.startTag = chunk.endTag = ""; - } - } - }; - - commandProto.doList = function (chunk, postProcessing, isNumberedList) { - - // These are identical except at the very beginning and end. - // Should probably use the regex extension function to make this clearer. - var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/; - var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/; - - // The default bullet is a dash but others are possible. - // This has nothing to do with the particular HTML bullet, - // it's just a markdown bullet. - var bullet = "-"; - - // The number in a numbered list. - var num = 1; - - // Get the item prefix - e.g. " 1. " for a numbered list, " - " for a bulleted list. - var getItemPrefix = function () { - var prefix; - if (isNumberedList) { - prefix = " " + num + ". "; - num++; - } - else { - prefix = " " + bullet + " "; - } - return prefix; - }; - - // Fixes the prefixes of the other list items. - var getPrefixedItem = function (itemText) { - - // The numbering flag is unset when called by autoindent. - if (isNumberedList === undefined) { - isNumberedList = /^\s*\d/.test(itemText); - } - - // Renumber/bullet the list element. - itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm, - function (_) { - return getItemPrefix(); - }); - - return itemText; - }; - - chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null); - - if (chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)) { - chunk.before += chunk.startTag; - chunk.startTag = ""; - } - - if (chunk.startTag) { - - var hasDigits = /\d+[.]/.test(chunk.startTag); - chunk.startTag = ""; - chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n"); - this.unwrap(chunk); - chunk.skipLines(); - - if (hasDigits) { - // Have to renumber the bullet points if this is a numbered list. - chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem); - } - if (isNumberedList == hasDigits) { - return; - } - } - - var nLinesUp = 1; - - chunk.before = chunk.before.replace(previousItemsRegex, - function (itemText) { - if (/^\s*([*+-])/.test(itemText)) { - bullet = re.$1; - } - nLinesUp = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0; - return getPrefixedItem(itemText); - }); - - if (!chunk.selection) { - chunk.selection = "List item"; - } - - var prefix = getItemPrefix(); - - var nLinesDown = 1; - - chunk.after = chunk.after.replace(nextItemsRegex, - function (itemText) { - nLinesDown = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0; - return getPrefixedItem(itemText); - }); - - chunk.trimWhitespace(true); - chunk.skipLines(nLinesUp, nLinesDown, true); - chunk.startTag = prefix; - var spaces = prefix.replace(/./g, " "); - this.wrap(chunk, SETTINGS.lineLength - spaces.length); - chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces); - - }; - - commandProto.doHeading = function (chunk, postProcessing) { - - // Remove leading/trailing whitespace and reduce internal spaces to single spaces. - chunk.selection = chunk.selection.replace(/\s+/g, " "); - chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, ""); - - // If we clicked the button with no selected text, we just - // make a level 2 hash header around some default text. - if (!chunk.selection) { - chunk.startTag = "## "; - chunk.selection = "Heading"; - chunk.endTag = " ##"; - return; - } - - var headerLevel = 0; // The existing header level of the selected text. - - // Remove any existing hash heading markdown and save the header level. - chunk.findTags(/#+[ ]*/, /[ ]*#+/); - if (/#+/.test(chunk.startTag)) { - headerLevel = re.lastMatch.length; - } - chunk.startTag = chunk.endTag = ""; - - // Try to get the current header level by looking for - and = in the line - // below the selection. - chunk.findTags(null, /\s?(-+|=+)/); - if (/=+/.test(chunk.endTag)) { - headerLevel = 1; - } - if (/-+/.test(chunk.endTag)) { - headerLevel = 2; - } - - // Skip to the next line so we can create the header markdown. - chunk.startTag = chunk.endTag = ""; - chunk.skipLines(1, 1); - - // We make a level 2 header if there is no current header. - // If there is a header level, we substract one from the header level. - // If it's already a level 1 header, it's removed. - var headerLevelToCreate = headerLevel == 0 ? 2 : headerLevel - 1; - - if (headerLevelToCreate > 0) { - - // The button only creates level 1 and 2 underline headers. - // Why not have it iterate over hash header levels? Wouldn't that be easier and cleaner? - var headerChar = headerLevelToCreate >= 2 ? "-" : "="; - var len = chunk.selection.length; - if (len > SETTINGS.lineLength) { - len = SETTINGS.lineLength; - } - chunk.endTag = "\n"; - while (len--) { - chunk.endTag += headerChar; - } - } - }; - - commandProto.doHorizontalRule = function (chunk, postProcessing) { - chunk.startTag = "----------\n"; - chunk.selection = ""; - chunk.skipLines(2, 1, true); - } - - +// needs Markdown.Converter.js at the moment + +(function () { + + var util = {}, + position = {}, + ui = {}, + doc = window.document, + re = window.RegExp, + nav = window.navigator, + SETTINGS = { lineLength: 72 }, + + // Used to work around some browser bugs where we can't use feature testing. + uaSniffed = { + isIE: /msie/.test(nav.userAgent.toLowerCase()), + isIE_5or6: /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase()), + isOpera: /opera/.test(nav.userAgent.toLowerCase()) + }; + + + // ------------------------------------------------------------------- + // YOUR CHANGES GO HERE + // + // I've tried to localize the things you are likely to change to + // this area. + // ------------------------------------------------------------------- + + // The text that appears on the upper part of the dialog box when + // entering links. + var linkDialogText = "

    http://example.com/ \"optional title\"

    "; + var imageDialogText = "

    http://example.com/images/diagram.jpg \"optional title\"

    "; + + // The default text that appears in the dialog input box when entering + // links. + var imageDefaultText = "http://"; + var linkDefaultText = "http://"; + + var defaultHelpHoverTitle = "Markdown Editing Help"; + + // ------------------------------------------------------------------- + // END OF YOUR CHANGES + // ------------------------------------------------------------------- + + // help, if given, should have a property "handler", the click handler for the help button, + // and can have an optional property "title" for the button's tooltip (defaults to "Markdown Editing Help"). + // If help isn't given, not help button is created. + // + // The constructed editor object has the methods: + // - getConverter() returns the markdown converter object that was passed to the constructor + // - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op. + // - refreshPreview() forces the preview to be updated. This method is only available after run() was called. + Markdown.Editor = function (markdownConverter, idPostfix, help) { + + idPostfix = idPostfix || ""; + + var hooks = this.hooks = new Markdown.HookCollection(); + hooks.addNoop("onPreviewRefresh"); // called with no arguments after the preview has been refreshed + hooks.addNoop("postBlockquoteCreation"); // called with the user's selection *after* the blockquote was created; should return the actual to-be-inserted text + hooks.addFalse("insertImageDialog"); /* called with one parameter: a callback to be called with the URL of the image. If the application creates + * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen + * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used. + */ + + this.getConverter = function () { return markdownConverter; } + + var that = this, + panels; + + this.run = function () { + if (panels) + return; // already initialized + + panels = new PanelCollection(idPostfix); + var commandManager = new CommandManager(hooks); + var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); }); + var undoManager, uiManager; + + if (!/\?noundo/.test(doc.location.href)) { + undoManager = new UndoManager(function () { + previewManager.refresh(); + if (uiManager) // not available on the first call + uiManager.setUndoRedoButtonStates(); + }, panels); + this.textOperation = function (f) { + undoManager.setCommandMode(); + f(); + that.refreshPreview(); + } + } + + uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, help); + uiManager.setUndoRedoButtonStates(); + + var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); }; + + forceRefresh(); + }; + + } + + // before: contains all the text in the input box BEFORE the selection. + // after: contains all the text in the input box AFTER the selection. + function Chunks() { } + + // startRegex: a regular expression to find the start tag + // endRegex: a regular expresssion to find the end tag + Chunks.prototype.findTags = function (startRegex, endRegex) { + + var chunkObj = this; + var regex; + + if (startRegex) { + + regex = util.extendRegExp(startRegex, "", "$"); + + this.before = this.before.replace(regex, + function (match) { + chunkObj.startTag = chunkObj.startTag + match; + return ""; + }); + + regex = util.extendRegExp(startRegex, "^", ""); + + this.selection = this.selection.replace(regex, + function (match) { + chunkObj.startTag = chunkObj.startTag + match; + return ""; + }); + } + + if (endRegex) { + + regex = util.extendRegExp(endRegex, "", "$"); + + this.selection = this.selection.replace(regex, + function (match) { + chunkObj.endTag = match + chunkObj.endTag; + return ""; + }); + + regex = util.extendRegExp(endRegex, "^", ""); + + this.after = this.after.replace(regex, + function (match) { + chunkObj.endTag = match + chunkObj.endTag; + return ""; + }); + } + }; + + // If remove is false, the whitespace is transferred + // to the before/after regions. + // + // If remove is true, the whitespace disappears. + Chunks.prototype.trimWhitespace = function (remove) { + var beforeReplacer, afterReplacer, that = this; + if (remove) { + beforeReplacer = afterReplacer = ""; + } else { + beforeReplacer = function (s) { that.before += s; return ""; } + afterReplacer = function (s) { that.after = s + that.after; return ""; } + } + + this.selection = this.selection.replace(/^(\s*)/, beforeReplacer).replace(/(\s*)$/, afterReplacer); + }; + + + Chunks.prototype.skipLines = function (nLinesBefore, nLinesAfter, findExtraNewlines) { + + if (nLinesBefore === undefined) { + nLinesBefore = 1; + } + + if (nLinesAfter === undefined) { + nLinesAfter = 1; + } + + nLinesBefore++; + nLinesAfter++; + + var regexText; + var replacementText; + + // chrome bug ... documented at: http://meta.stackoverflow.com/questions/63307/blockquote-glitch-in-editor-in-chrome-6-and-7/65985#65985 + if (navigator.userAgent.match(/Chrome/)) { + "X".match(/()./); + } + + this.selection = this.selection.replace(/(^\n*)/, ""); + + this.startTag = this.startTag + re.$1; + + this.selection = this.selection.replace(/(\n*$)/, ""); + this.endTag = this.endTag + re.$1; + this.startTag = this.startTag.replace(/(^\n*)/, ""); + this.before = this.before + re.$1; + this.endTag = this.endTag.replace(/(\n*$)/, ""); + this.after = this.after + re.$1; + + if (this.before) { + + regexText = replacementText = ""; + + while (nLinesBefore--) { + regexText += "\\n?"; + replacementText += "\n"; + } + + if (findExtraNewlines) { + regexText = "\\n*"; + } + this.before = this.before.replace(new re(regexText + "$", ""), replacementText); + } + + if (this.after) { + + regexText = replacementText = ""; + + while (nLinesAfter--) { + regexText += "\\n?"; + replacementText += "\n"; + } + if (findExtraNewlines) { + regexText = "\\n*"; + } + + this.after = this.after.replace(new re(regexText, ""), replacementText); + } + }; + + // end of Chunks + + // A collection of the important regions on the page. + // Cached so we don't have to keep traversing the DOM. + // Also holds ieCachedRange and ieCachedScrollTop, where necessary; working around + // this issue: + // Internet explorer has problems with CSS sprite buttons that use HTML + // lists. When you click on the background image "button", IE will + // select the non-existent link text and discard the selection in the + // textarea. The solution to this is to cache the textarea selection + // on the button's mousedown event and set a flag. In the part of the + // code where we need to grab the selection, we check for the flag + // and, if it's set, use the cached area instead of querying the + // textarea. + // + // This ONLY affects Internet Explorer (tested on versions 6, 7 + // and 8) and ONLY on button clicks. Keyboard shortcuts work + // normally since the focus never leaves the textarea. + function PanelCollection(postfix) { + this.buttonBar = doc.getElementById("wmd-button-bar" + postfix); + this.preview = doc.getElementById("wmd-preview" + postfix); + this.input = doc.getElementById("wmd-input" + postfix); + }; + + // Returns true if the DOM element is visible, false if it's hidden. + // Checks if display is anything other than none. + util.isVisible = function (elem) { + + if (window.getComputedStyle) { + // Most browsers + return window.getComputedStyle(elem, null).getPropertyValue("display") !== "none"; + } + else if (elem.currentStyle) { + // IE + return elem.currentStyle["display"] !== "none"; + } + }; + + + // Adds a listener callback to a DOM element which is fired on a specified + // event. + util.addEvent = function (elem, event, listener) { + if (elem.attachEvent) { + // IE only. The "on" is mandatory. + elem.attachEvent("on" + event, listener); + } + else { + // Other browsers. + elem.addEventListener(event, listener, false); + } + }; + + + // Removes a listener callback from a DOM element which is fired on a specified + // event. + util.removeEvent = function (elem, event, listener) { + if (elem.detachEvent) { + // IE only. The "on" is mandatory. + elem.detachEvent("on" + event, listener); + } + else { + // Other browsers. + elem.removeEventListener(event, listener, false); + } + }; + + // Converts \r\n and \r to \n. + util.fixEolChars = function (text) { + text = text.replace(/\r\n/g, "\n"); + text = text.replace(/\r/g, "\n"); + return text; + }; + + // Extends a regular expression. Returns a new RegExp + // using pre + regex + post as the expression. + // Used in a few functions where we have a base + // expression and we want to pre- or append some + // conditions to it (e.g. adding "$" to the end). + // The flags are unchanged. + // + // regex is a RegExp, pre and post are strings. + util.extendRegExp = function (regex, pre, post) { + + if (pre === null || pre === undefined) { + pre = ""; + } + if (post === null || post === undefined) { + post = ""; + } + + var pattern = regex.toString(); + var flags; + + // Replace the flags with empty space and store them. + pattern = pattern.replace(/\/([gim]*)$/, function (wholeMatch, flagsPart) { + flags = flagsPart; + return ""; + }); + + // Remove the slash delimiters on the regular expression. + pattern = pattern.replace(/(^\/|\/$)/g, ""); + pattern = pre + pattern + post; + + return new re(pattern, flags); + } + + // UNFINISHED + // The assignment in the while loop makes jslint cranky. + // I'll change it to a better loop later. + position.getTop = function (elem, isInner) { + var result = elem.offsetTop; + if (!isInner) { + while (elem = elem.offsetParent) { + result += elem.offsetTop; + } + } + return result; + }; + + position.getHeight = function (elem) { + return elem.offsetHeight || elem.scrollHeight; + }; + + position.getWidth = function (elem) { + return elem.offsetWidth || elem.scrollWidth; + }; + + position.getPageSize = function () { + + var scrollWidth, scrollHeight; + var innerWidth, innerHeight; + + // It's not very clear which blocks work with which browsers. + if (self.innerHeight && self.scrollMaxY) { + scrollWidth = doc.body.scrollWidth; + scrollHeight = self.innerHeight + self.scrollMaxY; + } + else if (doc.body.scrollHeight > doc.body.offsetHeight) { + scrollWidth = doc.body.scrollWidth; + scrollHeight = doc.body.scrollHeight; + } + else { + scrollWidth = doc.body.offsetWidth; + scrollHeight = doc.body.offsetHeight; + } + + if (self.innerHeight) { + // Non-IE browser + innerWidth = self.innerWidth; + innerHeight = self.innerHeight; + } + else if (doc.documentElement && doc.documentElement.clientHeight) { + // Some versions of IE (IE 6 w/ a DOCTYPE declaration) + innerWidth = doc.documentElement.clientWidth; + innerHeight = doc.documentElement.clientHeight; + } + else if (doc.body) { + // Other versions of IE + innerWidth = doc.body.clientWidth; + innerHeight = doc.body.clientHeight; + } + + var maxWidth = Math.max(scrollWidth, innerWidth); + var maxHeight = Math.max(scrollHeight, innerHeight); + return [maxWidth, maxHeight, innerWidth, innerHeight]; + }; + + // Handles pushing and popping TextareaStates for undo/redo commands. + // I should rename the stack variables to list. + function UndoManager(callback, panels) { + + var undoObj = this; + var undoStack = []; // A stack of undo states + var stackPtr = 0; // The index of the current state + var mode = "none"; + var lastState; // The last state + var timer; // The setTimeout handle for cancelling the timer + var inputStateObj; + + // Set the mode for later logic steps. + var setMode = function (newMode, noSave) { + if (mode != newMode) { + mode = newMode; + if (!noSave) { + saveState(); + } + } + + if (!uaSniffed.isIE || mode != "moving") { + timer = setTimeout(refreshState, 1); + } + else { + inputStateObj = null; + } + }; + + var refreshState = function (isInitialState) { + inputStateObj = new TextareaState(panels, isInitialState); + timer = undefined; + }; + + this.setCommandMode = function () { + mode = "command"; + saveState(); + timer = setTimeout(refreshState, 0); + }; + + this.canUndo = function () { + return stackPtr > 1; + }; + + this.canRedo = function () { + if (undoStack[stackPtr + 1]) { + return true; + } + return false; + }; + + // Removes the last state and restores it. + this.undo = function () { + + if (undoObj.canUndo()) { + if (lastState) { + // What about setting state -1 to null or checking for undefined? + lastState.restore(); + lastState = null; + } + else { + undoStack[stackPtr] = new TextareaState(panels); + undoStack[--stackPtr].restore(); + + if (callback) { + callback(); + } + } + } + + mode = "none"; + panels.input.focus(); + refreshState(); + }; + + // Redo an action. + this.redo = function () { + + if (undoObj.canRedo()) { + + undoStack[++stackPtr].restore(); + + if (callback) { + callback(); + } + } + + mode = "none"; + panels.input.focus(); + refreshState(); + }; + + // Push the input area state to the stack. + var saveState = function () { + var currState = inputStateObj || new TextareaState(panels); + + if (!currState) { + return false; + } + if (mode == "moving") { + if (!lastState) { + lastState = currState; + } + return; + } + if (lastState) { + if (undoStack[stackPtr - 1].text != lastState.text) { + undoStack[stackPtr++] = lastState; + } + lastState = null; + } + undoStack[stackPtr++] = currState; + undoStack[stackPtr + 1] = null; + if (callback) { + callback(); + } + }; + + var handleCtrlYZ = function (event) { + + var handled = false; + + if (event.ctrlKey || event.metaKey) { + + // IE and Opera do not support charCode. + var keyCode = event.charCode || event.keyCode; + var keyCodeChar = String.fromCharCode(keyCode); + + switch (keyCodeChar) { + + case "y": + undoObj.redo(); + handled = true; + break; + + case "z": + if (!event.shiftKey) { + undoObj.undo(); + } + else { + undoObj.redo(); + } + handled = true; + break; + } + } + + if (handled) { + if (event.preventDefault) { + event.preventDefault(); + } + if (window.event) { + window.event.returnValue = false; + } + return; + } + }; + + // Set the mode depending on what is going on in the input area. + var handleModeChange = function (event) { + + if (!event.ctrlKey && !event.metaKey) { + + var keyCode = event.keyCode; + + if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) { + // 33 - 40: page up/dn and arrow keys + // 63232 - 63235: page up/dn and arrow keys on safari + setMode("moving"); + } + else if (keyCode == 8 || keyCode == 46 || keyCode == 127) { + // 8: backspace + // 46: delete + // 127: delete + setMode("deleting"); + } + else if (keyCode == 13) { + // 13: Enter + setMode("newlines"); + } + else if (keyCode == 27) { + // 27: escape + setMode("escape"); + } + else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) { + // 16-20 are shift, etc. + // 91: left window key + // I think this might be a little messed up since there are + // a lot of nonprinting keys above 20. + setMode("typing"); + } + } + }; + + var setEventHandlers = function () { + util.addEvent(panels.input, "keypress", function (event) { + // keyCode 89: y + // keyCode 90: z + if ((event.ctrlKey || event.metaKey) && (event.keyCode == 89 || event.keyCode == 90)) { + event.preventDefault(); + } + }); + + var handlePaste = function () { + if (uaSniffed.isIE || (inputStateObj && inputStateObj.text != panels.input.value)) { + if (timer == undefined) { + mode = "paste"; + saveState(); + refreshState(); + } + } + }; + + util.addEvent(panels.input, "keydown", handleCtrlYZ); + util.addEvent(panels.input, "keydown", handleModeChange); + util.addEvent(panels.input, "mousedown", function () { + setMode("moving"); + }); + + panels.input.onpaste = handlePaste; + panels.input.ondrop = handlePaste; + }; + + var init = function () { + setEventHandlers(); + refreshState(true); + saveState(); + }; + + init(); + } + + // end of UndoManager + + // The input textarea state/contents. + // This is used to implement undo/redo by the undo manager. + function TextareaState(panels, isInitialState) { + + // Aliases + var stateObj = this; + var inputArea = panels.input; + this.init = function () { + if (!util.isVisible(inputArea)) { + return; + } + if (!isInitialState && doc.activeElement && doc.activeElement !== inputArea) { // this happens when tabbing out of the input box + return; + } + + this.setInputAreaSelectionStartEnd(); + this.scrollTop = inputArea.scrollTop; + if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) { + this.text = inputArea.value; + } + + } + + // Sets the selected text in the input box after we've performed an + // operation. + this.setInputAreaSelection = function () { + + if (!util.isVisible(inputArea)) { + return; + } + + if (inputArea.selectionStart !== undefined && !uaSniffed.isOpera) { + + inputArea.focus(); + inputArea.selectionStart = stateObj.start; + inputArea.selectionEnd = stateObj.end; + inputArea.scrollTop = stateObj.scrollTop; + } + else if (doc.selection) { + + if (doc.activeElement && doc.activeElement !== inputArea) { + return; + } + + inputArea.focus(); + var range = inputArea.createTextRange(); + range.moveStart("character", -inputArea.value.length); + range.moveEnd("character", -inputArea.value.length); + range.moveEnd("character", stateObj.end); + range.moveStart("character", stateObj.start); + range.select(); + } + }; + + this.setInputAreaSelectionStartEnd = function () { + + if (!panels.ieCachedRange && (inputArea.selectionStart || inputArea.selectionStart === 0)) { + + stateObj.start = inputArea.selectionStart; + stateObj.end = inputArea.selectionEnd; + } + else if (doc.selection) { + + stateObj.text = util.fixEolChars(inputArea.value); + + // IE loses the selection in the textarea when buttons are + // clicked. On IE we cache the selection. Here, if something is cached, + // we take it. + var range = panels.ieCachedRange || doc.selection.createRange(); + + var fixedRange = util.fixEolChars(range.text); + var marker = "\x07"; + var markedRange = marker + fixedRange + marker; + range.text = markedRange; + var inputText = util.fixEolChars(inputArea.value); + + range.moveStart("character", -markedRange.length); + range.text = fixedRange; + + stateObj.start = inputText.indexOf(marker); + stateObj.end = inputText.lastIndexOf(marker) - marker.length; + + var len = stateObj.text.length - util.fixEolChars(inputArea.value).length; + + if (len) { + range.moveStart("character", -fixedRange.length); + while (len--) { + fixedRange += "\n"; + stateObj.end += 1; + } + range.text = fixedRange; + } + + if (panels.ieCachedRange) + stateObj.scrollTop = panels.ieCachedScrollTop; // this is set alongside with ieCachedRange + + panels.ieCachedRange = null; + + this.setInputAreaSelection(); + } + }; + + // Restore this state into the input area. + this.restore = function () { + + if (stateObj.text != undefined && stateObj.text != inputArea.value) { + inputArea.value = stateObj.text; + } + this.setInputAreaSelection(); + inputArea.scrollTop = stateObj.scrollTop; + }; + + // Gets a collection of HTML chunks from the inptut textarea. + this.getChunks = function () { + + var chunk = new Chunks(); + chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start)); + chunk.startTag = ""; + chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end)); + chunk.endTag = ""; + chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end)); + chunk.scrollTop = stateObj.scrollTop; + + return chunk; + }; + + // Sets the TextareaState properties given a chunk of markdown. + this.setChunks = function (chunk) { + + chunk.before = chunk.before + chunk.startTag; + chunk.after = chunk.endTag + chunk.after; + + this.start = chunk.before.length; + this.end = chunk.before.length + chunk.selection.length; + this.text = chunk.before + chunk.selection + chunk.after; + this.scrollTop = chunk.scrollTop; + }; + this.init(); + }; + + function PreviewManager(converter, panels, previewRefreshCallback) { + + var managerObj = this; + var timeout; + var elapsedTime; + var oldInputText; + var maxDelay = 3000; + var startType = "delayed"; // The other legal value is "manual" + + // Adds event listeners to elements + var setupEvents = function (inputElem, listener) { + + util.addEvent(inputElem, "input", listener); + inputElem.onpaste = listener; + inputElem.ondrop = listener; + + util.addEvent(inputElem, "keypress", listener); + util.addEvent(inputElem, "keydown", listener); + }; + + var getDocScrollTop = function () { + + var result = 0; + + if (window.innerHeight) { + result = window.pageYOffset; + } + else + if (doc.documentElement && doc.documentElement.scrollTop) { + result = doc.documentElement.scrollTop; + } + else + if (doc.body) { + result = doc.body.scrollTop; + } + + return result; + }; + + var makePreviewHtml = function () { + + // If there is no registered preview panel + // there is nothing to do. + if (!panels.preview) + return; + + + var text = panels.input.value; + if (text && text == oldInputText) { + return; // Input text hasn't changed. + } + else { + oldInputText = text; + } + + var prevTime = new Date().getTime(); + + text = converter.makeHtml(text); + + // Calculate the processing time of the HTML creation. + // It's used as the delay time in the event listener. + var currTime = new Date().getTime(); + elapsedTime = currTime - prevTime; + + pushPreviewHtml(text); + }; + + // setTimeout is already used. Used as an event listener. + var applyTimeout = function () { + + if (timeout) { + clearTimeout(timeout); + timeout = undefined; + } + + if (startType !== "manual") { + + var delay = 0; + + if (startType === "delayed") { + delay = elapsedTime; + } + + if (delay > maxDelay) { + delay = maxDelay; + } + timeout = setTimeout(makePreviewHtml, delay); + } + }; + + var getScaleFactor = function (panel) { + if (panel.scrollHeight <= panel.clientHeight) { + return 1; + } + return panel.scrollTop / (panel.scrollHeight - panel.clientHeight); + }; + + var setPanelScrollTops = function () { + if (panels.preview) { + panels.preview.scrollTop = (panels.preview.scrollHeight - panels.preview.clientHeight) * getScaleFactor(panels.preview); + } + }; + + this.refresh = function (requiresRefresh) { + + if (requiresRefresh) { + oldInputText = ""; + makePreviewHtml(); + } + else { + applyTimeout(); + } + }; + + this.processingTime = function () { + return elapsedTime; + }; + + var isFirstTimeFilled = true; + + // IE doesn't let you use innerHTML if the element is contained somewhere in a table + // (which is the case for inline editing) -- in that case, detach the element, set the + // value, and reattach. Yes, that *is* ridiculous. + var ieSafePreviewSet = function (text) { + var preview = panels.preview; + var parent = preview.parentNode; + var sibling = preview.nextSibling; + parent.removeChild(preview); + preview.innerHTML = text; + if (!sibling) + parent.appendChild(preview); + else + parent.insertBefore(preview, sibling); + } + + var nonSuckyBrowserPreviewSet = function (text) { + panels.preview.innerHTML = text; + } + + var previewSetter; + + var previewSet = function (text) { + if (previewSetter) + return previewSetter(text); + + try { + nonSuckyBrowserPreviewSet(text); + previewSetter = nonSuckyBrowserPreviewSet; + } catch (e) { + previewSetter = ieSafePreviewSet; + previewSetter(text); + } + }; + + var pushPreviewHtml = function (text) { + + var emptyTop = position.getTop(panels.input) - getDocScrollTop(); + + if (panels.preview) { + previewSet(text); + previewRefreshCallback(); + } + + setPanelScrollTops(); + + if (isFirstTimeFilled) { + isFirstTimeFilled = false; + return; + } + + var fullTop = position.getTop(panels.input) - getDocScrollTop(); + + if (uaSniffed.isIE) { + setTimeout(function () { + window.scrollBy(0, fullTop - emptyTop); + }, 0); + } + else { + window.scrollBy(0, fullTop - emptyTop); + } + }; + + var init = function () { + + setupEvents(panels.input, applyTimeout); + makePreviewHtml(); + + if (panels.preview) { + panels.preview.scrollTop = 0; + } + }; + + init(); + }; + + + // This simulates a modal dialog box and asks for the URL when you + // click the hyperlink or image buttons. + // + // text: The html for the input box. + // defaultInputText: The default value that appears in the input box. + // callback: The function which is executed when the prompt is dismissed, either via OK or Cancel. + // It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel + // was chosen). + ui.prompt = function (title, text, defaultInputText, callback) { + + // These variables need to be declared at this level since they are used + // in multiple functions. + var dialog; // The dialog box. + var input; // The text box where you enter the hyperlink. + + + if (defaultInputText === undefined) { + defaultInputText = ""; + } + + // Used as a keydown event handler. Esc dismisses the prompt. + // Key code 27 is ESC. + var checkEscape = function (key) { + var code = (key.charCode || key.keyCode); + if (code === 27) { + close(true); + } + }; + + // Dismisses the hyperlink input box. + // isCancel is true if we don't care about the input text. + // isCancel is false if we are going to keep the text. + var close = function (isCancel) { + util.removeEvent(doc.body, "keydown", checkEscape); + var text = input.value; + + if (isCancel) { + text = null; + } + else { + // Fixes common pasting errors. + text = text.replace(/^http:\/\/(https?|ftp):\/\//, '$1://'); + if (!/^(?:https?|ftp):\/\//.test(text)) + text = 'http://' + text; + } + + $(dialog).modal('hide'); + + callback(text); + return false; + }; + + + + // Create the text input box form/window. + var createDialog = function () { + // + + // The main dialog box. + dialog = doc.createElement("div"); + dialog.className = "modal hide fade"; + dialog.style.display = "none"; + + // The header. + var header = doc.createElement("div"); + header.className = "modal-header"; + header.innerHTML = '×

    ' + title + '

    '; + dialog.appendChild(header); + + // The body. + var body = doc.createElement("div"); + body.className = "modal-body"; + dialog.appendChild(body); + + // The footer. + var footer = doc.createElement("div"); + footer.className = "modal-footer"; + dialog.appendChild(footer); + + // The dialog text. + var question = doc.createElement("p"); + question.innerHTML = text; + question.style.padding = "5px"; + body.appendChild(question); + + // The web form container for the text box and buttons. + var form = doc.createElement("form"), + style = form.style; + form.onsubmit = function () { return close(false); }; + style.padding = "0"; + style.margin = "0"; + body.appendChild(form); + + // The input text box + input = doc.createElement("input"); + input.type = "text"; + input.value = defaultInputText; + style = input.style; + style.display = "block"; + style.width = "80%"; + style.marginLeft = style.marginRight = "auto"; + form.appendChild(input); + + // The ok button + var okButton = doc.createElement("button"); + okButton.className = "btn btn-primary"; + okButton.type = "button"; + okButton.onclick = function () { return close(false); }; + okButton.innerHTML = "OK"; + + // The cancel button + var cancelButton = doc.createElement("button"); + cancelButton.className = "btn btn-primary"; + cancelButton.type = "button"; + cancelButton.onclick = function () { return close(true); }; + cancelButton.innerHTML = "Cancel"; + + footer.appendChild(okButton); + footer.appendChild(cancelButton); + + util.addEvent(doc.body, "keydown", checkEscape); + + doc.body.appendChild(dialog); + + }; + + // Why is this in a zero-length timeout? + // Is it working around a browser bug? + setTimeout(function () { + + createDialog(); + + var defTextLen = defaultInputText.length; + if (input.selectionStart !== undefined) { + input.selectionStart = 0; + input.selectionEnd = defTextLen; + } + else if (input.createTextRange) { + var range = input.createTextRange(); + range.collapse(false); + range.moveStart("character", -defTextLen); + range.moveEnd("character", defTextLen); + range.select(); + } + + $(dialog).on('shown', function () { + input.focus(); + }) + + $(dialog).on('hidden', function () { + dialog.parentNode.removeChild(dialog); + }) + + $(dialog).modal() + + }, 0); + }; + + function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions) { + + var inputBox = panels.input, + buttons = {}; // buttons.undo, buttons.link, etc. The actual DOM elements. + + makeSpritedButtonRow(); + + var keyEvent = "keydown"; + if (uaSniffed.isOpera) { + keyEvent = "keypress"; + } + + util.addEvent(inputBox, keyEvent, function (key) { + + // Check to see if we have a button key and, if so execute the callback. + if ((key.ctrlKey || key.metaKey) && !key.altKey && !key.shiftKey) { + + var keyCode = key.charCode || key.keyCode; + var keyCodeStr = String.fromCharCode(keyCode).toLowerCase(); + + switch (keyCodeStr) { + case "b": + doClick(buttons.bold); + break; + case "i": + doClick(buttons.italic); + break; + case "l": + doClick(buttons.link); + break; + case "q": + doClick(buttons.quote); + break; + case "k": + doClick(buttons.code); + break; + case "g": + doClick(buttons.image); + break; + case "o": + doClick(buttons.olist); + break; + case "u": + doClick(buttons.ulist); + break; + case "h": + doClick(buttons.heading); + break; + case "r": + doClick(buttons.hr); + break; + case "y": + doClick(buttons.redo); + break; + case "z": + if (key.shiftKey) { + doClick(buttons.redo); + } + else { + doClick(buttons.undo); + } + break; + default: + return; + } + + + if (key.preventDefault) { + key.preventDefault(); + } + + if (window.event) { + window.event.returnValue = false; + } + } + }); + + // Auto-indent on shift-enter + util.addEvent(inputBox, "keyup", function (key) { + if (key.shiftKey && !key.ctrlKey && !key.metaKey) { + var keyCode = key.charCode || key.keyCode; + // Character 13 is Enter + if (keyCode === 13) { + var fakeButton = {}; + fakeButton.textOp = bindCommand("doAutoindent"); + doClick(fakeButton); + } + } + }); + + // special handler because IE clears the context of the textbox on ESC + if (uaSniffed.isIE) { + util.addEvent(inputBox, "keydown", function (key) { + var code = key.keyCode; + if (code === 27) { + return false; + } + }); + } + + + // Perform the button's action. + function doClick(button) { + + inputBox.focus(); + + if (button.textOp) { + + if (undoManager) { + undoManager.setCommandMode(); + } + + var state = new TextareaState(panels); + + if (!state) { + return; + } + + var chunks = state.getChunks(); + + // Some commands launch a "modal" prompt dialog. Javascript + // can't really make a modal dialog box and the WMD code + // will continue to execute while the dialog is displayed. + // This prevents the dialog pattern I'm used to and means + // I can't do something like this: + // + // var link = CreateLinkDialog(); + // makeMarkdownLink(link); + // + // Instead of this straightforward method of handling a + // dialog I have to pass any code which would execute + // after the dialog is dismissed (e.g. link creation) + // in a function parameter. + // + // Yes this is awkward and I think it sucks, but there's + // no real workaround. Only the image and link code + // create dialogs and require the function pointers. + var fixupInputArea = function () { + + inputBox.focus(); + + if (chunks) { + state.setChunks(chunks); + } + + state.restore(); + previewManager.refresh(); + }; + + var noCleanup = button.textOp(chunks, fixupInputArea); + + if (!noCleanup) { + fixupInputArea(); + } + + } + + if (button.execute) { + button.execute(undoManager); + } + }; + + function setupButton(button, isEnabled) { + + if (isEnabled) { + button.disabled = false; + + if (!button.isHelp) { + button.onclick = function () { + if (this.onmouseout) { + this.onmouseout(); + } + doClick(this); + return false; + } + } + } + else { + button.disabled = true; + } + } + + function bindCommand(method) { + if (typeof method === "string") + method = commandManager[method]; + return function () { method.apply(commandManager, arguments); } + } + + function makeSpritedButtonRow() { + + var buttonBar = panels.buttonBar; + var buttonRow = document.createElement("div"); + buttonRow.id = "wmd-button-row" + postfix; + buttonRow.className = 'btn-toolbar'; + buttonRow = buttonBar.appendChild(buttonRow); + + var makeButton = function (id, title, icon, textOp, group) { + var button = document.createElement("button"); + button.className = "btn"; + var buttonImage = document.createElement("i"); + buttonImage.className = icon; + button.id = id + postfix; + button.appendChild(buttonImage); + button.title = title; + $(button).tooltip({ placement: 'bottom' }) + if (textOp) + button.textOp = textOp; + setupButton(button, true); + if (group) { + group.appendChild(button); + } else { + buttonRow.appendChild(button); + } + return button; + }; + var makeGroup = function (num) { + var group = document.createElement("div"); + group.className = "btn-group wmd-button-group" + num; + group.id = "wmd-button-group" + num + postfix; + buttonRow.appendChild(group); + return group + } + + group1 = makeGroup(1); + buttons.bold = makeButton("wmd-bold-button", "Bold - Ctrl+B", "icon-drop", bindCommand("doBold"), group1); + buttons.italic = makeButton("wmd-italic-button", "Italic - Ctrl+I", "icon-font", bindCommand("doItalic"), group1); + + group2 = makeGroup(2); + buttons.link = makeButton("wmd-link-button", "Link - Ctrl+L", "icon-link", bindCommand(function (chunk, postProcessing) { + return this.doLinkOrImage(chunk, postProcessing, false); + }), group2); + buttons.quote = makeButton("wmd-quote-button", "Blockquote - Ctrl+Q", "icon-quote", bindCommand("doBlockquote"), group2); + buttons.code = makeButton("wmd-code-button", "Code Sample - Ctrl+K", "icon-code", bindCommand("doCode"), group2); + buttons.image = makeButton("wmd-image-button", "Image - Ctrl+G", "icon-picture", bindCommand(function (chunk, postProcessing) { + return this.doLinkOrImage(chunk, postProcessing, true); + }), group2); + + group3 = makeGroup(3); + buttons.olist = makeButton("wmd-olist-button", "Numbered List - Ctrl+O", "icon-ordered-list", bindCommand(function (chunk, postProcessing) { + this.doList(chunk, postProcessing, true); + }), group3); + buttons.ulist = makeButton("wmd-ulist-button", "Bulleted List - Ctrl+U", "icon-bulleted-list", bindCommand(function (chunk, postProcessing) { + this.doList(chunk, postProcessing, false); + }), group3); + buttons.heading = makeButton("wmd-heading-button", "Heading - Ctrl+H", "icon-shift", bindCommand("doHeading"), group3); + buttons.hr = makeButton("wmd-hr-button", "Horizontal Rule - Ctrl+R", "icon-remove", bindCommand("doHorizontalRule"), group3); + + group4 = makeGroup(4); + buttons.undo = makeButton("wmd-undo-button", "Undo - Ctrl+Z", "icon-undo", null, group4); + buttons.undo.execute = function (manager) { if (manager) manager.undo(); }; + + var redoTitle = /win/.test(nav.platform.toLowerCase()) ? + "Redo - Ctrl+Y" : + "Redo - Ctrl+Shift+Z"; // mac and other non-Windows platforms + + buttons.redo = makeButton("wmd-redo-button", redoTitle, "icon-share-alt", null, group4); + buttons.redo.execute = function (manager) { if (manager) manager.redo(); }; + + if (helpOptions) { + group5 = makeGroup(5); + group5.className = group5.className + " pull-right"; + var helpButton = document.createElement("button"); + var helpButtonImage = document.createElement("i"); + helpButtonImage.className = "icon-question-sign"; + helpButton.appendChild(helpButtonImage); + helpButton.className = "btn"; + helpButton.id = "wmd-help-button" + postfix; + helpButton.isHelp = true; + helpButton.title = helpOptions.title || defaultHelpHoverTitle; + $(helpButton).tooltip({ placement: 'bottom' }) + helpButton.onclick = helpOptions.handler; + + setupButton(helpButton, true); + group5.appendChild(helpButton); + buttons.help = helpButton; + } + + setUndoRedoButtonStates(); + } + + function setUndoRedoButtonStates() { + if (undoManager) { + setupButton(buttons.undo, undoManager.canUndo()); + setupButton(buttons.redo, undoManager.canRedo()); + } + }; + + this.setUndoRedoButtonStates = setUndoRedoButtonStates; + + } + + function CommandManager(pluginHooks) { + this.hooks = pluginHooks; + } + + var commandProto = CommandManager.prototype; + + // The markdown symbols - 4 spaces = code, > = blockquote, etc. + commandProto.prefixes = "(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)"; + + // Remove markdown symbols from the chunk selection. + commandProto.unwrap = function (chunk) { + var txt = new re("([^\\n])\\n(?!(\\n|" + this.prefixes + "))", "g"); + chunk.selection = chunk.selection.replace(txt, "$1 $2"); + }; + + commandProto.wrap = function (chunk, len) { + this.unwrap(chunk); + var regex = new re("(.{1," + len + "})( +|$\\n?)", "gm"), + that = this; + + chunk.selection = chunk.selection.replace(regex, function (line, marked) { + if (new re("^" + that.prefixes, "").test(line)) { + return line; + } + return marked + "\n"; + }); + + chunk.selection = chunk.selection.replace(/\s+$/, ""); + }; + + commandProto.doBold = function (chunk, postProcessing) { + return this.doBorI(chunk, postProcessing, 2, "strong text"); + }; + + commandProto.doItalic = function (chunk, postProcessing) { + return this.doBorI(chunk, postProcessing, 1, "emphasized text"); + }; + + // chunk: The selected region that will be enclosed with */** + // nStars: 1 for italics, 2 for bold + // insertText: If you just click the button without highlighting text, this gets inserted + commandProto.doBorI = function (chunk, postProcessing, nStars, insertText) { + + // Get rid of whitespace and fixup newlines. + chunk.trimWhitespace(); + chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n"); + + // Look for stars before and after. Is the chunk already marked up? + // note that these regex matches cannot fail + var starsBefore = /(\**$)/.exec(chunk.before)[0]; + var starsAfter = /(^\**)/.exec(chunk.after)[0]; + + var prevStars = Math.min(starsBefore.length, starsAfter.length); + + // Remove stars if we have to since the button acts as a toggle. + if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) { + chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), ""); + chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), ""); + } + else if (!chunk.selection && starsAfter) { + // It's not really clear why this code is necessary. It just moves + // some arbitrary stuff around. + chunk.after = chunk.after.replace(/^([*_]*)/, ""); + chunk.before = chunk.before.replace(/(\s?)$/, ""); + var whitespace = re.$1; + chunk.before = chunk.before + starsAfter + whitespace; + } + else { + + // In most cases, if you don't have any selected text and click the button + // you'll get a selected, marked up region with the default text inserted. + if (!chunk.selection && !starsAfter) { + chunk.selection = insertText; + } + + // Add the true markup. + var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ? + chunk.before = chunk.before + markup; + chunk.after = markup + chunk.after; + } + + return; + }; + + commandProto.stripLinkDefs = function (text, defsToAdd) { + + text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm, + function (totalMatch, id, link, newlines, title) { + defsToAdd[id] = totalMatch.replace(/\s*$/, ""); + if (newlines) { + // Strip the title and return that separately. + defsToAdd[id] = totalMatch.replace(/["(](.+?)[")]$/, ""); + return newlines + title; + } + return ""; + }); + + return text; + }; + + commandProto.addLinkDef = function (chunk, linkDef) { + + var refNumber = 0; // The current reference number + var defsToAdd = {}; // + // Start with a clean slate by removing all previous link definitions. + chunk.before = this.stripLinkDefs(chunk.before, defsToAdd); + chunk.selection = this.stripLinkDefs(chunk.selection, defsToAdd); + chunk.after = this.stripLinkDefs(chunk.after, defsToAdd); + + var defs = ""; + var regex = /(\[)((?:\[[^\]]*\]|[^\[\]])*)(\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g; + + var addDefNumber = function (def) { + refNumber++; + def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, " [" + refNumber + "]:"); + defs += "\n" + def; + }; + + // note that + // a) the recursive call to getLink cannot go infinite, because by definition + // of regex, inner is always a proper substring of wholeMatch, and + // b) more than one level of nesting is neither supported by the regex + // nor making a lot of sense (the only use case for nesting is a linked image) + var getLink = function (wholeMatch, before, inner, afterInner, id, end) { + inner = inner.replace(regex, getLink); + if (defsToAdd[id]) { + addDefNumber(defsToAdd[id]); + return before + inner + afterInner + refNumber + end; + } + return wholeMatch; + }; + + chunk.before = chunk.before.replace(regex, getLink); + + if (linkDef) { + addDefNumber(linkDef); + } + else { + chunk.selection = chunk.selection.replace(regex, getLink); + } + + var refOut = refNumber; + + chunk.after = chunk.after.replace(regex, getLink); + + if (chunk.after) { + chunk.after = chunk.after.replace(/\n*$/, ""); + } + if (!chunk.after) { + chunk.selection = chunk.selection.replace(/\n*$/, ""); + } + + chunk.after += "\n\n" + defs; + + return refOut; + }; + + // takes the line as entered into the add link/as image dialog and makes + // sure the URL and the optinal title are "nice". + function properlyEncoded(linkdef) { + return linkdef.replace(/^\s*(.*?)(?:\s+"(.+)")?\s*$/, function (wholematch, link, title) { + link = link.replace(/\?.*$/, function (querypart) { + return querypart.replace(/\+/g, " "); // in the query string, a plus and a space are identical + }); + link = decodeURIComponent(link); // unencode first, to prevent double encoding + link = encodeURI(link).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29'); + link = link.replace(/\?.*$/, function (querypart) { + return querypart.replace(/\+/g, "%2b"); // since we replaced plus with spaces in the query part, all pluses that now appear where originally encoded + }); + if (title) { + title = title.trim ? title.trim() : title.replace(/^\s*/, "").replace(/\s*$/, ""); + title = $.trim(title).replace(/"/g, "quot;").replace(/\(/g, "(").replace(/\)/g, ")").replace(//g, ">"); + } + return title ? link + ' "' + title + '"' : link; + }); + } + + commandProto.doLinkOrImage = function (chunk, postProcessing, isImage) { + + chunk.trimWhitespace(); + chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/); + var background; + + if (chunk.endTag.length > 1 && chunk.startTag.length > 0) { + + chunk.startTag = chunk.startTag.replace(/!?\[/, ""); + chunk.endTag = ""; + this.addLinkDef(chunk, null); + + } + else { + + // We're moving start and end tag back into the selection, since (as we're in the else block) we're not + // *removing* a link, but *adding* one, so whatever findTags() found is now back to being part of the + // link text. linkEnteredCallback takes care of escaping any brackets. + chunk.selection = chunk.startTag + chunk.selection + chunk.endTag; + chunk.startTag = chunk.endTag = ""; + + if (/\n\n/.test(chunk.selection)) { + this.addLinkDef(chunk, null); + return; + } + var that = this; + // The function to be executed when you enter a link and press OK or Cancel. + // Marks up the link and adds the ref. + var linkEnteredCallback = function (link) { + + if (link !== null) { + // ( $1 + // [^\\] anything that's not a backslash + // (?:\\\\)* an even number (this includes zero) of backslashes + // ) + // (?= followed by + // [[\]] an opening or closing bracket + // ) + // + // In other words, a non-escaped bracket. These have to be escaped now to make sure they + // don't count as the end of the link or similar. + // Note that the actual bracket has to be a lookahead, because (in case of to subsequent brackets), + // the bracket in one match may be the "not a backslash" character in the next match, so it + // should not be consumed by the first match. + // The "prepend a space and finally remove it" steps makes sure there is a "not a backslash" at the + // start of the string, so this also works if the selection begins with a bracket. We cannot solve + // this by anchoring with ^, because in the case that the selection starts with two brackets, this + // would mean a zero-width match at the start. Since zero-width matches advance the string position, + // the first bracket could then not act as the "not a backslash" for the second. + chunk.selection = (" " + chunk.selection).replace(/([^\\](?:\\\\)*)(?=[[\]])/g, "$1\\").substr(1); + + var linkDef = " [999]: " + properlyEncoded(link); + + var num = that.addLinkDef(chunk, linkDef); + chunk.startTag = isImage ? "![" : "["; + chunk.endTag = "][" + num + "]"; + + if (!chunk.selection) { + if (isImage) { + chunk.selection = "enter image description here"; + } + else { + chunk.selection = "enter link description here"; + } + } + } + postProcessing(); + }; + + + if (isImage) { + if (!this.hooks.insertImageDialog(linkEnteredCallback)) + ui.prompt('Insert Image', imageDialogText, imageDefaultText, linkEnteredCallback); + } + else { + ui.prompt('Insert Link', linkDialogText, linkDefaultText, linkEnteredCallback); + } + return true; + } + }; + + // When making a list, hitting shift-enter will put your cursor on the next line + // at the current indent level. + commandProto.doAutoindent = function (chunk, postProcessing) { + + var commandMgr = this, + fakeSelection = false; + + chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n"); + chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n"); + chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n"); + + // There's no selection, end the cursor wasn't at the end of the line: + // The user wants to split the current list item / code line / blockquote line + // (for the latter it doesn't really matter) in two. Temporarily select the + // (rest of the) line to achieve this. + if (!chunk.selection && !/^[ \t]*(?:\n|$)/.test(chunk.after)) { + chunk.after = chunk.after.replace(/^[^\n]*/, function (wholeMatch) { + chunk.selection = wholeMatch; + return ""; + }); + fakeSelection = true; + } + + if (/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(chunk.before)) { + if (commandMgr.doList) { + commandMgr.doList(chunk); + } + } + if (/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)) { + if (commandMgr.doBlockquote) { + commandMgr.doBlockquote(chunk); + } + } + if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) { + if (commandMgr.doCode) { + commandMgr.doCode(chunk); + } + } + + if (fakeSelection) { + chunk.after = chunk.selection + chunk.after; + chunk.selection = ""; + } + }; + + commandProto.doBlockquote = function (chunk, postProcessing) { + + chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/, + function (totalMatch, newlinesBefore, text, newlinesAfter) { + chunk.before += newlinesBefore; + chunk.after = newlinesAfter + chunk.after; + return text; + }); + + chunk.before = chunk.before.replace(/(>[ \t]*)$/, + function (totalMatch, blankLine) { + chunk.selection = blankLine + chunk.selection; + return ""; + }); + + chunk.selection = chunk.selection.replace(/^(\s|>)+$/, ""); + chunk.selection = chunk.selection || "Blockquote"; + + // The original code uses a regular expression to find out how much of the + // text *directly before* the selection already was a blockquote: + + /* + if (chunk.before) { + chunk.before = chunk.before.replace(/\n?$/, "\n"); + } + chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/, + function (totalMatch) { + chunk.startTag = totalMatch; + return ""; + }); + */ + + // This comes down to: + // Go backwards as many lines a possible, such that each line + // a) starts with ">", or + // b) is almost empty, except for whitespace, or + // c) is preceeded by an unbroken chain of non-empty lines + // leading up to a line that starts with ">" and at least one more character + // and in addition + // d) at least one line fulfills a) + // + // Since this is essentially a backwards-moving regex, it's susceptible to + // catstrophic backtracking and can cause the browser to hang; + // see e.g. http://meta.stackoverflow.com/questions/9807. + // + // Hence we replaced this by a simple state machine that just goes through the + // lines and checks for a), b), and c). + + var match = "", + leftOver = "", + line; + if (chunk.before) { + var lines = chunk.before.replace(/\n$/, "").split("\n"); + var inChain = false; + for (var i = 0; i < lines.length; i++) { + var good = false; + line = lines[i]; + inChain = inChain && line.length > 0; // c) any non-empty line continues the chain + if (/^>/.test(line)) { // a) + good = true; + if (!inChain && line.length > 1) // c) any line that starts with ">" and has at least one more character starts the chain + inChain = true; + } else if (/^[ \t]*$/.test(line)) { // b) + good = true; + } else { + good = inChain; // c) the line is not empty and does not start with ">", so it matches if and only if we're in the chain + } + if (good) { + match += line + "\n"; + } else { + leftOver += match + line; + match = "\n"; + } + } + if (!/(^|\n)>/.test(match)) { // d) + leftOver += match; + match = ""; + } + } + + chunk.startTag = match; + chunk.before = leftOver; + + // end of change + + if (chunk.after) { + chunk.after = chunk.after.replace(/^\n?/, "\n"); + } + + chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/, + function (totalMatch) { + chunk.endTag = totalMatch; + return ""; + } + ); + + var replaceBlanksInTags = function (useBracket) { + + var replacement = useBracket ? "> " : ""; + + if (chunk.startTag) { + chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/, + function (totalMatch, markdown) { + return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n"; + }); + } + if (chunk.endTag) { + chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/, + function (totalMatch, markdown) { + return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n"; + }); + } + }; + + if (/^(?![ ]{0,3}>)/m.test(chunk.selection)) { + this.wrap(chunk, SETTINGS.lineLength - 2); + chunk.selection = chunk.selection.replace(/^/gm, "> "); + replaceBlanksInTags(true); + chunk.skipLines(); + } else { + chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, ""); + this.unwrap(chunk); + replaceBlanksInTags(false); + + if (!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag) { + chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n"); + } + + if (!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag) { + chunk.endTag = chunk.endTag.replace(/^\n{0,2}/, "\n\n"); + } + } + + chunk.selection = this.hooks.postBlockquoteCreation(chunk.selection); + + if (!/\n/.test(chunk.selection)) { + chunk.selection = chunk.selection.replace(/^(> *)/, + function (wholeMatch, blanks) { + chunk.startTag += blanks; + return ""; + }); + } + }; + + commandProto.doCode = function (chunk, postProcessing) { + + var hasTextBefore = /\S[ ]*$/.test(chunk.before); + var hasTextAfter = /^[ ]*\S/.test(chunk.after); + + // Use 'four space' markdown if the selection is on its own + // line or is multiline. + if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) { + + chunk.before = chunk.before.replace(/[ ]{4}$/, + function (totalMatch) { + chunk.selection = totalMatch + chunk.selection; + return ""; + }); + + var nLinesBack = 1; + var nLinesForward = 1; + + if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) { + nLinesBack = 0; + } + if (/^\n(\t|[ ]{4,})/.test(chunk.after)) { + nLinesForward = 0; + } + + chunk.skipLines(nLinesBack, nLinesForward); + + if (!chunk.selection) { + chunk.startTag = " "; + chunk.selection = "enter code here"; + } + else { + if (/^[ ]{0,3}\S/m.test(chunk.selection)) { + if (/\n/.test(chunk.selection)) + chunk.selection = chunk.selection.replace(/^/gm, " "); + else // if it's not multiline, do not select the four added spaces; this is more consistent with the doList behavior + chunk.before += " "; + } + else { + chunk.selection = chunk.selection.replace(/^[ ]{4}/gm, ""); + } + } + } + else { + // Use backticks (`) to delimit the code block. + + chunk.trimWhitespace(); + chunk.findTags(/`/, /`/); + + if (!chunk.startTag && !chunk.endTag) { + chunk.startTag = chunk.endTag = "`"; + if (!chunk.selection) { + chunk.selection = "enter code here"; + } + } + else if (chunk.endTag && !chunk.startTag) { + chunk.before += chunk.endTag; + chunk.endTag = ""; + } + else { + chunk.startTag = chunk.endTag = ""; + } + } + }; + + commandProto.doList = function (chunk, postProcessing, isNumberedList) { + + // These are identical except at the very beginning and end. + // Should probably use the regex extension function to make this clearer. + var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/; + var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/; + + // The default bullet is a dash but others are possible. + // This has nothing to do with the particular HTML bullet, + // it's just a markdown bullet. + var bullet = "-"; + + // The number in a numbered list. + var num = 1; + + // Get the item prefix - e.g. " 1. " for a numbered list, " - " for a bulleted list. + var getItemPrefix = function () { + var prefix; + if (isNumberedList) { + prefix = " " + num + ". "; + num++; + } + else { + prefix = " " + bullet + " "; + } + return prefix; + }; + + // Fixes the prefixes of the other list items. + var getPrefixedItem = function (itemText) { + + // The numbering flag is unset when called by autoindent. + if (isNumberedList === undefined) { + isNumberedList = /^\s*\d/.test(itemText); + } + + // Renumber/bullet the list element. + itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm, + function (_) { + return getItemPrefix(); + }); + + return itemText; + }; + + chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null); + + if (chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)) { + chunk.before += chunk.startTag; + chunk.startTag = ""; + } + + if (chunk.startTag) { + + var hasDigits = /\d+[.]/.test(chunk.startTag); + chunk.startTag = ""; + chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n"); + this.unwrap(chunk); + chunk.skipLines(); + + if (hasDigits) { + // Have to renumber the bullet points if this is a numbered list. + chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem); + } + if (isNumberedList == hasDigits) { + return; + } + } + + var nLinesUp = 1; + + chunk.before = chunk.before.replace(previousItemsRegex, + function (itemText) { + if (/^\s*([*+-])/.test(itemText)) { + bullet = re.$1; + } + nLinesUp = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0; + return getPrefixedItem(itemText); + }); + + if (!chunk.selection) { + chunk.selection = "List item"; + } + + var prefix = getItemPrefix(); + + var nLinesDown = 1; + + chunk.after = chunk.after.replace(nextItemsRegex, + function (itemText) { + nLinesDown = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0; + return getPrefixedItem(itemText); + }); + + chunk.trimWhitespace(true); + chunk.skipLines(nLinesUp, nLinesDown, true); + chunk.startTag = prefix; + var spaces = prefix.replace(/./g, " "); + this.wrap(chunk, SETTINGS.lineLength - spaces.length); + chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces); + + }; + + commandProto.doHeading = function (chunk, postProcessing) { + + // Remove leading/trailing whitespace and reduce internal spaces to single spaces. + chunk.selection = chunk.selection.replace(/\s+/g, " "); + chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, ""); + + // If we clicked the button with no selected text, we just + // make a level 2 hash header around some default text. + if (!chunk.selection) { + chunk.startTag = "## "; + chunk.selection = "Heading"; + chunk.endTag = " ##"; + return; + } + + var headerLevel = 0; // The existing header level of the selected text. + + // Remove any existing hash heading markdown and save the header level. + chunk.findTags(/#+[ ]*/, /[ ]*#+/); + if (/#+/.test(chunk.startTag)) { + headerLevel = re.lastMatch.length; + } + chunk.startTag = chunk.endTag = ""; + + // Try to get the current header level by looking for - and = in the line + // below the selection. + chunk.findTags(null, /\s?(-+|=+)/); + if (/=+/.test(chunk.endTag)) { + headerLevel = 1; + } + if (/-+/.test(chunk.endTag)) { + headerLevel = 2; + } + + // Skip to the next line so we can create the header markdown. + chunk.startTag = chunk.endTag = ""; + chunk.skipLines(1, 1); + + // We make a level 2 header if there is no current header. + // If there is a header level, we substract one from the header level. + // If it's already a level 1 header, it's removed. + var headerLevelToCreate = headerLevel == 0 ? 2 : headerLevel - 1; + + if (headerLevelToCreate > 0) { + + // The button only creates level 1 and 2 underline headers. + // Why not have it iterate over hash header levels? Wouldn't that be easier and cleaner? + var headerChar = headerLevelToCreate >= 2 ? "-" : "="; + var len = chunk.selection.length; + if (len > SETTINGS.lineLength) { + len = SETTINGS.lineLength; + } + chunk.endTag = "\n"; + while (len--) { + chunk.endTag += headerChar; + } + } + }; + + commandProto.doHorizontalRule = function (chunk, postProcessing) { + chunk.startTag = "----------\n"; + chunk.selection = ""; + chunk.skipLines(2, 1, true); + } + + })(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.sanitizer.js b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.sanitizer.js index 72aaac70ef..6076514962 100644 --- a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.sanitizer.js +++ b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.sanitizer.js @@ -1,111 +1,111 @@ -(function () { - var output, Converter; - if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module - output = exports; - Converter = require("./Markdown.Converter").Converter; - } else { - output = window.Markdown; - Converter = output.Converter; - } - - output.getSanitizingConverter = function () { - var converter = new Converter(); - converter.hooks.chain("postConversion", sanitizeHtml); - converter.hooks.chain("postConversion", balanceTags); - return converter; - } - - function sanitizeHtml(html) { - return html.replace(/<[^>]*>?/gi, sanitizeTag); - } - - // (tags that can be opened/closed) | (tags that stand alone) - var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i; - // | - var a_white = /^(]+")?\s?>|<\/a>)$/i; - - // ]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i; - - //
    |
    for twitter bootstrap - var pre_white = /^(|<\/pre>)$/i; - - function sanitizeTag(tag) { - if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white) || tag.match(pre_white)) - return tag; - else - return ""; - } - - /// - /// attempt to balance HTML tags in the html string - /// by removing any unmatched opening or closing tags - /// IMPORTANT: we *assume* HTML has *already* been - /// sanitized and is safe/sane before balancing! - /// - /// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593 - /// - function balanceTags(html) { - - if (html == "") - return ""; - - var re = /<\/?\w+[^>]*(\s|$|>)/g; - // convert everything to lower case; this makes - // our case insensitive comparisons easier - var tags = html.toLowerCase().match(re); - - // no HTML tags present? nothing to do; exit now - var tagcount = (tags || []).length; - if (tagcount == 0) - return html; - - var tagname, tag; - var ignoredtags = "



  • "; - var match; - var tagpaired = []; - var tagremove = []; - var needsRemoval = false; - - // loop through matched tags in forward order - for (var ctag = 0; ctag < tagcount; ctag++) { - tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1"); - // skip any already paired tags - // and skip tags in our ignore list; assume they're self-closed - if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1) - continue; - - tag = tags[ctag]; - match = -1; - - if (!/^<\//.test(tag)) { - // this is an opening tag - // search forwards (next tags), look for closing tags - for (var ntag = ctag + 1; ntag < tagcount; ntag++) { - if (!tagpaired[ntag] && tags[ntag] == "") { - match = ntag; - break; - } - } - } - - if (match == -1) - needsRemoval = tagremove[ctag] = true; // mark for removal - else - tagpaired[match] = true; // mark paired - } - - if (!needsRemoval) - return html; - - // delete all orphaned tags from the string - - var ctag = 0; - html = html.replace(re, function (match) { - var res = tagremove[ctag] ? "" : match; - ctag++; - return res; - }); - return html; - } -})(); +(function () { + var output, Converter; + if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module + output = exports; + Converter = require("./Markdown.Converter").Converter; + } else { + output = window.Markdown; + Converter = output.Converter; + } + + output.getSanitizingConverter = function () { + var converter = new Converter(); + converter.hooks.chain("postConversion", sanitizeHtml); + converter.hooks.chain("postConversion", balanceTags); + return converter; + } + + function sanitizeHtml(html) { + return html.replace(/<[^>]*>?/gi, sanitizeTag); + } + + // (tags that can be opened/closed) | (tags that stand alone) + var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i; + // | + var a_white = /^(]+")?\s?>|<\/a>)$/i; + + // ]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i; + + //
    |
    for twitter bootstrap + var pre_white = /^(|<\/pre>)$/i; + + function sanitizeTag(tag) { + if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white) || tag.match(pre_white)) + return tag; + else + return ""; + } + + /// + /// attempt to balance HTML tags in the html string + /// by removing any unmatched opening or closing tags + /// IMPORTANT: we *assume* HTML has *already* been + /// sanitized and is safe/sane before balancing! + /// + /// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593 + /// + function balanceTags(html) { + + if (html == "") + return ""; + + var re = /<\/?\w+[^>]*(\s|$|>)/g; + // convert everything to lower case; this makes + // our case insensitive comparisons easier + var tags = html.toLowerCase().match(re); + + // no HTML tags present? nothing to do; exit now + var tagcount = (tags || []).length; + if (tagcount == 0) + return html; + + var tagname, tag; + var ignoredtags = "



  • "; + var match; + var tagpaired = []; + var tagremove = []; + var needsRemoval = false; + + // loop through matched tags in forward order + for (var ctag = 0; ctag < tagcount; ctag++) { + tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1"); + // skip any already paired tags + // and skip tags in our ignore list; assume they're self-closed + if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1) + continue; + + tag = tags[ctag]; + match = -1; + + if (!/^<\//.test(tag)) { + // this is an opening tag + // search forwards (next tags), look for closing tags + for (var ntag = ctag + 1; ntag < tagcount; ntag++) { + if (!tagpaired[ntag] && tags[ntag] == "") { + match = ntag; + break; + } + } + } + + if (match == -1) + needsRemoval = tagremove[ctag] = true; // mark for removal + else + tagpaired[match] = true; // mark paired + } + + if (!needsRemoval) + return html; + + // delete all orphaned tags from the string + + var ctag = 0; + html = html.replace(re, function (match) { + var res = tagremove[ctag] ? "" : match; + ctag++; + return res; + }); + return html; + } +})(); diff --git a/src/Umbraco.Web.UI.Client/lib/markdown/red.css b/src/Umbraco.Web.UI.Client/lib/markdown/red.css index 0a3c3dc792..b317f56b86 100644 --- a/src/Umbraco.Web.UI.Client/lib/markdown/red.css +++ b/src/Umbraco.Web.UI.Client/lib/markdown/red.css @@ -1,6 +1,6 @@ -.icon-rs-custom{ - background: red; - display: inline-block; - height: 32px; - width: 32px; +.icon-rs-custom{ + background: red; + display: inline-block; + height: 32px; + width: 32px; }; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/content.min.css b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/content.min.css index f64b70e6b1..cf0fac0565 100755 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/content.min.css +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/content.min.css @@ -1,99 +1,99 @@ -body.mce-content-body { - background-color: #fff; - font-family: Verdana,Arial,Helvetica,sans-serif; - font-size: 14px; - line-height: 1.5em; - scrollbar-3dlight-color: #f0f0ee; - scrollbar-arrow-color: #676662; - scrollbar-base-color: #f0f0ee; - scrollbar-darkshadow-color: #ddd; - scrollbar-face-color: #e0e0dd; - scrollbar-highlight-color: #f0f0ee; - scrollbar-shadow-color: #f0f0ee; - scrollbar-track-color: #f5f5f5; -} - -td, th { - font-family: Verdana,Arial,Helvetica,sans-serif; - font-size: 11px; -} - -.mce-object { - border: 1px dotted #3a3a3a; - background: #d5d5d5 url(img/object.gif) no-repeat center; -} - -.mce-pagebreak { - cursor: default; - display: block; - border: 0; - width: 100%; - height: 5px; - border: 1px dashed #666; - margin-top: 15px; -} - -.mce-item-anchor { - cursor: default; - display: inline-block; - -webkit-user-select: all; - -webkit-user-modify: read-only; - -moz-user-select: all; - -moz-user-modify: read-only; - width: 9px!important; - height: 9px!important; - border: 1px dotted #3a3a3a; - background: #d5d5d5 url(img/anchor.gif) no-repeat center; -} - -.mce-nbsp { - background: #AAA; -} - -hr { - cursor: default; -} - -.mce-match-marker { - background: green; - color: #fff; -} - -.mce-spellchecker-word { - background: url(img/wline.gif) repeat-x bottom left; - cursor: default; -} - -.mce-item-table, .mce-item-table td, .mce-item-table th, .mce-item-table caption { - border: 1px dashed #BBB; -} - -td.mce-item-selected, th.mce-item-selected { - background-color: #39f!important; -} - -.mce-edit-focus { - outline: 1px dotted #333; -} - -/* TINYMCE Macro styles*/ -.mce-content-body .umb-macro-holder -{ - border: 3px dotted orange; - padding: 7px; - display:block; - margin:3px; -} - -/* loader for macro loading in tinymce*/ - .mce-content-body .umb-macro-holder.loading { - background: url(img/loader.gif) right no-repeat; - -moz-background-size: 18px; - -o-background-size: 18px; - -webkit-background-size: 18px; - background-size: 18px; - background-position-x: 99%; - } - -/* TINYMCE IMAGE RESIZING LIMITS */ +body.mce-content-body { + background-color: #fff; + font-family: Verdana,Arial,Helvetica,sans-serif; + font-size: 14px; + line-height: 1.5em; + scrollbar-3dlight-color: #f0f0ee; + scrollbar-arrow-color: #676662; + scrollbar-base-color: #f0f0ee; + scrollbar-darkshadow-color: #ddd; + scrollbar-face-color: #e0e0dd; + scrollbar-highlight-color: #f0f0ee; + scrollbar-shadow-color: #f0f0ee; + scrollbar-track-color: #f5f5f5; +} + +td, th { + font-family: Verdana,Arial,Helvetica,sans-serif; + font-size: 11px; +} + +.mce-object { + border: 1px dotted #3a3a3a; + background: #d5d5d5 url(img/object.gif) no-repeat center; +} + +.mce-pagebreak { + cursor: default; + display: block; + border: 0; + width: 100%; + height: 5px; + border: 1px dashed #666; + margin-top: 15px; +} + +.mce-item-anchor { + cursor: default; + display: inline-block; + -webkit-user-select: all; + -webkit-user-modify: read-only; + -moz-user-select: all; + -moz-user-modify: read-only; + width: 9px!important; + height: 9px!important; + border: 1px dotted #3a3a3a; + background: #d5d5d5 url(img/anchor.gif) no-repeat center; +} + +.mce-nbsp { + background: #AAA; +} + +hr { + cursor: default; +} + +.mce-match-marker { + background: green; + color: #fff; +} + +.mce-spellchecker-word { + background: url(img/wline.gif) repeat-x bottom left; + cursor: default; +} + +.mce-item-table, .mce-item-table td, .mce-item-table th, .mce-item-table caption { + border: 1px dashed #BBB; +} + +td.mce-item-selected, th.mce-item-selected { + background-color: #39f!important; +} + +.mce-edit-focus { + outline: 1px dotted #333; +} + +/* TINYMCE Macro styles*/ +.mce-content-body .umb-macro-holder +{ + border: 3px dotted orange; + padding: 7px; + display:block; + margin:3px; +} + +/* loader for macro loading in tinymce*/ + .mce-content-body .umb-macro-holder.loading { + background: url(img/loader.gif) right no-repeat; + -moz-background-size: 18px; + -o-background-size: 18px; + -webkit-background-size: 18px; + background-size: 18px; + background-position-x: 99%; + } + +/* TINYMCE IMAGE RESIZING LIMITS */ #mceResizeHandlen, #mceResizeHandles, #mceResizeHandlee, #mceResizeHandlew{display: none !important; visibility: hidden !important;} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js b/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js index 07defe12c9..78124ac625 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js @@ -1,342 +1,342 @@ -(function () { - - //JavaScript extension methods on the core JavaScript objects (like String, Date, etc...) - if (!Date.prototype.toIsoDateTimeString) { - /** Converts a Date object to a globally acceptable ISO string, NOTE: This is different from the built in - JavaScript toISOString method which returns date/time like "2013-08-07T02:04:11.487Z" but we want "yyyy-MM-dd HH:mm:ss" */ - Date.prototype.toIsoDateTimeString = function (str) { - var month = (this.getMonth() + 1).toString(); - if (month.length === 1) { - month = "0" + month; - } - var day = this.getDate().toString(); - if (day.length === 1) { - day = "0" + day; - } - var hour = this.getHours().toString(); - if (hour.length === 1) { - hour = "0" + hour; - } - var mins = this.getMinutes().toString(); - if (mins.length === 1) { - mins = "0" + mins; - } - var secs = this.getSeconds().toString(); - if (secs.length === 1) { - secs = "0" + secs; - } - return this.getFullYear() + "-" + month + "-" + day + " " + hour + ":" + mins + ":" + secs; - }; - } - - if (!Date.prototype.toIsoDateString) { - /** Converts a Date object to a globally acceptable ISO string, NOTE: This is different from the built in - JavaScript toISOString method which returns date/time like "2013-08-07T02:04:11.487Z" but we want "yyyy-MM-dd" */ - Date.prototype.toIsoDateString = function (str) { - var month = (this.getMonth() + 1).toString(); - if (month.length === 1) { - month = "0" + month; - } - var day = this.getDate().toString(); - if (day.length === 1) { - day = "0" + day; - } - - return this.getFullYear() + "-" + month + "-" + day; - }; - } - - //create guid method on the String - if (String.CreateGuid == null) { - - /** generates a new Guid */ - String.CreateGuid = function () { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - }; - } - - if (!window.__debug__) { - - /** global method to send debug statements to console that is cross browser (or at least checks if its possible)*/ - window.__debug__ = function (msg, category, isErr) { - if (((typeof console) != "undefined") && console.log && console.error) { - if (isErr) console.error(category + ": " + msg); - else console.log(category + ": " + msg); - } - }; - } - - if (!String.prototype.startsWith) { - /** startsWith extension method for string */ - String.prototype.startsWith = function (str) { - return this.substr(0, str.length) === str; - }; - } - - if (!String.prototype.endsWith) { - - /** endsWith extension method for string*/ - String.prototype.endsWith = function (str) { - return this.substr(this.length - str.length) === str; - }; - } - - /** trims the start of the string*/ - String.prototype.trimStart = function (str) { - if (this.startsWith(str)) { - return this.substring(str.length); - } - return this; - }; - - /** trims the end of the string*/ - String.prototype.trimEnd = function (str) { - if (this.endsWith(str)) { - return this.substring(0, this.length - str.length); - } - return this; - }; - - if (!String.prototype.utf8Encode) { - - /** UTF8 encoder for string*/ - String.prototype.utf8Encode = function () { - var str = this.replace(/\r\n/g, "\n"); - var utftext = ""; - for (var n = 0; n < str.length; n++) { - var c = str.charCodeAt(n); - if (c < 128) { - utftext += String.fromCharCode(c); - } - else if ((c > 127) && (c < 2048)) { - utftext += String.fromCharCode((c >> 6) | 192); - utftext += String.fromCharCode((c & 63) | 128); - } - else { - utftext += String.fromCharCode((c >> 12) | 224); - utftext += String.fromCharCode(((c >> 6) & 63) | 128); - utftext += String.fromCharCode((c & 63) | 128); - } - } - return utftext; - }; - } - - if (!String.prototype.utf8Decode) { - - /** UTF8 decoder for string*/ - String.prototype.utf8Decode = function () { - var utftext = this; - var string = ""; - var i = 0; - var c = c1 = c2 = 0; - - while (i < utftext.length) { - - c = utftext.charCodeAt(i); - - if (c < 128) { - string += String.fromCharCode(c); - i++; - } - else if ((c > 191) && (c < 224)) { - c2 = utftext.charCodeAt(i + 1); - string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); - i += 2; - } - else { - c2 = utftext.charCodeAt(i + 1); - c3 = utftext.charCodeAt(i + 2); - string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); - i += 3; - } - - } - - return string; - }; - } - - if (!String.prototype.base64Encode) { - - /** Base64 encoder for string*/ - String.prototype.base64Encode = function () { - var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - var output = ""; - var chr1, chr2, chr3, enc1, enc2, enc3, enc4; - var i = 0; - - var input = this.utf8Encode(); - - while (i < input.length) { - - chr1 = input.charCodeAt(i++); - chr2 = input.charCodeAt(i++); - chr3 = input.charCodeAt(i++); - - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3)) { - enc4 = 64; - } - - output = output + - keyStr.charAt(enc1) + keyStr.charAt(enc2) + - keyStr.charAt(enc3) + keyStr.charAt(enc4); - - } - - return output; - }; - } - - if (!String.prototype.base64Decode) { - - /** Base64 decoder for string*/ - String.prototype.base64Decode = function () { - - var input = this; - var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - var output = ""; - var chr1, chr2, chr3; - var enc1, enc2, enc3, enc4; - var i = 0; - - input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); - - while (i < input.length) { - - enc1 = keyStr.indexOf(input.charAt(i++)); - enc2 = keyStr.indexOf(input.charAt(i++)); - enc3 = keyStr.indexOf(input.charAt(i++)); - enc4 = keyStr.indexOf(input.charAt(i++)); - - chr1 = (enc1 << 2) | (enc2 >> 4); - chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); - chr3 = ((enc3 & 3) << 6) | enc4; - - output = output + String.fromCharCode(chr1); - - if (enc3 != 64) { - output = output + String.fromCharCode(chr2); - } - if (enc4 != 64) { - output = output + String.fromCharCode(chr3); - } - - } - - return output.utf8Decode(); - - }; - } - - - if (!Math.randomRange) { - - /** randomRange extension for math*/ - Math.randomRange = function (from, to) { - return Math.floor(Math.random() * (to - from + 1) + from); - }; - } - - if (!String.prototype.toCamelCase) { - - /** toCamelCase extension method for string*/ - String.prototype.toCamelCase = function () { - - var s = this.toPascalCase(); - if ($.trim(s) == "") - return ""; - if (s.length > 1) { - var regex = /^([A-Z]*)([A-Z].*)/g; - if (s.match(regex)) { - var match = regex.exec(s); - s = match[1].toLowerCase() + match[2]; - s = s.substr(0, 1).toLowerCase() + s.substr(1); - } - } else { - s = s.toLowerCase(); - } - return s; - }; - } - - if (!String.prototype.toPascalCase) { - - /** toPascalCase extension method for string*/ - String.prototype.toPascalCase = function () { - - var s = ""; - $.each($.trim(this).split(/[\s\.-]+/g), function (idx, val) { - if ($.trim(val) == "") - return; - if (val.length > 1) - s += val.substr(0, 1).toUpperCase() + val.substr(1); - else - s += val.toUpperCase(); - }); - return s; - }; - } - - if (!String.prototype.toUmbracoAlias) { - - /** toUmbracoAlias extension method for string*/ - String.prototype.toUmbracoAlias = function () { - - var s = this.replace(/[^a-zA-Z0-9\s\.-]+/g, ''); // Strip none alphanumeric chars - return s.toCamelCase(); // Convert to camelCase - }; - } - - if (!String.prototype.toFunction) { - - /** Converts a string into a function if it is found */ - String.prototype.toFunction = function () { - var arr = this.split("."); - var fn = (window || this); - for (var i = 0, len = arr.length; i < len; i++) { - fn = fn[arr[i]]; - } - if (typeof fn !== "function") { - throw new Error("function not found"); - } - return fn; - }; - } - - if (!String.prototype.detectIsJson) { - - /** Rudimentary check to see if the string is a json encoded string */ - String.prototype.detectIsJson = function () { - if ((this.startsWith("{") && this.endsWith("}")) || (this.startsWith("[") || this.endsWith("]"))) { - return true; - } - return false; - }; - } - - if (!Object.toBoolean) { - - /** Converts a string/integer/bool to true/false */ - Object.toBoolean = function (obj) { - if ((typeof obj) === "boolean") { - return obj; - } - if (obj === "1" || obj === 1 || obj === "true") { - return true; - } - return false; - }; - } - -})(); +(function () { + + //JavaScript extension methods on the core JavaScript objects (like String, Date, etc...) + if (!Date.prototype.toIsoDateTimeString) { + /** Converts a Date object to a globally acceptable ISO string, NOTE: This is different from the built in + JavaScript toISOString method which returns date/time like "2013-08-07T02:04:11.487Z" but we want "yyyy-MM-dd HH:mm:ss" */ + Date.prototype.toIsoDateTimeString = function (str) { + var month = (this.getMonth() + 1).toString(); + if (month.length === 1) { + month = "0" + month; + } + var day = this.getDate().toString(); + if (day.length === 1) { + day = "0" + day; + } + var hour = this.getHours().toString(); + if (hour.length === 1) { + hour = "0" + hour; + } + var mins = this.getMinutes().toString(); + if (mins.length === 1) { + mins = "0" + mins; + } + var secs = this.getSeconds().toString(); + if (secs.length === 1) { + secs = "0" + secs; + } + return this.getFullYear() + "-" + month + "-" + day + " " + hour + ":" + mins + ":" + secs; + }; + } + + if (!Date.prototype.toIsoDateString) { + /** Converts a Date object to a globally acceptable ISO string, NOTE: This is different from the built in + JavaScript toISOString method which returns date/time like "2013-08-07T02:04:11.487Z" but we want "yyyy-MM-dd" */ + Date.prototype.toIsoDateString = function (str) { + var month = (this.getMonth() + 1).toString(); + if (month.length === 1) { + month = "0" + month; + } + var day = this.getDate().toString(); + if (day.length === 1) { + day = "0" + day; + } + + return this.getFullYear() + "-" + month + "-" + day; + }; + } + + //create guid method on the String + if (String.CreateGuid == null) { + + /** generates a new Guid */ + String.CreateGuid = function () { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }; + } + + if (!window.__debug__) { + + /** global method to send debug statements to console that is cross browser (or at least checks if its possible)*/ + window.__debug__ = function (msg, category, isErr) { + if (((typeof console) != "undefined") && console.log && console.error) { + if (isErr) console.error(category + ": " + msg); + else console.log(category + ": " + msg); + } + }; + } + + if (!String.prototype.startsWith) { + /** startsWith extension method for string */ + String.prototype.startsWith = function (str) { + return this.substr(0, str.length) === str; + }; + } + + if (!String.prototype.endsWith) { + + /** endsWith extension method for string*/ + String.prototype.endsWith = function (str) { + return this.substr(this.length - str.length) === str; + }; + } + + /** trims the start of the string*/ + String.prototype.trimStart = function (str) { + if (this.startsWith(str)) { + return this.substring(str.length); + } + return this; + }; + + /** trims the end of the string*/ + String.prototype.trimEnd = function (str) { + if (this.endsWith(str)) { + return this.substring(0, this.length - str.length); + } + return this; + }; + + if (!String.prototype.utf8Encode) { + + /** UTF8 encoder for string*/ + String.prototype.utf8Encode = function () { + var str = this.replace(/\r\n/g, "\n"); + var utftext = ""; + for (var n = 0; n < str.length; n++) { + var c = str.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if ((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + } + return utftext; + }; + } + + if (!String.prototype.utf8Decode) { + + /** UTF8 decoder for string*/ + String.prototype.utf8Decode = function () { + var utftext = this; + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + + while (i < utftext.length) { + + c = utftext.charCodeAt(i); + + if (c < 128) { + string += String.fromCharCode(c); + i++; + } + else if ((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i + 1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } + else { + c2 = utftext.charCodeAt(i + 1); + c3 = utftext.charCodeAt(i + 2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + + } + + return string; + }; + } + + if (!String.prototype.base64Encode) { + + /** Base64 encoder for string*/ + String.prototype.base64Encode = function () { + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + + var input = this.utf8Encode(); + + while (i < input.length) { + + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + keyStr.charAt(enc1) + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + keyStr.charAt(enc4); + + } + + return output; + }; + } + + if (!String.prototype.base64Decode) { + + /** Base64 decoder for string*/ + String.prototype.base64Decode = function () { + + var input = this; + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + } + + return output.utf8Decode(); + + }; + } + + + if (!Math.randomRange) { + + /** randomRange extension for math*/ + Math.randomRange = function (from, to) { + return Math.floor(Math.random() * (to - from + 1) + from); + }; + } + + if (!String.prototype.toCamelCase) { + + /** toCamelCase extension method for string*/ + String.prototype.toCamelCase = function () { + + var s = this.toPascalCase(); + if ($.trim(s) == "") + return ""; + if (s.length > 1) { + var regex = /^([A-Z]*)([A-Z].*)/g; + if (s.match(regex)) { + var match = regex.exec(s); + s = match[1].toLowerCase() + match[2]; + s = s.substr(0, 1).toLowerCase() + s.substr(1); + } + } else { + s = s.toLowerCase(); + } + return s; + }; + } + + if (!String.prototype.toPascalCase) { + + /** toPascalCase extension method for string*/ + String.prototype.toPascalCase = function () { + + var s = ""; + $.each($.trim(this).split(/[\s\.-]+/g), function (idx, val) { + if ($.trim(val) == "") + return; + if (val.length > 1) + s += val.substr(0, 1).toUpperCase() + val.substr(1); + else + s += val.toUpperCase(); + }); + return s; + }; + } + + if (!String.prototype.toUmbracoAlias) { + + /** toUmbracoAlias extension method for string*/ + String.prototype.toUmbracoAlias = function () { + + var s = this.replace(/[^a-zA-Z0-9\s\.-]+/g, ''); // Strip none alphanumeric chars + return s.toCamelCase(); // Convert to camelCase + }; + } + + if (!String.prototype.toFunction) { + + /** Converts a string into a function if it is found */ + String.prototype.toFunction = function () { + var arr = this.split("."); + var fn = (window || this); + for (var i = 0, len = arr.length; i < len; i++) { + fn = fn[arr[i]]; + } + if (typeof fn !== "function") { + throw new Error("function not found"); + } + return fn; + }; + } + + if (!String.prototype.detectIsJson) { + + /** Rudimentary check to see if the string is a json encoded string */ + String.prototype.detectIsJson = function () { + if ((this.startsWith("{") && this.endsWith("}")) || (this.startsWith("[") || this.endsWith("]"))) { + return true; + } + return false; + }; + } + + if (!Object.toBoolean) { + + /** Converts a string/integer/bool to true/false */ + Object.toBoolean = function (obj) { + if ((typeof obj) === "boolean") { + return obj; + } + if (obj === "1" || obj === 1 || obj === "true") { + return true; + } + return false; + }; + } + +})(); diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacySpeechBubble.js b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacySpeechBubble.js index 70ea5ee8f1..f973cbac49 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacySpeechBubble.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacySpeechBubble.js @@ -1,76 +1,76 @@ - -//TODO: WE NEED TO CONVERT ALL OF THESE METHODS TO PROXY TO OUR APPLICATION SINCE MANY CUSTOM APPS USE THIS! - -Umbraco.Sys.registerNamespace("Umbraco.Application"); - -(function($) { - Umbraco.Application.SpeechBubble = function() { - - /** - * @ngdoc function - * @name getRootScope - * @methodOf UmbClientMgr - * @function - * - * @description - * Returns the root angular scope - */ - function getRootScope() { - return angular.element(document.getElementById("umbracoMainPageBody")).scope(); - } - - /** - * @ngdoc function - * @name getRootInjector - * @methodOf UmbClientMgr - * @function - * - * @description - * Returns the root angular injector - */ - function getRootInjector() { - return angular.element(document.getElementById("umbracoMainPageBody")).injector(); - } - - - return { - - /** - * @ngdoc function - * @name ShowMessage - * @methodOf Umbraco.Application.SpeechBubble - * @function - * - * @description - * Proxies a legacy call to the new notification service - */ - ShowMessage: function (icon, header, message) { - //get our angular navigation service - var injector = getRootInjector(); - var notifyService = injector.get("notificationsService"); - - switch(icon){ - case "save": - notifyService.success(header, message); - break; - case "success": - notifyService.success(header, message); - break; - case "warning": - notifyService.warning(header, message); - break; - case "error": - notifyService.error(header, message); - break; - default: - notifyService.info(header, message); - } - - - } - }; - }; -})(jQuery); - -//define alias for use throughout application -var UmbSpeechBubble = new Umbraco.Application.SpeechBubble(); + +//TODO: WE NEED TO CONVERT ALL OF THESE METHODS TO PROXY TO OUR APPLICATION SINCE MANY CUSTOM APPS USE THIS! + +Umbraco.Sys.registerNamespace("Umbraco.Application"); + +(function($) { + Umbraco.Application.SpeechBubble = function() { + + /** + * @ngdoc function + * @name getRootScope + * @methodOf UmbClientMgr + * @function + * + * @description + * Returns the root angular scope + */ + function getRootScope() { + return angular.element(document.getElementById("umbracoMainPageBody")).scope(); + } + + /** + * @ngdoc function + * @name getRootInjector + * @methodOf UmbClientMgr + * @function + * + * @description + * Returns the root angular injector + */ + function getRootInjector() { + return angular.element(document.getElementById("umbracoMainPageBody")).injector(); + } + + + return { + + /** + * @ngdoc function + * @name ShowMessage + * @methodOf Umbraco.Application.SpeechBubble + * @function + * + * @description + * Proxies a legacy call to the new notification service + */ + ShowMessage: function (icon, header, message) { + //get our angular navigation service + var injector = getRootInjector(); + var notifyService = injector.get("notificationsService"); + + switch(icon){ + case "save": + notifyService.success(header, message); + break; + case "success": + notifyService.success(header, message); + break; + case "warning": + notifyService.warning(header, message); + break; + case "error": + notifyService.error(header, message); + break; + default: + notifyService.info(header, message); + } + + + } + }; + }; +})(jQuery); + +//define alias for use throughout application +var UmbSpeechBubble = new Umbraco.Application.SpeechBubble(); diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js index 8f1b53082d..5fad4b944e 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js @@ -1,354 +1,354 @@ - -//TODO: WE NEED TO CONVERT ALL OF THESE METHODS TO PROXY TO OUR APPLICATION SINCE MANY CUSTOM APPS USE THIS! - -//TEST to mock iframe, this intercepts calls directly -//to the old iframe, and funnels requests to angular directly -//var right = {document: {location: {}}}; -/**/ - -Umbraco.Sys.registerNamespace("Umbraco.Application"); - -(function($) { - Umbraco.Application.ClientManager = function() { - - //to support those trying to call right.document.etc - var fakeFrame = {}; - Object.defineProperty(fakeFrame, "href", { - get: function() { - return this._href ? this._href : ""; - }, - set: function(value) { - this._href = value; - UmbClientMgr.contentFrame(value); - }, - }); - - /** - * @ngdoc function - * @name getRootScope - * @methodOf UmbClientMgr - * @function - * - * @description - * Returns the root angular scope - */ - function getRootScope() { - return angular.element(document.getElementById("umbracoMainPageBody")).scope(); - } - - /** - * @ngdoc function - * @name getRootInjector - * @methodOf UmbClientMgr - * @function - * - * @description - * Returns the root angular injector - */ - function getRootInjector() { - return angular.element(document.getElementById("umbracoMainPageBody")).injector(); - } - - - return { - _isDirty: false, - _isDebug: false, - _mainTree: null, - _appActions: null, - _historyMgr: null, - _rootPath: "/umbraco", //this is the default - _modal: new Array(), //track all modal window objects (they get stacked) - - historyManager: function () { - - throw "Not implemented!"; - - //if (!this._historyMgr) { - // this._historyMgr = new Umbraco.Controls.HistoryManager(); - //} - //return this._historyMgr; - }, - - setUmbracoPath: function(strPath) { - /// - /// sets the Umbraco root path folder - /// - this._debug("setUmbracoPath: " + strPath); - this._rootPath = strPath; - }, - - mainWindow: function() { - return top; - }, - mainTree: function () { - - var injector = getRootInjector(); - var navService = injector.get("navigationService"); - var appState = injector.get("appState"); - var angularHelper = injector.get("angularHelper"); - var $rootScope = injector.get("$rootScope"); - - //mimic the API of the legacy tree - var tree = { - syncTree: function (path, forceReload) { - angularHelper.safeApply($rootScope, function() { - navService._syncPath(path, forceReload); - }); - }, - clearTreeCache: function(){ - var treeService = injector.get("treeService"); - angularHelper.safeApply($rootScope, function() { - treeService.clearCache(); - }); - }, - childNodeCreated: function() { - //no-op, just needs to be here for legacy reasons - }, - reloadActionNode: function () { - angularHelper.safeApply($rootScope, function() { - var currentMenuNode = appState.getMenuState("currentNode"); - if (currentMenuNode) { - navService.reloadNode(currentMenuNode); - } - }); - }, - refreshTree: function (treeAlias) { - //no-op, just needs to be here for legacy reasons - }, - moveNode: function (id, path) { - angularHelper.safeApply($rootScope, function() { - var currentMenuNode = appState.getMenuState("currentNode"); - if (currentMenuNode) { - var treeService = injector.get("treeService"); - var treeRoot = treeService.getTreeRoot(currentMenuNode); - if (treeRoot) { - var found = treeService.getDescendantNode(treeRoot, id); - if (found) { - treeService.removeNode(found); - } - } - } - navService._syncPath(path, true); - }); - }, - getActionNode: function () { - //need to replicate the legacy tree node - var currentMenuNode = appState.getMenuState("currentNode"); - if (!currentMenuNode) { - return null; - } - - var legacyNode = { - nodeId: currentMenuNode.id, - nodeName: currentMenuNode.name, - nodeType: currentMenuNode.nodeType, - treeType: currentMenuNode.nodeType, - sourceUrl: currentMenuNode.childNodesUrl, - updateDefinition: function() { - throw "'updateDefinition' method is not supported in Umbraco 7, consider upgrading to the new v7 APIs"; - } - }; - //defined getters that will throw a not implemented/supported exception - Object.defineProperty(legacyNode, "menu", { - get: function () { - throw "'menu' property is not supported in Umbraco 7, consider upgrading to the new v7 APIs"; - } - }); - Object.defineProperty(legacyNode, "jsNode", { - get: function () { - throw "'jsNode' property is not supported in Umbraco 7, consider upgrading to the new v7 APIs"; - } - }); - Object.defineProperty(legacyNode, "jsTree", { - get: function () { - throw "'jsTree' property is not supported in Umbraco 7, consider upgrading to the new v7 APIs"; - } - }); - - return legacyNode; - } - }; - - return tree; - }, - appActions: function() { - var injector = getRootInjector(); - var navService = injector.get("navigationService"); - var localizationService = injector.get("localizationService"); - var usersResource = injector.get("usersResource"); - //var appState = injector.get("appState"); - var angularHelper = injector.get("angularHelper"); - var $rootScope = injector.get("$rootScope"); - - var actions = { - openDashboard : function(section){ - navService.changeSection(section); - } - }; - - return actions; - }, - uiKeys: function() { - - throw "Not implemented!"; - - ////TODO: If there is no main window, we need to go retrieve the appActions from the server! - //return this.mainWindow().uiKeys; - }, - contentFrameAndSection: function(app, rightFrameUrl) { - - throw "Not implemented!"; - - ////this.appActions().shiftApp(app, this.uiKeys()['sections_' + app]); - //var self = this; - //self.mainWindow().UmbClientMgr.historyManager().addHistory(app, true); - //window.setTimeout(function() { - // self.mainWindow().UmbClientMgr.contentFrame(rightFrameUrl); - //}, 200); - }, - - /** - * @ngdoc function - * @name contentFrame - * @methodOf UmbClientMgr - * @function - * - * @description - * This will tell our angular app to create and load in an iframe at the specified location - * @param strLocation {String} The URL to load the iframe in - */ - contentFrame: function (strLocation) { - - if (!strLocation || strLocation == "") { - //SD: NOTE: We used to return the content iframe object but now I'm not sure we should do that ?! - - if (typeof top.right != "undefined") { - return top.right; - } - else { - return top; //return the current window if the content frame doesn't exist in the current context - } - //return; - } - - this._debug("contentFrame: " + strLocation); - - //get our angular navigation service - var injector = getRootInjector(); - - var rootScope = injector.get("$rootScope"); - var angularHelper = injector.get("angularHelper"); - var navService = injector.get("navigationService"); - var locationService = injector.get("$location"); - - var self = this; - - angularHelper.safeApply(rootScope, function() { - if (strLocation.startsWith("#")) { - locationService.path(strLocation.trimStart("#")).search(""); - } - else { - //if the path doesn't start with "/" or with the root path then - //prepend the root path - if (!strLocation.startsWith("/")) { - strLocation = self._rootPath + "/" + strLocation; - } - else if (strLocation.length >= self._rootPath.length - && strLocation.substr(0, self._rootPath.length) != self._rootPath) { - strLocation = self._rootPath + "/" + strLocation; - } - - navService.loadLegacyIFrame(strLocation); - } - }); - }, - - getFakeFrame : function() { - return fakeFrame; - }, - - /** This is used to launch an angular based modal window instead of the legacy window */ - openAngularModalWindow: function (options) { - - //get our angular navigation service - var injector = getRootInjector(); - var dialogService = injector.get("dialogService"); - - var dialog = dialogService.open(options); - - ////add the callback to the jquery data for the modal so we can call it on close to support the legacy way dialogs worked. - //dialog.element.data("modalCb", onCloseCallback); - - //this._modal.push(dialog); - //return dialog; - }, - - openModalWindow: function(url, name, showHeader, width, height, top, leftOffset, closeTriggers, onCloseCallback) { - //need to create the modal on the top window if the top window has a client manager, if not, create it on the current window - - //get our angular navigation service - var injector = getRootInjector(); - var navService = injector.get("navigationService"); - var dialogService = injector.get("dialogService"); - - var self = this; - - //based on what state the nav ui is in, depends on how we are going to launch a model / dialog. A modal - // will show up on the right hand side and a dialog will show up as if it is in the menu. - // with the legacy API we cannot know what is expected so we can only check if the menu is active, if it is - // we'll launch a dialog, otherwise a modal. - var appState = injector.get("appState"); - var navMode = appState.getGlobalState("navMode"); - var dialog; - if (navMode === "menu") { - dialog = navService.showDialog({ - //create a 'fake' action to passin with the specified actionUrl since it needs to load into an iframe - action: { - name: name, - metaData: { - actionUrl: url - } - }, - node: {} - }); - } - else { - dialog = dialogService.open({ - template: url, - width: width, - height: height, - iframe: true, - show: true - }); - } - - - //add the callback to the jquery data for the modal so we can call it on close to support the legacy way dialogs worked. - dialog.element.data("modalCb", onCloseCallback); - //add the close triggers - if (angular.isArray(closeTriggers)) { - for (var i = 0; i < closeTriggers.length; i++) { - var e = dialog.find(closeTriggers[i]); - if (e.length > 0) { - e.click(function () { - self.closeModalWindow(); - }); - } - } - } - - this._modal.push(dialog); - return dialog; - }, - rootScope : function(){ - return getRootScope(); - }, + +//TODO: WE NEED TO CONVERT ALL OF THESE METHODS TO PROXY TO OUR APPLICATION SINCE MANY CUSTOM APPS USE THIS! + +//TEST to mock iframe, this intercepts calls directly +//to the old iframe, and funnels requests to angular directly +//var right = {document: {location: {}}}; +/**/ + +Umbraco.Sys.registerNamespace("Umbraco.Application"); + +(function($) { + Umbraco.Application.ClientManager = function() { + + //to support those trying to call right.document.etc + var fakeFrame = {}; + Object.defineProperty(fakeFrame, "href", { + get: function() { + return this._href ? this._href : ""; + }, + set: function(value) { + this._href = value; + UmbClientMgr.contentFrame(value); + }, + }); + + /** + * @ngdoc function + * @name getRootScope + * @methodOf UmbClientMgr + * @function + * + * @description + * Returns the root angular scope + */ + function getRootScope() { + return angular.element(document.getElementById("umbracoMainPageBody")).scope(); + } + + /** + * @ngdoc function + * @name getRootInjector + * @methodOf UmbClientMgr + * @function + * + * @description + * Returns the root angular injector + */ + function getRootInjector() { + return angular.element(document.getElementById("umbracoMainPageBody")).injector(); + } + + + return { + _isDirty: false, + _isDebug: false, + _mainTree: null, + _appActions: null, + _historyMgr: null, + _rootPath: "/umbraco", //this is the default + _modal: new Array(), //track all modal window objects (they get stacked) + + historyManager: function () { + + throw "Not implemented!"; + + //if (!this._historyMgr) { + // this._historyMgr = new Umbraco.Controls.HistoryManager(); + //} + //return this._historyMgr; + }, + + setUmbracoPath: function(strPath) { + /// + /// sets the Umbraco root path folder + /// + this._debug("setUmbracoPath: " + strPath); + this._rootPath = strPath; + }, + + mainWindow: function() { + return top; + }, + mainTree: function () { + + var injector = getRootInjector(); + var navService = injector.get("navigationService"); + var appState = injector.get("appState"); + var angularHelper = injector.get("angularHelper"); + var $rootScope = injector.get("$rootScope"); + + //mimic the API of the legacy tree + var tree = { + syncTree: function (path, forceReload) { + angularHelper.safeApply($rootScope, function() { + navService._syncPath(path, forceReload); + }); + }, + clearTreeCache: function(){ + var treeService = injector.get("treeService"); + angularHelper.safeApply($rootScope, function() { + treeService.clearCache(); + }); + }, + childNodeCreated: function() { + //no-op, just needs to be here for legacy reasons + }, + reloadActionNode: function () { + angularHelper.safeApply($rootScope, function() { + var currentMenuNode = appState.getMenuState("currentNode"); + if (currentMenuNode) { + navService.reloadNode(currentMenuNode); + } + }); + }, + refreshTree: function (treeAlias) { + //no-op, just needs to be here for legacy reasons + }, + moveNode: function (id, path) { + angularHelper.safeApply($rootScope, function() { + var currentMenuNode = appState.getMenuState("currentNode"); + if (currentMenuNode) { + var treeService = injector.get("treeService"); + var treeRoot = treeService.getTreeRoot(currentMenuNode); + if (treeRoot) { + var found = treeService.getDescendantNode(treeRoot, id); + if (found) { + treeService.removeNode(found); + } + } + } + navService._syncPath(path, true); + }); + }, + getActionNode: function () { + //need to replicate the legacy tree node + var currentMenuNode = appState.getMenuState("currentNode"); + if (!currentMenuNode) { + return null; + } + + var legacyNode = { + nodeId: currentMenuNode.id, + nodeName: currentMenuNode.name, + nodeType: currentMenuNode.nodeType, + treeType: currentMenuNode.nodeType, + sourceUrl: currentMenuNode.childNodesUrl, + updateDefinition: function() { + throw "'updateDefinition' method is not supported in Umbraco 7, consider upgrading to the new v7 APIs"; + } + }; + //defined getters that will throw a not implemented/supported exception + Object.defineProperty(legacyNode, "menu", { + get: function () { + throw "'menu' property is not supported in Umbraco 7, consider upgrading to the new v7 APIs"; + } + }); + Object.defineProperty(legacyNode, "jsNode", { + get: function () { + throw "'jsNode' property is not supported in Umbraco 7, consider upgrading to the new v7 APIs"; + } + }); + Object.defineProperty(legacyNode, "jsTree", { + get: function () { + throw "'jsTree' property is not supported in Umbraco 7, consider upgrading to the new v7 APIs"; + } + }); + + return legacyNode; + } + }; + + return tree; + }, + appActions: function() { + var injector = getRootInjector(); + var navService = injector.get("navigationService"); + var localizationService = injector.get("localizationService"); + var usersResource = injector.get("usersResource"); + //var appState = injector.get("appState"); + var angularHelper = injector.get("angularHelper"); + var $rootScope = injector.get("$rootScope"); + + var actions = { + openDashboard : function(section){ + navService.changeSection(section); + } + }; + + return actions; + }, + uiKeys: function() { + + throw "Not implemented!"; + + ////TODO: If there is no main window, we need to go retrieve the appActions from the server! + //return this.mainWindow().uiKeys; + }, + contentFrameAndSection: function(app, rightFrameUrl) { + + throw "Not implemented!"; + + ////this.appActions().shiftApp(app, this.uiKeys()['sections_' + app]); + //var self = this; + //self.mainWindow().UmbClientMgr.historyManager().addHistory(app, true); + //window.setTimeout(function() { + // self.mainWindow().UmbClientMgr.contentFrame(rightFrameUrl); + //}, 200); + }, + + /** + * @ngdoc function + * @name contentFrame + * @methodOf UmbClientMgr + * @function + * + * @description + * This will tell our angular app to create and load in an iframe at the specified location + * @param strLocation {String} The URL to load the iframe in + */ + contentFrame: function (strLocation) { + + if (!strLocation || strLocation == "") { + //SD: NOTE: We used to return the content iframe object but now I'm not sure we should do that ?! + + if (typeof top.right != "undefined") { + return top.right; + } + else { + return top; //return the current window if the content frame doesn't exist in the current context + } + //return; + } + + this._debug("contentFrame: " + strLocation); + + //get our angular navigation service + var injector = getRootInjector(); + + var rootScope = injector.get("$rootScope"); + var angularHelper = injector.get("angularHelper"); + var navService = injector.get("navigationService"); + var locationService = injector.get("$location"); + + var self = this; + + angularHelper.safeApply(rootScope, function() { + if (strLocation.startsWith("#")) { + locationService.path(strLocation.trimStart("#")).search(""); + } + else { + //if the path doesn't start with "/" or with the root path then + //prepend the root path + if (!strLocation.startsWith("/")) { + strLocation = self._rootPath + "/" + strLocation; + } + else if (strLocation.length >= self._rootPath.length + && strLocation.substr(0, self._rootPath.length) != self._rootPath) { + strLocation = self._rootPath + "/" + strLocation; + } + + navService.loadLegacyIFrame(strLocation); + } + }); + }, + + getFakeFrame : function() { + return fakeFrame; + }, + + /** This is used to launch an angular based modal window instead of the legacy window */ + openAngularModalWindow: function (options) { + + //get our angular navigation service + var injector = getRootInjector(); + var dialogService = injector.get("dialogService"); + + var dialog = dialogService.open(options); + + ////add the callback to the jquery data for the modal so we can call it on close to support the legacy way dialogs worked. + //dialog.element.data("modalCb", onCloseCallback); + + //this._modal.push(dialog); + //return dialog; + }, + + openModalWindow: function(url, name, showHeader, width, height, top, leftOffset, closeTriggers, onCloseCallback) { + //need to create the modal on the top window if the top window has a client manager, if not, create it on the current window + + //get our angular navigation service + var injector = getRootInjector(); + var navService = injector.get("navigationService"); + var dialogService = injector.get("dialogService"); + + var self = this; + + //based on what state the nav ui is in, depends on how we are going to launch a model / dialog. A modal + // will show up on the right hand side and a dialog will show up as if it is in the menu. + // with the legacy API we cannot know what is expected so we can only check if the menu is active, if it is + // we'll launch a dialog, otherwise a modal. + var appState = injector.get("appState"); + var navMode = appState.getGlobalState("navMode"); + var dialog; + if (navMode === "menu") { + dialog = navService.showDialog({ + //create a 'fake' action to passin with the specified actionUrl since it needs to load into an iframe + action: { + name: name, + metaData: { + actionUrl: url + } + }, + node: {} + }); + } + else { + dialog = dialogService.open({ + template: url, + width: width, + height: height, + iframe: true, + show: true + }); + } + + + //add the callback to the jquery data for the modal so we can call it on close to support the legacy way dialogs worked. + dialog.element.data("modalCb", onCloseCallback); + //add the close triggers + if (angular.isArray(closeTriggers)) { + for (var i = 0; i < closeTriggers.length; i++) { + var e = dialog.find(closeTriggers[i]); + if (e.length > 0) { + e.click(function () { + self.closeModalWindow(); + }); + } + } + } + + this._modal.push(dialog); + return dialog; + }, + rootScope : function(){ + return getRootScope(); + }, /** This will reload the content frame based on it's current route, if pathToMatch is specified it will only reload it if the current location matches the path - */ - reloadLocation: function(pathToMatch) { + */ + reloadLocation: function(pathToMatch) { var injector = getRootInjector(); var doChange = true; @@ -356,72 +356,72 @@ Umbraco.Sys.registerNamespace("Umbraco.Application"); var $location = injector.get("$location"); var path = $location.path(); if (path != pathToMatch) { - doChange = false; - } + doChange = false; + } } if (doChange) { - var $route = injector.get("$route"); - $route.reload(); - var $rootScope = injector.get("$rootScope"); - $rootScope.$apply(); - } - }, - - closeModalWindow: function(rVal) { - - //get our angular navigation service - var injector = getRootInjector(); - var dialogService = injector.get("dialogService"); - - // all legacy calls to closeModalWindow are expecting to just close the last opened one so we'll ensure - // that this is still the case. - if (this._modal != null && this._modal.length > 0) { - - var lastModal = this._modal.pop(); - - //if we've stored a callback on this modal call it before we close. - var self = this; - //get the compat callback from the modal element - var onCloseCallback = lastModal.element.data("modalCb"); - if (typeof onCloseCallback == "function") { - onCloseCallback.apply(self, [{ outVal: rVal }]); - } - - //just call the native dialog close() method to remove the dialog - lastModal.close(); - - //if it's the last one close them all - if (this._modal.length == 0) { - getRootScope().$emit("app.closeDialogs", undefined); - } - } - else { - //instead of calling just the dialog service we funnel it through the global - //event emitter - getRootScope().$emit("app.closeDialogs", undefined); - } - }, - /* This is used for the package installer to call in order to reload all app assets so we don't have to reload the window */ - _packageInstalled: function() { - var injector = getRootInjector(); - var packageHelper = injector.get("packageHelper"); - packageHelper.packageInstalled(); - }, - _debug: function(strMsg) { - if (this._isDebug) { - Sys.Debug.trace("UmbClientMgr: " + strMsg); - } - }, - get_isDirty: function() { - return this._isDirty; - }, - set_isDirty: function(value) { - this._isDirty = value; - } - }; - }; -})(jQuery); - -//define alias for use throughout application -var UmbClientMgr = new Umbraco.Application.ClientManager(); + var $route = injector.get("$route"); + $route.reload(); + var $rootScope = injector.get("$rootScope"); + $rootScope.$apply(); + } + }, + + closeModalWindow: function(rVal) { + + //get our angular navigation service + var injector = getRootInjector(); + var dialogService = injector.get("dialogService"); + + // all legacy calls to closeModalWindow are expecting to just close the last opened one so we'll ensure + // that this is still the case. + if (this._modal != null && this._modal.length > 0) { + + var lastModal = this._modal.pop(); + + //if we've stored a callback on this modal call it before we close. + var self = this; + //get the compat callback from the modal element + var onCloseCallback = lastModal.element.data("modalCb"); + if (typeof onCloseCallback == "function") { + onCloseCallback.apply(self, [{ outVal: rVal }]); + } + + //just call the native dialog close() method to remove the dialog + lastModal.close(); + + //if it's the last one close them all + if (this._modal.length == 0) { + getRootScope().$emit("app.closeDialogs", undefined); + } + } + else { + //instead of calling just the dialog service we funnel it through the global + //event emitter + getRootScope().$emit("app.closeDialogs", undefined); + } + }, + /* This is used for the package installer to call in order to reload all app assets so we don't have to reload the window */ + _packageInstalled: function() { + var injector = getRootInjector(); + var packageHelper = injector.get("packageHelper"); + packageHelper.packageInstalled(); + }, + _debug: function(strMsg) { + if (this._isDebug) { + Sys.Debug.trace("UmbClientMgr: " + strMsg); + } + }, + get_isDirty: function() { + return this._isDirty; + }, + set_isDirty: function(value) { + this._isDirty = value; + } + }; + }; +})(jQuery); + +//define alias for use throughout application +var UmbClientMgr = new Umbraco.Application.ClientManager(); diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/NamespaceManager.js b/src/Umbraco.Web.UI.Client/lib/umbraco/NamespaceManager.js index 9b8289e478..9d4b86b2ba 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/NamespaceManager.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/NamespaceManager.js @@ -1,17 +1,17 @@ -if (typeof Umbraco == 'undefined') var Umbraco = {}; -if (!Umbraco.Sys) Umbraco.Sys = {}; - -Umbraco.Sys.registerNamespace = function(namespace) { - /// - /// Used to easily register namespaces for classes without doing the syntax listed on line 1/2 for each class. - /// Pretty much the same as ASP.NET's Type.registerNamespace, except in order to use it, you must register - /// all of your scripts with ScriptManager, this class doesn't require this. - /// - namespace = namespace.split('.'); - if (!window[namespace[0]]) window[namespace[0]] = {}; - var strFullNamespace = namespace[0]; - for (var i = 1; i < namespace.length; i++) { - strFullNamespace += "." + namespace[i]; - eval("if(!window." + strFullNamespace + ")window." + strFullNamespace + "={};"); - } +if (typeof Umbraco == 'undefined') var Umbraco = {}; +if (!Umbraco.Sys) Umbraco.Sys = {}; + +Umbraco.Sys.registerNamespace = function(namespace) { + /// + /// Used to easily register namespaces for classes without doing the syntax listed on line 1/2 for each class. + /// Pretty much the same as ASP.NET's Type.registerNamespace, except in order to use it, you must register + /// all of your scripts with ScriptManager, this class doesn't require this. + /// + namespace = namespace.split('.'); + if (!window[namespace[0]]) window[namespace[0]] = {}; + var strFullNamespace = namespace[0]; + for (var i = 1; i < namespace.length; i++) { + strFullNamespace += "." + namespace[i]; + eval("if(!window." + strFullNamespace + ")window." + strFullNamespace + "={};"); + } }; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/compat.js b/src/Umbraco.Web.UI.Client/lib/umbraco/compat.js index 873f8ecbe0..a7c0b32938 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/compat.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/compat.js @@ -1,47 +1,47 @@ -/* contains random bits and pieces we neede to make the U6 UI behave */ - -(function ($) { - - $(document).ready(function () { - scaleScrollables("body"); - - $(window).bind("resize", function () { - scaleScrollables("body"); - }); - - $("body").click(function (event) { - var el = event.target.nodeName; - var els = ["INPUT","A","BUTTON"]; - - if(els.indexOf(el) >= 0){return;} - - var parents = $(event.target).parents("a,button"); - if(parents.length > 0){ - return; - } - - var click = $(event.target).attr('onClick'); - if(click){ - return; - } - - - UmbClientMgr.closeModalWindow(undefined); - }); - }); - - function scaleScrollables(selector) { - $(".umb-scrollable").each(function() { - var el = jQuery(this); - var totalOffset = 0; - var offsety = el.data("offset-y"); - - if (offsety != undefined) - totalOffset += offsety; - - el.height($(window).height() - (el.offset().top + totalOffset)); - }); - } - - +/* contains random bits and pieces we neede to make the U6 UI behave */ + +(function ($) { + + $(document).ready(function () { + scaleScrollables("body"); + + $(window).bind("resize", function () { + scaleScrollables("body"); + }); + + $("body").click(function (event) { + var el = event.target.nodeName; + var els = ["INPUT","A","BUTTON"]; + + if(els.indexOf(el) >= 0){return;} + + var parents = $(event.target).parents("a,button"); + if(parents.length > 0){ + return; + } + + var click = $(event.target).attr('onClick'); + if(click){ + return; + } + + + UmbClientMgr.closeModalWindow(undefined); + }); + }); + + function scaleScrollables(selector) { + $(".umb-scrollable").each(function() { + var el = jQuery(this); + var totalOffset = 0; + var offsety = el.data("offset-y"); + + if (offsety != undefined) + totalOffset += offsety; + + el.height($(window).height() - (el.offset().top + totalOffset)); + }); + } + + })(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/app.js b/src/Umbraco.Web.UI.Client/src/app.js index a4ed9669c5..c7b813c1bf 100644 --- a/src/Umbraco.Web.UI.Client/src/app.js +++ b/src/Umbraco.Web.UI.Client/src/app.js @@ -1,94 +1,94 @@ -var app = angular.module('umbraco', [ - 'umbraco.filters', - 'umbraco.directives', - 'umbraco.resources', - 'umbraco.services', - 'umbraco.packages', - 'umbraco.views', - - 'ngRoute', - 'ngAnimate', - 'ngCookies', - 'ngSanitize', +var app = angular.module('umbraco', [ + 'umbraco.filters', + 'umbraco.directives', + 'umbraco.resources', + 'umbraco.services', + 'umbraco.packages', + 'umbraco.views', + + 'ngRoute', + 'ngAnimate', + 'ngCookies', + 'ngSanitize', 'ngTouch', - 'ngMessages', - 'tmh.dynamicLocale', - 'ngFileUpload', - 'LocalStorageModule' -]); - -app.config(['$compileProvider', function ($compileProvider) { - // when not in debug mode remove all angularjs debug css classes and HTML comments from the dom - $compileProvider.debugInfoEnabled(Umbraco.Sys.ServerVariables.isDebuggingEnabled); - // don't execute directives inside comments - $compileProvider.commentDirectivesEnabled(false); - // don't execute directives inside css classes - $compileProvider.cssClassDirectivesEnabled(false); -}]); - -// I configure the $animate service during bootstrap. -angular.module("umbraco").config( - function configureAnimate( $animateProvider ) { - // By default, the $animate service will check for animation styling - // on every structural change. This requires a lot of animateFrame-based - // DOM-inspection. However, we can tell $animate to only check for - // animations on elements that have a specific class name RegExp pattern - // present. In this case, we are requiring the "umb-animated" class. - $animateProvider.classNameFilter( /\bumb-animated\b/ ); - } -); - -var packages = angular.module("umbraco.packages", []); - -//this ensures we can inject our own views into templateCache and clear -//the entire cache before the app runs, due to the module -//order, clearing will always happen before umbraco.views and umbraco -//module is initilized. -angular.module("umbraco.views", ["umbraco.viewcache"]); -angular.module("umbraco.viewcache", []) - .run(function ($rootScope, $templateCache, localStorageService) { - /** For debug mode, always clear template cache to cut down on - dev frustration and chrome cache on templates */ - if (Umbraco.Sys.ServerVariables.isDebuggingEnabled) { - $templateCache.removeAll(); - } - else { - var storedVersion = localStorageService.get("umbVersion"); - if (!storedVersion || storedVersion !== Umbraco.Sys.ServerVariables.application.cacheBuster) { - //if the stored version doesn't match our cache bust version, clear the template cache - $templateCache.removeAll(); - //store the current version - localStorageService.set("umbVersion", Umbraco.Sys.ServerVariables.application.cacheBuster); - } - } - }) - .config([ - //This ensures that all of our angular views are cache busted, if the path starts with views/ and ends with .html, then - // we will append the cache busting value to it. This way all upgraded sites will not have to worry about browser cache. - "$provide", function($provide) { - return $provide.decorator("$http", [ - "$delegate", function($delegate) { - var get = $delegate.get; - $delegate.get = function (url, config) { - - if (Umbraco.Sys.ServerVariables.application && url.startsWith("views/") && url.endsWith(".html")) { - var rnd = Umbraco.Sys.ServerVariables.application.cacheBuster; - var _op = (url.indexOf("?") > 0) ? "&" : "?"; - url += _op + "umb__rnd=" + rnd; - } - - return get(url, config); - }; - return $delegate; - } - ]); - } - ]); - -//Call a document callback if defined, this is sort of a dodgy hack to -// be able to configure angular values in the Default.cshtml -// view which is much easier to do that configuring values by injecting them in the back office controller -// to follow through to the js initialization stuff -if (angular.isFunction(document.angularReady)) { - document.angularReady.apply(this, [app]); -} + 'ngMessages', + 'tmh.dynamicLocale', + 'ngFileUpload', + 'LocalStorageModule' +]); + +app.config(['$compileProvider', function ($compileProvider) { + // when not in debug mode remove all angularjs debug css classes and HTML comments from the dom + $compileProvider.debugInfoEnabled(Umbraco.Sys.ServerVariables.isDebuggingEnabled); + // don't execute directives inside comments + $compileProvider.commentDirectivesEnabled(false); + // don't execute directives inside css classes + $compileProvider.cssClassDirectivesEnabled(false); +}]); + +// I configure the $animate service during bootstrap. +angular.module("umbraco").config( + function configureAnimate( $animateProvider ) { + // By default, the $animate service will check for animation styling + // on every structural change. This requires a lot of animateFrame-based + // DOM-inspection. However, we can tell $animate to only check for + // animations on elements that have a specific class name RegExp pattern + // present. In this case, we are requiring the "umb-animated" class. + $animateProvider.classNameFilter( /\bumb-animated\b/ ); + } +); + +var packages = angular.module("umbraco.packages", []); + +//this ensures we can inject our own views into templateCache and clear +//the entire cache before the app runs, due to the module +//order, clearing will always happen before umbraco.views and umbraco +//module is initilized. +angular.module("umbraco.views", ["umbraco.viewcache"]); +angular.module("umbraco.viewcache", []) + .run(function ($rootScope, $templateCache, localStorageService) { + /** For debug mode, always clear template cache to cut down on + dev frustration and chrome cache on templates */ + if (Umbraco.Sys.ServerVariables.isDebuggingEnabled) { + $templateCache.removeAll(); + } + else { + var storedVersion = localStorageService.get("umbVersion"); + if (!storedVersion || storedVersion !== Umbraco.Sys.ServerVariables.application.cacheBuster) { + //if the stored version doesn't match our cache bust version, clear the template cache + $templateCache.removeAll(); + //store the current version + localStorageService.set("umbVersion", Umbraco.Sys.ServerVariables.application.cacheBuster); + } + } + }) + .config([ + //This ensures that all of our angular views are cache busted, if the path starts with views/ and ends with .html, then + // we will append the cache busting value to it. This way all upgraded sites will not have to worry about browser cache. + "$provide", function($provide) { + return $provide.decorator("$http", [ + "$delegate", function($delegate) { + var get = $delegate.get; + $delegate.get = function (url, config) { + + if (Umbraco.Sys.ServerVariables.application && url.startsWith("views/") && url.endsWith(".html")) { + var rnd = Umbraco.Sys.ServerVariables.application.cacheBuster; + var _op = (url.indexOf("?") > 0) ? "&" : "?"; + url += _op + "umb__rnd=" + rnd; + } + + return get(url, config); + }; + return $delegate; + } + ]); + } + ]); + +//Call a document callback if defined, this is sort of a dodgy hack to +// be able to configure angular values in the Default.cshtml +// view which is much easier to do that configuring values by injecting them in the back office controller +// to follow through to the js initialization stuff +if (angular.isFunction(document.angularReady)) { + document.angularReady.apply(this, [app]); +} diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/helveticons/helveticons.svg b/src/Umbraco.Web.UI.Client/src/assets/fonts/helveticons/helveticons.svg index 166168f14a..e4ae7c7536 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/fonts/helveticons/helveticons.svg +++ b/src/Umbraco.Web.UI.Client/src/assets/fonts/helveticons/helveticons.svg @@ -1,6176 +1,6176 @@ - - - - -This is a custom SVG font generated by IcoMoon. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + +This is a custom SVG font generated by IcoMoon. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/applicationIcons/help.svg b/src/Umbraco.Web.UI.Client/src/assets/img/applicationIcons/help.svg index 90a8575487..16edca4492 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/img/applicationIcons/help.svg +++ b/src/Umbraco.Web.UI.Client/src/assets/img/applicationIcons/help.svg @@ -1,22 +1,22 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/applicationIcons/hlvticons-umbraco.svg b/src/Umbraco.Web.UI.Client/src/assets/img/applicationIcons/hlvticons-umbraco.svg index 8884b6de26..5025acdf9d 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/img/applicationIcons/hlvticons-umbraco.svg +++ b/src/Umbraco.Web.UI.Client/src/assets/img/applicationIcons/hlvticons-umbraco.svg @@ -1,87 +1,87 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/_module.js b/src/Umbraco.Web.UI.Client/src/common/directives/_module.js index 823c324641..4b39210b2a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/_module.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_module.js @@ -1,4 +1,4 @@ -angular.module("umbraco.directives", ["umbraco.directives.editors", "umbraco.directives.html", "umbraco.directives.validation", "ui.sortable"]); -angular.module("umbraco.directives.editors", []); -angular.module("umbraco.directives.html", []); +angular.module("umbraco.directives", ["umbraco.directives.editors", "umbraco.directives.html", "umbraco.directives.validation", "ui.sortable"]); +angular.module("umbraco.directives.editors", []); +angular.module("umbraco.directives.html", []); angular.module("umbraco.directives.validation", []); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbItemSorter.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbItemSorter.directive.js index 056bb9559c..30a01e43c1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbItemSorter.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbItemSorter.directive.js @@ -1,69 +1,69 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbItemSorter -* @deprecated -* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. -* @function -* @element ANY -* @restrict E -* @description A re-usable directive for sorting items -**/ - -function umbItemSorter(angularHelper) { - return { - scope: { - model: "=" - }, - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/directives/_obsolete/umb-item-sorter.html', - link: function(scope, element, attrs, ctrl) { - var defaultModel = { - okButton: "Ok", - successMsg: "Sorting successful", - complete: false - }; - //assign user vals to default - angular.extend(defaultModel, scope.model); - //re-assign merged to user - scope.model = defaultModel; - - scope.performSort = function() { - scope.$emit("umbItemSorter.sorting", { - sortedItems: scope.model.itemsToSort - }); - }; - - scope.handleCancel = function () { - scope.$emit("umbItemSorter.cancel"); - }; - - scope.handleOk = function() { - scope.$emit("umbItemSorter.ok"); - }; - - //defines the options for the jquery sortable - scope.sortableOptions = { - axis: 'y', - cursor: "move", - placeholder: "ui-sortable-placeholder", - update: function (ev, ui) { - //highlight the item when the position is changed - $(ui.item).effect("highlight", { color: "#049cdb" }, 500); - }, - stop: function (ev, ui) { - //the ui-sortable directive already ensures that our list is re-sorted, so now we just - // need to update the sortOrder to the index of each item - angularHelper.safeApply(scope, function () { - angular.forEach(scope.itemsToSort, function (val, index) { - val.sortOrder = index + 1; - }); - - }); - } - }; - } - }; -} - -angular.module('umbraco.directives').directive("umbItemSorter", umbItemSorter); +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbItemSorter +* @deprecated +* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. +* @function +* @element ANY +* @restrict E +* @description A re-usable directive for sorting items +**/ + +function umbItemSorter(angularHelper) { + return { + scope: { + model: "=" + }, + restrict: "E", // restrict to an element + replace: true, // replace the html element with the template + templateUrl: 'views/directives/_obsolete/umb-item-sorter.html', + link: function(scope, element, attrs, ctrl) { + var defaultModel = { + okButton: "Ok", + successMsg: "Sorting successful", + complete: false + }; + //assign user vals to default + angular.extend(defaultModel, scope.model); + //re-assign merged to user + scope.model = defaultModel; + + scope.performSort = function() { + scope.$emit("umbItemSorter.sorting", { + sortedItems: scope.model.itemsToSort + }); + }; + + scope.handleCancel = function () { + scope.$emit("umbItemSorter.cancel"); + }; + + scope.handleOk = function() { + scope.$emit("umbItemSorter.ok"); + }; + + //defines the options for the jquery sortable + scope.sortableOptions = { + axis: 'y', + cursor: "move", + placeholder: "ui-sortable-placeholder", + update: function (ev, ui) { + //highlight the item when the position is changed + $(ui.item).effect("highlight", { color: "#049cdb" }, 500); + }, + stop: function (ev, ui) { + //the ui-sortable directive already ensures that our list is re-sorted, so now we just + // need to update the sortOrder to the index of each item + angularHelper.safeApply(scope, function () { + angular.forEach(scope.itemsToSort, function (val, index) { + val.sortOrder = index + 1; + }); + + }); + } + }; + } + }; +} + +angular.module('umbraco.directives').directive("umbItemSorter", umbItemSorter); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbcontentname.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbcontentname.directive.js index ec1d260d10..fd7145ff80 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbcontentname.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbcontentname.directive.js @@ -1,92 +1,92 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbContentName -* @deprecated -* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. -* @restrict E -* @function -* @description -* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. -**/ - -angular.module("umbraco.directives") - .directive('umbContentName', function ($timeout, localizationService) { - return { - require: "ngModel", - restrict: 'E', - replace: true, - templateUrl: 'views/directives/_obsolete/umb-content-name.html', - - scope: { - placeholder: '@placeholder', - model: '=ngModel', - ngDisabled: '=' - }, - link: function(scope, element, attrs, ngModel) { - - var inputElement = element.find("input"); - if(scope.placeholder && scope.placeholder[0] === "@"){ - localizationService.localize(scope.placeholder.substring(1)) - .then(function(value){ - scope.placeholder = value; - }); - } - - var mX, mY, distance; - - function calculateDistance(elem, mouseX, mouseY) { - - var cx = Math.max(Math.min(mouseX, elem.offset().left + elem.width()), elem.offset().left); - var cy = Math.max(Math.min(mouseY, elem.offset().top + elem.height()), elem.offset().top); - return Math.sqrt((mouseX - cx) * (mouseX - cx) + (mouseY - cy) * (mouseY - cy)); - } - - var mouseMoveDebounce = _.throttle(function (e) { - mX = e.pageX; - mY = e.pageY; - // not focused and not over element - if (!inputElement.is(":focus") && !inputElement.hasClass("ng-invalid")) { - // on page - if (mX >= inputElement.offset().left) { - distance = calculateDistance(inputElement, mX, mY); - if (distance <= 155) { - - distance = 1 - (100 / 150 * distance / 100); - inputElement.css("border", "1px solid rgba(175,175,175, " + distance + ")"); - inputElement.css("background-color", "rgba(255,255,255, " + distance + ")"); - } - } - - } - - }, 15); - - $(document).bind("mousemove", mouseMoveDebounce); - - $timeout(function(){ - if(!scope.model){ - scope.goEdit(); - } - }, 100, false); - - scope.goEdit = function(){ - scope.editMode = true; - - $timeout(function () { - inputElement.focus(); - }, 100, false); - }; - - scope.exitEdit = function(){ - if(scope.model && scope.model !== ""){ - scope.editMode = false; - } - }; - - //unbind doc event! - scope.$on('$destroy', function () { - $(document).unbind("mousemove", mouseMoveDebounce); - }); - } - }; - }); +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbContentName +* @deprecated +* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. +* @restrict E +* @function +* @description +* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. +**/ + +angular.module("umbraco.directives") + .directive('umbContentName', function ($timeout, localizationService) { + return { + require: "ngModel", + restrict: 'E', + replace: true, + templateUrl: 'views/directives/_obsolete/umb-content-name.html', + + scope: { + placeholder: '@placeholder', + model: '=ngModel', + ngDisabled: '=' + }, + link: function(scope, element, attrs, ngModel) { + + var inputElement = element.find("input"); + if(scope.placeholder && scope.placeholder[0] === "@"){ + localizationService.localize(scope.placeholder.substring(1)) + .then(function(value){ + scope.placeholder = value; + }); + } + + var mX, mY, distance; + + function calculateDistance(elem, mouseX, mouseY) { + + var cx = Math.max(Math.min(mouseX, elem.offset().left + elem.width()), elem.offset().left); + var cy = Math.max(Math.min(mouseY, elem.offset().top + elem.height()), elem.offset().top); + return Math.sqrt((mouseX - cx) * (mouseX - cx) + (mouseY - cy) * (mouseY - cy)); + } + + var mouseMoveDebounce = _.throttle(function (e) { + mX = e.pageX; + mY = e.pageY; + // not focused and not over element + if (!inputElement.is(":focus") && !inputElement.hasClass("ng-invalid")) { + // on page + if (mX >= inputElement.offset().left) { + distance = calculateDistance(inputElement, mX, mY); + if (distance <= 155) { + + distance = 1 - (100 / 150 * distance / 100); + inputElement.css("border", "1px solid rgba(175,175,175, " + distance + ")"); + inputElement.css("background-color", "rgba(255,255,255, " + distance + ")"); + } + } + + } + + }, 15); + + $(document).bind("mousemove", mouseMoveDebounce); + + $timeout(function(){ + if(!scope.model){ + scope.goEdit(); + } + }, 100, false); + + scope.goEdit = function(){ + scope.editMode = true; + + $timeout(function () { + inputElement.focus(); + }, 100, false); + }; + + scope.exitEdit = function(){ + if(scope.model && scope.model !== ""){ + scope.editMode = false; + } + }; + + //unbind doc event! + scope.$on('$destroy', function () { + $(document).unbind("mousemove", mouseMoveDebounce); + }); + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umblogin.directive.js index a4dbd5fd13..775c20181c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umblogin.directive.js @@ -1,19 +1,19 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbLogin -* @deprecated -* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. -* @function -* @element ANY -* @restrict E -**/ - -function loginDirective() { - return { - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/directives/_obsolete/umb-login.html' - }; -} - -angular.module('umbraco.directives').directive("umbLogin", loginDirective); +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbLogin +* @deprecated +* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. +* @function +* @element ANY +* @restrict E +**/ + +function loginDirective() { + return { + restrict: "E", // restrict to an element + replace: true, // replace the html element with the template + templateUrl: 'views/directives/_obsolete/umb-login.html' + }; +} + +angular.module('umbraco.directives').directive("umbLogin", loginDirective); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbphotofolder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbphotofolder.directive.js index 1c907dae9e..c6c628741f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbphotofolder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_obsolete/umbphotofolder.directive.js @@ -1,70 +1,70 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbPhotoFolder -* @deprecated -* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. -* @restrict E -**/ - -angular.module("umbraco.directives.html") - .directive('umbPhotoFolder', function($compile, $log, $timeout, $filter, umbPhotoFolderHelper) { - - return { - restrict: 'E', - replace: true, - require: '?ngModel', - terminate: true, - templateUrl: 'views/directives/_obsolete/umb-photo-folder.html', - link: function(scope, element, attrs, ngModel) { - - var lastWatch = null; - - ngModel.$render = function() { - if (ngModel.$modelValue) { - - $timeout(function() { - var photos = ngModel.$modelValue; - - scope.clickHandler = scope.$eval(element.attr('on-click')); - - - var imagesOnly = element.attr('images-only') === "true"; - - - var margin = element.attr('border') ? parseInt(element.attr('border'), 10) : 5; - var startingIndex = element.attr('baseline') ? parseInt(element.attr('baseline'), 10) : 0; - var minWidth = element.attr('min-width') ? parseInt(element.attr('min-width'), 10) : 420; - var minHeight = element.attr('min-height') ? parseInt(element.attr('min-height'), 10) : 100; - var maxHeight = element.attr('max-height') ? parseInt(element.attr('max-height'), 10) : 300; - var idealImgPerRow = element.attr('ideal-items-per-row') ? parseInt(element.attr('ideal-items-per-row'), 10) : 5; - var fixedRowWidth = Math.max(element.width(), minWidth); - - scope.containerStyle = { width: fixedRowWidth + "px" }; - scope.rows = umbPhotoFolderHelper.buildGrid(photos, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly); - - if (attrs.filterBy) { - - //we track the watches that we create, we don't want to create multiple, so clear it - // if it already exists before creating another. - if (lastWatch) { - lastWatch(); - } - - //TODO: Need to debounce this so it doesn't filter too often! - lastWatch = scope.$watch(attrs.filterBy, function (newVal, oldVal) { - if (newVal && newVal !== oldVal) { - var p = $filter('filter')(photos, newVal, false); - scope.baseline = 0; - var m = umbPhotoFolderHelper.buildGrid(p, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly); - scope.rows = m; - } - }); - } - - }, 500); //end timeout - } //end if modelValue - - }; //end $render - } - }; - }); +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbPhotoFolder +* @deprecated +* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. +* @restrict E +**/ + +angular.module("umbraco.directives.html") + .directive('umbPhotoFolder', function($compile, $log, $timeout, $filter, umbPhotoFolderHelper) { + + return { + restrict: 'E', + replace: true, + require: '?ngModel', + terminate: true, + templateUrl: 'views/directives/_obsolete/umb-photo-folder.html', + link: function(scope, element, attrs, ngModel) { + + var lastWatch = null; + + ngModel.$render = function() { + if (ngModel.$modelValue) { + + $timeout(function() { + var photos = ngModel.$modelValue; + + scope.clickHandler = scope.$eval(element.attr('on-click')); + + + var imagesOnly = element.attr('images-only') === "true"; + + + var margin = element.attr('border') ? parseInt(element.attr('border'), 10) : 5; + var startingIndex = element.attr('baseline') ? parseInt(element.attr('baseline'), 10) : 0; + var minWidth = element.attr('min-width') ? parseInt(element.attr('min-width'), 10) : 420; + var minHeight = element.attr('min-height') ? parseInt(element.attr('min-height'), 10) : 100; + var maxHeight = element.attr('max-height') ? parseInt(element.attr('max-height'), 10) : 300; + var idealImgPerRow = element.attr('ideal-items-per-row') ? parseInt(element.attr('ideal-items-per-row'), 10) : 5; + var fixedRowWidth = Math.max(element.width(), minWidth); + + scope.containerStyle = { width: fixedRowWidth + "px" }; + scope.rows = umbPhotoFolderHelper.buildGrid(photos, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly); + + if (attrs.filterBy) { + + //we track the watches that we create, we don't want to create multiple, so clear it + // if it already exists before creating another. + if (lastWatch) { + lastWatch(); + } + + //TODO: Need to debounce this so it doesn't filter too often! + lastWatch = scope.$watch(attrs.filterBy, function (newVal, oldVal) { + if (newVal && newVal !== oldVal) { + var p = $filter('filter')(photos, newVal, false); + scope.baseline = 0; + var m = umbPhotoFolderHelper.buildGrid(p, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly); + scope.rows = m; + } + }); + } + + }, 500); //end timeout + } //end if modelValue + + }; //end $render + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js index a21c9e4cf1..45c5c200cb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js @@ -1,22 +1,22 @@ -angular.module("umbraco.directives") -.directive('umbContextMenu', function (navigationService) { - return { - scope: { - menuDialogTitle: "@", - currentSection: "@", - currentNode: "=", - menuActions: "=" - }, - restrict: 'E', - replace: true, - templateUrl: 'views/components/application/umb-contextmenu.html', - link: function (scope, element, attrs, ctrl) { - - //adds a handler to the context menu item click, we need to handle this differently - //depending on what the menu item is supposed to do. - scope.executeMenuItem = function (action) { - navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection); - }; - } - }; -}); +angular.module("umbraco.directives") +.directive('umbContextMenu', function (navigationService) { + return { + scope: { + menuDialogTitle: "@", + currentSection: "@", + currentNode: "=", + menuActions: "=" + }, + restrict: 'E', + replace: true, + templateUrl: 'views/components/application/umb-contextmenu.html', + link: function (scope, element, attrs, ctrl) { + + //adds a handler to the context menu item click, we need to handle this differently + //depending on what the menu item is supposed to do. + scope.executeMenuItem = function (action) { + navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection); + }; + } + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbnavigation.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbnavigation.directive.js index baf0f59643..375ab34a64 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbnavigation.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbnavigation.directive.js @@ -1,14 +1,14 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbNavigation -* @restrict E -**/ -function umbNavigationDirective() { - return { - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/components/application/umb-navigation.html' - }; -} - -angular.module('umbraco.directives').directive("umbNavigation", umbNavigationDirective); +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbNavigation +* @restrict E +**/ +function umbNavigationDirective() { + return { + restrict: "E", // restrict to an element + replace: true, // replace the html element with the template + templateUrl: 'views/components/application/umb-navigation.html' + }; +} + +angular.module('umbraco.directives').directive("umbNavigation", umbNavigationDirective); 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 23664ed842..2793a0ec57 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 @@ -1,147 +1,147 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbSections -* @restrict E -**/ -function sectionsDirective($timeout, $window, navigationService, treeService, sectionService, appState, eventsService, $location, historyService) { - return { - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/components/application/umb-sections.html', - link: function (scope, element, attr, ctrl) { - - var sectionItemsWidth = []; - var evts = []; - var maxSections = 7; - - //setup scope vars - scope.maxSections = maxSections; - scope.overflowingSections = 0; - scope.sections = []; - scope.currentSection = appState.getSectionState("currentSection"); - scope.showTray = false; //appState.getGlobalState("showTray"); - scope.stickyNavigation = appState.getGlobalState("stickyNavigation"); - scope.needTray = false; - - function loadSections(){ - sectionService.getSectionsForUser() - .then(function (result) { - scope.sections = result; - // store the width of each section so we can hide/show them based on browser width - // we store them because the sections get removed from the dom and then we - // can't tell when to show them gain - $timeout(function(){ - $("#applications .sections li").each(function(index) { - sectionItemsWidth.push($(this).outerWidth()); - }); - }); - calculateWidth(); - }); - } - - function calculateWidth(){ - $timeout(function(){ - //total width minus room for avatar, search, and help icon - var windowWidth = $(window).width()-200; - var sectionsWidth = 0; - scope.totalSections = scope.sections.length; - scope.maxSections = maxSections; - scope.overflowingSections = 0; - scope.needTray = false; - - // detect how many sections we can show on the screen - for (var i = 0; i < sectionItemsWidth.length; i++) { - var sectionItemWidth = sectionItemsWidth[i]; - sectionsWidth += sectionItemWidth; - - if(sectionsWidth > windowWidth) { - scope.needTray = true; - scope.maxSections = i - 1; - scope.overflowingSections = scope.maxSections - scope.totalSections; - break; - } - } - }); - } - - //Listen for global state changes - evts.push(eventsService.on("appState.globalState.changed", function(e, args) { - if (args.key === "showTray") { - scope.showTray = args.value; - } - if (args.key === "stickyNavigation") { - scope.stickyNavigation = args.value; - } - })); - - evts.push(eventsService.on("appState.sectionState.changed", function(e, args) { - if (args.key === "currentSection") { - scope.currentSection = args.value; - } - })); - - evts.push(eventsService.on("app.reInitialize", function(e, args) { - //re-load the sections if we're re-initializing (i.e. package installed) - loadSections(); - })); - - //ensure to unregister from all events! - scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - - //on page resize - window.onresize = calculateWidth; - - scope.sectionClick = function (event, section) { - - if (event.ctrlKey || - event.shiftKey || - event.metaKey || // apple - (event.button && event.button === 1) // middle click, >IE9 + everyone else - ) { - return; - } - - if (scope.userDialog) { - closeUserDialog(); - } - - - navigationService.hideSearch(); - 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 { - var lastAccessed = historyService.getLastAccessedItemForSection(section.alias); - var path = lastAccessed != null ? lastAccessed.link : section.alias; - $location.path(path); +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbSections +* @restrict E +**/ +function sectionsDirective($timeout, $window, navigationService, treeService, sectionService, appState, eventsService, $location, historyService) { + return { + restrict: "E", // restrict to an element + replace: true, // replace the html element with the template + templateUrl: 'views/components/application/umb-sections.html', + link: function (scope, element, attr, ctrl) { + + var sectionItemsWidth = []; + var evts = []; + var maxSections = 7; + + //setup scope vars + scope.maxSections = maxSections; + scope.overflowingSections = 0; + scope.sections = []; + scope.currentSection = appState.getSectionState("currentSection"); + scope.showTray = false; //appState.getGlobalState("showTray"); + scope.stickyNavigation = appState.getGlobalState("stickyNavigation"); + scope.needTray = false; + + function loadSections(){ + sectionService.getSectionsForUser() + .then(function (result) { + scope.sections = result; + // store the width of each section so we can hide/show them based on browser width + // we store them because the sections get removed from the dom and then we + // can't tell when to show them gain + $timeout(function(){ + $("#applications .sections li").each(function(index) { + sectionItemsWidth.push($(this).outerWidth()); + }); + }); + calculateWidth(); + }); + } + + function calculateWidth(){ + $timeout(function(){ + //total width minus room for avatar, search, and help icon + var windowWidth = $(window).width()-200; + var sectionsWidth = 0; + scope.totalSections = scope.sections.length; + scope.maxSections = maxSections; + scope.overflowingSections = 0; + scope.needTray = false; + + // detect how many sections we can show on the screen + for (var i = 0; i < sectionItemsWidth.length; i++) { + var sectionItemWidth = sectionItemsWidth[i]; + sectionsWidth += sectionItemWidth; + + if(sectionsWidth > windowWidth) { + scope.needTray = true; + scope.maxSections = i - 1; + scope.overflowingSections = scope.maxSections - scope.totalSections; + break; + } + } + }); + } + + //Listen for global state changes + evts.push(eventsService.on("appState.globalState.changed", function(e, args) { + if (args.key === "showTray") { + scope.showTray = args.value; } - navigationService.clearSearch(); - - }; - - scope.sectionDblClick = function(section){ - navigationService.reloadSection(section.alias); - }; - - scope.trayClick = function () { - if (appState.getGlobalState("showTray") === true) { - navigationService.hideTray(); - } else { - navigationService.showTray(); - } - }; - - loadSections(); - - } - }; -} - -angular.module('umbraco.directives').directive("umbSections", sectionsDirective); + if (args.key === "stickyNavigation") { + scope.stickyNavigation = args.value; + } + })); + + evts.push(eventsService.on("appState.sectionState.changed", function(e, args) { + if (args.key === "currentSection") { + scope.currentSection = args.value; + } + })); + + evts.push(eventsService.on("app.reInitialize", function(e, args) { + //re-load the sections if we're re-initializing (i.e. package installed) + loadSections(); + })); + + //ensure to unregister from all events! + scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + + //on page resize + window.onresize = calculateWidth; + + scope.sectionClick = function (event, section) { + + if (event.ctrlKey || + event.shiftKey || + event.metaKey || // apple + (event.button && event.button === 1) // middle click, >IE9 + everyone else + ) { + return; + } + + if (scope.userDialog) { + closeUserDialog(); + } + + + navigationService.hideSearch(); + 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 { + var lastAccessed = historyService.getLastAccessedItemForSection(section.alias); + var path = lastAccessed != null ? lastAccessed.link : section.alias; + $location.path(path); + } + navigationService.clearSearch(); + + }; + + scope.sectionDblClick = function(section){ + navigationService.reloadSection(section.alias); + }; + + scope.trayClick = function () { + if (appState.getGlobalState("showTray") === true) { + navigationService.hideTray(); + } else { + navigationService.showTray(); + } + }; + + loadSections(); + + } + }; +} + +angular.module('umbraco.directives').directive("umbSections", sectionsDirective); 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 886f7be4af..6717b23061 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 @@ -1,55 +1,55 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbControlGroup -* @restrict E -**/ -angular.module("umbraco.directives.html") - .directive('umbControlGroup', function (localizationService) { - return { - scope: { - label: "@label", - description: "@", - hideLabel: "@", - alias: "@", - labelFor: "@", - required: "@?" - }, - require: '?^^form', - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/html/umb-control-group.html', - link: function (scope, element, attr, formCtrl) { - - scope.formValid = function () { - if (formCtrl && scope.labelFor) { - //if a label-for has been set, use that for the validation - return formCtrl[scope.labelFor].$valid; - } - //there is no form. - return true; - }; - - if (scope.label && scope.label[0] === "@") { - localizationService.localize(scope.label.substring(1)) - .then(function(data){ - scope.labelstring = data; - }); - } - else { - scope.labelstring = scope.label; - } - - if (scope.description && scope.description[0] === "@") { - localizationService.localize(scope.description.substring(1)) - .then(function(data){ - scope.descriptionstring = data; - }); - } - else { - scope.descriptionstring = scope.description; - } - - } - }; - }); +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbControlGroup +* @restrict E +**/ +angular.module("umbraco.directives.html") + .directive('umbControlGroup', function (localizationService) { + return { + scope: { + label: "@label", + description: "@", + hideLabel: "@", + alias: "@", + labelFor: "@", + required: "@?" + }, + require: '?^^form', + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/html/umb-control-group.html', + link: function (scope, element, attr, formCtrl) { + + scope.formValid = function () { + if (formCtrl && scope.labelFor) { + //if a label-for has been set, use that for the validation + return formCtrl[scope.labelFor].$valid; + } + //there is no form. + return true; + }; + + if (scope.label && scope.label[0] === "@") { + localizationService.localize(scope.label.substring(1)) + .then(function(data){ + scope.labelstring = data; + }); + } + else { + scope.labelstring = scope.label; + } + + if (scope.description && scope.description[0] === "@") { + localizationService.localize(scope.description.substring(1)) + .then(function(data){ + scope.descriptionstring = data; + }); + } + else { + scope.descriptionstring = scope.description; + } + + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbpanel.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbpanel.directive.js index 7c69437f4a..569cf17ed8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbpanel.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbpanel.directive.js @@ -1,14 +1,14 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbPanel -* @restrict E -**/ -angular.module("umbraco.directives.html") - .directive('umbPanel', function($timeout, $log){ - return { - restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/components/html/umb-panel.html' - }; - }); +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbPanel +* @restrict E +**/ +angular.module("umbraco.directives.html") + .directive('umbPanel', function($timeout, $log){ + return { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/components/html/umb-panel.html' + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/notifications/umbnotifications.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/notifications/umbnotifications.directive.js index d9f8f76576..3b74693d65 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/notifications/umbnotifications.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/notifications/umbnotifications.directive.js @@ -1,36 +1,36 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:umbNotifications - */ - -(function() { - 'use strict'; - - function NotificationDirective(notificationsService) { - - function link(scope, el, attr, ctrl) { - - //subscribes to notifications in the notification service - scope.notifications = notificationsService.current; - scope.$watch('notificationsService.current', function (newVal, oldVal, scope) { - if (newVal) { - scope.notifications = newVal; - } - }); - - } - - var directive = { - restrict: "E", - replace: true, - templateUrl: 'views/components/notifications/umb-notifications.html', - link: link - }; - - return directive; - - } - - angular.module('umbraco.directives').directive('umbNotifications', NotificationDirective); - -})(); +/** + * @ngdoc directive + * @name umbraco.directives.directive:umbNotifications + */ + +(function() { + 'use strict'; + + function NotificationDirective(notificationsService) { + + function link(scope, el, attr, ctrl) { + + //subscribes to notifications in the notification service + scope.notifications = notificationsService.current; + scope.$watch('notificationsService.current', function (newVal, oldVal, scope) { + if (newVal) { + scope.notifications = newVal; + } + }); + + } + + var directive = { + restrict: "E", + replace: true, + templateUrl: 'views/components/notifications/umb-notifications.html', + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbNotifications', NotificationDirective); + +})(); 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 03464eb066..93c9ff8c29 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 @@ -1,368 +1,368 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbTree -* @restrict E -**/ -function umbTreeDirective($q, $rootScope, treeService, notificationsService, userService) { - - return { - restrict: 'E', - replace: true, - terminal: false, - templateUrl: 'views/components/tree/umb-tree.html', - scope: { - section: '@', - treealias: '@', - hideoptions: '@', - hideheader: '@', - cachekey: '@', - isdialog: '@', - onlyInitialized: '@', - //Custom query string arguments to pass in to the tree as a string, example: "startnodeid=123&something=value" - customtreeparams: '@', - enablecheckboxes: '@', - enablelistviewsearch: '@', - enablelistviewexpand: '@', - api: '=?', - onInit: '&?' - }, - controller: function ($scope, $element) { - - var vm = this; - - var registeredCallbacks = { - treeNodeExpanded: [], - treeNodeSelect: [], - treeLoaded: [], - treeSynced: [], - treeOptionsClick: [], - treeNodeAltSelect: [] - }; - - //this is the API exposed by this directive, for either hosting controllers or for other directives - vm.callbacks = { - treeNodeExpanded: function (f) { - registeredCallbacks.treeNodeExpanded.push(f); - }, - treeNodeSelect: function (f) { - registeredCallbacks.treeNodeSelect.push(f); - }, - treeLoaded: function (f) { - registeredCallbacks.treeLoaded.push(f); - }, - treeSynced: function (f) { - registeredCallbacks.treeSynced.push(f); - }, - treeOptionsClick: function (f) { - registeredCallbacks.treeOptionsClick.push(f); - }, - treeNodeAltSelect: function (f) { - registeredCallbacks.treeNodeAltSelect.push(f); - } - }; - vm.emitEvent = emitEvent; - vm.load = load; - vm.reloadNode = reloadNode; - vm.syncTree = syncTree; - vm.loadChildren = loadChildren; - - //wire up the exposed api object for hosting controllers - if ($scope.api) { - $scope.api.callbacks = vm.callbacks; - $scope.api.load = vm.load; - $scope.api.reloadNode = vm.reloadNode; - $scope.api.syncTree = vm.syncTree; - } - - //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should - // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover - // outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the - // entire tree again since we already still have it in memory. Of course if the section is different we will - // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times - // since it saves on data retreival and DOM processing. - var lastSection = ""; - - /** Helper function to emit tree events */ - function emitEvent(eventName, args) { - if (registeredCallbacks[eventName] && angular.isArray(registeredCallbacks[eventName])) { - _.each(registeredCallbacks[eventName], function (c) { - c(args);//call it - }); - } - } - - function clearCache(section) { - treeService.clearCache({ section: section }); - } - - /** - * Re-loads the tree with the updated parameters - * @param {any} args either a string representing the 'section' or an object containing: 'section', 'treeAlias', 'customTreeParams', 'cacheKey' - */ - function load(args) { - if (angular.isString(args)) { - $scope.section = args; - } - else if (args) { - if (args.section) { - $scope.section = args.section; - } - if (args.customTreeParams) { - $scope.customtreeparams = args.customTreeParams; - } - if (args.treeAlias) { - $scope.treealias = args.treeAlias; - } - if (args.cacheKey) { - $scope.cachekey = args.cacheKey; - } - } - - return loadTree(); - } - - function reloadNode(node) { - - if (!node) { - node = $scope.currentNode; - } - - if (node) { - return $scope.loadChildren(node, true); - } - - return $q.reject(); - } - - /** - * Used to do the tree syncing - * @param {any} args - * @returns a promise with an object containing 'node' and 'activate' - */ - function syncTree(args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.path) { - throw "args.path cannot be null"; - } - - if (angular.isString(args.path)) { - args.path = args.path.replace('"', '').split(','); - } - - //Filter the path for root node ids (we don't want to pass in -1 or 'init') - - args.path = _.filter(args.path, function (item) { return (item !== "init" && item !== "-1"); }); - - var treeNode = loadActiveTree(args.tree); - - return treeService.syncTree({ - node: treeNode, - path: args.path, - forceReload: args.forceReload - }).then(function (data) { - - if (args.activate === undefined || args.activate === true) { - $scope.currentNode = data; - } - - emitEvent("treeSynced", { node: data, activate: args.activate }); - - return $q.when({ node: data, activate: args.activate }); - }); - - } - - //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node - function loadActiveTree(treeAlias) { - - if (!$scope.tree) { - throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null"; - } - - //if its not specified, it should have been specified before - if (!treeAlias) { - if (!$scope.activeTree) { - throw "Err in umbtree.directive.loadActiveTree, $scope.activeTree is null"; - } - return $scope.activeTree; - } - - var childrenAndSelf = [$scope.tree.root].concat($scope.tree.root.children); - $scope.activeTree = _.find(childrenAndSelf, function (node) { - if (node && node.metaData && node.metaData.treeAlias) { - return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); - } - return false; - }); - - if (!$scope.activeTree) { - throw "Could not find the tree " + treeAlias; - } - - emitEvent("activeTreeLoaded", { tree: $scope.activeTree }); - - return $scope.activeTree; - } - - /** Method to load in the tree data */ - - function loadTree() { - if (!$scope.loading && $scope.section) { - $scope.loading = true; - - //default args - var args = { section: $scope.section, tree: $scope.treealias, cacheKey: $scope.cachekey, isDialog: $scope.isdialog ? $scope.isdialog : false, onlyinitialized: $scope.onlyInitialized }; - - //add the extra query string params if specified - if ($scope.customtreeparams) { - args["queryString"] = $scope.customtreeparams; - } - - return treeService.getTree(args) - .then(function (data) { - - //set the data once we have it - $scope.tree = data; - - $scope.loading = false; - - //set the root as the current active tree - $scope.activeTree = $scope.tree.root; - - emitEvent("treeLoaded", { tree: $scope.tree }); - emitEvent("treeNodeExpanded", { tree: $scope.tree, node: $scope.tree.root, children: $scope.tree.root.children }); - return $q.when(data); - }, function (reason) { - $scope.loading = false; - notificationsService.error("Tree Error", reason); - return $q.reject(reason); - }); - } - else { - return $q.reject(); - } - } - - function loadChildren(node, forceReload) { - //emit treeNodeExpanding event, if a callback object is set on the tree - emitEvent("treeNodeExpanding", { tree: $scope.tree, node: node }); - - //standardising - if (!node.children) { - node.children = []; - } - - if (forceReload || (node.hasChildren && node.children.length === 0)) { - //get the children from the tree service - return treeService.loadNodeChildren({ node: node, section: $scope.section }) - .then(function(data) { - //emit expanded event - emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: data }); - - return $q.when(data); - }); - } - else { - emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: node.children }); - node.expanded = true; - - return $q.when(node.children); - } - } - - /** 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 ? - 'icon umb-tree-icon sprTree icon-check green temporary' : - '' : - ''; - }; - - /* helper to force reloading children of a tree node */ - $scope.loadChildren = function(node, forceReload) { - return loadChildren(node, forceReload); - }; - - /** - Method called when the options button next to the root node is called. - The tree doesnt know about this, so it raises an event to tell the parent controller - about it. - */ - $scope.options = function (n, ev) { - emitEvent("treeOptionsClick", { element: $element, node: n, event: ev }); - }; - - /** - Method called when an item is clicked in the tree, this passes the - DOM element, the tree node object and the original click - and emits it as a treeNodeSelect element if there is a callback object - 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 - $scope.currentNode = null; - - emitEvent("treeNodeSelect", { element: $element, node: n, event: ev }); - }; - - $scope.altSelect = function (n, ev) { - emitEvent("treeNodeAltSelect", { element: $element, tree: $scope.tree, node: n, event: ev }); - }; - - //call the onInit method, if the result is a promise then load the tree after that resolves (if it's not a promise this will just resolve automatically). - //NOTE: The promise cannot be rejected, else the tree won't be loaded and we'll get exceptions if some API calls syncTree or similar. - $q.when($scope.onInit(), function (args) { - - //the promise resolution can pass in parameters - if (args) { - if (args.section) { - $scope.section = args.section; - } - if (args.cacheKey) { - $scope.cachekey = args.cacheKey; - } - } - - //load the tree - loadTree().then(function () { - //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else - //like normal JS promises we could do resolve(...).then() - if (args && args.onLoaded && angular.isFunction(args.onLoaded)) { - args.onLoaded(); - } - }); - }); - } - }; -} - -angular.module("umbraco.directives").directive('umbTree', umbTreeDirective); +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbTree +* @restrict E +**/ +function umbTreeDirective($q, $rootScope, treeService, notificationsService, userService) { + + return { + restrict: 'E', + replace: true, + terminal: false, + templateUrl: 'views/components/tree/umb-tree.html', + scope: { + section: '@', + treealias: '@', + hideoptions: '@', + hideheader: '@', + cachekey: '@', + isdialog: '@', + onlyInitialized: '@', + //Custom query string arguments to pass in to the tree as a string, example: "startnodeid=123&something=value" + customtreeparams: '@', + enablecheckboxes: '@', + enablelistviewsearch: '@', + enablelistviewexpand: '@', + api: '=?', + onInit: '&?' + }, + controller: function ($scope, $element) { + + var vm = this; + + var registeredCallbacks = { + treeNodeExpanded: [], + treeNodeSelect: [], + treeLoaded: [], + treeSynced: [], + treeOptionsClick: [], + treeNodeAltSelect: [] + }; + + //this is the API exposed by this directive, for either hosting controllers or for other directives + vm.callbacks = { + treeNodeExpanded: function (f) { + registeredCallbacks.treeNodeExpanded.push(f); + }, + treeNodeSelect: function (f) { + registeredCallbacks.treeNodeSelect.push(f); + }, + treeLoaded: function (f) { + registeredCallbacks.treeLoaded.push(f); + }, + treeSynced: function (f) { + registeredCallbacks.treeSynced.push(f); + }, + treeOptionsClick: function (f) { + registeredCallbacks.treeOptionsClick.push(f); + }, + treeNodeAltSelect: function (f) { + registeredCallbacks.treeNodeAltSelect.push(f); + } + }; + vm.emitEvent = emitEvent; + vm.load = load; + vm.reloadNode = reloadNode; + vm.syncTree = syncTree; + vm.loadChildren = loadChildren; + + //wire up the exposed api object for hosting controllers + if ($scope.api) { + $scope.api.callbacks = vm.callbacks; + $scope.api.load = vm.load; + $scope.api.reloadNode = vm.reloadNode; + $scope.api.syncTree = vm.syncTree; + } + + //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should + // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover + // outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the + // entire tree again since we already still have it in memory. Of course if the section is different we will + // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times + // since it saves on data retreival and DOM processing. + var lastSection = ""; + + /** Helper function to emit tree events */ + function emitEvent(eventName, args) { + if (registeredCallbacks[eventName] && angular.isArray(registeredCallbacks[eventName])) { + _.each(registeredCallbacks[eventName], function (c) { + c(args);//call it + }); + } + } + + function clearCache(section) { + treeService.clearCache({ section: section }); + } + + /** + * Re-loads the tree with the updated parameters + * @param {any} args either a string representing the 'section' or an object containing: 'section', 'treeAlias', 'customTreeParams', 'cacheKey' + */ + function load(args) { + if (angular.isString(args)) { + $scope.section = args; + } + else if (args) { + if (args.section) { + $scope.section = args.section; + } + if (args.customTreeParams) { + $scope.customtreeparams = args.customTreeParams; + } + if (args.treeAlias) { + $scope.treealias = args.treeAlias; + } + if (args.cacheKey) { + $scope.cachekey = args.cacheKey; + } + } + + return loadTree(); + } + + function reloadNode(node) { + + if (!node) { + node = $scope.currentNode; + } + + if (node) { + return $scope.loadChildren(node, true); + } + + return $q.reject(); + } + + /** + * Used to do the tree syncing + * @param {any} args + * @returns a promise with an object containing 'node' and 'activate' + */ + function syncTree(args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.path) { + throw "args.path cannot be null"; + } + + if (angular.isString(args.path)) { + args.path = args.path.replace('"', '').split(','); + } + + //Filter the path for root node ids (we don't want to pass in -1 or 'init') + + args.path = _.filter(args.path, function (item) { return (item !== "init" && item !== "-1"); }); + + var treeNode = loadActiveTree(args.tree); + + return treeService.syncTree({ + node: treeNode, + path: args.path, + forceReload: args.forceReload + }).then(function (data) { + + if (args.activate === undefined || args.activate === true) { + $scope.currentNode = data; + } + + emitEvent("treeSynced", { node: data, activate: args.activate }); + + return $q.when({ node: data, activate: args.activate }); + }); + + } + + //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node + function loadActiveTree(treeAlias) { + + if (!$scope.tree) { + throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null"; + } + + //if its not specified, it should have been specified before + if (!treeAlias) { + if (!$scope.activeTree) { + throw "Err in umbtree.directive.loadActiveTree, $scope.activeTree is null"; + } + return $scope.activeTree; + } + + var childrenAndSelf = [$scope.tree.root].concat($scope.tree.root.children); + $scope.activeTree = _.find(childrenAndSelf, function (node) { + if (node && node.metaData && node.metaData.treeAlias) { + return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); + } + return false; + }); + + if (!$scope.activeTree) { + throw "Could not find the tree " + treeAlias; + } + + emitEvent("activeTreeLoaded", { tree: $scope.activeTree }); + + return $scope.activeTree; + } + + /** Method to load in the tree data */ + + function loadTree() { + if (!$scope.loading && $scope.section) { + $scope.loading = true; + + //default args + var args = { section: $scope.section, tree: $scope.treealias, cacheKey: $scope.cachekey, isDialog: $scope.isdialog ? $scope.isdialog : false, onlyinitialized: $scope.onlyInitialized }; + + //add the extra query string params if specified + if ($scope.customtreeparams) { + args["queryString"] = $scope.customtreeparams; + } + + return treeService.getTree(args) + .then(function (data) { + + //set the data once we have it + $scope.tree = data; + + $scope.loading = false; + + //set the root as the current active tree + $scope.activeTree = $scope.tree.root; + + emitEvent("treeLoaded", { tree: $scope.tree }); + emitEvent("treeNodeExpanded", { tree: $scope.tree, node: $scope.tree.root, children: $scope.tree.root.children }); + return $q.when(data); + }, function (reason) { + $scope.loading = false; + notificationsService.error("Tree Error", reason); + return $q.reject(reason); + }); + } + else { + return $q.reject(); + } + } + + function loadChildren(node, forceReload) { + //emit treeNodeExpanding event, if a callback object is set on the tree + emitEvent("treeNodeExpanding", { tree: $scope.tree, node: node }); + + //standardising + if (!node.children) { + node.children = []; + } + + if (forceReload || (node.hasChildren && node.children.length === 0)) { + //get the children from the tree service + return treeService.loadNodeChildren({ node: node, section: $scope.section }) + .then(function(data) { + //emit expanded event + emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: data }); + + return $q.when(data); + }); + } + else { + emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: node.children }); + node.expanded = true; + + return $q.when(node.children); + } + } + + /** 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 ? + 'icon umb-tree-icon sprTree icon-check green temporary' : + '' : + ''; + }; + + /* helper to force reloading children of a tree node */ + $scope.loadChildren = function(node, forceReload) { + return loadChildren(node, forceReload); + }; + + /** + Method called when the options button next to the root node is called. + The tree doesnt know about this, so it raises an event to tell the parent controller + about it. + */ + $scope.options = function (n, ev) { + emitEvent("treeOptionsClick", { element: $element, node: n, event: ev }); + }; + + /** + Method called when an item is clicked in the tree, this passes the + DOM element, the tree node object and the original click + and emits it as a treeNodeSelect element if there is a callback object + 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 + $scope.currentNode = null; + + emitEvent("treeNodeSelect", { element: $element, node: n, event: ev }); + }; + + $scope.altSelect = function (n, ev) { + emitEvent("treeNodeAltSelect", { element: $element, tree: $scope.tree, node: n, event: ev }); + }; + + //call the onInit method, if the result is a promise then load the tree after that resolves (if it's not a promise this will just resolve automatically). + //NOTE: The promise cannot be rejected, else the tree won't be loaded and we'll get exceptions if some API calls syncTree or similar. + $q.when($scope.onInit(), function (args) { + + //the promise resolution can pass in parameters + if (args) { + if (args.section) { + $scope.section = args.section; + } + if (args.cacheKey) { + $scope.cachekey = args.cacheKey; + } + } + + //load the tree + loadTree().then(function () { + //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else + //like normal JS promises we could do resolve(...).then() + if (args && args.onLoaded && angular.isFunction(args.onLoaded)) { + args.onLoaded(); + } + }); + }); + } + }; +} + +angular.module("umbraco.directives").directive('umbTree', umbTreeDirective); 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 3243b5eae1..bfd4888def 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 @@ -1,206 +1,206 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:umbTreeItem - * @element li - * @function - * - * @description - * Renders a list item, representing a single node in the tree. - * Includes element to toggle children, and a menu toggling button - * - * **note:** This directive is only used internally in the umbTree directive - * - * @example - - - - - - */ -angular.module("umbraco.directives") - .directive('umbTreeItem', function(treeService, $timeout, localizationService, eventsService, appState) { - return { - restrict: 'E', - replace: true, - require: '^umbTree', - templateUrl: 'views/components/tree/umb-tree-item.html', - scope: { - section: '@', - currentNode: '=', - enablelistviewexpand: '@', - node: '=', - tree: '=' - }, - - link: function (scope, element, attrs, umbTreeCtrl) { - - localizationService.localize("general_search").then(function (value) { - scope.searchAltText = value; - }); - - // updates the node's DOM/styles - function setupNodeDom(node, tree) { - - //get the first div element - element.children(":first") - //set the padding - .css("padding-left", (node.level * 20) + "px"); - - // add a unique data element to each tree item so it is easy to navigate with code - if(!node.metaData.treeAlias) { - node.dataElement = node.name; - } else { - node.dataElement = node.metaData.treeAlias; - } - - } - - /** 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); - }); - } - if (node.selected) { - css.push("umb-tree-node-checked"); - } - - return css.join(" "); - }; - - //add a method to the node which we can use to call to update the node data if we need to , - // this is done by sync tree, we don't want to add a $watch for each node as that would be crazy insane slow - // so we have to do this - scope.node.updateNodeData = function (newNode) { - _.extend(scope.node, newNode); - //now update the styles - setupNodeDom(scope.node, scope.tree); - }; - - /** - Method called when the options button next to a node is called - In the main tree this opens the menu, but internally the tree doesnt - know about this, so it simply raises an event to tell the parent controller - about it. - */ - scope.options = function (n, ev) { - umbTreeCtrl.emitEvent("treeOptionsClick", { element: element, tree: scope.tree, node: n, event: ev }); - }; - - /** - Method called when an item is clicked in the tree, this passes the - DOM element, the tree node object and the original click - and emits it as a treeNodeSelect element if there is a callback object - defined on the tree - */ - scope.select = function (n, ev) { - if (ev.ctrlKey || - ev.shiftKey || - ev.metaKey || // apple - (ev.button && ev.button === 1) // middle click, >IE9 + everyone else - ) { - return; - } - - if (n.metaData && n.metaData.noAccess === true) { - ev.preventDefault(); - return; - } - - umbTreeCtrl.emitEvent("treeNodeSelect", { element: element, tree: scope.tree, node: n, event: ev }); - ev.preventDefault(); - }; - - /** - Method called when an item is right-clicked in the tree, this passes the - DOM element, the tree node object and the original click - and emits it as a treeNodeSelect element if there is a callback object - defined on the tree - */ - scope.altSelect = function(n, ev) { - umbTreeCtrl.emitEvent("treeNodeAltSelect", { element: element, tree: scope.tree, node: n, event: ev }); - }; - - /** - Method called when a node in the tree is expanded, when clicking the arrow - takes the arrow DOM element and node data as parameters - emits treeNodeCollapsing event if already expanded and treeNodeExpanding if collapsed - */ - scope.load = function (node) { - if (node.expanded && !node.metaData.isContainer) { - umbTreeCtrl.emitEvent("treeNodeCollapsing", { tree: scope.tree, node: node, element: element }); - node.expanded = false; - } - else { - scope.loadChildren(node, false); - } - }; - - /* helper to force reloading children of a tree node */ - scope.loadChildren = function(node, forceReload) { - return umbTreeCtrl.loadChildren(node, forceReload); - }; - - //if the current path contains the node id, we will auto-expand the tree item children - 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 evts = []; - - //listen for section changes - evts.push(eventsService.on("appState.sectionState.changed", function(e, args) { - if (args.key === "currentSection") { - //when the section changes disable all delete animations - scope.node.deleteAnimations = false; - } - })); - - /** Depending on if any menu is shown and if the menu is shown for the current node, toggle delete animations */ - function toggleDeleteAnimations() { - //if both are false then remove animations - var hide = !appState.getMenuState("showMenuDialog") && !appState.getMenuState("showMenu"); - if (hide) { - scope.node.deleteAnimations = false; - } - else { - //enable animations for this node if it is the node currently showing a context menu - var currentNode = appState.getMenuState("currentNode"); - if (currentNode && currentNode.id == scope.node.id) { - scope.node.deleteAnimations = true; - } - else { - scope.node.deleteAnimations = false; - } - } - } - - //listen for context menu and current node changes - evts.push(eventsService.on("appState.menuState.changed", function(e, args) { - if (args.key === "showMenuDialog" || args.key == "showMenu" || args.key == "currentNode") { - toggleDeleteAnimations(); - } - })); - - //cleanup events - scope.$on('$destroy', function() { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - } - }; -}); +/** + * @ngdoc directive + * @name umbraco.directives.directive:umbTreeItem + * @element li + * @function + * + * @description + * Renders a list item, representing a single node in the tree. + * Includes element to toggle children, and a menu toggling button + * + * **note:** This directive is only used internally in the umbTree directive + * + * @example + + + + + + */ +angular.module("umbraco.directives") + .directive('umbTreeItem', function(treeService, $timeout, localizationService, eventsService, appState) { + return { + restrict: 'E', + replace: true, + require: '^umbTree', + templateUrl: 'views/components/tree/umb-tree-item.html', + scope: { + section: '@', + currentNode: '=', + enablelistviewexpand: '@', + node: '=', + tree: '=' + }, + + link: function (scope, element, attrs, umbTreeCtrl) { + + localizationService.localize("general_search").then(function (value) { + scope.searchAltText = value; + }); + + // updates the node's DOM/styles + function setupNodeDom(node, tree) { + + //get the first div element + element.children(":first") + //set the padding + .css("padding-left", (node.level * 20) + "px"); + + // add a unique data element to each tree item so it is easy to navigate with code + if(!node.metaData.treeAlias) { + node.dataElement = node.name; + } else { + node.dataElement = node.metaData.treeAlias; + } + + } + + /** 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); + }); + } + if (node.selected) { + css.push("umb-tree-node-checked"); + } + + return css.join(" "); + }; + + //add a method to the node which we can use to call to update the node data if we need to , + // this is done by sync tree, we don't want to add a $watch for each node as that would be crazy insane slow + // so we have to do this + scope.node.updateNodeData = function (newNode) { + _.extend(scope.node, newNode); + //now update the styles + setupNodeDom(scope.node, scope.tree); + }; + + /** + Method called when the options button next to a node is called + In the main tree this opens the menu, but internally the tree doesnt + know about this, so it simply raises an event to tell the parent controller + about it. + */ + scope.options = function (n, ev) { + umbTreeCtrl.emitEvent("treeOptionsClick", { element: element, tree: scope.tree, node: n, event: ev }); + }; + + /** + Method called when an item is clicked in the tree, this passes the + DOM element, the tree node object and the original click + and emits it as a treeNodeSelect element if there is a callback object + defined on the tree + */ + scope.select = function (n, ev) { + if (ev.ctrlKey || + ev.shiftKey || + ev.metaKey || // apple + (ev.button && ev.button === 1) // middle click, >IE9 + everyone else + ) { + return; + } + + if (n.metaData && n.metaData.noAccess === true) { + ev.preventDefault(); + return; + } + + umbTreeCtrl.emitEvent("treeNodeSelect", { element: element, tree: scope.tree, node: n, event: ev }); + ev.preventDefault(); + }; + + /** + Method called when an item is right-clicked in the tree, this passes the + DOM element, the tree node object and the original click + and emits it as a treeNodeSelect element if there is a callback object + defined on the tree + */ + scope.altSelect = function(n, ev) { + umbTreeCtrl.emitEvent("treeNodeAltSelect", { element: element, tree: scope.tree, node: n, event: ev }); + }; + + /** + Method called when a node in the tree is expanded, when clicking the arrow + takes the arrow DOM element and node data as parameters + emits treeNodeCollapsing event if already expanded and treeNodeExpanding if collapsed + */ + scope.load = function (node) { + if (node.expanded && !node.metaData.isContainer) { + umbTreeCtrl.emitEvent("treeNodeCollapsing", { tree: scope.tree, node: node, element: element }); + node.expanded = false; + } + else { + scope.loadChildren(node, false); + } + }; + + /* helper to force reloading children of a tree node */ + scope.loadChildren = function(node, forceReload) { + return umbTreeCtrl.loadChildren(node, forceReload); + }; + + //if the current path contains the node id, we will auto-expand the tree item children + 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 evts = []; + + //listen for section changes + evts.push(eventsService.on("appState.sectionState.changed", function(e, args) { + if (args.key === "currentSection") { + //when the section changes disable all delete animations + scope.node.deleteAnimations = false; + } + })); + + /** Depending on if any menu is shown and if the menu is shown for the current node, toggle delete animations */ + function toggleDeleteAnimations() { + //if both are false then remove animations + var hide = !appState.getMenuState("showMenuDialog") && !appState.getMenuState("showMenu"); + if (hide) { + scope.node.deleteAnimations = false; + } + else { + //enable animations for this node if it is the node currently showing a context menu + var currentNode = appState.getMenuState("currentNode"); + if (currentNode && currentNode.id == scope.node.id) { + scope.node.deleteAnimations = true; + } + else { + scope.node.deleteAnimations = false; + } + } + } + + //listen for context menu and current node changes + evts.push(eventsService.on("appState.menuState.changed", function(e, args) { + if (args.key === "showMenuDialog" || args.key == "showMenu" || args.key == "currentNode") { + toggleDeleteAnimations(); + } + })); + + //cleanup events + scope.$on('$destroy', function() { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + } + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js index 507032e4b4..18e08bb973 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js @@ -1,73 +1,73 @@ -/** -@ngdoc directive -@name umbraco.directives.directive:umbConfirm -@restrict E -@scope - -@description -A confirmation dialog - - -

    Markup example

    -
    -	
    - - - -
    -
    - -

    Controller example

    -
    -	(function () {
    -		"use strict";
    -
    -		function Controller() {
    -
    -            var vm = this;
    -
    -            vm.onConfirm = function() {
    -                alert('Confirm clicked');
    -            };
    -
    -            vm.onCancel = function() {
    -                alert('Cancel clicked');
    -            }
    -
    -
    -        }
    -
    -		angular.module("umbraco").controller("My.Controller", Controller);
    -
    -	})();
    -
    - -@param {string} caption (attribute): The caption shown above the buttons -@param {callback} on-confirm (attribute): The call back when the "OK" button is clicked. If not set the button will not be shown -@param {callback} on-cancel (atribute): The call back when the "Cancel" button is clicked. If not set the button will not be shown -**/ -function confirmDirective() { - return { - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/components/umb-confirm.html', - scope: { - onConfirm: '=', - onCancel: '=', - caption: '@' - }, - link: function (scope, element, attr, ctrl) { - scope.showCancel = false; - scope.showConfirm = false; - - if (scope.onConfirm) { - scope.showConfirm = true; - } - - if (scope.onCancel) { - scope.showCancel = true; - } - } - }; -} -angular.module('umbraco.directives').directive("umbConfirm", confirmDirective); +/** +@ngdoc directive +@name umbraco.directives.directive:umbConfirm +@restrict E +@scope + +@description +A confirmation dialog + + +

    Markup example

    +
    +	
    + + + +
    +
    + +

    Controller example

    +
    +	(function () {
    +		"use strict";
    +
    +		function Controller() {
    +
    +            var vm = this;
    +
    +            vm.onConfirm = function() {
    +                alert('Confirm clicked');
    +            };
    +
    +            vm.onCancel = function() {
    +                alert('Cancel clicked');
    +            }
    +
    +
    +        }
    +
    +		angular.module("umbraco").controller("My.Controller", Controller);
    +
    +	})();
    +
    + +@param {string} caption (attribute): The caption shown above the buttons +@param {callback} on-confirm (attribute): The call back when the "OK" button is clicked. If not set the button will not be shown +@param {callback} on-cancel (atribute): The call back when the "Cancel" button is clicked. If not set the button will not be shown +**/ +function confirmDirective() { + return { + restrict: "E", // restrict to an element + replace: true, // replace the html element with the template + templateUrl: 'views/components/umb-confirm.html', + scope: { + onConfirm: '=', + onCancel: '=', + caption: '@' + }, + link: function (scope, element, attr, ctrl) { + scope.showCancel = false; + scope.showConfirm = false; + + if (scope.onConfirm) { + scope.showConfirm = true; + } + + if (scope.onCancel) { + scope.showCancel = true; + } + } + }; +} +angular.module('umbraco.directives').directive("umbConfirm", confirmDirective); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js index c45a9f78e5..399ce8877a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js @@ -1,186 +1,186 @@ -/** -@ngdoc directive -@name umbraco.directives.directive:umbTable -@restrict E -@scope - -@description -Added in Umbraco v. 7.4: Use this directive to render a data table. - -

    Markup example

    -
    -    
    - - - - -
    -
    - -

    Controller example

    -
    -    (function () {
    -        "use strict";
    -    
    -        function Controller() {
    -    
    -            var vm = this;
    -    
    -            vm.items = [
    -                {
    -                    "icon": "icon-document",
    -                    "name": "My node 1",
    -                    "published": true,
    -                    "description": "A short description of my node",
    -                    "author": "Author 1"
    -                },
    -                {
    -                    "icon": "icon-document",
    -                    "name": "My node 2",
    -                    "published": true,
    -                    "description": "A short description of my node",
    -                    "author": "Author 2"
    -                }
    -            ];
    -
    -            vm.options = {
    -                includeProperties: [
    -                    { alias: "description", header: "Description" },
    -                    { alias: "author", header: "Author" }
    -                ]
    -            };
    -    
    -            vm.selectItem = selectItem;
    -            vm.clickItem = clickItem;
    -            vm.selectAll = selectAll;
    -            vm.isSelectedAll = isSelectedAll;
    -            vm.isSortDirection = isSortDirection;
    -            vm.sort = sort;
    -
    -            function selectAll($event) {
    -                alert("select all");
    -            }
    -
    -            function isSelectedAll() {
    -                
    -            }
    -    
    -            function clickItem(item) {
    -                alert("click node");
    -            }
    -
    -            function selectItem(selectedItem, $index, $event) {
    -                alert("select node");
    -            }
    -            
    -            function isSortDirection(col, direction) {
    -                
    -            }
    -            
    -            function sort(field, allow, isSystem) {
    -                
    -            }
    -    
    -        }
    -    
    -        angular.module("umbraco").controller("My.TableController", Controller);
    -    
    -    })();
    -
    - -@param {string} icon (binding): The node icon. -@param {string} name (binding): The node name. -@param {string} published (binding): The node published state. -@param {function} onSelect (expression): Callback function when the row is selected. -@param {function} onClick (expression): Callback function when the "Name" column link is clicked. -@param {function} onSelectAll (expression): Callback function when selecting all items. -@param {function} onSelectedAll (expression): Callback function when all items are selected. -@param {function} onSortingDirection (expression): Callback function when sorting direction is changed. -@param {function} onSort (expression): Callback function when sorting items. -**/ - -(function () { - 'use strict'; - - function TableDirective(iconHelper) { - - function link(scope, el, attr, ctrl) { - - scope.clickItem = function (item, $event) { - if (scope.onClick) { - scope.onClick(item); - $event.stopPropagation(); - } - }; - - scope.selectItem = function (item, $index, $event) { - if (scope.onSelect) { - scope.onSelect(item, $index, $event); - $event.stopPropagation(); - } - }; - - scope.selectAll = function ($event) { - if (scope.onSelectAll) { - scope.onSelectAll($event); - } - }; - - scope.isSelectedAll = function () { - if (scope.onSelectedAll && scope.items && scope.items.length > 0) { - return scope.onSelectedAll(); - } - }; - - scope.isSortDirection = function (col, direction) { - if (scope.onSortingDirection) { - return scope.onSortingDirection(col, direction); - } - }; - - scope.sort = function (field, allow, isSystem) { - if (scope.onSort) { - scope.onSort(field, allow, isSystem); - } - }; - - scope.getIcon = function (entry) { - return iconHelper.convertFromLegacyIcon(entry.icon); - }; - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-table.html', - scope: { - items: '=', - itemProperties: '=', - allowSelectAll: '=', - onSelect: '=', - onClick: '=', - onSelectAll: '=', - onSelectedAll: '=', - onSortingDirection: '=', - onSort: '=' - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbTable', TableDirective); - -})(); +/** +@ngdoc directive +@name umbraco.directives.directive:umbTable +@restrict E +@scope + +@description +Added in Umbraco v. 7.4: Use this directive to render a data table. + +

    Markup example

    +
    +    
    + + + + +
    +
    + +

    Controller example

    +
    +    (function () {
    +        "use strict";
    +    
    +        function Controller() {
    +    
    +            var vm = this;
    +    
    +            vm.items = [
    +                {
    +                    "icon": "icon-document",
    +                    "name": "My node 1",
    +                    "published": true,
    +                    "description": "A short description of my node",
    +                    "author": "Author 1"
    +                },
    +                {
    +                    "icon": "icon-document",
    +                    "name": "My node 2",
    +                    "published": true,
    +                    "description": "A short description of my node",
    +                    "author": "Author 2"
    +                }
    +            ];
    +
    +            vm.options = {
    +                includeProperties: [
    +                    { alias: "description", header: "Description" },
    +                    { alias: "author", header: "Author" }
    +                ]
    +            };
    +    
    +            vm.selectItem = selectItem;
    +            vm.clickItem = clickItem;
    +            vm.selectAll = selectAll;
    +            vm.isSelectedAll = isSelectedAll;
    +            vm.isSortDirection = isSortDirection;
    +            vm.sort = sort;
    +
    +            function selectAll($event) {
    +                alert("select all");
    +            }
    +
    +            function isSelectedAll() {
    +                
    +            }
    +    
    +            function clickItem(item) {
    +                alert("click node");
    +            }
    +
    +            function selectItem(selectedItem, $index, $event) {
    +                alert("select node");
    +            }
    +            
    +            function isSortDirection(col, direction) {
    +                
    +            }
    +            
    +            function sort(field, allow, isSystem) {
    +                
    +            }
    +    
    +        }
    +    
    +        angular.module("umbraco").controller("My.TableController", Controller);
    +    
    +    })();
    +
    + +@param {string} icon (binding): The node icon. +@param {string} name (binding): The node name. +@param {string} published (binding): The node published state. +@param {function} onSelect (expression): Callback function when the row is selected. +@param {function} onClick (expression): Callback function when the "Name" column link is clicked. +@param {function} onSelectAll (expression): Callback function when selecting all items. +@param {function} onSelectedAll (expression): Callback function when all items are selected. +@param {function} onSortingDirection (expression): Callback function when sorting direction is changed. +@param {function} onSort (expression): Callback function when sorting items. +**/ + +(function () { + 'use strict'; + + function TableDirective(iconHelper) { + + function link(scope, el, attr, ctrl) { + + scope.clickItem = function (item, $event) { + if (scope.onClick) { + scope.onClick(item); + $event.stopPropagation(); + } + }; + + scope.selectItem = function (item, $index, $event) { + if (scope.onSelect) { + scope.onSelect(item, $index, $event); + $event.stopPropagation(); + } + }; + + scope.selectAll = function ($event) { + if (scope.onSelectAll) { + scope.onSelectAll($event); + } + }; + + scope.isSelectedAll = function () { + if (scope.onSelectedAll && scope.items && scope.items.length > 0) { + return scope.onSelectedAll(); + } + }; + + scope.isSortDirection = function (col, direction) { + if (scope.onSortingDirection) { + return scope.onSortingDirection(col, direction); + } + }; + + scope.sort = function (field, allow, isSystem) { + if (scope.onSort) { + scope.onSort(field, allow, isSystem); + } + }; + + scope.getIcon = function (entry) { + return iconHelper.convertFromLegacyIcon(entry.icon); + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-table.html', + scope: { + items: '=', + itemProperties: '=', + allowSelectAll: '=', + onSelect: '=', + onClick: '=', + onSelectAll: '=', + onSelectedAll: '=', + onSortingDirection: '=', + onSort: '=' + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbTable', TableDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js index 1d43678e6a..6d53b20427 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js @@ -1,24 +1,24 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbFileUpload -* @function -* @restrict A -* @scope -* @description -* Listens for file input control changes and emits events when files are selected for use in other controllers. -**/ -function umbFileUpload() { - return { - restrict: "A", - scope: true, //create a new scope - link: function (scope, el, attrs) { - el.bind('change', function (event) { - var files = event.target.files; - //emit event upward - scope.$emit("filesSelected", { files: files }); - }); - } - }; -} - +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbFileUpload +* @function +* @restrict A +* @scope +* @description +* Listens for file input control changes and emits events when files are selected for use in other controllers. +**/ +function umbFileUpload() { + return { + restrict: "A", + scope: true, //create a new scope + link: function (scope, el, attrs) { + el.bind('change', function (event) { + var files = event.target.files; + //emit event upward + scope.$emit("filesSelected", { files: files }); + }); + } + }; +} + angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/editors/prevalues.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/editors/prevalues.mocks.js index a2d0a6fa3b..1b8560fa10 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/editors/prevalues.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/editors/prevalues.mocks.js @@ -1,81 +1,81 @@ -angular.module('umbraco.mocks'). - factory('prevaluesMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function getRichTextConfiguration(status, data, headers) { - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - else { - return [200, { "plugins": [{ "name": "code", "useOnFrontend": true }, { "name": "paste", "useOnFrontend": true }, { "name": "umbracolink", "useOnFrontend": true }], "commands": [{ "icon": "images/editor/code.gif", "command": "code", "alias": "code", "userInterface": "false", "frontEndCommand": "code", "value": "", "priority": 1, "isStylePicker": false }, { "icon": "images/editor/removeformat.gif", "command": "removeformat", "alias": "removeformat", "userInterface": "false", "frontEndCommand": "removeformat", "value": "", "priority": 2, "isStylePicker": false }, { "icon": "images/editor/undo.gif", "command": "undo", "alias": "undo", "userInterface": "false", "frontEndCommand": "undo", "value": "", "priority": 11, "isStylePicker": false }, { "icon": "images/editor/redo.gif", "command": "redo", "alias": "redo", "userInterface": "false", "frontEndCommand": "redo", "value": "", "priority": 12, "isStylePicker": false }, { "icon": "images/editor/cut.gif", "command": "cut", "alias": "cut", "userInterface": "false", "frontEndCommand": "cut", "value": "", "priority": 13, "isStylePicker": false }, { "icon": "images/editor/copy.gif", "command": "copy", "alias": "copy", "userInterface": "false", "frontEndCommand": "copy", "value": "", "priority": 14, "isStylePicker": false }, { "icon": "images/editor/showStyles.png", "command": "styleselect", "alias": "styleselect", "userInterface": "false", "frontEndCommand": "styleselect", "value": "", "priority": 20, "isStylePicker": false }, { "icon": "images/editor/bold.gif", "command": "bold", "alias": "bold", "userInterface": "false", "frontEndCommand": "bold", "value": "", "priority": 21, "isStylePicker": false }, { "icon": "images/editor/italic.gif", "command": "italic", "alias": "italic", "userInterface": "false", "frontEndCommand": "italic", "value": "", "priority": 22, "isStylePicker": false }, { "icon": "images/editor/underline.gif", "command": "underline", "alias": "underline", "userInterface": "false", "frontEndCommand": "underline", "value": "", "priority": 23, "isStylePicker": false }, { "icon": "images/editor/strikethrough.gif", "command": "strikethrough", "alias": "strikethrough", "userInterface": "false", "frontEndCommand": "strikethrough", "value": "", "priority": 24, "isStylePicker": false }, { "icon": "images/editor/justifyleft.gif", "command": "justifyleft", "alias": "justifyleft", "userInterface": "false", "frontEndCommand": "alignleft", "value": "", "priority": 31, "isStylePicker": false }, { "icon": "images/editor/justifycenter.gif", "command": "justifycenter", "alias": "justifycenter", "userInterface": "false", "frontEndCommand": "aligncenter", "value": "", "priority": 32, "isStylePicker": false }, { "icon": "images/editor/justifyright.gif", "command": "justifyright", "alias": "justifyright", "userInterface": "false", "frontEndCommand": "alignright", "value": "", "priority": 33, "isStylePicker": false }, { "icon": "images/editor/justifyfull.gif", "command": "justifyfull", "alias": "justifyfull", "userInterface": "false", "frontEndCommand": "alignfull", "value": "", "priority": 34, "isStylePicker": false }, { "icon": "images/editor/bullist.gif", "command": "bullist", "alias": "bullist", "userInterface": "false", "frontEndCommand": "bullist", "value": "", "priority": 41, "isStylePicker": false }, { "icon": "images/editor/numlist.gif", "command": "numlist", "alias": "numlist", "userInterface": "false", "frontEndCommand": "numlist", "value": "", "priority": 42, "isStylePicker": false }, { "icon": "images/editor/outdent.gif", "command": "outdent", "alias": "outdent", "userInterface": "false", "frontEndCommand": "outdent", "value": "", "priority": 43, "isStylePicker": false }, { "icon": "images/editor/indent.gif", "command": "indent", "alias": "indent", "userInterface": "false", "frontEndCommand": "indent", "value": "", "priority": 44, "isStylePicker": false }, { "icon": "images/editor/link.gif", "command": "link", "alias": "mcelink", "userInterface": "true", "frontEndCommand": "link", "value": "", "priority": 51, "isStylePicker": false }, { "icon": "images/editor/unLink.gif", "command": "unlink", "alias": "unlink", "userInterface": "false", "frontEndCommand": "unlink", "value": "", "priority": 52, "isStylePicker": false }, { "icon": "images/editor/anchor.gif", "command": "anchor", "alias": "mceinsertanchor", "userInterface": "false", "frontEndCommand": "anchor", "value": "", "priority": 53, "isStylePicker": false }, { "icon": "images/editor/image.gif", "command": "image", "alias": "mceimage", "userInterface": "true", "frontEndCommand": "umbmediapicker", "value": "", "priority": 61, "isStylePicker": false }, { "icon": "images/editor/insMacro.gif", "command": "umbracomacro", "alias": "umbracomacro", "userInterface": "true", "frontEndCommand": "umbmacro", "value": "", "priority": 62, "isStylePicker": false }, { "icon": "images/editor/table.gif", "command": "table", "alias": "mceinserttable", "userInterface": "true", "frontEndCommand": "table", "value": "", "priority": 63, "isStylePicker": false }, { "icon": "images/editor/media.gif", "command": "umbracoembed", "alias": "umbracoembed", "userInterface": "true", "frontEndCommand": "umbembeddialog", "value": "", "priority": 66, "isStylePicker": false }, { "icon": "images/editor/hr.gif", "command": "hr", "alias": "inserthorizontalrule", "userInterface": "false", "frontEndCommand": "hr", "value": "", "priority": 71, "isStylePicker": false }, { "icon": "images/editor/sub.gif", "command": "sub", "alias": "subscript", "userInterface": "false", "frontEndCommand": "sub", "value": "", "priority": 72, "isStylePicker": false }, { "icon": "images/editor/sup.gif", "command": "sup", "alias": "superscript", "userInterface": "false", "frontEndCommand": "sup", "value": "", "priority": 73, "isStylePicker": false }, { "icon": "images/editor/charmap.gif", "command": "charmap", "alias": "mcecharmap", "userInterface": "false", "frontEndCommand": "charmap", "value": "", "priority": 74, "isStylePicker": false }], "validElements": "+a[id|style|rel|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align],-h2[id|dir|class|align],-h3[id|dir|class|align],-h4[id|dir|class|align],-h5[id|dir|class|align],-h6[id|style|dir|class|align],hr[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*]", "inValidElements": "font", "customConfig": { "entity_encoding": "raw" } }, null]; - } - } - - function getCanvasEditorConfiguration(status, data, headers){ - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - else { - return [200, - [ - { - "name": "Rich text editor", - "alias": "rte", - "view": "rte", - "icon": "icon-article" - }, - { - "name": "Image", - "alias": "media", - "view": "media", - "icon": "icon-picture" - }, - { - "name": "Macro", - "alias": "macro", - "view": "macro", - "icon": "icon-settings-alt" - }, - { - "name": "Embed", - "alias": "embed", - "view": "embed", - "icon": "icon-movie-alt" - }, - { - "name": "Headline", - "alias": "headline", - "view": "textstring", - "icon": "icon-coin", - "config": { - "style": "font-size: 36px; line-height: 45px; font-weight: bold", - "markup": "

    #value#

    " - } - }, - { - "name": "Quote", - "alias": "quote", - "view": "textstring", - "icon": "icon-quote", - "config": { - "style": "border-left: 3px solid #ccc; padding: 10px; color: #ccc; font-family: serif; font-style: italic; font-size: 18px", - "markup": "
    #value#
    " - } - } - ], null]; - } - - } - - - return { - register: function() { - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/RichTextPreValue/GetConfiguration')) - .respond(getRichTextConfiguration); - $httpBackend - .whenGET(mocksUtils.urlRegex('../config/canvas.editors.config.js')) - .respond(getCanvasEditorConfiguration); - } - }; +angular.module('umbraco.mocks'). + factory('prevaluesMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function getRichTextConfiguration(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + else { + return [200, { "plugins": [{ "name": "code", "useOnFrontend": true }, { "name": "paste", "useOnFrontend": true }, { "name": "umbracolink", "useOnFrontend": true }], "commands": [{ "icon": "images/editor/code.gif", "command": "code", "alias": "code", "userInterface": "false", "frontEndCommand": "code", "value": "", "priority": 1, "isStylePicker": false }, { "icon": "images/editor/removeformat.gif", "command": "removeformat", "alias": "removeformat", "userInterface": "false", "frontEndCommand": "removeformat", "value": "", "priority": 2, "isStylePicker": false }, { "icon": "images/editor/undo.gif", "command": "undo", "alias": "undo", "userInterface": "false", "frontEndCommand": "undo", "value": "", "priority": 11, "isStylePicker": false }, { "icon": "images/editor/redo.gif", "command": "redo", "alias": "redo", "userInterface": "false", "frontEndCommand": "redo", "value": "", "priority": 12, "isStylePicker": false }, { "icon": "images/editor/cut.gif", "command": "cut", "alias": "cut", "userInterface": "false", "frontEndCommand": "cut", "value": "", "priority": 13, "isStylePicker": false }, { "icon": "images/editor/copy.gif", "command": "copy", "alias": "copy", "userInterface": "false", "frontEndCommand": "copy", "value": "", "priority": 14, "isStylePicker": false }, { "icon": "images/editor/showStyles.png", "command": "styleselect", "alias": "styleselect", "userInterface": "false", "frontEndCommand": "styleselect", "value": "", "priority": 20, "isStylePicker": false }, { "icon": "images/editor/bold.gif", "command": "bold", "alias": "bold", "userInterface": "false", "frontEndCommand": "bold", "value": "", "priority": 21, "isStylePicker": false }, { "icon": "images/editor/italic.gif", "command": "italic", "alias": "italic", "userInterface": "false", "frontEndCommand": "italic", "value": "", "priority": 22, "isStylePicker": false }, { "icon": "images/editor/underline.gif", "command": "underline", "alias": "underline", "userInterface": "false", "frontEndCommand": "underline", "value": "", "priority": 23, "isStylePicker": false }, { "icon": "images/editor/strikethrough.gif", "command": "strikethrough", "alias": "strikethrough", "userInterface": "false", "frontEndCommand": "strikethrough", "value": "", "priority": 24, "isStylePicker": false }, { "icon": "images/editor/justifyleft.gif", "command": "justifyleft", "alias": "justifyleft", "userInterface": "false", "frontEndCommand": "alignleft", "value": "", "priority": 31, "isStylePicker": false }, { "icon": "images/editor/justifycenter.gif", "command": "justifycenter", "alias": "justifycenter", "userInterface": "false", "frontEndCommand": "aligncenter", "value": "", "priority": 32, "isStylePicker": false }, { "icon": "images/editor/justifyright.gif", "command": "justifyright", "alias": "justifyright", "userInterface": "false", "frontEndCommand": "alignright", "value": "", "priority": 33, "isStylePicker": false }, { "icon": "images/editor/justifyfull.gif", "command": "justifyfull", "alias": "justifyfull", "userInterface": "false", "frontEndCommand": "alignfull", "value": "", "priority": 34, "isStylePicker": false }, { "icon": "images/editor/bullist.gif", "command": "bullist", "alias": "bullist", "userInterface": "false", "frontEndCommand": "bullist", "value": "", "priority": 41, "isStylePicker": false }, { "icon": "images/editor/numlist.gif", "command": "numlist", "alias": "numlist", "userInterface": "false", "frontEndCommand": "numlist", "value": "", "priority": 42, "isStylePicker": false }, { "icon": "images/editor/outdent.gif", "command": "outdent", "alias": "outdent", "userInterface": "false", "frontEndCommand": "outdent", "value": "", "priority": 43, "isStylePicker": false }, { "icon": "images/editor/indent.gif", "command": "indent", "alias": "indent", "userInterface": "false", "frontEndCommand": "indent", "value": "", "priority": 44, "isStylePicker": false }, { "icon": "images/editor/link.gif", "command": "link", "alias": "mcelink", "userInterface": "true", "frontEndCommand": "link", "value": "", "priority": 51, "isStylePicker": false }, { "icon": "images/editor/unLink.gif", "command": "unlink", "alias": "unlink", "userInterface": "false", "frontEndCommand": "unlink", "value": "", "priority": 52, "isStylePicker": false }, { "icon": "images/editor/anchor.gif", "command": "anchor", "alias": "mceinsertanchor", "userInterface": "false", "frontEndCommand": "anchor", "value": "", "priority": 53, "isStylePicker": false }, { "icon": "images/editor/image.gif", "command": "image", "alias": "mceimage", "userInterface": "true", "frontEndCommand": "umbmediapicker", "value": "", "priority": 61, "isStylePicker": false }, { "icon": "images/editor/insMacro.gif", "command": "umbracomacro", "alias": "umbracomacro", "userInterface": "true", "frontEndCommand": "umbmacro", "value": "", "priority": 62, "isStylePicker": false }, { "icon": "images/editor/table.gif", "command": "table", "alias": "mceinserttable", "userInterface": "true", "frontEndCommand": "table", "value": "", "priority": 63, "isStylePicker": false }, { "icon": "images/editor/media.gif", "command": "umbracoembed", "alias": "umbracoembed", "userInterface": "true", "frontEndCommand": "umbembeddialog", "value": "", "priority": 66, "isStylePicker": false }, { "icon": "images/editor/hr.gif", "command": "hr", "alias": "inserthorizontalrule", "userInterface": "false", "frontEndCommand": "hr", "value": "", "priority": 71, "isStylePicker": false }, { "icon": "images/editor/sub.gif", "command": "sub", "alias": "subscript", "userInterface": "false", "frontEndCommand": "sub", "value": "", "priority": 72, "isStylePicker": false }, { "icon": "images/editor/sup.gif", "command": "sup", "alias": "superscript", "userInterface": "false", "frontEndCommand": "sup", "value": "", "priority": 73, "isStylePicker": false }, { "icon": "images/editor/charmap.gif", "command": "charmap", "alias": "mcecharmap", "userInterface": "false", "frontEndCommand": "charmap", "value": "", "priority": 74, "isStylePicker": false }], "validElements": "+a[id|style|rel|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align],-h2[id|dir|class|align],-h3[id|dir|class|align],-h4[id|dir|class|align],-h5[id|dir|class|align],-h6[id|style|dir|class|align],hr[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*]", "inValidElements": "font", "customConfig": { "entity_encoding": "raw" } }, null]; + } + } + + function getCanvasEditorConfiguration(status, data, headers){ + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + else { + return [200, + [ + { + "name": "Rich text editor", + "alias": "rte", + "view": "rte", + "icon": "icon-article" + }, + { + "name": "Image", + "alias": "media", + "view": "media", + "icon": "icon-picture" + }, + { + "name": "Macro", + "alias": "macro", + "view": "macro", + "icon": "icon-settings-alt" + }, + { + "name": "Embed", + "alias": "embed", + "view": "embed", + "icon": "icon-movie-alt" + }, + { + "name": "Headline", + "alias": "headline", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 36px; line-height: 45px; font-weight: bold", + "markup": "

    #value#

    " + } + }, + { + "name": "Quote", + "alias": "quote", + "view": "textstring", + "icon": "icon-quote", + "config": { + "style": "border-left: 3px solid #ccc; padding: 10px; color: #ccc; font-family: serif; font-style: italic; font-size: 18px", + "markup": "
    #value#
    " + } + } + ], null]; + } + + } + + + return { + register: function() { + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/RichTextPreValue/GetConfiguration')) + .respond(getRichTextConfiguration); + $httpBackend + .whenGET(mocksUtils.urlRegex('../config/canvas.editors.config.js')) + .respond(getCanvasEditorConfiguration); + } + }; }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js index ef508c0f80..6e6eb00da7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js @@ -1,355 +1,355 @@ -angular.module('umbraco.mocks'). - factory('mocksUtils', ['$cookies', function ($cookies) { - 'use strict'; - - //by default we will perform authorization - var doAuth = true; - - return { - - getMockDataType: function(id, selectedId) { - var dataType = { - id: id, - name: "Simple editor " + id, - selectedEditor: selectedId, - availableEditors: [ - { name: "Simple editor 1", editorId: String.CreateGuid() }, - { name: "Simple editor 2", editorId: String.CreateGuid() }, - { name: "Simple editor " + id, editorId: selectedId }, - { name: "Simple editor 4", editorId: String.CreateGuid() }, - { name: "Simple editor 5", editorId: String.CreateGuid() }, - { name: "Simple editor 6", editorId: String.CreateGuid() } - ], - preValues: [ - { - label: "Custom pre value 1 for editor " + selectedId, - description: "Enter a value for this pre-value", - key: "myPreVal1", - view: "requiredfield" - }, - { - label: "Custom pre value 2 for editor " + selectedId, - description: "Enter a value for this pre-value", - key: "myPreVal2", - view: "requiredfield" - } - ] - - }; - return dataType; - }, - - /** Creats a mock content object */ - getMockContent: function(id) { - var node = { - name: "My content with id: " + id, - updateDate: new Date().toIsoDateTimeString(), - publishDate: new Date().toIsoDateTimeString(), - createDate: new Date().toIsoDateTimeString(), - id: id, - parentId: 1234, - icon: "icon-umb-content", - owner: { name: "Administrator", id: 0 }, - updater: { name: "Per Ploug Krogslund", id: 1 }, - path: "-1,1234,2455", - allowedActions: ["U", "H", "A"], - tabs: [ - { - label: "Grid", - id: 1, - active: true, - properties: [ - { alias: "grid", - label: "Grid", - view: "grid", - value: "", - hideLabel: true, - config: { - items:{ - - styles:[ - { - label: "Set a background image", - description: "Set a row background", - key: "background-image", - view: "imagepicker", - modifier: "url({0})" - } - ], - config:[ - { - label: "Class", - description: "Set a css class", - key: "class", - view: "textstring" - } - ], - columns: 12, - templates:[ - { - name: "1 column layout", - sections: [ - { - grid: 12, - } - ] - }, - { - name: "2 column layout", - sections: [ - { - grid: 4, - }, - { - grid: 8 - } - ] - } - ], - - - layouts:[ - { - name: "Headline", - areas: [ - { - grid: 12, - editors: ["headline"] - } - ] - }, - { - name: "Article", - areas: [ - { - grid: 12 - }, - { - grid: 4 - }, - { - grid: 8 - }, - { - grid: 12 - } - ] - } - ] - } - - } - }, - ] - }, - { - label: "Content", - id: 2, - properties: [ - { alias: "valTest", label: "Validation test", view: "validationtest", value: "asdfasdf" }, - { alias: "bodyText", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

    askjdkasj lasjd

    ", config: {} }, - { alias: "textarea", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, - { alias: "media", label: "Media picker", view: "mediapicker", value: "1234,23242,23232,23231", config: {multiPicker: 1} } - ] - }, - { - label: "Sample Editor", - id: 3, - properties: [ - { alias: "datepicker", label: "Datepicker", view: "datepicker", config: { pickTime: false, format: "yyyy-MM-dd" } }, - { alias: "tags", label: "Tags", view: "tags", value: "" } - ] - }, - { - label: "This", - id: 4, - properties: [ - { alias: "valTest4", label: "Validation test", view: "validationtest", value: "asdfasdf" }, - { alias: "bodyText4", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

    askjdkasj lasjd

    ", config: {} }, - { alias: "textarea4", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, - { alias: "content4", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } - ] - }, - { - label: "Is", - id: 5, - properties: [ - { alias: "valTest5", label: "Validation test", view: "validationtest", value: "asdfasdf" }, - { alias: "bodyText5", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

    askjdkasj lasjd

    ", config: {} }, - { alias: "textarea5", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, - { alias: "content5", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } - ] - }, - { - label: "Overflown", - id: 6, - properties: [ - { alias: "valTest6", label: "Validation test", view: "validationtest", value: "asdfasdf" }, - { alias: "bodyText6", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

    askjdkasj lasjd

    ", config: {} }, - { alias: "textarea6", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, - { alias: "content6", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } - ] - }, - { - label: "Generic Properties", - id: 0, - properties: [ - { - label: 'Id', - value: 1234, - view: "readonlyvalue", - alias: "_umb_id" - }, - { - label: 'Created by', - description: 'Original author', - value: "Administrator", - view: "readonlyvalue", - alias: "_umb_createdby" - }, - { - label: 'Created', - description: 'Date/time this document was created', - value: new Date().toIsoDateTimeString(), - view: "readonlyvalue", - alias: "_umb_createdate" - }, - { - label: 'Updated', - description: 'Date/time this document was created', - value: new Date().toIsoDateTimeString(), - view: "readonlyvalue", - alias: "_umb_updatedate" - }, - { - label: 'Document Type', - value: "Home page", - view: "readonlyvalue", - alias: "_umb_doctype" - }, - { - label: 'Publish at', - description: 'Date/time to publish this document', - value: new Date().toIsoDateTimeString(), - view: "datepicker", - alias: "_umb_releasedate" - }, - { - label: 'Unpublish at', - description: 'Date/time to un-publish this document', - value: new Date().toIsoDateTimeString(), - view: "datepicker", - alias: "_umb_expiredate" - }, - { - label: 'Template', - value: "myTemplate", - view: "dropdown", - alias: "_umb_template", - config: { - items: { - "" : "-- Choose template --", - "myTemplate" : "My Templates", - "home" : "Home Page", - "news" : "News Page" - } - } - }, - { - label: 'Link to document', - value: ["/testing" + id, "http://localhost/testing" + id, "http://mydomain.com/testing" + id].join(), - view: "urllist", - alias: "_umb_urllist" - }, - { - alias: "test", label: "Stuff", view: "test", value: "", - config: { - fields: [ - { alias: "embedded", label: "Embbeded", view: "textstring", value: "" }, - { alias: "embedded2", label: "Embbeded 2", view: "contentpicker", value: "" }, - { alias: "embedded3", label: "Embbeded 3", view: "textarea", value: "" }, - { alias: "embedded4", label: "Embbeded 4", view: "datepicker", value: "" } - ] - } - } - ] - } - ] - }; - - return node; - }, - - getMockEntity : function(id){ - return {name: "hello", id: id, icon: "icon-file"}; - }, - - /** generally used for unit tests, calling this will disable the auth check and always return true */ - disableAuth: function() { - doAuth = false; - }, - - /** generally used for unit tests, calling this will enabled the auth check */ - enabledAuth: function() { - doAuth = true; - }, - - /** Checks for our mock auth cookie, if it's not there, returns false */ - checkAuth: function () { - if (doAuth) { - var mockAuthCookie = $cookies.get("mockAuthCookie"); - if (!mockAuthCookie) { - return false; - } - return true; - } - else { - return true; - } - }, - - /** Creates/sets the auth cookie with a value indicating the user is now authenticated */ - setAuth: function() { - //set the cookie for loging - $cookies.put("mockAuthCookie", "Logged in!"); - }, - - /** removes the auth cookie */ - clearAuth: function() { - $cookies.remove("mockAuthCookie"); - }, - - urlRegex: function(url) { - url = url.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - return new RegExp("^" + url); - }, - - getParameterByName: function(url, name) { - name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); - - var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(url); - - return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); - }, - - getParametersByName: function(url, name) { - name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); - - var regex = new RegExp(name + "=([^&#]*)", "mg"), results = []; - var match; - - while ( ( match = regex.exec(url) ) !== null ) - { - results.push(decodeURIComponent(match[1].replace(/\+/g, " "))); - } - - return results; - }, - - getObjectPropertyFromJsonString: function(data, name) { - var obj = JSON.parse(data); - return obj[name]; - } - }; - }]); +angular.module('umbraco.mocks'). + factory('mocksUtils', ['$cookies', function ($cookies) { + 'use strict'; + + //by default we will perform authorization + var doAuth = true; + + return { + + getMockDataType: function(id, selectedId) { + var dataType = { + id: id, + name: "Simple editor " + id, + selectedEditor: selectedId, + availableEditors: [ + { name: "Simple editor 1", editorId: String.CreateGuid() }, + { name: "Simple editor 2", editorId: String.CreateGuid() }, + { name: "Simple editor " + id, editorId: selectedId }, + { name: "Simple editor 4", editorId: String.CreateGuid() }, + { name: "Simple editor 5", editorId: String.CreateGuid() }, + { name: "Simple editor 6", editorId: String.CreateGuid() } + ], + preValues: [ + { + label: "Custom pre value 1 for editor " + selectedId, + description: "Enter a value for this pre-value", + key: "myPreVal1", + view: "requiredfield" + }, + { + label: "Custom pre value 2 for editor " + selectedId, + description: "Enter a value for this pre-value", + key: "myPreVal2", + view: "requiredfield" + } + ] + + }; + return dataType; + }, + + /** Creats a mock content object */ + getMockContent: function(id) { + var node = { + name: "My content with id: " + id, + updateDate: new Date().toIsoDateTimeString(), + publishDate: new Date().toIsoDateTimeString(), + createDate: new Date().toIsoDateTimeString(), + id: id, + parentId: 1234, + icon: "icon-umb-content", + owner: { name: "Administrator", id: 0 }, + updater: { name: "Per Ploug Krogslund", id: 1 }, + path: "-1,1234,2455", + allowedActions: ["U", "H", "A"], + tabs: [ + { + label: "Grid", + id: 1, + active: true, + properties: [ + { alias: "grid", + label: "Grid", + view: "grid", + value: "", + hideLabel: true, + config: { + items:{ + + styles:[ + { + label: "Set a background image", + description: "Set a row background", + key: "background-image", + view: "imagepicker", + modifier: "url({0})" + } + ], + config:[ + { + label: "Class", + description: "Set a css class", + key: "class", + view: "textstring" + } + ], + columns: 12, + templates:[ + { + name: "1 column layout", + sections: [ + { + grid: 12, + } + ] + }, + { + name: "2 column layout", + sections: [ + { + grid: 4, + }, + { + grid: 8 + } + ] + } + ], + + + layouts:[ + { + name: "Headline", + areas: [ + { + grid: 12, + editors: ["headline"] + } + ] + }, + { + name: "Article", + areas: [ + { + grid: 12 + }, + { + grid: 4 + }, + { + grid: 8 + }, + { + grid: 12 + } + ] + } + ] + } + + } + }, + ] + }, + { + label: "Content", + id: 2, + properties: [ + { alias: "valTest", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

    askjdkasj lasjd

    ", config: {} }, + { alias: "textarea", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "media", label: "Media picker", view: "mediapicker", value: "1234,23242,23232,23231", config: {multiPicker: 1} } + ] + }, + { + label: "Sample Editor", + id: 3, + properties: [ + { alias: "datepicker", label: "Datepicker", view: "datepicker", config: { pickTime: false, format: "yyyy-MM-dd" } }, + { alias: "tags", label: "Tags", view: "tags", value: "" } + ] + }, + { + label: "This", + id: 4, + properties: [ + { alias: "valTest4", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText4", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

    askjdkasj lasjd

    ", config: {} }, + { alias: "textarea4", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "content4", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } + ] + }, + { + label: "Is", + id: 5, + properties: [ + { alias: "valTest5", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText5", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

    askjdkasj lasjd

    ", config: {} }, + { alias: "textarea5", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "content5", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } + ] + }, + { + label: "Overflown", + id: 6, + properties: [ + { alias: "valTest6", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText6", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

    askjdkasj lasjd

    ", config: {} }, + { alias: "textarea6", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "content6", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } + ] + }, + { + label: "Generic Properties", + id: 0, + properties: [ + { + label: 'Id', + value: 1234, + view: "readonlyvalue", + alias: "_umb_id" + }, + { + label: 'Created by', + description: 'Original author', + value: "Administrator", + view: "readonlyvalue", + alias: "_umb_createdby" + }, + { + label: 'Created', + description: 'Date/time this document was created', + value: new Date().toIsoDateTimeString(), + view: "readonlyvalue", + alias: "_umb_createdate" + }, + { + label: 'Updated', + description: 'Date/time this document was created', + value: new Date().toIsoDateTimeString(), + view: "readonlyvalue", + alias: "_umb_updatedate" + }, + { + label: 'Document Type', + value: "Home page", + view: "readonlyvalue", + alias: "_umb_doctype" + }, + { + label: 'Publish at', + description: 'Date/time to publish this document', + value: new Date().toIsoDateTimeString(), + view: "datepicker", + alias: "_umb_releasedate" + }, + { + label: 'Unpublish at', + description: 'Date/time to un-publish this document', + value: new Date().toIsoDateTimeString(), + view: "datepicker", + alias: "_umb_expiredate" + }, + { + label: 'Template', + value: "myTemplate", + view: "dropdown", + alias: "_umb_template", + config: { + items: { + "" : "-- Choose template --", + "myTemplate" : "My Templates", + "home" : "Home Page", + "news" : "News Page" + } + } + }, + { + label: 'Link to document', + value: ["/testing" + id, "http://localhost/testing" + id, "http://mydomain.com/testing" + id].join(), + view: "urllist", + alias: "_umb_urllist" + }, + { + alias: "test", label: "Stuff", view: "test", value: "", + config: { + fields: [ + { alias: "embedded", label: "Embbeded", view: "textstring", value: "" }, + { alias: "embedded2", label: "Embbeded 2", view: "contentpicker", value: "" }, + { alias: "embedded3", label: "Embbeded 3", view: "textarea", value: "" }, + { alias: "embedded4", label: "Embbeded 4", view: "datepicker", value: "" } + ] + } + } + ] + } + ] + }; + + return node; + }, + + getMockEntity : function(id){ + return {name: "hello", id: id, icon: "icon-file"}; + }, + + /** generally used for unit tests, calling this will disable the auth check and always return true */ + disableAuth: function() { + doAuth = false; + }, + + /** generally used for unit tests, calling this will enabled the auth check */ + enabledAuth: function() { + doAuth = true; + }, + + /** Checks for our mock auth cookie, if it's not there, returns false */ + checkAuth: function () { + if (doAuth) { + var mockAuthCookie = $cookies.get("mockAuthCookie"); + if (!mockAuthCookie) { + return false; + } + return true; + } + else { + return true; + } + }, + + /** Creates/sets the auth cookie with a value indicating the user is now authenticated */ + setAuth: function() { + //set the cookie for loging + $cookies.put("mockAuthCookie", "Logged in!"); + }, + + /** removes the auth cookie */ + clearAuth: function() { + $cookies.remove("mockAuthCookie"); + }, + + urlRegex: function(url) { + url = url.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + return new RegExp("^" + url); + }, + + getParameterByName: function(url, name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(url); + + return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); + }, + + getParametersByName: function(url, name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + + var regex = new RegExp(name + "=([^&#]*)", "mg"), results = []; + var match; + + while ( ( match = regex.exec(url) ) !== null ) + { + results.push(decodeURIComponent(match[1].replace(/\+/g, " "))); + } + + return results; + }, + + getObjectPropertyFromJsonString: function(data, name) { + var obj = JSON.parse(data); + return obj[name]; + } + }; + }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js index 964b523058..19234ed063 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js @@ -1,172 +1,172 @@ -angular.module('umbraco.mocks'). - factory('contentMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function returnChildren(status, data, headers) { - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var pageNumber = Number(mocksUtils.getParameterByName(data, "pageNumber")); - var filter = mocksUtils.getParameterByName(data, "filter"); - var pageSize = Number(mocksUtils.getParameterByName(data, "pageSize")); - var parentId = Number(mocksUtils.getParameterByName(data, "id")); - - if (pageNumber === 0) { - pageNumber = 1; - } - var collection = { pageSize: pageSize, totalItems: 68, totalPages: 7, pageNumber: pageNumber, filter: filter }; - collection.totalItems = 56 - (filter.length); - if (pageSize > 0) { - collection.totalPages = Math.round(collection.totalItems / collection.pageSize); - } - else { - collection.totalPages = 1; - } - collection.items = []; - - if (collection.totalItems < pageSize || pageSize < 1) { - collection.pageSize = collection.totalItems; - } else { - collection.pageSize = pageSize; - } - - var id = 0; - for (var i = 0; i < collection.pageSize; i++) { - id = (parentId + i) * pageNumber; - var cnt = mocksUtils.getMockContent(id); - - //here we fake filtering - if (filter !== '') { - cnt.name = filter + cnt.name; - } - - //set a fake sortOrder - cnt.sortOrder = i + 1; - - collection.items.push(cnt); - } - - return [200, collection, null]; - } - - function returnDeletedNode(status, data, headers) { - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - return [200, null, null]; - } - - function returnEmptyNode(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var response = returnNodebyId(200, "", null); - var node = response[1]; - var parentId = mocksUtils.getParameterByName(data, "parentId") || 1234; - - node.name = ""; - node.id = 0; - node.parentId = parentId; - - $(node.tabs).each(function(i,tab){ - $(tab.properties).each(function(i, property){ - property.value = ""; - }); - }); - - return response; - } - - function returnNodebyId(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var id = mocksUtils.getParameterByName(data, "id") || "1234"; - id = parseInt(id, 10); - - var node = mocksUtils.getMockContent(id); - - return [200, node, null]; - } - - function returnNodebyIds(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var ids = mocksUtils.getParameterByName(data, "ids") || [1234,23324,2323,23424]; - var nodes = []; - - $(ids).each(function(i, id){ - var _id = parseInt(id, 10); - nodes.push(mocksUtils.getMockContent(_id)); - }); - - return [200, nodes, null]; - } - - function returnSort(status, data, headers) { - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - return [200, null, null]; - } - - function returnSave(status, data, headers) { - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - return [200, null, null]; - } - - return { - register: function () { - - $httpBackend - .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/PostSave')) - .respond(returnSave); - - $httpBackend - .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/PostSort')) - .respond(returnSort); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetChildren')) - .respond(returnChildren); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetByIds')) - .respond(returnNodebyIds); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById?')) - .respond(returnNodebyId); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetEmpty')) - .respond(returnEmptyNode); - - $httpBackend - .whenDELETE(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/DeleteById')) - .respond(returnDeletedNode); - - $httpBackend - .whenDELETE(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/EmptyRecycleBin')) - .respond(returnDeletedNode); - }, - - expectGetById: function() { - $httpBackend - .expectGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById')); - } - }; +angular.module('umbraco.mocks'). + factory('contentMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function returnChildren(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var pageNumber = Number(mocksUtils.getParameterByName(data, "pageNumber")); + var filter = mocksUtils.getParameterByName(data, "filter"); + var pageSize = Number(mocksUtils.getParameterByName(data, "pageSize")); + var parentId = Number(mocksUtils.getParameterByName(data, "id")); + + if (pageNumber === 0) { + pageNumber = 1; + } + var collection = { pageSize: pageSize, totalItems: 68, totalPages: 7, pageNumber: pageNumber, filter: filter }; + collection.totalItems = 56 - (filter.length); + if (pageSize > 0) { + collection.totalPages = Math.round(collection.totalItems / collection.pageSize); + } + else { + collection.totalPages = 1; + } + collection.items = []; + + if (collection.totalItems < pageSize || pageSize < 1) { + collection.pageSize = collection.totalItems; + } else { + collection.pageSize = pageSize; + } + + var id = 0; + for (var i = 0; i < collection.pageSize; i++) { + id = (parentId + i) * pageNumber; + var cnt = mocksUtils.getMockContent(id); + + //here we fake filtering + if (filter !== '') { + cnt.name = filter + cnt.name; + } + + //set a fake sortOrder + cnt.sortOrder = i + 1; + + collection.items.push(cnt); + } + + return [200, collection, null]; + } + + function returnDeletedNode(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + return [200, null, null]; + } + + function returnEmptyNode(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var response = returnNodebyId(200, "", null); + var node = response[1]; + var parentId = mocksUtils.getParameterByName(data, "parentId") || 1234; + + node.name = ""; + node.id = 0; + node.parentId = parentId; + + $(node.tabs).each(function(i,tab){ + $(tab.properties).each(function(i, property){ + property.value = ""; + }); + }); + + return response; + } + + function returnNodebyId(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var id = mocksUtils.getParameterByName(data, "id") || "1234"; + id = parseInt(id, 10); + + var node = mocksUtils.getMockContent(id); + + return [200, node, null]; + } + + function returnNodebyIds(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var ids = mocksUtils.getParameterByName(data, "ids") || [1234,23324,2323,23424]; + var nodes = []; + + $(ids).each(function(i, id){ + var _id = parseInt(id, 10); + nodes.push(mocksUtils.getMockContent(_id)); + }); + + return [200, nodes, null]; + } + + function returnSort(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + return [200, null, null]; + } + + function returnSave(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + return [200, null, null]; + } + + return { + register: function () { + + $httpBackend + .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/PostSave')) + .respond(returnSave); + + $httpBackend + .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/PostSort')) + .respond(returnSort); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetChildren')) + .respond(returnChildren); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetByIds')) + .respond(returnNodebyIds); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById?')) + .respond(returnNodebyId); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetEmpty')) + .respond(returnEmptyNode); + + $httpBackend + .whenDELETE(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/DeleteById')) + .respond(returnDeletedNode); + + $httpBackend + .whenDELETE(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/EmptyRecycleBin')) + .respond(returnDeletedNode); + }, + + expectGetById: function() { + $httpBackend + .expectGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById')); + } + }; }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/contenttype.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/contenttype.mocks.js index 0d31eca3c3..6d0770b7e3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/contenttype.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/contenttype.mocks.js @@ -1,31 +1,31 @@ -angular.module('umbraco.mocks'). - factory('contentTypeMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function returnAllowedChildren(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var types = [ - { name: "News Article", description: "Standard news article", alias: "newsArticle", id: 1234, icon: "icon-file", thumbnail: "icon-file" }, - { name: "News Area", description: "Area to hold all news articles, there should be only one", alias: "newsArea", id: 1234, icon: "icon-suitcase", thumbnail: "icon-suitcase" }, - { name: "Employee", description: "Employee profile information page", alias: "employee", id: 1234, icon: "icon-user", thumbnail: "icon-user" } - ]; - return [200, types, null]; - } - - return { - register: function() { - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/Api/ContentType/GetAllowedChildren')) - .respond(returnAllowedChildren); - - }, - expectAllowedChildren: function(){ - console.log("expecting get"); - $httpBackend.expectGET(mocksUtils.urlRegex('/umbraco/Api/ContentType/GetAllowedChildren')); - } - }; +angular.module('umbraco.mocks'). + factory('contentTypeMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function returnAllowedChildren(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var types = [ + { name: "News Article", description: "Standard news article", alias: "newsArticle", id: 1234, icon: "icon-file", thumbnail: "icon-file" }, + { name: "News Area", description: "Area to hold all news articles, there should be only one", alias: "newsArea", id: 1234, icon: "icon-suitcase", thumbnail: "icon-suitcase" }, + { name: "Employee", description: "Employee profile information page", alias: "employee", id: 1234, icon: "icon-user", thumbnail: "icon-user" } + ]; + return [200, types, null]; + } + + return { + register: function() { + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/Api/ContentType/GetAllowedChildren')) + .respond(returnAllowedChildren); + + }, + expectAllowedChildren: function(){ + console.log("expecting get"); + $httpBackend.expectGET(mocksUtils.urlRegex('/umbraco/Api/ContentType/GetAllowedChildren')); + } + }; }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/dashboard.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/dashboard.mocks.js index d51cd2d6c4..7582db6740 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/dashboard.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/dashboard.mocks.js @@ -1,24 +1,24 @@ -angular.module('umbraco.mocks'). - factory('dashboardMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function getDashboard(status, data, headers) { - //check for existence of a cookie so we can do login/logout in the belle app (ignore for tests). - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - else { - //TODO: return real mocked data - return [200, [], null]; - } - } - - return { - register: function() { - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Dashboard/GetDashboard')) - .respond(getDashboard); - } - }; +angular.module('umbraco.mocks'). + factory('dashboardMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function getDashboard(status, data, headers) { + //check for existence of a cookie so we can do login/logout in the belle app (ignore for tests). + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + else { + //TODO: return real mocked data + return [200, [], null]; + } + } + + return { + register: function() { + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Dashboard/GetDashboard')) + .respond(getDashboard); + } + }; }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js index 87e88fc6c3..054f0aa66d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js @@ -1,113 +1,113 @@ -angular.module('umbraco.mocks'). - factory('dataTypeMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function returnById(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var id = mocksUtils.getParameterByName(data, "id") || 1234; - - var selectedId = String.CreateGuid(); - - var dataType = mocksUtils.getMockDataType(id, selectedId); - - return [200, dataType, null]; - } - - function returnEmpty(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var response = returnById(200, "", null); - var node = response[1]; - - node.name = ""; - node.selectedEditor = ""; - node.id = 0; - node.preValues = []; - - return response; - } - - function returnPreValues(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var editorId = mocksUtils.getParameterByName(data, "editorId") || "83E9AD36-51A7-4440-8C07-8A5623AC6979"; - - var preValues = [ - { - label: "Custom pre value 1 for editor " + editorId, - description: "Enter a value for this pre-value", - key: "myPreVal", - view: "requiredfield", - validation: [ - { - type: "Required" - } - ] - }, - { - label: "Custom pre value 2 for editor " + editorId, - description: "Enter a value for this pre-value", - key: "myPreVal", - view: "requiredfield", - validation: [ - { - type: "Required" - } - ] - } - ]; - return [200, preValues, null]; - } - - function returnSave(status, data, headers) { - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var postedData = angular.fromJson(headers); - - var dataType = mocksUtils.getMockDataType(postedData.id, postedData.selectedEditor); - dataType.notifications = [{ - header: "Saved", - message: "Data type saved", - type: 0 - }]; - - return [200, dataType, null]; - } - - return { - register: function() { - - $httpBackend - .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/PostSave')) - .respond(returnSave); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetById')) - .respond(returnById); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetEmpty')) - .respond(returnEmpty); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetPreValues')) - .respond(returnPreValues); - }, - expectGetById: function() { - $httpBackend - .expectGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetById')); - } - }; - }]); +angular.module('umbraco.mocks'). + factory('dataTypeMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function returnById(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var id = mocksUtils.getParameterByName(data, "id") || 1234; + + var selectedId = String.CreateGuid(); + + var dataType = mocksUtils.getMockDataType(id, selectedId); + + return [200, dataType, null]; + } + + function returnEmpty(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var response = returnById(200, "", null); + var node = response[1]; + + node.name = ""; + node.selectedEditor = ""; + node.id = 0; + node.preValues = []; + + return response; + } + + function returnPreValues(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var editorId = mocksUtils.getParameterByName(data, "editorId") || "83E9AD36-51A7-4440-8C07-8A5623AC6979"; + + var preValues = [ + { + label: "Custom pre value 1 for editor " + editorId, + description: "Enter a value for this pre-value", + key: "myPreVal", + view: "requiredfield", + validation: [ + { + type: "Required" + } + ] + }, + { + label: "Custom pre value 2 for editor " + editorId, + description: "Enter a value for this pre-value", + key: "myPreVal", + view: "requiredfield", + validation: [ + { + type: "Required" + } + ] + } + ]; + return [200, preValues, null]; + } + + function returnSave(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var postedData = angular.fromJson(headers); + + var dataType = mocksUtils.getMockDataType(postedData.id, postedData.selectedEditor); + dataType.notifications = [{ + header: "Saved", + message: "Data type saved", + type: 0 + }]; + + return [200, dataType, null]; + } + + return { + register: function() { + + $httpBackend + .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/PostSave')) + .respond(returnSave); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetById')) + .respond(returnById); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetEmpty')) + .respond(returnEmpty); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetPreValues')) + .respond(returnPreValues); + }, + expectGetById: function() { + $httpBackend + .expectGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetById')); + } + }; + }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js index bab7c53630..2c2007dd91 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/entity.mocks.js @@ -1,89 +1,89 @@ -angular.module('umbraco.mocks'). - factory('entityMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function returnEntitybyId(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var id = mocksUtils.getParameterByName(data, "id") || "1234"; - id = parseInt(id, 10); - - var node = mocksUtils.getMockEntity(id); - - return [200, node, null]; - } - - function returnEntitybyIds(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var ids = mocksUtils.getParametersByName(data, "ids") || [1234, 23324, 2323, 23424]; - - var nodes = []; - - $(ids).each(function (i, id) { - var _id = parseInt(id, 10); - nodes.push(mocksUtils.getMockEntity(_id)); - }); - - return [200, nodes, null]; - } - - function returnEntitybyIdsPost(method, url, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var ids = mocksUtils.getObjectPropertyFromJsonString(data, "ids") || [1234, 23324, 2323, 23424]; - - var nodes = []; - - $(ids).each(function (i, id) { - var _id = parseInt(id, 10); - nodes.push(mocksUtils.getMockEntity(_id)); - }); - - return [200, nodes, null]; - } - - function returnEntityUrl() { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - return [200, "url", null]; - - } - - return { - register: function () { - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetByIds')) - .respond(returnEntitybyIds); - - $httpBackend - .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetByIds')) - .respond(returnEntitybyIdsPost); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetAncestors')) - .respond(returnEntitybyIds); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetById?')) - .respond(returnEntitybyId); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetUrl?')) - .respond(returnEntityUrl); - } - }; +angular.module('umbraco.mocks'). + factory('entityMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function returnEntitybyId(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var id = mocksUtils.getParameterByName(data, "id") || "1234"; + id = parseInt(id, 10); + + var node = mocksUtils.getMockEntity(id); + + return [200, node, null]; + } + + function returnEntitybyIds(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var ids = mocksUtils.getParametersByName(data, "ids") || [1234, 23324, 2323, 23424]; + + var nodes = []; + + $(ids).each(function (i, id) { + var _id = parseInt(id, 10); + nodes.push(mocksUtils.getMockEntity(_id)); + }); + + return [200, nodes, null]; + } + + function returnEntitybyIdsPost(method, url, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var ids = mocksUtils.getObjectPropertyFromJsonString(data, "ids") || [1234, 23324, 2323, 23424]; + + var nodes = []; + + $(ids).each(function (i, id) { + var _id = parseInt(id, 10); + nodes.push(mocksUtils.getMockEntity(_id)); + }); + + return [200, nodes, null]; + } + + function returnEntityUrl() { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + return [200, "url", null]; + + } + + return { + register: function () { + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetByIds')) + .respond(returnEntitybyIds); + + $httpBackend + .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetByIds')) + .respond(returnEntitybyIdsPost); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetAncestors')) + .respond(returnEntitybyIds); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetById?')) + .respond(returnEntitybyId); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetUrl?')) + .respond(returnEntityUrl); + } + }; }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/macro.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/macro.mocks.js index 7ec5f670d8..435bdd3486 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/macro.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/macro.mocks.js @@ -1,32 +1,32 @@ -angular.module('umbraco.mocks'). - factory('macroMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function returnParameters(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var nodes = [{ - alias: "parameter1", - name: "Parameter 1" - }, { - alias: "parameter2", - name: "Parameter 2" - }]; - - return [200, nodes, null]; - } - - - return { - register: function () { - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Macro/GetMacroParameters')) - .respond(returnParameters); - - } - }; +angular.module('umbraco.mocks'). + factory('macroMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function returnParameters(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var nodes = [{ + alias: "parameter1", + name: "Parameter 1" + }, { + alias: "parameter2", + name: "Parameter 2" + }]; + + return [200, nodes, null]; + } + + + return { + register: function () { + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Macro/GetMacroParameters')) + .respond(returnParameters); + + } + }; }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/media.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/media.mocks.js index 6017bcf4a8..a23fc86210 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/media.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/media.mocks.js @@ -1,81 +1,81 @@ -angular.module('umbraco.mocks'). - factory('mediaMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function returnNodeCollection(status, data, headers){ - var nodes = [{"properties":[{"id":348,"value":"/media/1045/windows95.jpg","alias":"umbracoFile"},{"id":349,"value":"640","alias":"umbracoWidth"},{"id":350,"value":"472","alias":"umbracoHeight"},{"id":351,"value":"53472","alias":"umbracoBytes"},{"id":352,"value":"jpg","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:08","createDate":"2013-08-27 15:50:08","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":0,"name":"windows95.jpg","id":1128,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":353,"value":"/media/1046/pete.png","alias":"umbracoFile"},{"id":354,"value":"240","alias":"umbracoWidth"},{"id":355,"value":"240","alias":"umbracoHeight"},{"id":356,"value":"87408","alias":"umbracoBytes"},{"id":357,"value":"png","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:08","createDate":"2013-08-27 15:50:08","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":1,"name":"pete.png","id":1129,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":358,"value":"/media/1047/unicorn.jpg","alias":"umbracoFile"},{"id":359,"value":"640","alias":"umbracoWidth"},{"id":360,"value":"640","alias":"umbracoHeight"},{"id":361,"value":"577380","alias":"umbracoBytes"},{"id":362,"value":"jpg","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:09","createDate":"2013-08-27 15:50:09","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":2,"name":"unicorn.jpg","id":1130,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":363,"value":"/media/1049/exploding-head.gif","alias":"umbracoFile"},{"id":364,"value":"500","alias":"umbracoWidth"},{"id":365,"value":"279","alias":"umbracoHeight"},{"id":366,"value":"451237","alias":"umbracoBytes"},{"id":367,"value":"gif","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:09","createDate":"2013-08-27 15:50:09","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":3,"name":"exploding head.gif","id":1131,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":368,"value":"/media/1048/bighead.jpg","alias":"umbracoFile"},{"id":369,"value":"1240","alias":"umbracoWidth"},{"id":370,"value":"1655","alias":"umbracoHeight"},{"id":371,"value":"836261","alias":"umbracoBytes"},{"id":372,"value":"jpg","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:09","createDate":"2013-08-27 15:50:09","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":4,"name":"bighead.jpg","id":1132,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":373,"value":"/media/1050/powerlines.jpg","alias":"umbracoFile"},{"id":374,"value":"636","alias":"umbracoWidth"},{"id":375,"value":"423","alias":"umbracoHeight"},{"id":376,"value":"79874","alias":"umbracoBytes"},{"id":377,"value":"jpg","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:09","createDate":"2013-08-27 15:50:09","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":5,"name":"powerlines.jpg","id":1133,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":430,"value":"","alias":"contents"}],"updateDate":"2013-08-30 08:53:22","createDate":"2013-08-30 08:53:22","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Folder","sortOrder":6,"name":"new folder","id":1146,"icon":"folder.gif","parentId":1127}]; - return [200, nodes, null]; - } - - function returnNodebyIds(status, data, headers) { - var ids = mocksUtils.getParameterByName(data, "ids") || "1234,1234,4234"; - var items = []; - - _.each(ids, function(id){ - items.push(_getNode( parseInt( id, 10 )) ); - }); - - return [200, items, null]; - } - - function returnNodebyId(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var id = mocksUtils.getParameterByName(data, "id") || 1234; - id = parseInt(id, 10); - - - - return [200, _getNode(id), null]; - } - - function _getNode(id){ - var node = { - name: "My media with id: " + id, - updateDate: new Date(), - publishDate: new Date(), - id: id, - parentId: 1234, - icon: "icon-file-alt", - owner: {name: "Administrator", id: 0}, - updater: {name: "Per Ploug Krogslund", id: 1}, - path: "-1,1234,2455", - tabs: [ - { - label: "Media", - alias: "tab0", - id: 0, - properties: [ - { alias: "umbracoFile", label: "File", description:"Some file", view: "rte", value: "/media/1234/random.jpg" } - ] - } - ] - }; - - return node; - } - - return { - register: function() { - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetById?')) - .respond(returnNodebyId); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetByIds?')) - .respond(returnNodebyIds); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetChildren')) - .respond(returnNodeCollection); - - }, - expectGetById: function() { - $httpBackend - .expectGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetById')); - } - }; - }]); +angular.module('umbraco.mocks'). + factory('mediaMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function returnNodeCollection(status, data, headers){ + var nodes = [{"properties":[{"id":348,"value":"/media/1045/windows95.jpg","alias":"umbracoFile"},{"id":349,"value":"640","alias":"umbracoWidth"},{"id":350,"value":"472","alias":"umbracoHeight"},{"id":351,"value":"53472","alias":"umbracoBytes"},{"id":352,"value":"jpg","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:08","createDate":"2013-08-27 15:50:08","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":0,"name":"windows95.jpg","id":1128,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":353,"value":"/media/1046/pete.png","alias":"umbracoFile"},{"id":354,"value":"240","alias":"umbracoWidth"},{"id":355,"value":"240","alias":"umbracoHeight"},{"id":356,"value":"87408","alias":"umbracoBytes"},{"id":357,"value":"png","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:08","createDate":"2013-08-27 15:50:08","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":1,"name":"pete.png","id":1129,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":358,"value":"/media/1047/unicorn.jpg","alias":"umbracoFile"},{"id":359,"value":"640","alias":"umbracoWidth"},{"id":360,"value":"640","alias":"umbracoHeight"},{"id":361,"value":"577380","alias":"umbracoBytes"},{"id":362,"value":"jpg","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:09","createDate":"2013-08-27 15:50:09","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":2,"name":"unicorn.jpg","id":1130,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":363,"value":"/media/1049/exploding-head.gif","alias":"umbracoFile"},{"id":364,"value":"500","alias":"umbracoWidth"},{"id":365,"value":"279","alias":"umbracoHeight"},{"id":366,"value":"451237","alias":"umbracoBytes"},{"id":367,"value":"gif","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:09","createDate":"2013-08-27 15:50:09","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":3,"name":"exploding head.gif","id":1131,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":368,"value":"/media/1048/bighead.jpg","alias":"umbracoFile"},{"id":369,"value":"1240","alias":"umbracoWidth"},{"id":370,"value":"1655","alias":"umbracoHeight"},{"id":371,"value":"836261","alias":"umbracoBytes"},{"id":372,"value":"jpg","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:09","createDate":"2013-08-27 15:50:09","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":4,"name":"bighead.jpg","id":1132,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":373,"value":"/media/1050/powerlines.jpg","alias":"umbracoFile"},{"id":374,"value":"636","alias":"umbracoWidth"},{"id":375,"value":"423","alias":"umbracoHeight"},{"id":376,"value":"79874","alias":"umbracoBytes"},{"id":377,"value":"jpg","alias":"umbracoExtension"}],"updateDate":"2013-08-27 15:50:09","createDate":"2013-08-27 15:50:09","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Image","sortOrder":5,"name":"powerlines.jpg","id":1133,"icon":"mediaPhoto.gif","parentId":1127},{"properties":[{"id":430,"value":"","alias":"contents"}],"updateDate":"2013-08-30 08:53:22","createDate":"2013-08-30 08:53:22","owner":{"id":0,"name":"admin"},"updater":null,"contentTypeAlias":"Folder","sortOrder":6,"name":"new folder","id":1146,"icon":"folder.gif","parentId":1127}]; + return [200, nodes, null]; + } + + function returnNodebyIds(status, data, headers) { + var ids = mocksUtils.getParameterByName(data, "ids") || "1234,1234,4234"; + var items = []; + + _.each(ids, function(id){ + items.push(_getNode( parseInt( id, 10 )) ); + }); + + return [200, items, null]; + } + + function returnNodebyId(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var id = mocksUtils.getParameterByName(data, "id") || 1234; + id = parseInt(id, 10); + + + + return [200, _getNode(id), null]; + } + + function _getNode(id){ + var node = { + name: "My media with id: " + id, + updateDate: new Date(), + publishDate: new Date(), + id: id, + parentId: 1234, + icon: "icon-file-alt", + owner: {name: "Administrator", id: 0}, + updater: {name: "Per Ploug Krogslund", id: 1}, + path: "-1,1234,2455", + tabs: [ + { + label: "Media", + alias: "tab0", + id: 0, + properties: [ + { alias: "umbracoFile", label: "File", description:"Some file", view: "rte", value: "/media/1234/random.jpg" } + ] + } + ] + }; + + return node; + } + + return { + register: function() { + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetById?')) + .respond(returnNodebyId); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetByIds?')) + .respond(returnNodebyIds); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetChildren')) + .respond(returnNodeCollection); + + }, + expectGetById: function() { + $httpBackend + .expectGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetById')); + } + }; + }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/section.resource.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/section.resource.js index 158d56f44d..387e7b4e63 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/section.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/section.resource.js @@ -1,38 +1,38 @@ -/** -* @ngdoc service -* @name umbraco.mocks.sectionMocks -* @description -* Mocks data retrival for the sections -**/ -function sectionMocks($httpBackend, mocksUtils) { - - /** internal method to mock the sections to be returned */ - function getSections() { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var sections = [ - { name: "Content", cssclass: "icon-umb-content", alias: "content" }, - { name: "Media", cssclass: "icon-umb-media", alias: "media" }, - { name: "Settings", cssclass: "icon-umb-settings", alias: "settings" }, - { name: "Developer", cssclass: "icon-umb-developer", alias: "developer" }, - { name: "Users", cssclass: "icon-umb-users", alias: "users" }, - { name: "Developer", cssclass: "icon-umb-developer", alias: "developer" }, - { name: "Users", cssclass: "icon-umb-users", alias: "users" } - ]; - - return [200, sections, null]; - } - - return { - register: function () { - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Section/GetSections')) - .respond(getSections); - } - }; -} - -angular.module('umbraco.mocks').factory('sectionMocks', ['$httpBackend', 'mocksUtils', sectionMocks]); +/** +* @ngdoc service +* @name umbraco.mocks.sectionMocks +* @description +* Mocks data retrival for the sections +**/ +function sectionMocks($httpBackend, mocksUtils) { + + /** internal method to mock the sections to be returned */ + function getSections() { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var sections = [ + { name: "Content", cssclass: "icon-umb-content", alias: "content" }, + { name: "Media", cssclass: "icon-umb-media", alias: "media" }, + { name: "Settings", cssclass: "icon-umb-settings", alias: "settings" }, + { name: "Developer", cssclass: "icon-umb-developer", alias: "developer" }, + { name: "Users", cssclass: "icon-umb-users", alias: "users" }, + { name: "Developer", cssclass: "icon-umb-developer", alias: "developer" }, + { name: "Users", cssclass: "icon-umb-users", alias: "users" } + ]; + + return [200, sections, null]; + } + + return { + register: function () { + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Section/GetSections')) + .respond(getSections); + } + }; +} + +angular.module('umbraco.mocks').factory('sectionMocks', ['$httpBackend', 'mocksUtils', sectionMocks]); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js index 92cc0cb2bb..9724c222b6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js @@ -1,242 +1,242 @@ -angular.module('umbraco.mocks'). - factory('treeMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function getMenuItems() { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var menu = [ - { name: "Create", cssclass: "plus", alias: "create", metaData: {} }, - - { seperator: true, name: "Delete", cssclass: "remove", alias: "delete", metaData: {} }, - { name: "Move", cssclass: "move", alias: "move", metaData: {} }, - { name: "Copy", cssclass: "copy", alias: "copy", metaData: {} }, - { name: "Sort", cssclass: "sort", alias: "sort", metaData: {} }, - - { seperator: true, name: "Publish", cssclass: "globe", alias: "publish", metaData: {} }, - { name: "Rollback", cssclass: "undo", alias: "rollback", metaData: {} }, - - { seperator: true, name: "Permissions", cssclass: "lock", alias: "permissions", metaData: {} }, - { name: "Audit Trail", cssclass: "time", alias: "audittrail", metaData: {} }, - { name: "Notifications", cssclass: "envelope", alias: "notifications", metaData: {} }, - - { seperator: true, name: "Hostnames", cssclass: "home", alias: "hostnames", metaData: {} }, - { name: "Public Access", cssclass: "group", alias: "publicaccess", metaData: {} }, - - { seperator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} }, - - { seperator: true, name: "Empty Recycle Bin", cssclass: "trash", alias: "emptyrecyclebin", metaData: {} } - ]; - - var result = { - menuItems: menu, - defaultAlias: "create" - }; - - return [200, result, null]; - } - - function returnChildren(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var id = mocksUtils.getParameterByName(data, "id"); - var section = mocksUtils.getParameterByName(data, "treeType"); - var level = mocksUtils.getParameterByName(data, "level")+1; - - var url = "/umbraco/UmbracoTrees/ApplicationTreeApi/GetChildren?treeType=" + section + "&id=1234&level=" + level; - var menuUrl = "/umbraco/UmbracoTrees/ApplicationTreeApi/GetMenu?treeType=" + section + "&id=1234&parentId=456"; - - //hack to have create as default content action - var action; - if (section === "content") { - action = "create"; - } - - var children = [ - { name: "child-of-" + section, childNodesUrl: url, id: level + "" + 1234, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1235, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1236, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1237, icon: "icon-document", routePath: "common/legacy/1237?p=" + encodeURI("developer/contentType.aspx?idequal1234"), children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl } - ]; - - return [200, children, null]; - } - - function returnDataTypes(status, data, headers) { - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var children = [ - { name: "Textstring", childNodesUrl: null, id: 10, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null }, - { name: "Multiple textstring", childNodesUrl: null, id: 11, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null }, - { name: "Yes/No", childNodesUrl: null, id: 12, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null }, - { name: "Rich Text Editor", childNodesUrl: null, id: 13, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null } - ]; - - return [200, children, null]; - } - - function returnDataTypeMenu(status, data, headers) { - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var menu = [ - { - name: "Create", cssclass: "plus", alias: "create", metaData: { - jsAction: "umbracoMenuActions.CreateChildEntity" - } - }, - { seperator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} } - ]; - - return [200, menu, null]; - } - - function returnApplicationTrees(status, data, headers) { - - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - - var section = mocksUtils.getParameterByName(data, "application"); - var url = "/umbraco/UmbracoTrees/ApplicationTreeApi/GetChildren?treeType=" + section + "&id=1234&level=1"; - var menuUrl = "/umbraco/UmbracoTrees/ApplicationTreeApi/GetMenu?treeType=" + section + "&id=1234&parentId=456"; - var t; - switch (section) { - - case "content": - t = { - name: "content", - id: -1, - children: [ - { name: "My website", id: 1234, childNodesUrl: url, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Components", id: 1235, childNodesUrl: url, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Archieve", id: 1236, childNodesUrl: url, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Recycle Bin", id: -20, childNodesUrl: url, icon: "icon-trash", routePath: section + "/recyclebin", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } - ], - expanded: true, - hasChildren: true, - level: 0, - menuUrl: menuUrl, - metaData: { treeAlias: "content" } - }; - - break; - case "media": - t = { - name: "media", - id: -1, - children: [ - { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } - ], - expanded: true, - hasChildren: true, - level: 0, - menuUrl: menuUrl, - metaData: { treeAlias: "media" } - }; - - break; - case "developer": - - var dataTypeChildrenUrl = "/umbraco/UmbracoTrees/DataTypeTree/GetNodes?id=-1&application=developer"; - var dataTypeMenuUrl = "/umbraco/UmbracoTrees/DataTypeTree/GetMenu?id=-1&application=developer"; - - t = { - name: "developer", - id: -1, - children: [ - { name: "Data types", childNodesUrl: dataTypeChildrenUrl, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: dataTypeMenuUrl, metaData: { treeAlias: "dataTypes" } }, - { name: "Macros", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "macros" } }, - { name: "Packages", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "packager" } }, - { name: "Partial View Macros", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "partialViewMacros" } } - ], - expanded: true, - hasChildren: true, - level: 0, - isContainer: true - }; - - break; - case "settings": - t = { - name: "settings", - id: -1, - children: [ - { name: "Stylesheets", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "stylesheets" } }, - { name: "Templates", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "templates" } }, - { name: "Dictionary", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "dictionary" } }, - { name: "Media types", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "mediaTypes" } }, - { name: "Document types", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "documentTypes" } } - ], - expanded: true, - hasChildren: true, - level: 0, - isContainer: true - }; - - break; - default: - - t = { - name: "randomTree", - id: -1, - children: [ - { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } - ], - expanded: true, - hasChildren: true, - level: 0, - menuUrl: menuUrl, - metaData: { treeAlias: "randomTree" } - }; - - break; - } - - - return [200, t, null]; - } - - - return { - register: function() { - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/ApplicationTreeApi/GetApplicationTrees')) - .respond(returnApplicationTrees); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/ApplicationTreeApi/GetChildren')) - .respond(returnChildren); - - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/DataTypeTree/GetNodes')) - .respond(returnDataTypes); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/DataTypeTree/GetMenu')) - .respond(returnDataTypeMenu); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/ApplicationTreeApi/GetMenu')) - .respond(getMenuItems); - - } - }; +angular.module('umbraco.mocks'). + factory('treeMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function getMenuItems() { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var menu = [ + { name: "Create", cssclass: "plus", alias: "create", metaData: {} }, + + { seperator: true, name: "Delete", cssclass: "remove", alias: "delete", metaData: {} }, + { name: "Move", cssclass: "move", alias: "move", metaData: {} }, + { name: "Copy", cssclass: "copy", alias: "copy", metaData: {} }, + { name: "Sort", cssclass: "sort", alias: "sort", metaData: {} }, + + { seperator: true, name: "Publish", cssclass: "globe", alias: "publish", metaData: {} }, + { name: "Rollback", cssclass: "undo", alias: "rollback", metaData: {} }, + + { seperator: true, name: "Permissions", cssclass: "lock", alias: "permissions", metaData: {} }, + { name: "Audit Trail", cssclass: "time", alias: "audittrail", metaData: {} }, + { name: "Notifications", cssclass: "envelope", alias: "notifications", metaData: {} }, + + { seperator: true, name: "Hostnames", cssclass: "home", alias: "hostnames", metaData: {} }, + { name: "Public Access", cssclass: "group", alias: "publicaccess", metaData: {} }, + + { seperator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} }, + + { seperator: true, name: "Empty Recycle Bin", cssclass: "trash", alias: "emptyrecyclebin", metaData: {} } + ]; + + var result = { + menuItems: menu, + defaultAlias: "create" + }; + + return [200, result, null]; + } + + function returnChildren(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var id = mocksUtils.getParameterByName(data, "id"); + var section = mocksUtils.getParameterByName(data, "treeType"); + var level = mocksUtils.getParameterByName(data, "level")+1; + + var url = "/umbraco/UmbracoTrees/ApplicationTreeApi/GetChildren?treeType=" + section + "&id=1234&level=" + level; + var menuUrl = "/umbraco/UmbracoTrees/ApplicationTreeApi/GetMenu?treeType=" + section + "&id=1234&parentId=456"; + + //hack to have create as default content action + var action; + if (section === "content") { + action = "create"; + } + + var children = [ + { name: "child-of-" + section, childNodesUrl: url, id: level + "" + 1234, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1235, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1236, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1237, icon: "icon-document", routePath: "common/legacy/1237?p=" + encodeURI("developer/contentType.aspx?idequal1234"), children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl } + ]; + + return [200, children, null]; + } + + function returnDataTypes(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var children = [ + { name: "Textstring", childNodesUrl: null, id: 10, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null }, + { name: "Multiple textstring", childNodesUrl: null, id: 11, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null }, + { name: "Yes/No", childNodesUrl: null, id: 12, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null }, + { name: "Rich Text Editor", childNodesUrl: null, id: 13, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null } + ]; + + return [200, children, null]; + } + + function returnDataTypeMenu(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var menu = [ + { + name: "Create", cssclass: "plus", alias: "create", metaData: { + jsAction: "umbracoMenuActions.CreateChildEntity" + } + }, + { seperator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} } + ]; + + return [200, menu, null]; + } + + function returnApplicationTrees(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var section = mocksUtils.getParameterByName(data, "application"); + var url = "/umbraco/UmbracoTrees/ApplicationTreeApi/GetChildren?treeType=" + section + "&id=1234&level=1"; + var menuUrl = "/umbraco/UmbracoTrees/ApplicationTreeApi/GetMenu?treeType=" + section + "&id=1234&parentId=456"; + var t; + switch (section) { + + case "content": + t = { + name: "content", + id: -1, + children: [ + { name: "My website", id: 1234, childNodesUrl: url, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Components", id: 1235, childNodesUrl: url, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Archieve", id: 1236, childNodesUrl: url, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Recycle Bin", id: -20, childNodesUrl: url, icon: "icon-trash", routePath: section + "/recyclebin", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + ], + expanded: true, + hasChildren: true, + level: 0, + menuUrl: menuUrl, + metaData: { treeAlias: "content" } + }; + + break; + case "media": + t = { + name: "media", + id: -1, + children: [ + { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + ], + expanded: true, + hasChildren: true, + level: 0, + menuUrl: menuUrl, + metaData: { treeAlias: "media" } + }; + + break; + case "developer": + + var dataTypeChildrenUrl = "/umbraco/UmbracoTrees/DataTypeTree/GetNodes?id=-1&application=developer"; + var dataTypeMenuUrl = "/umbraco/UmbracoTrees/DataTypeTree/GetMenu?id=-1&application=developer"; + + t = { + name: "developer", + id: -1, + children: [ + { name: "Data types", childNodesUrl: dataTypeChildrenUrl, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: dataTypeMenuUrl, metaData: { treeAlias: "dataTypes" } }, + { name: "Macros", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "macros" } }, + { name: "Packages", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "packager" } }, + { name: "Partial View Macros", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "partialViewMacros" } } + ], + expanded: true, + hasChildren: true, + level: 0, + isContainer: true + }; + + break; + case "settings": + t = { + name: "settings", + id: -1, + children: [ + { name: "Stylesheets", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "stylesheets" } }, + { name: "Templates", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "templates" } }, + { name: "Dictionary", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "dictionary" } }, + { name: "Media types", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "mediaTypes" } }, + { name: "Document types", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "documentTypes" } } + ], + expanded: true, + hasChildren: true, + level: 0, + isContainer: true + }; + + break; + default: + + t = { + name: "randomTree", + id: -1, + children: [ + { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + ], + expanded: true, + hasChildren: true, + level: 0, + menuUrl: menuUrl, + metaData: { treeAlias: "randomTree" } + }; + + break; + } + + + return [200, t, null]; + } + + + return { + register: function() { + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/ApplicationTreeApi/GetApplicationTrees')) + .respond(returnApplicationTrees); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/ApplicationTreeApi/GetChildren')) + .respond(returnChildren); + + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/DataTypeTree/GetNodes')) + .respond(returnDataTypes); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/DataTypeTree/GetMenu')) + .respond(returnDataTypeMenu); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/ApplicationTreeApi/GetMenu')) + .respond(getMenuItems); + + } + }; }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/user.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/user.mocks.js index 5b5d38d1b6..d91001ae07 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/user.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/user.mocks.js @@ -1,88 +1,88 @@ -angular.module('umbraco.mocks'). - factory('userMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function generateMockedUser() { - // Ensure a new user object each call - return { - name: "Per Ploug", - email: "test@test.com", - emailHash: "f9879d71855b5ff21e4963273a886bfc", - id: 0, - locale: 'da-DK', - remainingAuthSeconds: 600, - allowedSections: ["content", "media"] - }; - } - - function isAuthenticated() { - //check for existence of a cookie so we can do login/logout in the belle app (ignore for tests). - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - else { - return [200, null, null]; - } - } - - function getCurrentUser(status, data, headers) { - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - else { - return [200, generateMockedUser(), null]; - } - } - - function getRemainingTimeoutSeconds(status, data, headers) { - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - else { - return [200, 600, null]; - } - } - - function returnUser(status, data, headers) { - - //set the cookie for loging - mocksUtils.setAuth(); - - return [200, generateMockedUser(), null]; - } - - function logout() { - - mocksUtils.clearAuth(); - - return [200, null, null]; - - } - - return { - register: function() { - - $httpBackend - .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Authentication/PostLogin')) - .respond(returnUser); - - $httpBackend - .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Authentication/PostLogout')) - .respond(logout); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Authentication/IsAuthenticated')) - .respond(isAuthenticated); - - $httpBackend - .whenGET('/umbraco/UmbracoApi/Authentication/GetCurrentUser') - .respond(getCurrentUser); - - $httpBackend - .whenGET('/umbraco/UmbracoApi/Authentication/GetRemainingTimeoutSeconds') - .respond(getRemainingTimeoutSeconds); - - - } - }; +angular.module('umbraco.mocks'). + factory('userMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function generateMockedUser() { + // Ensure a new user object each call + return { + name: "Per Ploug", + email: "test@test.com", + emailHash: "f9879d71855b5ff21e4963273a886bfc", + id: 0, + locale: 'da-DK', + remainingAuthSeconds: 600, + allowedSections: ["content", "media"] + }; + } + + function isAuthenticated() { + //check for existence of a cookie so we can do login/logout in the belle app (ignore for tests). + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + else { + return [200, null, null]; + } + } + + function getCurrentUser(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + else { + return [200, generateMockedUser(), null]; + } + } + + function getRemainingTimeoutSeconds(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + else { + return [200, 600, null]; + } + } + + function returnUser(status, data, headers) { + + //set the cookie for loging + mocksUtils.setAuth(); + + return [200, generateMockedUser(), null]; + } + + function logout() { + + mocksUtils.clearAuth(); + + return [200, null, null]; + + } + + return { + register: function() { + + $httpBackend + .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Authentication/PostLogin')) + .respond(returnUser); + + $httpBackend + .whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Authentication/PostLogout')) + .respond(logout); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Authentication/IsAuthenticated')) + .respond(isAuthenticated); + + $httpBackend + .whenGET('/umbraco/UmbracoApi/Authentication/GetCurrentUser') + .respond(getCurrentUser); + + $httpBackend + .whenGET('/umbraco/UmbracoApi/Authentication/GetRemainingTimeoutSeconds') + .respond(getRemainingTimeoutSeconds); + + + } + }; }]); \ No newline at end of file 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 153f638ce1..651c54bfeb 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 @@ -1,755 +1,755 @@ -angular.module('umbraco.mocks'). - factory('localizationMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function getLanguageResource(status, data, headers) { - //check for existence of a cookie so we can do login/logout in the belle app (ignore for tests). - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - else { - return [200, { - "actions_assignDomain": "Culture and Hostnames", - "actions_auditTrail": "Audit Trail", - "actions_browse": "Browse Node", - "actions_changeDocType": "Change Document Type", - "actions_copy": "Copy", - "actions_create": "Create", - "actions_createPackage": "Create Package", - "actions_delete": "Delete", - "actions_disable": "Disable", - "actions_emptyTrashcan": "Empty recycle bin", - "actions_exportDocumentType": "Export Document Type", - "actions_importDocumentType": "Import Document Type", - "actions_importPackage": "Import Package", - "actions_liveEdit": "Edit in Canvas", - "actions_logout": "Exit", - "actions_move": "Move", - "actions_notify": "Notifications", - "actions_protect": "Public access", - "actions_publish": "Publish", - "actions_unpublish": "Unpublish", - "actions_refreshNode": "Reload nodes", - "actions_republish": "Republish entire site", - "actions_rights": "Permissions", - "actions_rollback": "Rollback", - "actions_sendtopublish": "Send To Publish", - "actions_sendToTranslate": "Send To Translation", - "actions_sort": "Sort", - "actions_toPublish": "Send to publication", - "actions_translate": "Translate", - "actions_update": "Update", - "actions_exportContourForm": "Export form", - "actions_importContourForm": "Import form", - "actions_archiveContourForm": "Archive form", - "actions_unarchiveContourForm": "Unarchive form", - "actions_defaultValue": "Default value", - "assignDomain_permissionDenied": "Permission denied.", - "assignDomain_addNew": "Add new Domain", - "assignDomain_remove": "remove", - "assignDomain_invalidNode": "Invalid node.", - "assignDomain_invalidDomain": "Invalid domain format.", - "assignDomain_duplicateDomain": "Domain has already been assigned.", - "assignDomain_domain": "Domain", - "assignDomain_language": "Language", - "assignDomain_domainCreated": "New domain '%0%' has been created", - "assignDomain_domainDeleted": "Domain '%0%' is deleted", - "assignDomain_domainExists": "Domain '%0%' has already been assigned", - "assignDomain_domainHelp": "Valid domain names are: 'example.com', 'www.example.com', 'example.com:8080' or 'https://www.example.com/'.

    One-level paths in domains are supported, eg. 'example.com/en'. However, they should be avoided. Better use the culture setting above.", - "assignDomain_domainUpdated": "Domain '%0%' has been updated", - "assignDomain_orEdit": "Edit Current Domains", - "assignDomain_inherit": "Inherit", - "assignDomain_setLanguage": "Culture", - "assignDomain_setLanguageHelp": "Set the culture for nodes below the current node,
    or inherit culture from parent nodes. Will also apply
    to the current node, unless a domain below applies too.", - "assignDomain_setDomains": "Domains", - "auditTrails_atViewingFor": "Viewing for", - "buttons_select": "Select", - "buttons_somethingElse": "Do something else", - "buttons_bold": "Bold", - "buttons_deindent": "Cancel Paragraph Indent", - "buttons_formFieldInsert": "Insert form field", - "buttons_graphicHeadline": "Insert graphic headline", - "buttons_htmlEdit": "Edit Html", - "buttons_indent": "Indent Paragraph", - "buttons_italic": "Italic", - "buttons_justifyCenter": "Center", - "buttons_justifyLeft": "Justify Left", - "buttons_justifyRight": "Justify Right", - "buttons_linkInsert": "Insert Link", - "buttons_linkLocal": "Insert local link (anchor)", - "buttons_listBullet": "Bullet List", - "buttons_listNumeric": "Numeric List", - "buttons_macroInsert": "Insert macro", - "buttons_pictureInsert": "Insert picture", - "buttons_relations": "Edit relations", - "buttons_save": "Save", - "buttons_saveAndPublish": "Save and publish", - "buttons_saveToPublish": "Save and send for approval", - "buttons_showPage": "Preview", - "buttons_showPageDisabled": "Preview is disabled because there's no template assigned", - "buttons_styleChoose": "Choose style", - "buttons_styleShow": "Show styles", - "buttons_tableInsert": "Insert table", - "changeDocType_changeDocTypeInstruction": "To change the document type for the selected content, first select from the list of valid types for this location.", - "changeDocType_changeDocTypeInstruction2": "Then confirm and/or amend the mapping of properties from the current type to the new, and click Save.", - "changeDocType_contentRepublished": "The content has been re-published.", - "changeDocType_currentProperty": "Current Property", - "changeDocType_currentType": "Current type", - "changeDocType_docTypeCannotBeChanged": "The document type cannot be changed, as there are no alternatives valid for this location.", - "changeDocType_docTypeChanged": "Document Type Changed", - "changeDocType_mapProperties": "Map Properties", - "changeDocType_mapToProperty": "Map to Property", - "changeDocType_newTemplate": "New Template", - "changeDocType_newType": "New Type", - "changeDocType_none": "none", - "changeDocType_selectedContent": "Content", - "changeDocType_selectNewDocType": "Select New Document Type", - "changeDocType_successMessage": "The document type of the selected content has been successfully changed to [new type] and the following properties mapped:", - "changeDocType_to": "to", - "changeDocType_validationErrorPropertyWithMoreThanOneMapping": "Could not complete property mapping as one or more properties have more than one mapping defined.", - "changeDocType_validDocTypesNote": "Only alternate types valid for the current location are displayed.", - "content_about": "About this page", - "content_alias": "Alias", - "content_alternativeTextHelp": "(how would you describe the picture over the phone)", - "content_alternativeUrls": "Alternative Links", - "content_clickToEdit": "Click to edit this item", - "content_createBy": "Created by", - "content_createByDesc": "Original autho", - "content_updatedBy": "Updated by", - "content_createDate": "Created", - "content_createDateDesc": "Date/time this document was created", - "content_documentType": "Document Type", - "content_editing": "Editing", - "content_expireDate": "Remove at", - "content_itemChanged": "This item has been changed after publication", - "content_itemNotPublished": "This item is not published", - "content_lastPublished": "Last published", - "content_listViewNoItems": "There are no items show in the list.", - "content_mediatype": "Media Type", - "content_mediaLinks": "Link to media item(s)", - "content_membergroup": "Member Group", - "content_memberrole": "Role", - "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", - "content_publish": "Publish", - "content_publishStatus": "Publication Status", - "content_releaseDate": "Publish at", - "content_removeDate": "Clear Date", - "content_sortDone": "Sortorder is updated", - "content_sortHelp": "To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the 'shift' or 'control' key while selecting", - "content_statistics": "Statistics", - "content_titleOptional": "Title (optional)", - "content_type": "Type", - "content_unPublish": "Unpublish", - "content_updateDate": "Last edited", - "content_updateDateDesc": "Date/time this document was created", - "content_uploadClear": "Remove file", +angular.module('umbraco.mocks'). + factory('localizationMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function getLanguageResource(status, data, headers) { + //check for existence of a cookie so we can do login/logout in the belle app (ignore for tests). + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + else { + return [200, { + "actions_assignDomain": "Culture and Hostnames", + "actions_auditTrail": "Audit Trail", + "actions_browse": "Browse Node", + "actions_changeDocType": "Change Document Type", + "actions_copy": "Copy", + "actions_create": "Create", + "actions_createPackage": "Create Package", + "actions_delete": "Delete", + "actions_disable": "Disable", + "actions_emptyTrashcan": "Empty recycle bin", + "actions_exportDocumentType": "Export Document Type", + "actions_importDocumentType": "Import Document Type", + "actions_importPackage": "Import Package", + "actions_liveEdit": "Edit in Canvas", + "actions_logout": "Exit", + "actions_move": "Move", + "actions_notify": "Notifications", + "actions_protect": "Public access", + "actions_publish": "Publish", + "actions_unpublish": "Unpublish", + "actions_refreshNode": "Reload nodes", + "actions_republish": "Republish entire site", + "actions_rights": "Permissions", + "actions_rollback": "Rollback", + "actions_sendtopublish": "Send To Publish", + "actions_sendToTranslate": "Send To Translation", + "actions_sort": "Sort", + "actions_toPublish": "Send to publication", + "actions_translate": "Translate", + "actions_update": "Update", + "actions_exportContourForm": "Export form", + "actions_importContourForm": "Import form", + "actions_archiveContourForm": "Archive form", + "actions_unarchiveContourForm": "Unarchive form", + "actions_defaultValue": "Default value", + "assignDomain_permissionDenied": "Permission denied.", + "assignDomain_addNew": "Add new Domain", + "assignDomain_remove": "remove", + "assignDomain_invalidNode": "Invalid node.", + "assignDomain_invalidDomain": "Invalid domain format.", + "assignDomain_duplicateDomain": "Domain has already been assigned.", + "assignDomain_domain": "Domain", + "assignDomain_language": "Language", + "assignDomain_domainCreated": "New domain '%0%' has been created", + "assignDomain_domainDeleted": "Domain '%0%' is deleted", + "assignDomain_domainExists": "Domain '%0%' has already been assigned", + "assignDomain_domainHelp": "Valid domain names are: 'example.com', 'www.example.com', 'example.com:8080' or 'https://www.example.com/'.

    One-level paths in domains are supported, eg. 'example.com/en'. However, they should be avoided. Better use the culture setting above.", + "assignDomain_domainUpdated": "Domain '%0%' has been updated", + "assignDomain_orEdit": "Edit Current Domains", + "assignDomain_inherit": "Inherit", + "assignDomain_setLanguage": "Culture", + "assignDomain_setLanguageHelp": "Set the culture for nodes below the current node,
    or inherit culture from parent nodes. Will also apply
    to the current node, unless a domain below applies too.", + "assignDomain_setDomains": "Domains", + "auditTrails_atViewingFor": "Viewing for", + "buttons_select": "Select", + "buttons_somethingElse": "Do something else", + "buttons_bold": "Bold", + "buttons_deindent": "Cancel Paragraph Indent", + "buttons_formFieldInsert": "Insert form field", + "buttons_graphicHeadline": "Insert graphic headline", + "buttons_htmlEdit": "Edit Html", + "buttons_indent": "Indent Paragraph", + "buttons_italic": "Italic", + "buttons_justifyCenter": "Center", + "buttons_justifyLeft": "Justify Left", + "buttons_justifyRight": "Justify Right", + "buttons_linkInsert": "Insert Link", + "buttons_linkLocal": "Insert local link (anchor)", + "buttons_listBullet": "Bullet List", + "buttons_listNumeric": "Numeric List", + "buttons_macroInsert": "Insert macro", + "buttons_pictureInsert": "Insert picture", + "buttons_relations": "Edit relations", + "buttons_save": "Save", + "buttons_saveAndPublish": "Save and publish", + "buttons_saveToPublish": "Save and send for approval", + "buttons_showPage": "Preview", + "buttons_showPageDisabled": "Preview is disabled because there's no template assigned", + "buttons_styleChoose": "Choose style", + "buttons_styleShow": "Show styles", + "buttons_tableInsert": "Insert table", + "changeDocType_changeDocTypeInstruction": "To change the document type for the selected content, first select from the list of valid types for this location.", + "changeDocType_changeDocTypeInstruction2": "Then confirm and/or amend the mapping of properties from the current type to the new, and click Save.", + "changeDocType_contentRepublished": "The content has been re-published.", + "changeDocType_currentProperty": "Current Property", + "changeDocType_currentType": "Current type", + "changeDocType_docTypeCannotBeChanged": "The document type cannot be changed, as there are no alternatives valid for this location.", + "changeDocType_docTypeChanged": "Document Type Changed", + "changeDocType_mapProperties": "Map Properties", + "changeDocType_mapToProperty": "Map to Property", + "changeDocType_newTemplate": "New Template", + "changeDocType_newType": "New Type", + "changeDocType_none": "none", + "changeDocType_selectedContent": "Content", + "changeDocType_selectNewDocType": "Select New Document Type", + "changeDocType_successMessage": "The document type of the selected content has been successfully changed to [new type] and the following properties mapped:", + "changeDocType_to": "to", + "changeDocType_validationErrorPropertyWithMoreThanOneMapping": "Could not complete property mapping as one or more properties have more than one mapping defined.", + "changeDocType_validDocTypesNote": "Only alternate types valid for the current location are displayed.", + "content_about": "About this page", + "content_alias": "Alias", + "content_alternativeTextHelp": "(how would you describe the picture over the phone)", + "content_alternativeUrls": "Alternative Links", + "content_clickToEdit": "Click to edit this item", + "content_createBy": "Created by", + "content_createByDesc": "Original autho", + "content_updatedBy": "Updated by", + "content_createDate": "Created", + "content_createDateDesc": "Date/time this document was created", + "content_documentType": "Document Type", + "content_editing": "Editing", + "content_expireDate": "Remove at", + "content_itemChanged": "This item has been changed after publication", + "content_itemNotPublished": "This item is not published", + "content_lastPublished": "Last published", + "content_listViewNoItems": "There are no items show in the list.", + "content_mediatype": "Media Type", + "content_mediaLinks": "Link to media item(s)", + "content_membergroup": "Member Group", + "content_memberrole": "Role", + "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", + "content_publish": "Publish", + "content_publishStatus": "Publication Status", + "content_releaseDate": "Publish at", + "content_removeDate": "Clear Date", + "content_sortDone": "Sortorder is updated", + "content_sortHelp": "To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the 'shift' or 'control' key while selecting", + "content_statistics": "Statistics", + "content_titleOptional": "Title (optional)", + "content_type": "Type", + "content_unPublish": "Unpublish", + "content_updateDate": "Last edited", + "content_updateDateDesc": "Date/time this document was created", + "content_uploadClear": "Remove file", "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", - "create_chooseNode": "Where do you want to create the new %0%", - "create_createUnder": "Create a page under", - "create_updateData": "Choose a type and a title", - "create_noDocumentTypes": "There are no allowed document types available. You must enable these in the settings section under 'document types'.", - "create_noMediaTypes": "There are no allowed media types available. You must enable these in the settings section under 'media types'.", - "dashboard_browser": "Browse your website", - "dashboard_dontShowAgain": "- Hide", - "dashboard_nothinghappens": "If Umbraco isn't opening, you might need to allow popups from this site", - "dashboard_openinnew": "has opened in a new window", - "dashboard_restart": "Restart", - "dashboard_visit": "Visit", - "dashboard_welcome": "Welcome", - "defaultdialogs_anchorInsert": "Name", - "defaultdialogs_assignDomain": "Manage hostnames", - "defaultdialogs_closeThisWindow": "Close this window", - "defaultdialogs_confirmdelete": "Are you sure you want to delete", - "defaultdialogs_confirmdisable": "Are you sure you want to disable", - "defaultdialogs_confirmEmptyTrashcan": "Please check this box to confirm deletion of %0% item(s)", - "defaultdialogs_confirmlogout": "Are you sure?", - "defaultdialogs_confirmSure": "Are you sure?", - "defaultdialogs_cut": "Cut", - "defaultdialogs_editdictionary": "Edit Dictionary Item", - "defaultdialogs_editlanguage": "Edit Language", - "defaultdialogs_insertAnchor": "Insert local link", - "defaultdialogs_insertCharacter": "Insert character", - "defaultdialogs_insertgraphicheadline": "Insert graphic headline", - "defaultdialogs_insertimage": "Insert picture", - "defaultdialogs_insertlink": "Insert link", - "defaultdialogs_insertMacro": "Click to add a Macro", - "defaultdialogs_inserttable": "Insert table", - "defaultdialogs_lastEdited": "Last Edited", - "defaultdialogs_link": "Link", - "defaultdialogs_linkinternal": "Internal link:", - "defaultdialogs_linklocaltip": "When using local links, insert '#' infront of link", - "defaultdialogs_linknewwindow": "Open in new window?", - "defaultdialogs_macroContainerSettings": "Macro Settings", - "defaultdialogs_macroDoesNotHaveProperties": "This macro does not contain any properties you can edit", - "defaultdialogs_paste": "Paste", - "defaultdialogs_permissionsEdit": "Edit Permissions for", - "defaultdialogs_recycleBinDeleting": "The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place", - "defaultdialogs_recycleBinIsEmpty": "The recycle bin is now empty", - "defaultdialogs_recycleBinWarning": "When items are deleted from the recycle bin, they will be gone forever", - "defaultdialogs_regexSearchError": "regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.", - "defaultdialogs_regexSearchHelp": "Search for a regular expression to add validation to a form field. Exemple: 'email, 'zip-code' 'url'", - "defaultdialogs_removeMacro": "Remove Macro", - "defaultdialogs_requiredField": "Required Field", - "defaultdialogs_sitereindexed": "Site is reindexed", - "defaultdialogs_siterepublished": "The website cache has been refreshed. All publish content is now uptodate. While all unpublished content is still unpublished", - "defaultdialogs_siterepublishHelp": "The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished.", - "defaultdialogs_tableColumns": "Number of columns", - "defaultdialogs_tableRows": "Number of rows", - "defaultdialogs_templateContentAreaHelp": "Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates, by refering this ID using a <asp:content /> element.", - "defaultdialogs_templateContentPlaceHolderHelp": "Select a placeholder id from the list below. You can only choose Id's from the current template's master.", - "defaultdialogs_thumbnailimageclickfororiginal": "Click on the image to see full size", - "defaultdialogs_treepicker": "Pick item", - "defaultdialogs_viewCacheItem": "View Cache Item", - "dictionaryItem_description": "Edit the different language versions for the dictionary item '%0%' below
    You can add additional languages under the 'languages' in the menu on the left ", - "dictionaryItem_displayName": "Culture Name", - "placeholders_username": "Enter your username", - "placeholders_password": "Enter your password", - "placeholders_entername": "Enter a name...", - "placeholders_nameentity": "Name the %0%...", - "placeholders_search": "Type to search...", - "placeholders_filter": "Type to filter...", - "editcontenttype_allowedchildnodetypes": "Allowed child nodetypes", - "editcontenttype_create": "Create", - "editcontenttype_deletetab": "Delete tab", - "editcontenttype_description": "Description", - "editcontenttype_newtab": "New tab", - "editcontenttype_tab": "Tab", - "editcontenttype_thumbnail": "Thumbnail", - "editcontenttype_iscontainercontenttype": "Use as container content type", - "editdatatype_addPrevalue": "Add prevalue", - "editdatatype_dataBaseDatatype": "Database datatype", - "editdatatype_guid": "Property editor GUID", - "editdatatype_renderControl": "Property editor", - "editdatatype_rteButtons": "Buttons", - "editdatatype_rteEnableAdvancedSettings": "Enable advanced settings for", - "editdatatype_rteEnableContextMenu": "Enable context menu", - "editdatatype_rteMaximumDefaultImgSize": "Maximum default size of inserted images", - "editdatatype_rteRelatedStylesheets": "Related stylesheets", - "editdatatype_rteShowLabel": "Show label", - "editdatatype_rteWidthAndHeight": "Width and height", - "errorHandling_errorButDataWasSaved": "Your data has been saved, but before you can publish this page there are some errors you need to fix first:", - "errorHandling_errorChangingProviderPassword": "The current MemberShip Provider does not support changing password (EnablePasswordRetrieval need to be true)", - "errorHandling_errorExistsWithoutTab": "%0% already exists", - "errorHandling_errorHeader": "There were errors:", - "errorHandling_errorHeaderWithoutTab": "There were errors:", - "errorHandling_errorInPasswordFormat": "The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s)", - "errorHandling_errorIntegerWithoutTab": "%0% must be an integer", - "errorHandling_errorMandatory": "The %0% field in the %1% tab is mandatory", - "errorHandling_errorMandatoryWithoutTab": "%0% is a mandatory field", - "errorHandling_errorRegExp": "%0% at %1% is not in a correct format", - "errorHandling_errorRegExpWithoutTab": "%0% is not in a correct format", - "errors_dissallowedMediaType": "The specified file type has been dissallowed by the administrator", - "errors_codemirroriewarning": "NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough.", - "errors_contentTypeAliasAndNameNotNull": "Please fill both alias and name on the new propertytype!", - "errors_filePermissionsError": "There is a problem with read/write access to a specific file or folder", - "errors_missingTitle": "Please enter a title", - "errors_missingType": "Please choose a type", - "errors_pictureResizeBiggerThanOrg": "You're about to make the picture larger than the original size. Are you sure that you want to proceed?", - "errors_startNodeDoesNotExists": "Startnode deleted, please contact your administrator", - "errors_stylesMustMarkBeforeSelect": "Please mark content before changing style", - "errors_stylesNoStylesOnPage": "No active styles available", - "errors_tableColMergeLeft": "Please place cursor at the left of the two cells you wish to merge", - "errors_tableSplitNotSplittable": "You cannot split a cell that hasn't been merged.", - "general_about": "About", - "general_action": "Action", - "general_add": "Add", - "general_alias": "Alias", - "general_areyousure": "Are you sure?", - "general_border": "Border", - "general_by": "or", - "general_cancel": "Cancel", - "general_cellMargin": "Cell margin", - "general_choose": "Choose", - "general_close": "Close", - "general_closewindow": "Close Window", - "general_comment": "Comment", - "general_confirm": "Confirm", - "general_constrainProportions": "Constrain proportions", - "general_continue": "Continue", - "general_copy": "Copy", - "general_create": "Create", - "general_database": "Database", - "general_date": "Date", - "general_default": "Default", - "general_delete": "Delete", - "general_deleted": "Deleted", - "general_deleting": "Deleting...", - "general_design": "Design", - "general_dimensions": "Dimensions", - "general_down": "Down", - "general_download": "Download", - "general_edit": "Edit", - "general_edited": "Edited", - "general_elements": "Elements", - "general_email": "Email", - "general_error": "Error", - "general_findDocument": "Find", - "general_height": "Height", - "general_help": "Help", - "general_icon": "Icon", - "general_import": "Import", - "general_innerMargin": "Inner margin", - "general_insert": "Insert", - "general_install": "Install", - "general_justify": "Justify", - "general_language": "Language", - "general_layout": "Layout", - "general_loading": "Loading", - "general_locked": "Locked", - "general_login": "Login", - "general_logoff": "Log off", - "general_logout": "Logout", - "general_macro": "Macro", - "general_move": "Move", - "general_name": "Name", - "general_new": "New", - "general_next": "Next", - "general_no": "No", - "general_of": "of", - "general_ok": "OK", - "general_open": "Open", - "general_or": "or", - "general_password": "Password", - "general_path": "Path", - "general_placeHolderID": "Placeholder ID", - "general_pleasewait": "One moment please...", - "general_previous": "Previous", - "general_properties": "Properties", - "general_reciept": "Email to receive form data", - "general_recycleBin": "Recycle Bin", - "general_remaining": "Remaining", - "general_rename": "Rename", - "general_renew": "Renew", - "general_required": "Required", - "general_retry": "Retry", - "general_rights": "Permissions", - "general_search": "Search", - "general_server": "Server", - "general_show": "Show", - "general_showPageOnSend": "Show page on Send", - "general_size": "Size", - "general_sort": "Sort", - "general_type": "Type", - "general_typeToSearch": "Type to search...", - "general_up": "Up", - "general_update": "Update", - "general_upgrade": "Upgrade", - "general_upload": "Upload", - "general_url": "Url", - "general_user": "User", - "general_username": "Username", - "general_value": "Value", - "general_view": "View", - "general_welcome": "Welcome...", - "general_width": "Width", - "general_yes": "Yes", - "general_folder": "Folder", - "general_searchResults": "Search results", - "graphicheadline_backgroundcolor": "Background color", - "graphicheadline_bold": "Bold", - "graphicheadline_color": "Text color", - "graphicheadline_font": "Font", - "graphicheadline_text": "Text", - "headers_page": "Page", - "installer_databaseErrorCannotConnect": "The installer cannot connect to the database.", - "installer_databaseErrorWebConfig": "Could not save the web.config file. Please modify the connection string manually.", - "installer_databaseFound": "Your database has been found and is identified as", - "installer_databaseHeader": "Database configuration", - "installer_databaseInstall": " Press the install button to install the Umbraco %0% database ", - "installer_databaseInstallDone": "Umbraco %0% has now been copied to your database. Press Next to proceed.", - "installer_databaseNotFound": "

    Database not found! Please check that the information in the 'connection string' of the \"web.config\" file is correct.

    To proceed, please edit the 'web.config' file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named 'UmbracoDbDSN' and save the file.

    Click the retry button when done.
    More information on editing web.config here.

    ", - "installer_databaseText": "To complete this step, you must know some information regarding your database server ('connection string').
    Please contact your ISP if necessary. If you're installing on a local machine or server you might need information from your system administrator.", - "installer_databaseUpgrade": "

    Press the upgrade button to upgrade your database to Umbraco %0%

    Don't worry - no content will be deleted and everything will continue working afterwards!

    ", - "installer_databaseUpgradeDone": "Your database has been upgraded to the final version %0%.
    Press Next to proceed. ", - "installer_databaseUpToDate": "Your current database is up-to-date!. Click next to continue the configuration wizard", - "installer_defaultUserChangePass": "The Default users' password needs to be changed!", - "installer_defaultUserDisabled": "The Default user has been disabled or has no access to Umbraco!

    No further actions needs to be taken. Click Next to proceed.", - "installer_defaultUserPassChanged": "The Default user's password has been successfully changed since the installation!

    No further actions needs to be taken. Click Next to proceed.", - "installer_defaultUserPasswordChanged": "The password is changed!", - "installer_defaultUserText": "

    Umbraco creates a default user with a login ('admin') and password ('default'). It's important that the password is changed to something unique.

    This step will check the default user's password and suggest if it needs to be changed.

    ", - "installer_greatStart": "Get a great start, watch our introduction videos", - "installer_licenseText": "By clicking the next button (or modifying the UmbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI.", - "installer_None": "Not installed yet.", - "installer_permissionsAffectedFolders": "Affected files and folders", - "installer_permissionsAffectedFoldersMoreInfo": "More information on setting up permissions for Umbraco here", - "installer_permissionsAffectedFoldersText": "You need to grant ASP.NET modify permissions to the following files/folders", - "installer_permissionsAlmostPerfect": "Your permission settings are almost perfect!

    You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.", - "installer_permissionsHowtoResolve": "How to Resolve", - "installer_permissionsHowtoResolveLink": "Click here to read the text version", - "installer_permissionsHowtoResolveText": "Watch our video tutorial on setting up folder permissions for Umbraco or read the text version.", - "installer_permissionsMaybeAnIssue": "Your permission settings might be an issue!

    You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.", - "installer_permissionsNotReady": "Your permission settings are not ready for Umbraco!

    In order to run Umbraco, you'll need to update your permission settings.", - "installer_permissionsPerfect": "Your permission settings are perfect!

    You are ready to run Umbraco and install packages!", - "installer_permissionsResolveFolderIssues": "Resolving folder issue", - "installer_permissionsResolveFolderIssuesLink": "Follow this link for more information on problems with ASP.NET and creating folders", - "installer_permissionsSettingUpPermissions": "Setting up folder permissions", - "installer_permissionsText": " Umbraco needs write/modify access to certain directories in order to store files like pictures and PDF's. It also stores temporary data (aka: cache) for enhancing the performance of your website. ", - "installer_runwayFromScratch": "I want to start from scratch", - "installer_runwayFromScratchText": " Your website is completely empty at the moment, so that's perfect if you want to start from scratch and create your own document types and templates. (learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. ", - "installer_runwayHeader": "You've just set up a clean Umbraco platform. What do you want to do next?", - "installer_runwayInstalled": "Runway is installed", - "installer_runwayInstalledText": " You have the foundation in place. Select what modules you wish to install on top of it.
    This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules ", - "installer_runwayOnlyProUsers": "Only recommended for experienced users", - "installer_runwaySimpleSite": "I want to start with a simple website", - "installer_runwaySimpleSiteText": "

    'Runway' is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, Runway offers an easy foundation based on best practices to get you started faster than ever. If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.

    Included with Runway: Home page, Getting Started page, Installing Modules page.
    Optional Modules: Top Navigation, Sitemap, Contact, Gallery.
    ", - "installer_runwayWhatIsRunway": "What is Runway", - "installer_step1": "Step 1/5 Accept license", - "installer_step2": "Step 2/5: Database configuration", - "installer_step3": "Step 3/5: Validating File Permissions", - "installer_step4": "Step 4/5: Check Umbraco security", - "installer_step5": "Step 5/5: Umbraco is ready to get you started", - "installer_thankYou": "Thank you for choosing Umbraco", - "installer_theEndBrowseSite": "

    Browse your new site

    You installed Runway, so why not see how your new website looks.", - "installer_theEndFurtherHelp": "

    Further help and information

    Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology", - "installer_theEndHeader": "Umbraco %0% is installed and ready for use", - "installer_theEndInstallFailed": "To finish the installation, you'll need to manually edit the /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.", - "installer_theEndInstallSuccess": "You can get started instantly by clicking the 'Launch Umbraco' button below.
    If you are new to Umbraco, you can find plenty of resources on our getting started pages.", - "installer_theEndOpenUmbraco": "

    Launch Umbraco

    To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality", - "installer_Unavailable": "Connection to database failed.", - "installer_Version3": "Umbraco Version 3", - "installer_Version4": "Umbraco Version 4", - "installer_watch": "Watch", - "installer_welcomeIntro": "This wizard will guide you through the process of configuring Umbraco %0% for a fresh install or upgrading from version 3.0.

    Press 'next' to start the wizard.", - "language_cultureCode": "Culture Code", - "language_displayName": "Culture Name", - "lockout_lockoutWillOccur": "You've been idle and logout will automatically occur in", - "lockout_renewSession": "Renew now to save your work", - "login_greeting1": "Happy super Sunday", - "login_greeting2": "Happy manic Monday ", - "login_greeting3": "Happy tremendous Tuesday", - "login_greeting4": "Happy wonderful Wednesday", - "login_greeting5": "Happy thunderous Thursday", - "login_greeting6": "Happy friendly Friday", - "login_greeting7": "Happy shiny Saturday", - "login_instruction": "Log in below:", - "login_bottomText": "

    © 2001 - %0%
    Umbraco.org

    ", - "main_dashboard": "Dashboard", - "main_sections": "Sections", - "main_tree": "Content", - "moveOrCopy_choose": "Choose page above...", - "moveOrCopy_copyDone": "%0% has been copied to %1%", - "moveOrCopy_copyTo": "Select where the document %0% should be copied to below", - "moveOrCopy_moveDone": "%0% has been moved to %1%", - "moveOrCopy_moveTo": "Select where the document %0% should be moved to below", - "moveOrCopy_nodeSelected": "has been selected as the root of your new content, click 'ok' below.", - "moveOrCopy_noNodeSelected": "No node selected yet, please select a node in the list above before clicking 'ok'", - "moveOrCopy_notAllowedByContentType": "The current node is not allowed under the chosen node because of its type", - "moveOrCopy_notAllowedByPath": "The current node cannot be moved to one of its subpages", - "moveOrCopy_notAllowedAtRoot": "The current node cannot exist at the root", - "moveOrCopy_notValid": "The action isn't allowed since you have insufficient permissions on 1 or more child documents.", - "moveOrCopy_relateToOriginal": "Relate copied items to original", - "notifications_editNotifications": "Edit your notification for %0%", - "notifications_mailBody": " Hi %0% This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%' Go to http://%4%/actions/editContent.aspx?id=%5% to edit. Have a nice day! Cheers from the Umbraco robot ", - "notifications_mailBodyHtml": "

    Hi %0%

    This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%'

    Update summary:

    %6%

    Have a nice day!

    Cheers from the Umbraco robot

    ", - "notifications_mailSubject": "[%0%] Notification about %1% performed on %2%", - "notifications_notifications": "Notifications", - "packager_chooseLocalPackageText": " Choose Package from your machine, by clicking the Browse
    button and locating the package. Umbraco packages usually have a '.umb' or '.zip' extension. ", - "packager_packageAuthor": "Author", - "packager_packageDemonstration": "Demonstration", - "packager_packageDocumentation": "Documentation", - "packager_packageMetaData": "Package meta data", - "packager_packageName": "Package name", - "packager_packageNoItemsHeader": "Package doesn't contain any items", - "packager_packageNoItemsText": "This package file doesn't contain any items to uninstall.

    You can safely remove this from the system by clicking 'uninstall package' below.", - "packager_packageNoUpgrades": "No upgrades available", - "packager_packageOptions": "Package options", - "packager_packageReadme": "Package readme", - "packager_packageRepository": "Package repository", - "packager_packageUninstallConfirm": "Confirm uninstall", - "packager_packageUninstalledHeader": "Package was uninstalled", - "packager_packageUninstalledText": "The package was successfully uninstalled", - "packager_packageUninstallHeader": "Uninstall package", - "packager_packageUninstallText": "You can unselect items you do not wish to remove, at this time, below. When you click 'confirm uninstall' all checked-off items will be removed.
    Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, so uninstall with caution. If in doubt, contact the package author.", - "packager_packageUpgradeDownload": "Download update from the repository", - "packager_packageUpgradeHeader": "Upgrade package", - "packager_packageUpgradeInstructions": "Upgrade instructions", - "packager_packageUpgradeText": " There's an upgrade available for this package. You can download it directly from the Umbraco package repository.", - "packager_packageVersion": "Package version", - "packager_packageVersionHistory": "Package version history", - "packager_viewPackageWebsite": "View package website", - "paste_doNothing": "Paste with full formatting (Not recommended)", - "paste_errorMessage": "The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web.", - "paste_removeAll": "Paste as raw text without any formatting at all", - "paste_removeSpecialFormattering": "Paste, but remove formatting (Recommended)", - "publicAccess_paAdvanced": "Role based protection", - "publicAccess_paAdvancedHelp": "If you wish to control access to the page using role-based authentication,
    using Umbraco's member groups.", - "publicAccess_paAdvancedNoGroups": "You need to create a membergroup before you can use
    role-based authentication.", - "publicAccess_paErrorPage": "Error Page", - "publicAccess_paErrorPageHelp": "Used when people are logged on, but do not have access", - "publicAccess_paHowWould": "Choose how to restict access to this page", - "publicAccess_paIsProtected": "%0% is now protected", - "publicAccess_paIsRemoved": "Protection removed from %0%", - "publicAccess_paLoginPage": "Login Page", - "publicAccess_paLoginPageHelp": "Choose the page that has the login formular", - "publicAccess_paRemoveProtection": "Remove Protection", - "publicAccess_paSelectPages": "Select the pages that contain login form and error messages", - "publicAccess_paSelectRoles": "Pick the roles who have access to this page", - "publicAccess_paSetLogin": "Set the login and password for this page", - "publicAccess_paSimple": "Single user protection", - "publicAccess_paSimpleHelp": "If you just want to setup simple protection using a single login and password", - "publish_contentPublishedFailedInvalid": " %0% could not be published because these properties: %1% did not pass validation rules. ", - "publish_contentPublishedFailedByEvent": " %0% could not be published, due to a 3rd party extension cancelling the action. ", - "publish_contentPublishedFailedByParent": " %0% can not be published, because a parent page is not published. ", - "publish_includeUnpublished": "Include unpublished child pages", - "publish_inProgress": "Publishing in progress - please wait...", - "publish_inProgressCounter": "%0% out of %1% pages have been published...", - "publish_nodePublish": "%0% has been published", - "publish_nodePublishAll": "%0% and subpages have been published", - "publish_publishAll": "Publish %0% and all its subpages", - "publish_publishHelp": "Click ok to publish %0% and thereby making it's content publicly available.

    You can publish this page and all it's sub-pages by checking publish all children below. ", - "relatedlinks_addExternal": "Add external link", - "relatedlinks_addInternal": "Add internal link", - "relatedlinks_addlink": "Add", - "relatedlinks_caption": "Caption", - "relatedlinks_internalPage": "Internal page", - "relatedlinks_linkurl": "URL", - "relatedlinks_modeDown": "Move Down", - "relatedlinks_modeUp": "Move Up", - "relatedlinks_newWindow": "Open in new window", - "relatedlinks_removeLink": "Remove link", - "rollback_currentVersion": "Current version", - "rollback_diffHelp": "This shows the differences between the current version and the selected version
    Red text will not be shown in the selected version. , green means added", - "rollback_documentRolledBack": "Document has been rolled back", - "rollback_htmlHelp": "This displays the selected version as html, if you wish to see the difference between 2 versions at the same time, use the diff view", - "rollback_rollbackTo": "Rollback to", - "rollback_selectVersion": "Select version", - "rollback_view": "View", - "scripts_editscript": "Edit script file", - "sections_concierge": "Concierge", - "sections_content": "Content", - "sections_courier": "Courier", - "sections_developer": "Developer", - "sections_installer": "Umbraco Configuration Wizard", - "sections_media": "Media", - "sections_member": "Members", - "sections_newsletters": "Newsletters", - "sections_settings": "Settings", - "sections_statistics": "Statistics", - "sections_translation": "Translation", - "sections_users": "Users", - "sections_contour": "Umbraco Contour", - "sections_help": "Help", - "settings_defaulttemplate": "Default template", - "settings_dictionary editor egenskab": "Dictionary Key", - "settings_importDocumentTypeHelp": "To import a document type, find the '.udt' file on your computer by clicking the 'Browse' button and click 'Import' (you'll be asked for confirmation on the next screen)", - "settings_newtabname": "New Tab Title", - "settings_nodetype": "Nodetype", - "settings_objecttype": "Type", - "settings_stylesheet": "Stylesheet", - "settings_stylesheet editor egenskab": "Stylesheet property", - "settings_tab": "Tab", - "settings_tabname": "Tab Title", - "settings_tabs": "Tabs", - "settings_contentTypeEnabled": "Master Content Type enabled", - "settings_contentTypeUses": "This Content Type uses", - "settings_asAContentMasterType": "as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself", - "settings_noPropertiesDefinedOnTab": "No properties defined on this tab. Click on the 'add a new property' link at the top to create a new property.", - "sort_sortDone": "Sorting complete.", - "sort_sortHelp": "Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items", - "sort_sortPleaseWait": " Please wait. Items are being sorted, this can take a while.

    Do not close this window during sorting", - "speechBubbles_contentPublishedFailedByEvent": "Publishing was cancelled by a 3rd party add-in", - "speechBubbles_contentTypeDublicatePropertyType": "Property type already exists", - "speechBubbles_contentTypePropertyTypeCreated": "Property type created", - "speechBubbles_contentTypePropertyTypeCreatedText": "Name: %0%
    DataType: %1%", - "speechBubbles_contentTypePropertyTypeDeleted": "Propertytype deleted", - "speechBubbles_contentTypeSavedHeader": "Document Type saved", - "speechBubbles_contentTypeTabCreated": "Tab created", - "speechBubbles_contentTypeTabDeleted": "Tab deleted", - "speechBubbles_contentTypeTabDeletedText": "Tab with id: %0% deleted", - "speechBubbles_cssErrorHeader": "Stylesheet not saved", - "speechBubbles_cssSavedHeader": "Stylesheet saved", - "speechBubbles_cssSavedText": "Stylesheet saved without any errors", - "speechBubbles_dataTypeSaved": "Datatype saved", - "speechBubbles_dictionaryItemSaved": "Dictionary item saved", - "speechBubbles_editContentPublishedFailedByParent": "Publishing failed because the parent page isn't published", - "speechBubbles_editContentPublishedHeader": "Content published", - "speechBubbles_editContentPublishedText": "and visible at the website", - "speechBubbles_editContentSavedHeader": "Content saved", - "speechBubbles_editContentSavedText": "Remember to publish to make changes visible", - "speechBubbles_editContentSendToPublish": "Sent For Approval", - "speechBubbles_editContentSendToPublishText": "Changes have been sent for approval", - "speechBubbles_editMediaSaved": "Media saved", - "speechBubbles_editMediaSavedText": "Media saved without any errors", - "speechBubbles_editMemberSaved": "Member saved", - "speechBubbles_editStylesheetPropertySaved": "Stylesheet Property Saved", - "speechBubbles_editStylesheetSaved": "Stylesheet saved", - "speechBubbles_editTemplateSaved": "Template saved", - "speechBubbles_editUserError": "Error saving user (check log)", - "speechBubbles_editUserSaved": "User Saved", - "speechBubbles_editUserTypeSaved": "User type saved", - "speechBubbles_fileErrorHeader": "File not saved", - "speechBubbles_fileErrorText": "file could not be saved. Please check file permissions", - "speechBubbles_fileSavedHeader": "File saved", - "speechBubbles_fileSavedText": "File saved without any errors", - "speechBubbles_languageSaved": "Language saved", - "speechBubbles_templateErrorHeader": "Template not saved", - "speechBubbles_templateErrorText": "Please make sure that you do not have 2 templates with the same alias", - "speechBubbles_templateSavedHeader": "Template saved", - "speechBubbles_templateSavedText": "Template saved without any errors!", - "speechBubbles_contentUnpublished": "Content unpublished", - "speechBubbles_partialViewSavedHeader": "Partial view saved", - "speechBubbles_partialViewSavedText": "Partial view saved without any errors!", - "speechBubbles_partialViewErrorHeader": "Partial view not saved", - "speechBubbles_partialViewErrorText": "An error occurred saving the file.", - "stylesheet_aliasHelp": "Uses CSS syntax ex: h1, .redHeader, .blueTex", - "stylesheet_editstylesheet": "Edit stylesheet", - "stylesheet_editstylesheetproperty": "Edit stylesheet property", - "stylesheet_nameHelp": "Name to identify the style property in the rich text editor ", - "stylesheet_preview": "Preview", - "stylesheet_styles": "Styles", - "template_edittemplate": "Edit template", - "template_insertContentArea": "Insert content area", - "template_insertContentAreaPlaceHolder": "Insert content area placeholder", - "template_insertDictionaryItem": "Insert dictionary item", - "template_insertMacro": "Insert Macro", - "template_insertPageField": "Insert Umbraco page field", - "template_mastertemplate": "Master template", - "template_quickGuide": "Quick Guide to Umbraco template tags", - "template_template": "Template", - "templateEditor_alternativeField": "Alternative field", - "templateEditor_alternativeText": "Alternative Text", - "templateEditor_casing": "Casing", - "templateEditor_encoding": "Encoding", - "templateEditor_chooseField": "Choose field", - "templateEditor_convertLineBreaks": "Convert Linebreaks", - "templateEditor_convertLineBreaksHelp": "Replaces linebreaks with html-tag <br>", - "templateEditor_customFields": "Custom Fields", - "templateEditor_dateOnly": "Yes, Date only", - "templateEditor_formatAsDate": "Format as date", - "templateEditor_htmlEncode": "HTML encode", - "templateEditor_htmlEncodeHelp": "Will replace special characters by their HTML equivalent.", - "templateEditor_insertedAfter": "Will be inserted after the field value", - "templateEditor_insertedBefore": "Will be inserted before the field value", - "templateEditor_lowercase": "Lowercase", - "templateEditor_none": "None", - "templateEditor_postContent": "Insert after field", - "templateEditor_preContent": "Insert before field", - "templateEditor_recursive": "Recursive", - "templateEditor_removeParagraph": "Remove Paragraph tags", - "templateEditor_removeParagraphHelp": "Will remove any <P> in the beginning and end of the text", - "templateEditor_standardFields": "Standard Fields", - "templateEditor_uppercase": "Uppercase", - "templateEditor_urlEncode": "URL encode", - "templateEditor_urlEncodeHelp": "Will format special characters in URLs", - "templateEditor_usedIfAllEmpty": "Will only be used when the field values above are empty", - "templateEditor_usedIfEmpty": "This field will only be used if the primary field is empty", - "templateEditor_withTime": "Yes, with time. Seperator: ", - "translation_assignedTasks": "Tasks assigned to you", - "translation_assignedTasksHelp": " The list below shows translation tasks assigned to you. To see a detailed view including comments, click on 'Details' or just the page name. You can also download the page as XML directly by clicking the 'Download Xml' link.
    To close a translation task, please go to the Details view and click the 'Close' button. ", - "translation_closeTask": "close task", - "translation_details": "Translation details", - "translation_downloadAllAsXml": "Download all translation tasks as xml", - "translation_downloadTaskAsXml": "Download xml", - "translation_DownloadXmlDTD": "Download xml DTD", - "translation_fields": "Fields", - "translation_includeSubpages": "Include subpages", - "translation_mailBody": " Hi %0% This is an automated mail to inform you that the document '%1%' has been requested for translation into '%5%' by %2%. Go to http://%3%/translation/details.aspx?id=%4% to edit. Or log into Umbraco to get an overview of your translation tasks http://%3% Have a nice day! Cheers from the Umbraco robot ", - "translation_mailSubject": "[%0%] Translation task for %1%", - "translation_noTranslators": "No translator users found. Please create a translator user before you start sending content to translation", - "translation_ownedTasks": "Tasks created by you", - "translation_ownedTasksHelp": " The list below shows pages created by you. To see a detailed view including comments, click on 'Details' or just the page name. You can also download the page as XML directly by clicking the 'Download Xml' link. To close a translation task, please go to the Details view and click the 'Close' button. ", - "translation_pageHasBeenSendToTranslation": "The page '%0%' has been send to translation", - "translation_sendToTranslate": "Send the page '%0%' to translation", - "translation_taskAssignedBy": "Assigned by", - "translation_taskOpened": "Task opened", - "translation_totalWords": "Total words", - "translation_translateTo": "Translate to", - "translation_translationDone": "Translation completed.", - "translation_translationDoneHelp": "You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages.", - "translation_translationFailed": "Translation failed, the xml file might be corrupt", - "translation_translationOptions": "Translation options", - "translation_translator": "Translator", - "translation_uploadTranslationXml": "Upload translation xml", - "treeHeaders_cacheBrowser": "Cache Browser", - "treeHeaders_contentRecycleBin": "Recycle Bin", - "treeHeaders_createdPackages": "Created packages", - "treeHeaders_datatype": "Data Types", - "treeHeaders_dictionary": "Dictionary", - "treeHeaders_installedPackages": "Installed packages", - "treeHeaders_installSkin": "Install skin", - "treeHeaders_installStarterKit": "Install starter kit", - "treeHeaders_languages": "Languages", - "treeHeaders_localPackage": "Install local package", - "treeHeaders_macros": "Macros", - "treeHeaders_mediaTypes": "Media Types", - "treeHeaders_member": "Members", - "treeHeaders_memberGroup": "Member Groups", - "treeHeaders_memberRoles": "Roles", - "treeHeaders_memberType": "Member Types", - "treeHeaders_nodeTypes": "Document Types", - "treeHeaders_packager": "Packages", - "treeHeaders_packages": "Packages", - "treeHeaders_repositories": "Install from repository", - "treeHeaders_runway": "Install Runway", - "treeHeaders_runwayModules": "Runway modules", - "treeHeaders_scripting": "Scripting Files", - "treeHeaders_scripts": "Scripts", - "treeHeaders_stylesheets": "Stylesheets", - "treeHeaders_templates": "Templates", - "update_updateAvailable": "New update ready", - "update_updateDownloadText": "%0% is ready, click here for download", - "update_updateNoServer": "No connection to server", - "update_updateNoServerError": "Error checking for update. Please review trace-stack for further information", - "user_administrators": "Administrator", - "user_categoryField": "Category field", - "user_changePassword": "Change Your Password", - "user_newPassword": "New password", - "user_confirmNewPassword": "Confirm new password", - "user_changePasswordDescription": "You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button", - "user_contentChannel": "Content Channel", - "user_descriptionField": "Description field", - "user_disabled": "Disable User", - "user_documentType": "Document Type", - "user_editors": "Editor", - "user_excerptField": "Excerpt field", - "user_language": "Language", - "user_loginname": "Login", - "user_mediastartnode": "Start Node in Media Library", - "user_modules": "Sections", - "user_noConsole": "Disable Umbraco Access", - "user_oldPassword": "Old password", - "user_password": "Password", - "user_resetPassword": "Reset password", - "user_passwordChanged": "Your password has been changed!", - "user_passwordConfirm": "Please confirm the new password", - "user_passwordEnterNew": "Enter your new password", - "user_passwordIsBlank": "Your new password cannot be blank!", - "user_passwordCurrent": "Current password", - "user_passwordInvalid": "Invalid current password", - "user_passwordIsDifferent": "There was a difference between the new password and the confirmed password. Please try again!", - "user_passwordMismatch": "The confirmed password doesn't match the new password!", - "user_permissionReplaceChildren": "Replace child node permssions", - "user_permissionSelectedPages": "You are currently modifying permissions for the pages:", - "user_permissionSelectPages": "Select pages to modify their permissions", - "user_searchAllChildren": "Search all children", - "user_startnode": "Start Node in Content", - "user_username": "Username", - "user_userPermissions": "User permissions", - "user_usertype": "User type", - "user_userTypes": "User types", - "user_writer": "Writer", - "user_yourProfile": "Your profile", - "user_yourHistory": "Your recent history", - "user_sessionExpires": "Session expires in" - }, null]; - } - } - - return { - register: function() { - $httpBackend - .whenGET(mocksUtils.urlRegex('LocalizedText')) - .respond(getLanguageResource); - } - }; + "defaultdialogs_urlLinkPicker":"Link", + "content_memberof": "Member of group(s)", + "content_notmemberof": "Not a member of group(s)", + "content_childItems": "Child items", + "create_chooseNode": "Where do you want to create the new %0%", + "create_createUnder": "Create a page under", + "create_updateData": "Choose a type and a title", + "create_noDocumentTypes": "There are no allowed document types available. You must enable these in the settings section under 'document types'.", + "create_noMediaTypes": "There are no allowed media types available. You must enable these in the settings section under 'media types'.", + "dashboard_browser": "Browse your website", + "dashboard_dontShowAgain": "- Hide", + "dashboard_nothinghappens": "If Umbraco isn't opening, you might need to allow popups from this site", + "dashboard_openinnew": "has opened in a new window", + "dashboard_restart": "Restart", + "dashboard_visit": "Visit", + "dashboard_welcome": "Welcome", + "defaultdialogs_anchorInsert": "Name", + "defaultdialogs_assignDomain": "Manage hostnames", + "defaultdialogs_closeThisWindow": "Close this window", + "defaultdialogs_confirmdelete": "Are you sure you want to delete", + "defaultdialogs_confirmdisable": "Are you sure you want to disable", + "defaultdialogs_confirmEmptyTrashcan": "Please check this box to confirm deletion of %0% item(s)", + "defaultdialogs_confirmlogout": "Are you sure?", + "defaultdialogs_confirmSure": "Are you sure?", + "defaultdialogs_cut": "Cut", + "defaultdialogs_editdictionary": "Edit Dictionary Item", + "defaultdialogs_editlanguage": "Edit Language", + "defaultdialogs_insertAnchor": "Insert local link", + "defaultdialogs_insertCharacter": "Insert character", + "defaultdialogs_insertgraphicheadline": "Insert graphic headline", + "defaultdialogs_insertimage": "Insert picture", + "defaultdialogs_insertlink": "Insert link", + "defaultdialogs_insertMacro": "Click to add a Macro", + "defaultdialogs_inserttable": "Insert table", + "defaultdialogs_lastEdited": "Last Edited", + "defaultdialogs_link": "Link", + "defaultdialogs_linkinternal": "Internal link:", + "defaultdialogs_linklocaltip": "When using local links, insert '#' infront of link", + "defaultdialogs_linknewwindow": "Open in new window?", + "defaultdialogs_macroContainerSettings": "Macro Settings", + "defaultdialogs_macroDoesNotHaveProperties": "This macro does not contain any properties you can edit", + "defaultdialogs_paste": "Paste", + "defaultdialogs_permissionsEdit": "Edit Permissions for", + "defaultdialogs_recycleBinDeleting": "The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place", + "defaultdialogs_recycleBinIsEmpty": "The recycle bin is now empty", + "defaultdialogs_recycleBinWarning": "When items are deleted from the recycle bin, they will be gone forever", + "defaultdialogs_regexSearchError": "regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.", + "defaultdialogs_regexSearchHelp": "Search for a regular expression to add validation to a form field. Exemple: 'email, 'zip-code' 'url'", + "defaultdialogs_removeMacro": "Remove Macro", + "defaultdialogs_requiredField": "Required Field", + "defaultdialogs_sitereindexed": "Site is reindexed", + "defaultdialogs_siterepublished": "The website cache has been refreshed. All publish content is now uptodate. While all unpublished content is still unpublished", + "defaultdialogs_siterepublishHelp": "The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished.", + "defaultdialogs_tableColumns": "Number of columns", + "defaultdialogs_tableRows": "Number of rows", + "defaultdialogs_templateContentAreaHelp": "Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates, by refering this ID using a <asp:content /> element.", + "defaultdialogs_templateContentPlaceHolderHelp": "Select a placeholder id from the list below. You can only choose Id's from the current template's master.", + "defaultdialogs_thumbnailimageclickfororiginal": "Click on the image to see full size", + "defaultdialogs_treepicker": "Pick item", + "defaultdialogs_viewCacheItem": "View Cache Item", + "dictionaryItem_description": "Edit the different language versions for the dictionary item '%0%' below
    You can add additional languages under the 'languages' in the menu on the left ", + "dictionaryItem_displayName": "Culture Name", + "placeholders_username": "Enter your username", + "placeholders_password": "Enter your password", + "placeholders_entername": "Enter a name...", + "placeholders_nameentity": "Name the %0%...", + "placeholders_search": "Type to search...", + "placeholders_filter": "Type to filter...", + "editcontenttype_allowedchildnodetypes": "Allowed child nodetypes", + "editcontenttype_create": "Create", + "editcontenttype_deletetab": "Delete tab", + "editcontenttype_description": "Description", + "editcontenttype_newtab": "New tab", + "editcontenttype_tab": "Tab", + "editcontenttype_thumbnail": "Thumbnail", + "editcontenttype_iscontainercontenttype": "Use as container content type", + "editdatatype_addPrevalue": "Add prevalue", + "editdatatype_dataBaseDatatype": "Database datatype", + "editdatatype_guid": "Property editor GUID", + "editdatatype_renderControl": "Property editor", + "editdatatype_rteButtons": "Buttons", + "editdatatype_rteEnableAdvancedSettings": "Enable advanced settings for", + "editdatatype_rteEnableContextMenu": "Enable context menu", + "editdatatype_rteMaximumDefaultImgSize": "Maximum default size of inserted images", + "editdatatype_rteRelatedStylesheets": "Related stylesheets", + "editdatatype_rteShowLabel": "Show label", + "editdatatype_rteWidthAndHeight": "Width and height", + "errorHandling_errorButDataWasSaved": "Your data has been saved, but before you can publish this page there are some errors you need to fix first:", + "errorHandling_errorChangingProviderPassword": "The current MemberShip Provider does not support changing password (EnablePasswordRetrieval need to be true)", + "errorHandling_errorExistsWithoutTab": "%0% already exists", + "errorHandling_errorHeader": "There were errors:", + "errorHandling_errorHeaderWithoutTab": "There were errors:", + "errorHandling_errorInPasswordFormat": "The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s)", + "errorHandling_errorIntegerWithoutTab": "%0% must be an integer", + "errorHandling_errorMandatory": "The %0% field in the %1% tab is mandatory", + "errorHandling_errorMandatoryWithoutTab": "%0% is a mandatory field", + "errorHandling_errorRegExp": "%0% at %1% is not in a correct format", + "errorHandling_errorRegExpWithoutTab": "%0% is not in a correct format", + "errors_dissallowedMediaType": "The specified file type has been dissallowed by the administrator", + "errors_codemirroriewarning": "NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough.", + "errors_contentTypeAliasAndNameNotNull": "Please fill both alias and name on the new propertytype!", + "errors_filePermissionsError": "There is a problem with read/write access to a specific file or folder", + "errors_missingTitle": "Please enter a title", + "errors_missingType": "Please choose a type", + "errors_pictureResizeBiggerThanOrg": "You're about to make the picture larger than the original size. Are you sure that you want to proceed?", + "errors_startNodeDoesNotExists": "Startnode deleted, please contact your administrator", + "errors_stylesMustMarkBeforeSelect": "Please mark content before changing style", + "errors_stylesNoStylesOnPage": "No active styles available", + "errors_tableColMergeLeft": "Please place cursor at the left of the two cells you wish to merge", + "errors_tableSplitNotSplittable": "You cannot split a cell that hasn't been merged.", + "general_about": "About", + "general_action": "Action", + "general_add": "Add", + "general_alias": "Alias", + "general_areyousure": "Are you sure?", + "general_border": "Border", + "general_by": "or", + "general_cancel": "Cancel", + "general_cellMargin": "Cell margin", + "general_choose": "Choose", + "general_close": "Close", + "general_closewindow": "Close Window", + "general_comment": "Comment", + "general_confirm": "Confirm", + "general_constrainProportions": "Constrain proportions", + "general_continue": "Continue", + "general_copy": "Copy", + "general_create": "Create", + "general_database": "Database", + "general_date": "Date", + "general_default": "Default", + "general_delete": "Delete", + "general_deleted": "Deleted", + "general_deleting": "Deleting...", + "general_design": "Design", + "general_dimensions": "Dimensions", + "general_down": "Down", + "general_download": "Download", + "general_edit": "Edit", + "general_edited": "Edited", + "general_elements": "Elements", + "general_email": "Email", + "general_error": "Error", + "general_findDocument": "Find", + "general_height": "Height", + "general_help": "Help", + "general_icon": "Icon", + "general_import": "Import", + "general_innerMargin": "Inner margin", + "general_insert": "Insert", + "general_install": "Install", + "general_justify": "Justify", + "general_language": "Language", + "general_layout": "Layout", + "general_loading": "Loading", + "general_locked": "Locked", + "general_login": "Login", + "general_logoff": "Log off", + "general_logout": "Logout", + "general_macro": "Macro", + "general_move": "Move", + "general_name": "Name", + "general_new": "New", + "general_next": "Next", + "general_no": "No", + "general_of": "of", + "general_ok": "OK", + "general_open": "Open", + "general_or": "or", + "general_password": "Password", + "general_path": "Path", + "general_placeHolderID": "Placeholder ID", + "general_pleasewait": "One moment please...", + "general_previous": "Previous", + "general_properties": "Properties", + "general_reciept": "Email to receive form data", + "general_recycleBin": "Recycle Bin", + "general_remaining": "Remaining", + "general_rename": "Rename", + "general_renew": "Renew", + "general_required": "Required", + "general_retry": "Retry", + "general_rights": "Permissions", + "general_search": "Search", + "general_server": "Server", + "general_show": "Show", + "general_showPageOnSend": "Show page on Send", + "general_size": "Size", + "general_sort": "Sort", + "general_type": "Type", + "general_typeToSearch": "Type to search...", + "general_up": "Up", + "general_update": "Update", + "general_upgrade": "Upgrade", + "general_upload": "Upload", + "general_url": "Url", + "general_user": "User", + "general_username": "Username", + "general_value": "Value", + "general_view": "View", + "general_welcome": "Welcome...", + "general_width": "Width", + "general_yes": "Yes", + "general_folder": "Folder", + "general_searchResults": "Search results", + "graphicheadline_backgroundcolor": "Background color", + "graphicheadline_bold": "Bold", + "graphicheadline_color": "Text color", + "graphicheadline_font": "Font", + "graphicheadline_text": "Text", + "headers_page": "Page", + "installer_databaseErrorCannotConnect": "The installer cannot connect to the database.", + "installer_databaseErrorWebConfig": "Could not save the web.config file. Please modify the connection string manually.", + "installer_databaseFound": "Your database has been found and is identified as", + "installer_databaseHeader": "Database configuration", + "installer_databaseInstall": " Press the install button to install the Umbraco %0% database ", + "installer_databaseInstallDone": "Umbraco %0% has now been copied to your database. Press Next to proceed.", + "installer_databaseNotFound": "

    Database not found! Please check that the information in the 'connection string' of the \"web.config\" file is correct.

    To proceed, please edit the 'web.config' file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named 'UmbracoDbDSN' and save the file.

    Click the retry button when done.
    More information on editing web.config here.

    ", + "installer_databaseText": "To complete this step, you must know some information regarding your database server ('connection string').
    Please contact your ISP if necessary. If you're installing on a local machine or server you might need information from your system administrator.", + "installer_databaseUpgrade": "

    Press the upgrade button to upgrade your database to Umbraco %0%

    Don't worry - no content will be deleted and everything will continue working afterwards!

    ", + "installer_databaseUpgradeDone": "Your database has been upgraded to the final version %0%.
    Press Next to proceed. ", + "installer_databaseUpToDate": "Your current database is up-to-date!. Click next to continue the configuration wizard", + "installer_defaultUserChangePass": "The Default users' password needs to be changed!", + "installer_defaultUserDisabled": "The Default user has been disabled or has no access to Umbraco!

    No further actions needs to be taken. Click Next to proceed.", + "installer_defaultUserPassChanged": "The Default user's password has been successfully changed since the installation!

    No further actions needs to be taken. Click Next to proceed.", + "installer_defaultUserPasswordChanged": "The password is changed!", + "installer_defaultUserText": "

    Umbraco creates a default user with a login ('admin') and password ('default'). It's important that the password is changed to something unique.

    This step will check the default user's password and suggest if it needs to be changed.

    ", + "installer_greatStart": "Get a great start, watch our introduction videos", + "installer_licenseText": "By clicking the next button (or modifying the UmbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI.", + "installer_None": "Not installed yet.", + "installer_permissionsAffectedFolders": "Affected files and folders", + "installer_permissionsAffectedFoldersMoreInfo": "More information on setting up permissions for Umbraco here", + "installer_permissionsAffectedFoldersText": "You need to grant ASP.NET modify permissions to the following files/folders", + "installer_permissionsAlmostPerfect": "Your permission settings are almost perfect!

    You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.", + "installer_permissionsHowtoResolve": "How to Resolve", + "installer_permissionsHowtoResolveLink": "Click here to read the text version", + "installer_permissionsHowtoResolveText": "Watch our video tutorial on setting up folder permissions for Umbraco or read the text version.", + "installer_permissionsMaybeAnIssue": "Your permission settings might be an issue!

    You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.", + "installer_permissionsNotReady": "Your permission settings are not ready for Umbraco!

    In order to run Umbraco, you'll need to update your permission settings.", + "installer_permissionsPerfect": "Your permission settings are perfect!

    You are ready to run Umbraco and install packages!", + "installer_permissionsResolveFolderIssues": "Resolving folder issue", + "installer_permissionsResolveFolderIssuesLink": "Follow this link for more information on problems with ASP.NET and creating folders", + "installer_permissionsSettingUpPermissions": "Setting up folder permissions", + "installer_permissionsText": " Umbraco needs write/modify access to certain directories in order to store files like pictures and PDF's. It also stores temporary data (aka: cache) for enhancing the performance of your website. ", + "installer_runwayFromScratch": "I want to start from scratch", + "installer_runwayFromScratchText": " Your website is completely empty at the moment, so that's perfect if you want to start from scratch and create your own document types and templates. (learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. ", + "installer_runwayHeader": "You've just set up a clean Umbraco platform. What do you want to do next?", + "installer_runwayInstalled": "Runway is installed", + "installer_runwayInstalledText": " You have the foundation in place. Select what modules you wish to install on top of it.
    This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules ", + "installer_runwayOnlyProUsers": "Only recommended for experienced users", + "installer_runwaySimpleSite": "I want to start with a simple website", + "installer_runwaySimpleSiteText": "

    'Runway' is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, Runway offers an easy foundation based on best practices to get you started faster than ever. If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.

    Included with Runway: Home page, Getting Started page, Installing Modules page.
    Optional Modules: Top Navigation, Sitemap, Contact, Gallery.
    ", + "installer_runwayWhatIsRunway": "What is Runway", + "installer_step1": "Step 1/5 Accept license", + "installer_step2": "Step 2/5: Database configuration", + "installer_step3": "Step 3/5: Validating File Permissions", + "installer_step4": "Step 4/5: Check Umbraco security", + "installer_step5": "Step 5/5: Umbraco is ready to get you started", + "installer_thankYou": "Thank you for choosing Umbraco", + "installer_theEndBrowseSite": "

    Browse your new site

    You installed Runway, so why not see how your new website looks.", + "installer_theEndFurtherHelp": "

    Further help and information

    Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology", + "installer_theEndHeader": "Umbraco %0% is installed and ready for use", + "installer_theEndInstallFailed": "To finish the installation, you'll need to manually edit the /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.", + "installer_theEndInstallSuccess": "You can get started instantly by clicking the 'Launch Umbraco' button below.
    If you are new to Umbraco, you can find plenty of resources on our getting started pages.", + "installer_theEndOpenUmbraco": "

    Launch Umbraco

    To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality", + "installer_Unavailable": "Connection to database failed.", + "installer_Version3": "Umbraco Version 3", + "installer_Version4": "Umbraco Version 4", + "installer_watch": "Watch", + "installer_welcomeIntro": "This wizard will guide you through the process of configuring Umbraco %0% for a fresh install or upgrading from version 3.0.

    Press 'next' to start the wizard.", + "language_cultureCode": "Culture Code", + "language_displayName": "Culture Name", + "lockout_lockoutWillOccur": "You've been idle and logout will automatically occur in", + "lockout_renewSession": "Renew now to save your work", + "login_greeting1": "Happy super Sunday", + "login_greeting2": "Happy manic Monday ", + "login_greeting3": "Happy tremendous Tuesday", + "login_greeting4": "Happy wonderful Wednesday", + "login_greeting5": "Happy thunderous Thursday", + "login_greeting6": "Happy friendly Friday", + "login_greeting7": "Happy shiny Saturday", + "login_instruction": "Log in below:", + "login_bottomText": "

    © 2001 - %0%
    Umbraco.org

    ", + "main_dashboard": "Dashboard", + "main_sections": "Sections", + "main_tree": "Content", + "moveOrCopy_choose": "Choose page above...", + "moveOrCopy_copyDone": "%0% has been copied to %1%", + "moveOrCopy_copyTo": "Select where the document %0% should be copied to below", + "moveOrCopy_moveDone": "%0% has been moved to %1%", + "moveOrCopy_moveTo": "Select where the document %0% should be moved to below", + "moveOrCopy_nodeSelected": "has been selected as the root of your new content, click 'ok' below.", + "moveOrCopy_noNodeSelected": "No node selected yet, please select a node in the list above before clicking 'ok'", + "moveOrCopy_notAllowedByContentType": "The current node is not allowed under the chosen node because of its type", + "moveOrCopy_notAllowedByPath": "The current node cannot be moved to one of its subpages", + "moveOrCopy_notAllowedAtRoot": "The current node cannot exist at the root", + "moveOrCopy_notValid": "The action isn't allowed since you have insufficient permissions on 1 or more child documents.", + "moveOrCopy_relateToOriginal": "Relate copied items to original", + "notifications_editNotifications": "Edit your notification for %0%", + "notifications_mailBody": " Hi %0% This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%' Go to http://%4%/actions/editContent.aspx?id=%5% to edit. Have a nice day! Cheers from the Umbraco robot ", + "notifications_mailBodyHtml": "

    Hi %0%

    This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%'

    Update summary:

    %6%

    Have a nice day!

    Cheers from the Umbraco robot

    ", + "notifications_mailSubject": "[%0%] Notification about %1% performed on %2%", + "notifications_notifications": "Notifications", + "packager_chooseLocalPackageText": " Choose Package from your machine, by clicking the Browse
    button and locating the package. Umbraco packages usually have a '.umb' or '.zip' extension. ", + "packager_packageAuthor": "Author", + "packager_packageDemonstration": "Demonstration", + "packager_packageDocumentation": "Documentation", + "packager_packageMetaData": "Package meta data", + "packager_packageName": "Package name", + "packager_packageNoItemsHeader": "Package doesn't contain any items", + "packager_packageNoItemsText": "This package file doesn't contain any items to uninstall.

    You can safely remove this from the system by clicking 'uninstall package' below.", + "packager_packageNoUpgrades": "No upgrades available", + "packager_packageOptions": "Package options", + "packager_packageReadme": "Package readme", + "packager_packageRepository": "Package repository", + "packager_packageUninstallConfirm": "Confirm uninstall", + "packager_packageUninstalledHeader": "Package was uninstalled", + "packager_packageUninstalledText": "The package was successfully uninstalled", + "packager_packageUninstallHeader": "Uninstall package", + "packager_packageUninstallText": "You can unselect items you do not wish to remove, at this time, below. When you click 'confirm uninstall' all checked-off items will be removed.
    Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, so uninstall with caution. If in doubt, contact the package author.", + "packager_packageUpgradeDownload": "Download update from the repository", + "packager_packageUpgradeHeader": "Upgrade package", + "packager_packageUpgradeInstructions": "Upgrade instructions", + "packager_packageUpgradeText": " There's an upgrade available for this package. You can download it directly from the Umbraco package repository.", + "packager_packageVersion": "Package version", + "packager_packageVersionHistory": "Package version history", + "packager_viewPackageWebsite": "View package website", + "paste_doNothing": "Paste with full formatting (Not recommended)", + "paste_errorMessage": "The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web.", + "paste_removeAll": "Paste as raw text without any formatting at all", + "paste_removeSpecialFormattering": "Paste, but remove formatting (Recommended)", + "publicAccess_paAdvanced": "Role based protection", + "publicAccess_paAdvancedHelp": "If you wish to control access to the page using role-based authentication,
    using Umbraco's member groups.", + "publicAccess_paAdvancedNoGroups": "You need to create a membergroup before you can use
    role-based authentication.", + "publicAccess_paErrorPage": "Error Page", + "publicAccess_paErrorPageHelp": "Used when people are logged on, but do not have access", + "publicAccess_paHowWould": "Choose how to restict access to this page", + "publicAccess_paIsProtected": "%0% is now protected", + "publicAccess_paIsRemoved": "Protection removed from %0%", + "publicAccess_paLoginPage": "Login Page", + "publicAccess_paLoginPageHelp": "Choose the page that has the login formular", + "publicAccess_paRemoveProtection": "Remove Protection", + "publicAccess_paSelectPages": "Select the pages that contain login form and error messages", + "publicAccess_paSelectRoles": "Pick the roles who have access to this page", + "publicAccess_paSetLogin": "Set the login and password for this page", + "publicAccess_paSimple": "Single user protection", + "publicAccess_paSimpleHelp": "If you just want to setup simple protection using a single login and password", + "publish_contentPublishedFailedInvalid": " %0% could not be published because these properties: %1% did not pass validation rules. ", + "publish_contentPublishedFailedByEvent": " %0% could not be published, due to a 3rd party extension cancelling the action. ", + "publish_contentPublishedFailedByParent": " %0% can not be published, because a parent page is not published. ", + "publish_includeUnpublished": "Include unpublished child pages", + "publish_inProgress": "Publishing in progress - please wait...", + "publish_inProgressCounter": "%0% out of %1% pages have been published...", + "publish_nodePublish": "%0% has been published", + "publish_nodePublishAll": "%0% and subpages have been published", + "publish_publishAll": "Publish %0% and all its subpages", + "publish_publishHelp": "Click ok to publish %0% and thereby making it's content publicly available.

    You can publish this page and all it's sub-pages by checking publish all children below. ", + "relatedlinks_addExternal": "Add external link", + "relatedlinks_addInternal": "Add internal link", + "relatedlinks_addlink": "Add", + "relatedlinks_caption": "Caption", + "relatedlinks_internalPage": "Internal page", + "relatedlinks_linkurl": "URL", + "relatedlinks_modeDown": "Move Down", + "relatedlinks_modeUp": "Move Up", + "relatedlinks_newWindow": "Open in new window", + "relatedlinks_removeLink": "Remove link", + "rollback_currentVersion": "Current version", + "rollback_diffHelp": "This shows the differences between the current version and the selected version
    Red text will not be shown in the selected version. , green means added", + "rollback_documentRolledBack": "Document has been rolled back", + "rollback_htmlHelp": "This displays the selected version as html, if you wish to see the difference between 2 versions at the same time, use the diff view", + "rollback_rollbackTo": "Rollback to", + "rollback_selectVersion": "Select version", + "rollback_view": "View", + "scripts_editscript": "Edit script file", + "sections_concierge": "Concierge", + "sections_content": "Content", + "sections_courier": "Courier", + "sections_developer": "Developer", + "sections_installer": "Umbraco Configuration Wizard", + "sections_media": "Media", + "sections_member": "Members", + "sections_newsletters": "Newsletters", + "sections_settings": "Settings", + "sections_statistics": "Statistics", + "sections_translation": "Translation", + "sections_users": "Users", + "sections_contour": "Umbraco Contour", + "sections_help": "Help", + "settings_defaulttemplate": "Default template", + "settings_dictionary editor egenskab": "Dictionary Key", + "settings_importDocumentTypeHelp": "To import a document type, find the '.udt' file on your computer by clicking the 'Browse' button and click 'Import' (you'll be asked for confirmation on the next screen)", + "settings_newtabname": "New Tab Title", + "settings_nodetype": "Nodetype", + "settings_objecttype": "Type", + "settings_stylesheet": "Stylesheet", + "settings_stylesheet editor egenskab": "Stylesheet property", + "settings_tab": "Tab", + "settings_tabname": "Tab Title", + "settings_tabs": "Tabs", + "settings_contentTypeEnabled": "Master Content Type enabled", + "settings_contentTypeUses": "This Content Type uses", + "settings_asAContentMasterType": "as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself", + "settings_noPropertiesDefinedOnTab": "No properties defined on this tab. Click on the 'add a new property' link at the top to create a new property.", + "sort_sortDone": "Sorting complete.", + "sort_sortHelp": "Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items", + "sort_sortPleaseWait": " Please wait. Items are being sorted, this can take a while.

    Do not close this window during sorting", + "speechBubbles_contentPublishedFailedByEvent": "Publishing was cancelled by a 3rd party add-in", + "speechBubbles_contentTypeDublicatePropertyType": "Property type already exists", + "speechBubbles_contentTypePropertyTypeCreated": "Property type created", + "speechBubbles_contentTypePropertyTypeCreatedText": "Name: %0%
    DataType: %1%", + "speechBubbles_contentTypePropertyTypeDeleted": "Propertytype deleted", + "speechBubbles_contentTypeSavedHeader": "Document Type saved", + "speechBubbles_contentTypeTabCreated": "Tab created", + "speechBubbles_contentTypeTabDeleted": "Tab deleted", + "speechBubbles_contentTypeTabDeletedText": "Tab with id: %0% deleted", + "speechBubbles_cssErrorHeader": "Stylesheet not saved", + "speechBubbles_cssSavedHeader": "Stylesheet saved", + "speechBubbles_cssSavedText": "Stylesheet saved without any errors", + "speechBubbles_dataTypeSaved": "Datatype saved", + "speechBubbles_dictionaryItemSaved": "Dictionary item saved", + "speechBubbles_editContentPublishedFailedByParent": "Publishing failed because the parent page isn't published", + "speechBubbles_editContentPublishedHeader": "Content published", + "speechBubbles_editContentPublishedText": "and visible at the website", + "speechBubbles_editContentSavedHeader": "Content saved", + "speechBubbles_editContentSavedText": "Remember to publish to make changes visible", + "speechBubbles_editContentSendToPublish": "Sent For Approval", + "speechBubbles_editContentSendToPublishText": "Changes have been sent for approval", + "speechBubbles_editMediaSaved": "Media saved", + "speechBubbles_editMediaSavedText": "Media saved without any errors", + "speechBubbles_editMemberSaved": "Member saved", + "speechBubbles_editStylesheetPropertySaved": "Stylesheet Property Saved", + "speechBubbles_editStylesheetSaved": "Stylesheet saved", + "speechBubbles_editTemplateSaved": "Template saved", + "speechBubbles_editUserError": "Error saving user (check log)", + "speechBubbles_editUserSaved": "User Saved", + "speechBubbles_editUserTypeSaved": "User type saved", + "speechBubbles_fileErrorHeader": "File not saved", + "speechBubbles_fileErrorText": "file could not be saved. Please check file permissions", + "speechBubbles_fileSavedHeader": "File saved", + "speechBubbles_fileSavedText": "File saved without any errors", + "speechBubbles_languageSaved": "Language saved", + "speechBubbles_templateErrorHeader": "Template not saved", + "speechBubbles_templateErrorText": "Please make sure that you do not have 2 templates with the same alias", + "speechBubbles_templateSavedHeader": "Template saved", + "speechBubbles_templateSavedText": "Template saved without any errors!", + "speechBubbles_contentUnpublished": "Content unpublished", + "speechBubbles_partialViewSavedHeader": "Partial view saved", + "speechBubbles_partialViewSavedText": "Partial view saved without any errors!", + "speechBubbles_partialViewErrorHeader": "Partial view not saved", + "speechBubbles_partialViewErrorText": "An error occurred saving the file.", + "stylesheet_aliasHelp": "Uses CSS syntax ex: h1, .redHeader, .blueTex", + "stylesheet_editstylesheet": "Edit stylesheet", + "stylesheet_editstylesheetproperty": "Edit stylesheet property", + "stylesheet_nameHelp": "Name to identify the style property in the rich text editor ", + "stylesheet_preview": "Preview", + "stylesheet_styles": "Styles", + "template_edittemplate": "Edit template", + "template_insertContentArea": "Insert content area", + "template_insertContentAreaPlaceHolder": "Insert content area placeholder", + "template_insertDictionaryItem": "Insert dictionary item", + "template_insertMacro": "Insert Macro", + "template_insertPageField": "Insert Umbraco page field", + "template_mastertemplate": "Master template", + "template_quickGuide": "Quick Guide to Umbraco template tags", + "template_template": "Template", + "templateEditor_alternativeField": "Alternative field", + "templateEditor_alternativeText": "Alternative Text", + "templateEditor_casing": "Casing", + "templateEditor_encoding": "Encoding", + "templateEditor_chooseField": "Choose field", + "templateEditor_convertLineBreaks": "Convert Linebreaks", + "templateEditor_convertLineBreaksHelp": "Replaces linebreaks with html-tag <br>", + "templateEditor_customFields": "Custom Fields", + "templateEditor_dateOnly": "Yes, Date only", + "templateEditor_formatAsDate": "Format as date", + "templateEditor_htmlEncode": "HTML encode", + "templateEditor_htmlEncodeHelp": "Will replace special characters by their HTML equivalent.", + "templateEditor_insertedAfter": "Will be inserted after the field value", + "templateEditor_insertedBefore": "Will be inserted before the field value", + "templateEditor_lowercase": "Lowercase", + "templateEditor_none": "None", + "templateEditor_postContent": "Insert after field", + "templateEditor_preContent": "Insert before field", + "templateEditor_recursive": "Recursive", + "templateEditor_removeParagraph": "Remove Paragraph tags", + "templateEditor_removeParagraphHelp": "Will remove any <P> in the beginning and end of the text", + "templateEditor_standardFields": "Standard Fields", + "templateEditor_uppercase": "Uppercase", + "templateEditor_urlEncode": "URL encode", + "templateEditor_urlEncodeHelp": "Will format special characters in URLs", + "templateEditor_usedIfAllEmpty": "Will only be used when the field values above are empty", + "templateEditor_usedIfEmpty": "This field will only be used if the primary field is empty", + "templateEditor_withTime": "Yes, with time. Seperator: ", + "translation_assignedTasks": "Tasks assigned to you", + "translation_assignedTasksHelp": " The list below shows translation tasks assigned to you. To see a detailed view including comments, click on 'Details' or just the page name. You can also download the page as XML directly by clicking the 'Download Xml' link.
    To close a translation task, please go to the Details view and click the 'Close' button. ", + "translation_closeTask": "close task", + "translation_details": "Translation details", + "translation_downloadAllAsXml": "Download all translation tasks as xml", + "translation_downloadTaskAsXml": "Download xml", + "translation_DownloadXmlDTD": "Download xml DTD", + "translation_fields": "Fields", + "translation_includeSubpages": "Include subpages", + "translation_mailBody": " Hi %0% This is an automated mail to inform you that the document '%1%' has been requested for translation into '%5%' by %2%. Go to http://%3%/translation/details.aspx?id=%4% to edit. Or log into Umbraco to get an overview of your translation tasks http://%3% Have a nice day! Cheers from the Umbraco robot ", + "translation_mailSubject": "[%0%] Translation task for %1%", + "translation_noTranslators": "No translator users found. Please create a translator user before you start sending content to translation", + "translation_ownedTasks": "Tasks created by you", + "translation_ownedTasksHelp": " The list below shows pages created by you. To see a detailed view including comments, click on 'Details' or just the page name. You can also download the page as XML directly by clicking the 'Download Xml' link. To close a translation task, please go to the Details view and click the 'Close' button. ", + "translation_pageHasBeenSendToTranslation": "The page '%0%' has been send to translation", + "translation_sendToTranslate": "Send the page '%0%' to translation", + "translation_taskAssignedBy": "Assigned by", + "translation_taskOpened": "Task opened", + "translation_totalWords": "Total words", + "translation_translateTo": "Translate to", + "translation_translationDone": "Translation completed.", + "translation_translationDoneHelp": "You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages.", + "translation_translationFailed": "Translation failed, the xml file might be corrupt", + "translation_translationOptions": "Translation options", + "translation_translator": "Translator", + "translation_uploadTranslationXml": "Upload translation xml", + "treeHeaders_cacheBrowser": "Cache Browser", + "treeHeaders_contentRecycleBin": "Recycle Bin", + "treeHeaders_createdPackages": "Created packages", + "treeHeaders_datatype": "Data Types", + "treeHeaders_dictionary": "Dictionary", + "treeHeaders_installedPackages": "Installed packages", + "treeHeaders_installSkin": "Install skin", + "treeHeaders_installStarterKit": "Install starter kit", + "treeHeaders_languages": "Languages", + "treeHeaders_localPackage": "Install local package", + "treeHeaders_macros": "Macros", + "treeHeaders_mediaTypes": "Media Types", + "treeHeaders_member": "Members", + "treeHeaders_memberGroup": "Member Groups", + "treeHeaders_memberRoles": "Roles", + "treeHeaders_memberType": "Member Types", + "treeHeaders_nodeTypes": "Document Types", + "treeHeaders_packager": "Packages", + "treeHeaders_packages": "Packages", + "treeHeaders_repositories": "Install from repository", + "treeHeaders_runway": "Install Runway", + "treeHeaders_runwayModules": "Runway modules", + "treeHeaders_scripting": "Scripting Files", + "treeHeaders_scripts": "Scripts", + "treeHeaders_stylesheets": "Stylesheets", + "treeHeaders_templates": "Templates", + "update_updateAvailable": "New update ready", + "update_updateDownloadText": "%0% is ready, click here for download", + "update_updateNoServer": "No connection to server", + "update_updateNoServerError": "Error checking for update. Please review trace-stack for further information", + "user_administrators": "Administrator", + "user_categoryField": "Category field", + "user_changePassword": "Change Your Password", + "user_newPassword": "New password", + "user_confirmNewPassword": "Confirm new password", + "user_changePasswordDescription": "You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button", + "user_contentChannel": "Content Channel", + "user_descriptionField": "Description field", + "user_disabled": "Disable User", + "user_documentType": "Document Type", + "user_editors": "Editor", + "user_excerptField": "Excerpt field", + "user_language": "Language", + "user_loginname": "Login", + "user_mediastartnode": "Start Node in Media Library", + "user_modules": "Sections", + "user_noConsole": "Disable Umbraco Access", + "user_oldPassword": "Old password", + "user_password": "Password", + "user_resetPassword": "Reset password", + "user_passwordChanged": "Your password has been changed!", + "user_passwordConfirm": "Please confirm the new password", + "user_passwordEnterNew": "Enter your new password", + "user_passwordIsBlank": "Your new password cannot be blank!", + "user_passwordCurrent": "Current password", + "user_passwordInvalid": "Invalid current password", + "user_passwordIsDifferent": "There was a difference between the new password and the confirmed password. Please try again!", + "user_passwordMismatch": "The confirmed password doesn't match the new password!", + "user_permissionReplaceChildren": "Replace child node permssions", + "user_permissionSelectedPages": "You are currently modifying permissions for the pages:", + "user_permissionSelectPages": "Select pages to modify their permissions", + "user_searchAllChildren": "Search all children", + "user_startnode": "Start Node in Content", + "user_username": "Username", + "user_userPermissions": "User permissions", + "user_usertype": "User type", + "user_userTypes": "User types", + "user_writer": "Writer", + "user_yourProfile": "Your profile", + "user_yourHistory": "Your recent history", + "user_sessionExpires": "Session expires in" + }, null]; + } + } + + return { + register: function() { + $httpBackend + .whenGET(mocksUtils.urlRegex('LocalizedText')) + .respond(getLanguageResource); + } + }; }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/util.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/util.mocks.js index 058c6a9d44..42e5d55a5b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/services/util.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/util.mocks.js @@ -1,22 +1,22 @@ -angular.module('umbraco.mocks'). - factory('utilMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { - 'use strict'; - - function getUpdateCheck(status, data, headers) { - //check for existence of a cookie so we can do login/logout in the belle app (ignore for tests). - if (!mocksUtils.checkAuth()) { - return [401, null, null]; - } - else { - return [200, null, null]; - } - } - - return { - register: function() { - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/Api/UpdateCheck/GetCheck')) - .respond(getUpdateCheck); - } - }; +angular.module('umbraco.mocks'). + factory('utilMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function getUpdateCheck(status, data, headers) { + //check for existence of a cookie so we can do login/logout in the belle app (ignore for tests). + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + else { + return [200, null, null]; + } + } + + return { + register: function() { + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/Api/UpdateCheck/GetCheck')) + .respond(getUpdateCheck); + } + }; }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.httpbackend.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.httpbackend.js index caccb92def..92c2a67d23 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.httpbackend.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.httpbackend.js @@ -1,31 +1,31 @@ -var umbracoAppDev = angular.module('umbraco.httpbackend', ['umbraco', 'ngMockE2E', 'umbraco.mocks']); - - -function initBackEnd($httpBackend, contentMocks, mediaMocks, treeMocks, userMocks, contentTypeMocks, sectionMocks, entityMocks, dataTypeMocks, dashboardMocks, macroMocks, utilMocks, localizationMocks, prevaluesMocks) { - - console.log("httpBackend inited"); - - //Register mocked http responses - contentMocks.register(); - mediaMocks.register(); - sectionMocks.register(); - treeMocks.register(); - dataTypeMocks.register(); - dashboardMocks.register(); - userMocks.register(); - macroMocks.register(); - contentTypeMocks.register(); - utilMocks.register(); - localizationMocks.register(); - prevaluesMocks.register(); - entityMocks.register(); - - $httpBackend.whenGET(/^..\/config\//).passThrough(); - $httpBackend.whenGET(/^views\//).passThrough(); - $httpBackend.whenGET(/^js\//).passThrough(); - $httpBackend.whenGET(/^lib\//).passThrough(); - $httpBackend.whenGET(/^assets\//).passThrough(); -} - - -umbracoAppDev.run(initBackEnd); +var umbracoAppDev = angular.module('umbraco.httpbackend', ['umbraco', 'ngMockE2E', 'umbraco.mocks']); + + +function initBackEnd($httpBackend, contentMocks, mediaMocks, treeMocks, userMocks, contentTypeMocks, sectionMocks, entityMocks, dataTypeMocks, dashboardMocks, macroMocks, utilMocks, localizationMocks, prevaluesMocks) { + + console.log("httpBackend inited"); + + //Register mocked http responses + contentMocks.register(); + mediaMocks.register(); + sectionMocks.register(); + treeMocks.register(); + dataTypeMocks.register(); + dashboardMocks.register(); + userMocks.register(); + macroMocks.register(); + contentTypeMocks.register(); + utilMocks.register(); + localizationMocks.register(); + prevaluesMocks.register(); + entityMocks.register(); + + $httpBackend.whenGET(/^..\/config\//).passThrough(); + $httpBackend.whenGET(/^views\//).passThrough(); + $httpBackend.whenGET(/^js\//).passThrough(); + $httpBackend.whenGET(/^lib\//).passThrough(); + $httpBackend.whenGET(/^assets\//).passThrough(); +} + + +umbracoAppDev.run(initBackEnd); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js index a68606efa7..7ba14485d4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js @@ -1,42 +1,42 @@ -//create the namespace (NOTE: This loads before any dependencies so we don't have a namespace mgr so we just create it manually) -var Umbraco = {}; -Umbraco.Sys = {}; -//define a global static object -Umbraco.Sys.ServerVariables = { - umbracoUrls: { - "contentApiBaseUrl": "/umbraco/UmbracoApi/Content/", - "mediaApiBaseUrl": "/umbraco/UmbracoApi/Media/", - "dataTypeApiBaseUrl": "/umbraco/UmbracoApi/DataType/", - "sectionApiBaseUrl": "/umbraco/UmbracoApi/Section/", - "treeApplicationApiBaseUrl": "/umbraco/UmbracoTrees/ApplicationTreeApi/", - "contentTypeApiBaseUrl": "/umbraco/Api/ContentType/", - "mediaTypeApiBaseUrl": "/umbraco/Api/MediaType/", - "macroApiBaseUrl": "/umbraco/Api/Macro/", - "authenticationApiBaseUrl": "/umbraco/UmbracoApi/Authentication/", - //For this we'll just provide a file that exists during the mock session since we don't really have legay js tree stuff - "legacyTreeJs": "/belle/lib/lazyload/empty.js", - "serverVarsJs": "/belle/lib/lazyload/empty.js", - "imagesApiBaseUrl": "/umbraco/UmbracoApi/Images/", - "entityApiBaseUrl": "/umbraco/UmbracoApi/Entity/", - "dashboardApiBaseUrl": "/umbraco/UmbracoApi/Dashboard/", - "updateCheckApiBaseUrl": "/umbraco/Api/UpdateCheck/", - "relationApiBaseUrl": "/umbraco/UmbracoApi/Relation/", - "rteApiBaseUrl": "/umbraco/UmbracoApi/RichTextPreValue/" - }, - umbracoSettings: { - "umbracoPath": "/umbraco", - "appPluginsPath" : "/App_Plugins", - "imageFileTypes": "jpeg,jpg,gif,bmp,png,tiff,tif", - "keepUserLoggedIn": true - }, - umbracoPlugins: { - trees: [ - { alias: "myTree", packageFolder: "MyPackage" } - ] - }, - isDebuggingEnabled: true, - application: { - assemblyVersion: "1", - version: "7" - } +//create the namespace (NOTE: This loads before any dependencies so we don't have a namespace mgr so we just create it manually) +var Umbraco = {}; +Umbraco.Sys = {}; +//define a global static object +Umbraco.Sys.ServerVariables = { + umbracoUrls: { + "contentApiBaseUrl": "/umbraco/UmbracoApi/Content/", + "mediaApiBaseUrl": "/umbraco/UmbracoApi/Media/", + "dataTypeApiBaseUrl": "/umbraco/UmbracoApi/DataType/", + "sectionApiBaseUrl": "/umbraco/UmbracoApi/Section/", + "treeApplicationApiBaseUrl": "/umbraco/UmbracoTrees/ApplicationTreeApi/", + "contentTypeApiBaseUrl": "/umbraco/Api/ContentType/", + "mediaTypeApiBaseUrl": "/umbraco/Api/MediaType/", + "macroApiBaseUrl": "/umbraco/Api/Macro/", + "authenticationApiBaseUrl": "/umbraco/UmbracoApi/Authentication/", + //For this we'll just provide a file that exists during the mock session since we don't really have legay js tree stuff + "legacyTreeJs": "/belle/lib/lazyload/empty.js", + "serverVarsJs": "/belle/lib/lazyload/empty.js", + "imagesApiBaseUrl": "/umbraco/UmbracoApi/Images/", + "entityApiBaseUrl": "/umbraco/UmbracoApi/Entity/", + "dashboardApiBaseUrl": "/umbraco/UmbracoApi/Dashboard/", + "updateCheckApiBaseUrl": "/umbraco/Api/UpdateCheck/", + "relationApiBaseUrl": "/umbraco/UmbracoApi/Relation/", + "rteApiBaseUrl": "/umbraco/UmbracoApi/RichTextPreValue/" + }, + umbracoSettings: { + "umbracoPath": "/umbraco", + "appPluginsPath" : "/App_Plugins", + "imageFileTypes": "jpeg,jpg,gif,bmp,png,tiff,tif", + "keepUserLoggedIn": true + }, + umbracoPlugins: { + trees: [ + { alias: "myTree", packageFolder: "MyPackage" } + ] + }, + isDebuggingEnabled: true, + application: { + assemblyVersion: "1", + version: "7" + } }; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index d29aa0218c..4f9b9a21f8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -1,433 +1,433 @@ -/** - * @ngdoc service - * @name umbraco.resources.authResource - * @description - * This Resource perfomrs actions to common authentication tasks for the Umbraco backoffice user - * - * @requires $q - * @requires $http - * @requires umbRequestHelper - * @requires angularHelper - */ -function authResource($q, $http, umbRequestHelper, angularHelper) { - - return { - - get2FAProviders: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "Get2FAProviders")), - 'Could not retrive two factor provider info'); - }, - - send2FACode: function (provider) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostSend2FACode"), - angular.toJson(provider)), - 'Could not send code'); - }, - - verify2FACode: function (provider, code) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostVerify2FACode"), - { - code: code, - provider: provider - }), - 'Could not verify code'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#performLogin - * @methodOf umbraco.resources.authResource - * - * @description - * Logs the Umbraco backoffice user in if the credentials are good - * - * ##usage - *
    -     * authResource.performLogin(login, password)
    -     *    .then(function(data) {
    -     *        //Do stuff for login...
    -     *    });
    -     * 
    - * @param {string} login Username of backoffice user - * @param {string} password Password of backoffice user - * @returns {Promise} resourcePromise object - * - */ - performLogin: function (username, password) { - - if (!username || !password) { - return $q.reject({ - errorMsg: 'Username or password cannot be empty' - }); - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostLogin"), { - username: username, - password: password - }), - 'Login failed for user ' + username); - }, - - /** +/** + * @ngdoc service + * @name umbraco.resources.authResource + * @description + * This Resource perfomrs actions to common authentication tasks for the Umbraco backoffice user + * + * @requires $q + * @requires $http + * @requires umbRequestHelper + * @requires angularHelper + */ +function authResource($q, $http, umbRequestHelper, angularHelper) { + + return { + + get2FAProviders: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "Get2FAProviders")), + 'Could not retrive two factor provider info'); + }, + + send2FACode: function (provider) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostSend2FACode"), + angular.toJson(provider)), + 'Could not send code'); + }, + + verify2FACode: function (provider, code) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostVerify2FACode"), + { + code: code, + provider: provider + }), + 'Could not verify code'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.authResource#performLogin + * @methodOf umbraco.resources.authResource + * + * @description + * Logs the Umbraco backoffice user in if the credentials are good + * + * ##usage + *
    +     * authResource.performLogin(login, password)
    +     *    .then(function(data) {
    +     *        //Do stuff for login...
    +     *    });
    +     * 
    + * @param {string} login Username of backoffice user + * @param {string} password Password of backoffice user + * @returns {Promise} resourcePromise object + * + */ + performLogin: function (username, password) { + + if (!username || !password) { + return $q.reject({ + errorMsg: 'Username or password cannot be empty' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostLogin"), { + username: username, + password: password + }), + 'Login failed for user ' + username); + }, + + /** * There are not parameters for this since when the user has clicked on their invite email they will be partially * logged in (but they will not be approved) so we need to use this method to verify the non approved logged in user's details. - * Using the getCurrentUser will not work since that only works for approved users - * @returns {} - */ - getCurrentInvitedUser: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "GetCurrentInvitedUser")), - 'Failed to verify invite'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#performRequestPasswordReset - * @methodOf umbraco.resources.authResource - * - * @description - * Checks to see if the provided email address is a valid user account and sends a link - * to allow them to reset their password - * - * ##usage - *
    -     * authResource.performRequestPasswordReset(email)
    -     *    .then(function(data) {
    -     *        //Do stuff for password reset request...
    -     *    });
    -     * 
    - * @param {string} email Email address of backoffice user - * @returns {Promise} resourcePromise object - * - */ - performRequestPasswordReset: function (email) { - - if (!email) { - return $q.reject({ - errorMsg: 'Email address cannot be empty' - }); - } - - //TODO: This validation shouldn't really be done here, the validation on the login dialog - // is pretty hacky which is why this is here, ideally validation on the login dialog would - // be done properly. - var emailRegex = /\S+@\S+\.\S+/; - if (!emailRegex.test(email)) { - return $q.reject({ - errorMsg: 'Email address is not valid' - }); - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostRequestPasswordReset"), { - email: email - }), - 'Request password reset failed for email ' + email); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#performValidatePasswordResetCode - * @methodOf umbraco.resources.authResource - * - * @description - * Checks to see if the provided password reset code is valid - * - * ##usage - *
    -     * authResource.performValidatePasswordResetCode(resetCode)
    -     *    .then(function(data) {
    -     *        //Allow reset of password
    -     *    });
    -     * 
    - * @param {integer} userId User Id - * @param {string} resetCode Password reset code - * @returns {Promise} resourcePromise object - * - */ - performValidatePasswordResetCode: function (userId, resetCode) { - - if (!userId) { - return $q.reject({ - errorMsg: 'User Id cannot be empty' - }); - } - - if (!resetCode) { - return $q.reject({ - errorMsg: 'Reset code cannot be empty' - }); - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostValidatePasswordResetCode"), - { - userId: userId, - resetCode: resetCode - }), - 'Password reset code validation failed for userId ' + userId + ', code' + resetCode); - }, - - /** - * @ngdoc method - * @name umbraco.resources.currentUserResource#getMembershipProviderConfig - * @methodOf umbraco.resources.currentUserResource - * - * @description - * Gets the configuration of the user membership provider which is used to configure the change password form - */ - getMembershipProviderConfig: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "GetMembershipProviderConfig")), - 'Failed to retrieve membership provider config'); + * Using the getCurrentUser will not work since that only works for approved users + * @returns {} + */ + getCurrentInvitedUser: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "GetCurrentInvitedUser")), + 'Failed to verify invite'); }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#performSetPassword - * @methodOf umbraco.resources.authResource - * - * @description - * Checks to see if the provided password reset code is valid and sets the user's password - * - * ##usage - *
    -     * authResource.performSetPassword(userId, password, confirmPassword, resetCode)
    -     *    .then(function(data) {
    -     *        //Password set
    -     *    });
    -     * 
    - * @param {integer} userId User Id - * @param {string} password New password - * @param {string} confirmPassword Confirmation of new password - * @param {string} resetCode Password reset code - * @returns {Promise} resourcePromise object - * - */ - performSetPassword: function (userId, password, confirmPassword, resetCode) { - - if (userId === undefined || userId === null) { - return $q.reject({ - errorMsg: 'User Id cannot be empty' - }); - } - - if (!password) { - return $q.reject({ - errorMsg: 'Password cannot be empty' - }); - } - - if (password !== confirmPassword) { - return $q.reject({ - errorMsg: 'Password and confirmation do not match' - }); - } - - if (!resetCode) { - return $q.reject({ - errorMsg: 'Reset code cannot be empty' - }); - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostSetPassword"), - { - userId: userId, - password: password, - resetCode: resetCode - }), - 'Password reset code validation failed for userId ' + userId); - }, - - unlinkLogin: function (loginProvider, providerKey) { - if (!loginProvider || !providerKey) { - return $q.reject({ - errorMsg: 'loginProvider or providerKey cannot be empty' - }); - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostUnLinkLogin"), { - loginProvider: loginProvider, - providerKey: providerKey - }), - 'Unlinking login provider failed'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#performLogout - * @methodOf umbraco.resources.authResource - * - * @description - * Logs out the Umbraco backoffice user - * - * ##usage - *
    -     * authResource.performLogout()
    -     *    .then(function(data) {
    -     *        //Do stuff for logging out...
    -     *    });
    -     * 
    - * @returns {Promise} resourcePromise object - * - */ - performLogout: function () { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostLogout"))); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#getCurrentUser - * @methodOf umbraco.resources.authResource - * - * @description - * Sends a request to the server to get the current user details, will return a 401 if the user is not logged in - * - * ##usage - *
    -     * authResource.getCurrentUser()
    -     *    .then(function(data) {
    -     *        //Do stuff for fetching the current logged in Umbraco backoffice user
    -     *    });
    -     * 
    - * @returns {Promise} resourcePromise object - * - */ - getCurrentUser: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "GetCurrentUser")), - 'Server call failed for getting current user'); - }, - - getCurrentUserLinkedLogins: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "GetCurrentUserLinkedLogins")), - 'Server call failed for getting current users linked logins'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#isAuthenticated - * @methodOf umbraco.resources.authResource - * - * @description - * Checks if the user is logged in or not - does not return 401 or 403 - * - * ##usage - *
    -     * authResource.isAuthenticated()
    -     *    .then(function(data) {
    -     *        //Do stuff to check if user is authenticated
    -     *    });
    -     * 
    - * @returns {Promise} resourcePromise object - * - */ - isAuthenticated: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "IsAuthenticated")), - { - success: function (data, status, headers, config) { - //if the response is false, they are not logged in so return a rejection - if (data === false || data === "false") { - return $q.reject('User is not logged in'); - } - return data; - }, - error: function (data, status, headers, config) { - return { - errorMsg: 'Server call failed for checking authentication', - data: data, - status: status - }; - } - }); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#getRemainingTimeoutSeconds - * @methodOf umbraco.resources.authResource - * - * @description - * Gets the user's remaining seconds before their login times out - * - * ##usage - *
    -     * authResource.getRemainingTimeoutSeconds()
    -     *    .then(function(data) {
    -     *        //Number of seconds is returned
    -     *    });
    -     * 
    - * @returns {Promise} resourcePromise object - * - */ - getRemainingTimeoutSeconds: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "GetRemainingTimeoutSeconds")), - 'Server call failed for checking remaining seconds'); - } - - }; -} - -angular.module('umbraco.resources').factory('authResource', authResource); + + /** + * @ngdoc method + * @name umbraco.resources.authResource#performRequestPasswordReset + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided email address is a valid user account and sends a link + * to allow them to reset their password + * + * ##usage + *
    +     * authResource.performRequestPasswordReset(email)
    +     *    .then(function(data) {
    +     *        //Do stuff for password reset request...
    +     *    });
    +     * 
    + * @param {string} email Email address of backoffice user + * @returns {Promise} resourcePromise object + * + */ + performRequestPasswordReset: function (email) { + + if (!email) { + return $q.reject({ + errorMsg: 'Email address cannot be empty' + }); + } + + //TODO: This validation shouldn't really be done here, the validation on the login dialog + // is pretty hacky which is why this is here, ideally validation on the login dialog would + // be done properly. + var emailRegex = /\S+@\S+\.\S+/; + if (!emailRegex.test(email)) { + return $q.reject({ + errorMsg: 'Email address is not valid' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostRequestPasswordReset"), { + email: email + }), + 'Request password reset failed for email ' + email); + }, + + /** + * @ngdoc method + * @name umbraco.resources.authResource#performValidatePasswordResetCode + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided password reset code is valid + * + * ##usage + *
    +     * authResource.performValidatePasswordResetCode(resetCode)
    +     *    .then(function(data) {
    +     *        //Allow reset of password
    +     *    });
    +     * 
    + * @param {integer} userId User Id + * @param {string} resetCode Password reset code + * @returns {Promise} resourcePromise object + * + */ + performValidatePasswordResetCode: function (userId, resetCode) { + + if (!userId) { + return $q.reject({ + errorMsg: 'User Id cannot be empty' + }); + } + + if (!resetCode) { + return $q.reject({ + errorMsg: 'Reset code cannot be empty' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostValidatePasswordResetCode"), + { + userId: userId, + resetCode: resetCode + }), + 'Password reset code validation failed for userId ' + userId + ', code' + resetCode); + }, + + /** + * @ngdoc method + * @name umbraco.resources.currentUserResource#getMembershipProviderConfig + * @methodOf umbraco.resources.currentUserResource + * + * @description + * Gets the configuration of the user membership provider which is used to configure the change password form + */ + getMembershipProviderConfig: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "GetMembershipProviderConfig")), + 'Failed to retrieve membership provider config'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.authResource#performSetPassword + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided password reset code is valid and sets the user's password + * + * ##usage + *
    +     * authResource.performSetPassword(userId, password, confirmPassword, resetCode)
    +     *    .then(function(data) {
    +     *        //Password set
    +     *    });
    +     * 
    + * @param {integer} userId User Id + * @param {string} password New password + * @param {string} confirmPassword Confirmation of new password + * @param {string} resetCode Password reset code + * @returns {Promise} resourcePromise object + * + */ + performSetPassword: function (userId, password, confirmPassword, resetCode) { + + if (userId === undefined || userId === null) { + return $q.reject({ + errorMsg: 'User Id cannot be empty' + }); + } + + if (!password) { + return $q.reject({ + errorMsg: 'Password cannot be empty' + }); + } + + if (password !== confirmPassword) { + return $q.reject({ + errorMsg: 'Password and confirmation do not match' + }); + } + + if (!resetCode) { + return $q.reject({ + errorMsg: 'Reset code cannot be empty' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostSetPassword"), + { + userId: userId, + password: password, + resetCode: resetCode + }), + 'Password reset code validation failed for userId ' + userId); + }, + + unlinkLogin: function (loginProvider, providerKey) { + if (!loginProvider || !providerKey) { + return $q.reject({ + errorMsg: 'loginProvider or providerKey cannot be empty' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostUnLinkLogin"), { + loginProvider: loginProvider, + providerKey: providerKey + }), + 'Unlinking login provider failed'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.authResource#performLogout + * @methodOf umbraco.resources.authResource + * + * @description + * Logs out the Umbraco backoffice user + * + * ##usage + *
    +     * authResource.performLogout()
    +     *    .then(function(data) {
    +     *        //Do stuff for logging out...
    +     *    });
    +     * 
    + * @returns {Promise} resourcePromise object + * + */ + performLogout: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostLogout"))); + }, + + /** + * @ngdoc method + * @name umbraco.resources.authResource#getCurrentUser + * @methodOf umbraco.resources.authResource + * + * @description + * Sends a request to the server to get the current user details, will return a 401 if the user is not logged in + * + * ##usage + *
    +     * authResource.getCurrentUser()
    +     *    .then(function(data) {
    +     *        //Do stuff for fetching the current logged in Umbraco backoffice user
    +     *    });
    +     * 
    + * @returns {Promise} resourcePromise object + * + */ + getCurrentUser: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "GetCurrentUser")), + 'Server call failed for getting current user'); + }, + + getCurrentUserLinkedLogins: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "GetCurrentUserLinkedLogins")), + 'Server call failed for getting current users linked logins'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.authResource#isAuthenticated + * @methodOf umbraco.resources.authResource + * + * @description + * Checks if the user is logged in or not - does not return 401 or 403 + * + * ##usage + *
    +     * authResource.isAuthenticated()
    +     *    .then(function(data) {
    +     *        //Do stuff to check if user is authenticated
    +     *    });
    +     * 
    + * @returns {Promise} resourcePromise object + * + */ + isAuthenticated: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "IsAuthenticated")), + { + success: function (data, status, headers, config) { + //if the response is false, they are not logged in so return a rejection + if (data === false || data === "false") { + return $q.reject('User is not logged in'); + } + return data; + }, + error: function (data, status, headers, config) { + return { + errorMsg: 'Server call failed for checking authentication', + data: data, + status: status + }; + } + }); + }, + + /** + * @ngdoc method + * @name umbraco.resources.authResource#getRemainingTimeoutSeconds + * @methodOf umbraco.resources.authResource + * + * @description + * Gets the user's remaining seconds before their login times out + * + * ##usage + *
    +     * authResource.getRemainingTimeoutSeconds()
    +     *    .then(function(data) {
    +     *        //Number of seconds is returned
    +     *    });
    +     * 
    + * @returns {Promise} resourcePromise object + * + */ + getRemainingTimeoutSeconds: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "GetRemainingTimeoutSeconds")), + 'Server call failed for checking remaining seconds'); + } + + }; +} + +angular.module('umbraco.resources').factory('authResource', authResource); 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 2b166aa814..67b50dddcb 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 @@ -1,772 +1,772 @@ -/** - * @ngdoc service - * @name umbraco.resources.contentResource - * @description Handles all transactions of content data - * from the angular application to the Umbraco database, using the Content WebApi controller - * - * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. - * - * @requires $q - * @requires $http - * @requires umbDataFormatter - * @requires umbRequestHelper - * - * ##usage - * To use, simply inject the contentResource into any controller or service that needs it, and make - * sure the umbraco.resources module is accesible - which it should be by default. - * - *
    -  *    contentResource.getById(1234)
    -  *          .then(function(data) {
    -  *              $scope.content = data;
    -  *          });    
    -  * 
    - **/ - -function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files, restApiUrl) { - return umbRequestHelper.postSaveContent({ - restApiUrl: restApiUrl, - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatContentPostData(c, a); - } - }); - } - - return { - - allowsCultureVariation: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "AllowsCultureVariation")), - 'Failed to retrieve variant content types'); +/** + * @ngdoc service + * @name umbraco.resources.contentResource + * @description Handles all transactions of content data + * from the angular application to the Umbraco database, using the Content WebApi controller + * + * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. + * + * @requires $q + * @requires $http + * @requires umbDataFormatter + * @requires umbRequestHelper + * + * ##usage + * To use, simply inject the contentResource into any controller or service that needs it, and make + * sure the umbraco.resources module is accesible - which it should be by default. + * + *
    +  *    contentResource.getById(1234)
    +  *          .then(function(data) {
    +  *              $scope.content = data;
    +  *          });    
    +  * 
    + **/ + +function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { + + /** internal method process the saving of data and post processing the result */ + function saveContentItem(content, action, files, restApiUrl) { + return umbRequestHelper.postSaveContent({ + restApiUrl: restApiUrl, + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatContentPostData(c, a); + } + }); + } + + return { + + allowsCultureVariation: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "AllowsCultureVariation")), + 'Failed to retrieve variant content types'); }, - - savePermissions: function (saveModel) { - if (!saveModel) { - throw "saveModel cannot be null"; - } - if (!saveModel.contentId) { - throw "saveModel.contentId cannot be null"; - } - if (!saveModel.permissions) { - throw "saveModel.permissions cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), - saveModel), - 'Failed to save permissions'); - }, - - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for content recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sort - * @methodOf umbraco.resources.contentResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
    -          * var ids = [123,34533,2334,23434];
    -          * contentResource.sort({ parentId: 1244, sortedIds: ids })
    -          *    .then(function() {
    -          *        $scope.complete = true;
    -          *    });
    -          * 
    - * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort content'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#move - * @methodOf umbraco.resources.contentResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
    -          * contentResource.move({ parentId: 1244, id: 123 })
    -          *    .then(function() {
    -          *        alert("node was moved");
    -          *    }, function(err){
    -          *      alert("node didnt move:" + err.data.Message); 
    -          *    });
    -          * 
    - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#copy - * @methodOf umbraco.resources.contentResource - * - * @description - * Copies a node underneath a new parentId - * - * ##usage - *
    -          * contentResource.copy({ parentId: 1244, id: 123 })
    -          *    .then(function() {
    -          *        alert("node was copied");
    -          *    }, function(err){
    -          *      alert("node wasnt copy:" + err.data.Message); 
    -          *    });
    -          * 
    - * @param {Object} args arguments object - * @param {Int} args.id the ID of the node to copy - * @param {Int} args.parentId the ID of the parent node to copy to - * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api - * @returns {Promise} resourcePromise object. - * - */ - copy: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), - args), - 'Failed to copy content'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#unPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Unpublishes a content item with a given Id - * - * ##usage - *
    -          * contentResource.unPublish(1234)
    -          *    .then(function() {
    -          *        alert("node was unpulished");
    -          *    }, function(err){
    -          *      alert("node wasnt unpublished:" + err.data.Message); 
    -          *    });
    -          * 
    - * @param {Int} id the ID of the node to unpublish - * @returns {Promise} resourcePromise object. - * - */ - unPublish: function (id, culture) { - if (!id) { - throw "id cannot be null"; - } + + savePermissions: function (saveModel) { + if (!saveModel) { + throw "saveModel cannot be null"; + } + if (!saveModel.contentId) { + throw "saveModel.contentId cannot be null"; + } + if (!saveModel.permissions) { + throw "saveModel.permissions cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), + saveModel), + 'Failed to save permissions'); + }, + + + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for content recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sort + * @methodOf umbraco.resources.contentResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
    +          * var ids = [123,34533,2334,23434];
    +          * contentResource.sort({ parentId: 1244, sortedIds: ids })
    +          *    .then(function() {
    +          *        $scope.complete = true;
    +          *    });
    +          * 
    + * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort content'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#move + * @methodOf umbraco.resources.contentResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
    +          * contentResource.move({ parentId: 1244, id: 123 })
    +          *    .then(function() {
    +          *        alert("node was moved");
    +          *    }, function(err){
    +          *      alert("node didnt move:" + err.data.Message); 
    +          *    });
    +          * 
    + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#copy + * @methodOf umbraco.resources.contentResource + * + * @description + * Copies a node underneath a new parentId + * + * ##usage + *
    +          * contentResource.copy({ parentId: 1244, id: 123 })
    +          *    .then(function() {
    +          *        alert("node was copied");
    +          *    }, function(err){
    +          *      alert("node wasnt copy:" + err.data.Message); 
    +          *    });
    +          * 
    + * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api + * @returns {Promise} resourcePromise object. + * + */ + copy: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), + args), + 'Failed to copy content'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#unPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Unpublishes a content item with a given Id + * + * ##usage + *
    +          * contentResource.unPublish(1234)
    +          *    .then(function() {
    +          *        alert("node was unpulished");
    +          *    }, function(err){
    +          *      alert("node wasnt unpublished:" + err.data.Message); 
    +          *    });
    +          * 
    + * @param {Int} id the ID of the node to unpublish + * @returns {Promise} resourcePromise object. + * + */ + unPublish: function (id, culture) { + if (!id) { + throw "id cannot be null"; + } if (!culture) { - culture = null; + culture = null; } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostUnPublish", - { id: id, culture: culture })), - 'Failed to publish content with id ' + id); - }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#emptyRecycleBin - * @methodOf umbraco.resources.contentResource - * - * @description - * Empties the content recycle bin - * - * ##usage - *
    -          * contentResource.emptyRecycleBin()
    -          *    .then(function() {
    -          *        alert('its empty!');
    -          *    });
    -          * 
    - * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#deleteById - * @methodOf umbraco.resources.contentResource - * - * @description - * Deletes a content item with a given id - * - * ##usage - *
    -          * contentResource.deleteById(1234)
    -          *    .then(function() {
    -          *        alert('its gone!');
    -          *    });
    -          * 
    - * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, - - deleteBlueprint: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteBlueprint", - [{ id: id }])), - 'Failed to delete blueprint ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getById - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets a content item with a given id - * - * ##usage - *
    -          * contentResource.getById(1234)
    -          *    .then(function(content) {
    -          *        var myDoc = content; 
    -          *        alert('its here!');
    -          *    });
    -          * 
    - * - * @param {Int} id id of content item to return - * @param {Int} culture optional culture to retrieve the item in - * @returns {Promise} resourcePromise object containing the content item. - * - */ - getById: function (id, culture) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetById", - { id: id, culture: culture })), - 'Failed to retrieve data for content id ' + id); - }, - - getBlueprintById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetBlueprintById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getByIds - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets an array of content items, given a collection of ids - * - * ##usage - *
    -          * contentResource.getByIds( [1234,2526,28262])
    -          *    .then(function(contentArray) {
    -          *        var myDoc = contentArray; 
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Array} ids ids of content items to return as an array - * @returns {Promise} resourcePromise object containing the content items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for content with multiple ids'); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * - * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * - * The scaffold is used to build editors for content that has not yet been populated with data. - * - * ##usage - *
    -          * contentResource.getScaffold(1234, 'homepage')
    -          *    .then(function(scaffold) {
    -          *        var myDoc = scaffold;
    -          *        myDoc.name = "My new document"; 
    -          *
    -          *        contentResource.publish(myDoc, true)
    -          *            .then(function(content){
    -          *                alert("Retrieved, updated and published again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the content scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty content item type ' + alias); - }, - - getBlueprintScaffold: function (parentId, blueprintId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ blueprintId: blueprintId }, { parentId: parentId }])), - 'Failed to retrieve blueprint for id ' + blueprintId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getNiceUrl - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a url, given a node ID - * - * ##usage - *
    -          * contentResource.getNiceUrl(id)
    -          *    .then(function(url) {
    -          *        alert('its here!');
    -          *    });
    -          * 
    - * - * @param {Int} id Id of node to return the public url to - * @returns {Promise} resourcePromise object containing the url. - * - */ - getNiceUrl: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNiceUrl", [{ id: id }])), - 'Failed to retrieve url for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getChildren - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets children of a content item with a given id - * - * ##usage - *
    -          * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
    -          *    .then(function(contentArray) {
    -          *        var children = contentArray; 
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @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 items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { - - var defaults = { - includeProperties: [], - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - //converts the value to a js bool - function toBool(v) { - if (angular.isNumber(v)) { - return v > 0; - } - if (angular.isString(v)) { - return v === "true"; - } - if (typeof v === "boolean") { - return v; - } - return false; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetChildren", - { - id: parentId, - includeProperties: _.pluck(options.includeProperties, 'alias').join(","), - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - orderBySystemField: toBool(options.orderBySystemField), - filter: options.filter - })), - 'Failed to retrieve children for content item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#hasPermission - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns true/false given a permission char to check against a nodeID - * for the current user - * - * ##usage - *
    -          * contentResource.hasPermission('p',1234)
    -          *    .then(function() {
    -          *        alert('You are allowed to publish this item');
    -          *    });
    -          * 
    - * - * @param {String} permission char representing the permission to check - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - checkPermission: function (permission, id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "HasPermission", - [{ permissionToCheck: permission }, { nodeId: id }])), - 'Failed to check permission for item ' + id); - }, - - getDetailedPermissions: function (contentId) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetDetailedPermissions", { contentId: contentId })), - 'Failed to retrieve permissions for content item ' + contentId); - }, - - getPermissions: function (nodeIds) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetPermissions"), - nodeIds), - 'Failed to get permissions'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#save - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
    -          * contentResource.getById(1234)
    -          *    .then(function(content) {
    -          *          content.name = "I want a new name!";
    -          *          contentResource.save(content, false)
    -          *            .then(function(content){
    -          *                alert("Retrieved, updated and saved again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - save: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); - }, - - saveBlueprint: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSaveBlueprint"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
    -          * contentResource.getById(1234)
    -          *    .then(function(content) {
    -          *          content.name = "I want a new name, and be published!";
    -          *          contentResource.publish(content, false)
    -          *            .then(function(content){
    -          *                alert("Retrieved, updated and published again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - publish: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sendToPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item, and notifies any subscribers about a pending publication - * - * ##usage - *
    -          * contentResource.getById(1234)
    -          *    .then(function(content) {
    -          *          content.name = "I want a new name, and be published!";
    -          *          contentResource.sendToPublish(content, false)
    -          *            .then(function(content){
    -          *                alert("Retrieved, updated and notication send off");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - sendToPublish: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publishByid - * @methodOf umbraco.resources.contentResource - * - * @description - * Publishes a content item with a given ID - * - * ##usage - *
    -          * contentResource.publishById(1234)
    -          *    .then(function(content) {
    -          *        alert("published");
    -          *    });
    -          * 
    - * - * @param {Int} id The ID of the conten to publish - * @returns {Promise} resourcePromise object containing the published content item. - * - */ - publishById: function (id) { - - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostPublishById", - [{ id: id }])), - 'Failed to publish content with id ' + id); - - }, - - createBlueprintFromContent: function (contentId, name) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl("contentApiBaseUrl", "CreateBlueprintFromContent", { - contentId: contentId, name: name - }) - ), - "Failed to create blueprint from content with id " + contentId - ); - } - - - }; -} - -angular.module('umbraco.resources').factory('contentResource', contentResource); + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostUnPublish", + { id: id, culture: culture })), + 'Failed to publish content with id ' + id); + }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#emptyRecycleBin + * @methodOf umbraco.resources.contentResource + * + * @description + * Empties the content recycle bin + * + * ##usage + *
    +          * contentResource.emptyRecycleBin()
    +          *    .then(function() {
    +          *        alert('its empty!');
    +          *    });
    +          * 
    + * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#deleteById + * @methodOf umbraco.resources.contentResource + * + * @description + * Deletes a content item with a given id + * + * ##usage + *
    +          * contentResource.deleteById(1234)
    +          *    .then(function() {
    +          *        alert('its gone!');
    +          *    });
    +          * 
    + * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, + + deleteBlueprint: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteBlueprint", + [{ id: id }])), + 'Failed to delete blueprint ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets a content item with a given id + * + * ##usage + *
    +          * contentResource.getById(1234)
    +          *    .then(function(content) {
    +          *        var myDoc = content; 
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} id id of content item to return + * @param {Int} culture optional culture to retrieve the item in + * @returns {Promise} resourcePromise object containing the content item. + * + */ + getById: function (id, culture) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + { id: id, culture: culture })), + 'Failed to retrieve data for content id ' + id); + }, + + getBlueprintById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetBlueprintById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getByIds + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets an array of content items, given a collection of ids + * + * ##usage + *
    +          * contentResource.getByIds( [1234,2526,28262])
    +          *    .then(function(contentArray) {
    +          *        var myDoc = contentArray; 
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Array} ids ids of content items to return as an array + * @returns {Promise} resourcePromise object containing the content items array. + * + */ + getByIds: function (ids) { + + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for content with multiple ids'); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. + * + * - Parent Id must be provided so umbraco knows where to store the content + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * + * The scaffold is used to build editors for content that has not yet been populated with data. + * + * ##usage + *
    +          * contentResource.getScaffold(1234, 'homepage')
    +          *    .then(function(scaffold) {
    +          *        var myDoc = scaffold;
    +          *        myDoc.name = "My new document"; 
    +          *
    +          *        contentResource.publish(myDoc, true)
    +          *            .then(function(content){
    +          *                alert("Retrieved, updated and published again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Int} parentId id of content item to return + * @param {String} alias contenttype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ + getScaffold: function (parentId, alias) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty content item type ' + alias); + }, + + getBlueprintScaffold: function (parentId, blueprintId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ blueprintId: blueprintId }, { parentId: parentId }])), + 'Failed to retrieve blueprint for id ' + blueprintId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getNiceUrl + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a url, given a node ID + * + * ##usage + *
    +          * contentResource.getNiceUrl(id)
    +          *    .then(function(url) {
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} id Id of node to return the public url to + * @returns {Promise} resourcePromise object containing the url. + * + */ + getNiceUrl: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNiceUrl", [{ id: id }])), + 'Failed to retrieve url for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getChildren + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets children of a content item with a given id + * + * ##usage + *
    +          * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
    +          *    .then(function(contentArray) {
    +          *        var children = contentArray; 
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @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 items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { + + var defaults = { + includeProperties: [], + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetChildren", + { + id: parentId, + includeProperties: _.pluck(options.includeProperties, 'alias').join(","), + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + orderBySystemField: toBool(options.orderBySystemField), + filter: options.filter + })), + 'Failed to retrieve children for content item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#hasPermission + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns true/false given a permission char to check against a nodeID + * for the current user + * + * ##usage + *
    +          * contentResource.hasPermission('p',1234)
    +          *    .then(function() {
    +          *        alert('You are allowed to publish this item');
    +          *    });
    +          * 
    + * + * @param {String} permission char representing the permission to check + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + checkPermission: function (permission, id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "HasPermission", + [{ permissionToCheck: permission }, { nodeId: id }])), + 'Failed to check permission for item ' + id); + }, + + getDetailedPermissions: function (contentId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetDetailedPermissions", { contentId: contentId })), + 'Failed to retrieve permissions for content item ' + contentId); + }, + + getPermissions: function (nodeIds) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetPermissions"), + nodeIds), + 'Failed to get permissions'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#save + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
    +          * contentResource.getById(1234)
    +          *    .then(function(content) {
    +          *          content.name = "I want a new name!";
    +          *          contentResource.save(content, false)
    +          *            .then(function(content){
    +          *                alert("Retrieved, updated and saved again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + save: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + }, + + saveBlueprint: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSaveBlueprint"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
    +          * contentResource.getById(1234)
    +          *    .then(function(content) {
    +          *          content.name = "I want a new name, and be published!";
    +          *          contentResource.publish(content, false)
    +          *            .then(function(content){
    +          *                alert("Retrieved, updated and published again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + publish: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sendToPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item, and notifies any subscribers about a pending publication + * + * ##usage + *
    +          * contentResource.getById(1234)
    +          *    .then(function(content) {
    +          *          content.name = "I want a new name, and be published!";
    +          *          contentResource.sendToPublish(content, false)
    +          *            .then(function(content){
    +          *                alert("Retrieved, updated and notication send off");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + sendToPublish: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publishByid + * @methodOf umbraco.resources.contentResource + * + * @description + * Publishes a content item with a given ID + * + * ##usage + *
    +          * contentResource.publishById(1234)
    +          *    .then(function(content) {
    +          *        alert("published");
    +          *    });
    +          * 
    + * + * @param {Int} id The ID of the conten to publish + * @returns {Promise} resourcePromise object containing the published content item. + * + */ + publishById: function (id) { + + if (!id) { + throw "id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostPublishById", + [{ id: id }])), + 'Failed to publish content with id ' + id); + + }, + + createBlueprintFromContent: function (contentId, name) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "CreateBlueprintFromContent", { + contentId: contentId, name: name + }) + ), + "Failed to create blueprint from content with id " + contentId + ); + } + + + }; +} + +angular.module('umbraco.resources').factory('contentResource', contentResource); 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 f31550781a..bf32cf0ad6 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 @@ -1,314 +1,314 @@ -/** - * @ngdoc service - * @name umbraco.resources.contentTypeResource - * @description Loads in data for content types - **/ -function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { - - return { - - getCount: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetCount")), - 'Failed to retrieve count'); - }, - - getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { - if (!filterContentTypes) { - filterContentTypes = []; - } - if (!filterPropertyTypes) { - filterPropertyTypes = []; - } - - var query = { - contentTypeId: contentTypeId, - filterContentTypes: filterContentTypes, - filterPropertyTypes: filterPropertyTypes - }; - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetAvailableCompositeContentTypes"), - query), - 'Failed to retrieve data for content type id ' + contentTypeId); - }, - /** - * @ngdoc method - * @name umbraco.resources.contentTypeResource#getWhereCompositionIsUsedInContentTypes - * @methodOf umbraco.resources.contentTypeResource - * - * @description - * Returns a list of content types which use a specific composition with a given id - * - * ##usage - *
    -         * contentTypeResource.getWhereCompositionIsUsedInContentTypes(1234)
    -         *    .then(function(contentTypeList) {
    -         *        console.log(contentTypeList);
    -         *    });
    -         * 
    - * @param {Int} contentTypeId id of the composition content type to retrieve the list of the content types where it has been used - * @returns {Promise} resourcePromise object. - * - */ - getWhereCompositionIsUsedInContentTypes: function (contentTypeId) { - var query = { - contentTypeId: contentTypeId - }; - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetWhereCompositionIsUsedInContentTypes"), - query), - 'Failed to retrieve data for content type id ' + contentTypeId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentTypeResource#getAllowedTypes - * @methodOf umbraco.resources.contentTypeResource - * - * @description - * Returns a list of allowed content types underneath a content item with a given ID - * - * ##usage - *
    -         * contentTypeResource.getAllowedTypes(1234)
    -         *    .then(function(array) {
    -         *        $scope.type = type;
    -         *    });
    -         * 
    - * @param {Int} contentTypeId id of the content item to retrive allowed child types for - * @returns {Promise} resourcePromise object. - * - */ - getAllowedTypes: function (contentTypeId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetAllowedChildren", - [{ contentId: contentTypeId }])), - 'Failed to retrieve data for content id ' + contentTypeId); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.contentTypeResource#getAllPropertyTypeAliases - * @methodOf umbraco.resources.contentTypeResource - * - * @description - * Returns a list of defined property type aliases - * - * @returns {Promise} resourcePromise object. - * - */ - getAllPropertyTypeAliases: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetAllPropertyTypeAliases")), - 'Failed to retrieve property type aliases'); - }, - - getAllStandardFields: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetAllStandardFields")), - 'Failed to retrieve standard fields'); - }, - - getPropertyTypeScaffold : function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetPropertyTypeScaffold", - [{ id: id }])), - 'Failed to retrieve property type scaffold'); - }, - - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve content type'); - }, - - deleteById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete content type'); - }, - - deleteContainerById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "DeleteContainer", - [{ id: id }])), - 'Failed to delete content type contaier'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentTypeResource#getAll - * @methodOf umbraco.resources.contentTypeResource - * - * @description - * Returns a list of all content types - * - * @returns {Promise} resourcePromise object. - * - */ - getAll: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetAll")), - 'Failed to retrieve all content types'); - }, - - getScaffold: function (parentId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetEmpty", { parentId: parentId })), - 'Failed to retrieve content type scaffold'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentTypeResource#save - * @methodOf umbraco.resources.contentTypeResource - * - * @description - * Saves or update a content type - * - * @param {Object} content data type object to create/update - * @returns {Promise} resourcePromise object. - * - */ - save: function (contentType) { - - var saveModel = umbDataFormatter.formatContentTypePostData(contentType); - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostSave"), saveModel), - 'Failed to save data for content type id ' + contentType.id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentTypeResource#move - * @methodOf umbraco.resources.contentTypeResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
    -         * contentTypeResource.move({ parentId: 1244, id: 123 })
    -         *    .then(function() {
    -         *        alert("node was moved");
    -         *    }, function(err){
    -         *      alert("node didnt move:" + err.data.Message);
    -         *    });
    -         * 
    - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, - - copy: function(args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCopy"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to copy content'); - }, - - createContainer: function(parentId, name) { - - return umbRequestHelper.resourcePromise( - $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 - ); - - } - - }; -} -angular.module('umbraco.resources').factory('contentTypeResource', contentTypeResource); +/** + * @ngdoc service + * @name umbraco.resources.contentTypeResource + * @description Loads in data for content types + **/ +function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { + + return { + + getCount: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetCount")), + 'Failed to retrieve count'); + }, + + getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { + if (!filterContentTypes) { + filterContentTypes = []; + } + if (!filterPropertyTypes) { + filterPropertyTypes = []; + } + + var query = { + contentTypeId: contentTypeId, + filterContentTypes: filterContentTypes, + filterPropertyTypes: filterPropertyTypes + }; + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetAvailableCompositeContentTypes"), + query), + 'Failed to retrieve data for content type id ' + contentTypeId); + }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getWhereCompositionIsUsedInContentTypes + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns a list of content types which use a specific composition with a given id + * + * ##usage + *
    +         * contentTypeResource.getWhereCompositionIsUsedInContentTypes(1234)
    +         *    .then(function(contentTypeList) {
    +         *        console.log(contentTypeList);
    +         *    });
    +         * 
    + * @param {Int} contentTypeId id of the composition content type to retrieve the list of the content types where it has been used + * @returns {Promise} resourcePromise object. + * + */ + getWhereCompositionIsUsedInContentTypes: function (contentTypeId) { + var query = { + contentTypeId: contentTypeId + }; + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetWhereCompositionIsUsedInContentTypes"), + query), + 'Failed to retrieve data for content type id ' + contentTypeId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getAllowedTypes + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns a list of allowed content types underneath a content item with a given ID + * + * ##usage + *
    +         * contentTypeResource.getAllowedTypes(1234)
    +         *    .then(function(array) {
    +         *        $scope.type = type;
    +         *    });
    +         * 
    + * @param {Int} contentTypeId id of the content item to retrive allowed child types for + * @returns {Promise} resourcePromise object. + * + */ + getAllowedTypes: function (contentTypeId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetAllowedChildren", + [{ contentId: contentTypeId }])), + 'Failed to retrieve data for content id ' + contentTypeId); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getAllPropertyTypeAliases + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns a list of defined property type aliases + * + * @returns {Promise} resourcePromise object. + * + */ + getAllPropertyTypeAliases: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetAllPropertyTypeAliases")), + 'Failed to retrieve property type aliases'); + }, + + getAllStandardFields: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetAllStandardFields")), + 'Failed to retrieve standard fields'); + }, + + getPropertyTypeScaffold : function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetPropertyTypeScaffold", + [{ id: id }])), + 'Failed to retrieve property type scaffold'); + }, + + getById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve content type'); + }, + + deleteById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete content type'); + }, + + deleteContainerById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete content type contaier'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getAll + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns a list of all content types + * + * @returns {Promise} resourcePromise object. + * + */ + getAll: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetAll")), + 'Failed to retrieve all content types'); + }, + + getScaffold: function (parentId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetEmpty", { parentId: parentId })), + 'Failed to retrieve content type scaffold'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#save + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Saves or update a content type + * + * @param {Object} content data type object to create/update + * @returns {Promise} resourcePromise object. + * + */ + save: function (contentType) { + + var saveModel = umbDataFormatter.formatContentTypePostData(contentType); + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostSave"), saveModel), + 'Failed to save data for content type id ' + contentType.id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#move + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
    +         * contentTypeResource.move({ parentId: 1244, id: 123 })
    +         *    .then(function() {
    +         *        alert("node was moved");
    +         *    }, function(err){
    +         *      alert("node didnt move:" + err.data.Message);
    +         *    });
    +         * 
    + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + + copy: function(args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCopy"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to copy content'); + }, + + createContainer: function(parentId, name) { + + return umbRequestHelper.resourcePromise( + $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 + ); + + } + + }; +} +angular.module('umbraco.resources').factory('contentTypeResource', contentTypeResource); 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 11b6969479..7c18e91364 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 @@ -1,374 +1,374 @@ -/** - * @ngdoc service - * @name umbraco.resources.dataTypeResource - * @description Loads in data for data types - **/ -function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { - - return { - - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getPreValues - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Retrieves available prevalues for a given data type + editor - * - * ##usage - *
    -         * dataTypeResource.getPreValues("Umbraco.MediaPicker", 1234)
    -         *    .then(function(prevalues) {
    -         *        alert('its gone!');
    -         *    });
    -         * 
    - * - * @param {String} editorAlias string alias of editor type to retrive prevalues configuration for - * @param {Int} id id of datatype to retrieve prevalues for - * @returns {Promise} resourcePromise object. - * - */ - getPreValues: function (editorAlias, dataTypeId) { - - if (!dataTypeId) { - dataTypeId = -1; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetPreValues", - [{ editorAlias: editorAlias }, { dataTypeId: dataTypeId }])), - "Failed to retrieve pre values for editor alias " + editorAlias); - }, - - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getById - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Gets a data type item with a given id - * - * ##usage - *
    -         * dataTypeResource.getById(1234)
    -         *    .then(function(datatype) {
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {Int} id id of data type to retrieve - * @returns {Promise} resourcePromise object. - * - */ - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetById", - [{ id: id }])), - "Failed to retrieve data for data type id " + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getByName - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Gets a data type item with a given name - * - * ##usage - *
    -         * dataTypeResource.getByName("upload")
    -         *    .then(function(datatype) {
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {String} name Name of data type to retrieve - * @returns {Promise} resourcePromise object. - * - */ - getByName: function (name) { - - return umbRequestHelper.resourcePromise( - $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"); - }, - - getGroupedDataTypes: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetGroupedDataTypes")), - "Failed to retrieve data"); - }, - - getGroupedPropertyEditors: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetGroupedPropertyEditors")), - "Failed to retrieve data"); - }, - - getAllPropertyEditors: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetAllPropertyEditors")), - "Failed to retrieve data"); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty data type item - * - * The scaffold is used to build editors for data types that has not yet been populated with data. - * - * ##usage - *
    -         * dataTypeResource.getScaffold()
    -         *    .then(function(scaffold) {
    -         *        var myType = scaffold;
    -         *        myType.name = "My new data type";
    -         *
    -         *        dataTypeResource.save(myType, myType.preValues, true)
    -         *            .then(function(type){
    -         *                alert("Retrieved, updated and saved again");
    -         *            });
    -         *    });
    -         * 
    - * - * @returns {Promise} resourcePromise object containing the data type scaffold. - * - */ - getScaffold: function (parentId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetEmpty", { parentId: parentId })), - "Failed to retrieve data for empty datatype"); - }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#deleteById - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Deletes a data type with a given id - * - * ##usage - *
    -         * dataTypeResource.deleteById(1234)
    -         *    .then(function() {
    -         *        alert('its gone!');
    -         *    });
    -         * 
    - * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "DeleteById", - [{ id: id }])), - "Failed to delete item " + id); - }, - - deleteContainerById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "DeleteContainer", - [{ id: id }])), - 'Failed to delete content type contaier'); - }, - - - - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getCustomListView - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Returns a custom listview, given a content types alias - * - * - * ##usage - *
    -         * dataTypeResource.getCustomListView("home")
    -         *    .then(function(listview) {
    -         *    });
    -         * 
    - * - * @returns {Promise} resourcePromise object containing the 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 - * @name umbraco.resources.dataTypeResource#createCustomListView - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Creates and returns a custom listview, given a content types alias - * - * ##usage - *
    -        * dataTypeResource.createCustomListView("home")
    -        *    .then(function(listview) {
    -        *    });
    -        * 
    - * - * @returns {Promise} resourcePromise object containing the listview datatype. - * - */ - createCustomListView: function (contentTypeAlias) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "PostCreateCustomListView", - { contentTypeAlias: contentTypeAlias } - )), - "Failed to create a custom listview datatype"); - }, - - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#save - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Saves or update a data type - * - * @param {Object} dataType data type object to create/update - * @param {Array} preValues collection of prevalues on the datatype - * @param {Bool} isNew set to true if type should be create instead of updated - * @returns {Promise} resourcePromise object. - * - */ - save: function (dataType, preValues, isNew) { - - var saveModel = umbDataFormatter.formatDataTypePostData(dataType, preValues, "save" + (isNew ? "New" : "")); - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), - "Failed to save data for data type id " + dataType.id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#move - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
    -         * dataTypeResource.move({ parentId: 1244, id: 123 })
    -         *    .then(function() {
    -         *        alert("node was moved");
    -         *    }, function(err){
    -         *      alert("node didnt move:" + err.data.Message); 
    -         *    });
    -         * 
    - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, - - createContainer: function (parentId, name) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "PostCreateContainer", - { parentId: parentId, name: name })), - 'Failed to create a folder under parent id ' + parentId); +/** + * @ngdoc service + * @name umbraco.resources.dataTypeResource + * @description Loads in data for data types + **/ +function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { + + return { + + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getPreValues + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Retrieves available prevalues for a given data type + editor + * + * ##usage + *
    +         * dataTypeResource.getPreValues("Umbraco.MediaPicker", 1234)
    +         *    .then(function(prevalues) {
    +         *        alert('its gone!');
    +         *    });
    +         * 
    + * + * @param {String} editorAlias string alias of editor type to retrive prevalues configuration for + * @param {Int} id id of datatype to retrieve prevalues for + * @returns {Promise} resourcePromise object. + * + */ + getPreValues: function (editorAlias, dataTypeId) { + + if (!dataTypeId) { + dataTypeId = -1; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetPreValues", + [{ editorAlias: editorAlias }, { dataTypeId: dataTypeId }])), + "Failed to retrieve pre values for editor alias " + editorAlias); }, - renameContainer: function (id, name) { - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getById + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Gets a data type item with a given id + * + * ##usage + *
    +         * dataTypeResource.getById(1234)
    +         *    .then(function(datatype) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {Int} id id of data type to retrieve + * @returns {Promise} resourcePromise object. + * + */ + getById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetById", + [{ id: id }])), + "Failed to retrieve data for data type id " + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getByName + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Gets a data type item with a given name + * + * ##usage + *
    +         * dataTypeResource.getByName("upload")
    +         *    .then(function(datatype) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {String} name Name of data type to retrieve + * @returns {Promise} resourcePromise object. + * + */ + getByName: function (name) { + + return umbRequestHelper.resourcePromise( + $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"); + }, + + getGroupedDataTypes: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetGroupedDataTypes")), + "Failed to retrieve data"); + }, + + getGroupedPropertyEditors: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetGroupedPropertyEditors")), + "Failed to retrieve data"); + }, + + getAllPropertyEditors: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetAllPropertyEditors")), + "Failed to retrieve data"); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty data type item + * + * The scaffold is used to build editors for data types that has not yet been populated with data. + * + * ##usage + *
    +         * dataTypeResource.getScaffold()
    +         *    .then(function(scaffold) {
    +         *        var myType = scaffold;
    +         *        myType.name = "My new data type";
    +         *
    +         *        dataTypeResource.save(myType, myType.preValues, true)
    +         *            .then(function(type){
    +         *                alert("Retrieved, updated and saved again");
    +         *            });
    +         *    });
    +         * 
    + * + * @returns {Promise} resourcePromise object containing the data type scaffold. + * + */ + getScaffold: function (parentId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetEmpty", { parentId: parentId })), + "Failed to retrieve data for empty datatype"); + }, + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#deleteById + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Deletes a data type with a given id + * + * ##usage + *
    +         * dataTypeResource.deleteById(1234)
    +         *    .then(function() {
    +         *        alert('its gone!');
    +         *    });
    +         * 
    + * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "DeleteById", + [{ id: id }])), + "Failed to delete item " + id); + }, + + deleteContainerById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete content type contaier'); + }, + + + + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getCustomListView + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Returns a custom listview, given a content types alias + * + * + * ##usage + *
    +         * dataTypeResource.getCustomListView("home")
    +         *    .then(function(listview) {
    +         *    });
    +         * 
    + * + * @returns {Promise} resourcePromise object containing the 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 + * @name umbraco.resources.dataTypeResource#createCustomListView + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Creates and returns a custom listview, given a content types alias + * + * ##usage + *
    +        * dataTypeResource.createCustomListView("home")
    +        *    .then(function(listview) {
    +        *    });
    +        * 
    + * + * @returns {Promise} resourcePromise object containing the listview datatype. + * + */ + createCustomListView: function (contentTypeAlias) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "PostCreateCustomListView", + { contentTypeAlias: contentTypeAlias } + )), + "Failed to create a custom listview datatype"); + }, + + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#save + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Saves or update a data type + * + * @param {Object} dataType data type object to create/update + * @param {Array} preValues collection of prevalues on the datatype + * @param {Bool} isNew set to true if type should be create instead of updated + * @returns {Promise} resourcePromise object. + * + */ + save: function (dataType, preValues, isNew) { + + var saveModel = umbDataFormatter.formatDataTypePostData(dataType, preValues, "save" + (isNew ? "New" : "")); + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), + "Failed to save data for data type id " + dataType.id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#move + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
    +         * dataTypeResource.move({ parentId: 1244, id: 123 })
    +         *    .then(function() {
    +         *        alert("node was moved");
    +         *    }, function(err){
    +         *      alert("node didnt move:" + err.data.Message); 
    +         *    });
    +         * 
    + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + + createContainer: function (parentId, name) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "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); - } - }; -} - -angular.module("umbraco.resources").factory("dataTypeResource", dataTypeResource); + "dataTypeApiBaseUrl", + "PostRenameContainer", + { id: id, name: name })), + "Failed to rename the folder with id " + id); + } + }; +} + +angular.module("umbraco.resources").factory("dataTypeResource", dataTypeResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 5dd353d9e0..056948ccd6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -1,555 +1,555 @@ -/** - * @ngdoc service - * @name umbraco.resources.entityResource - * @description Loads in basic data for all entities - * - * ##What is an entity? - * An entity is a basic **read-only** representation of an Umbraco node. It contains only the most - * basic properties used to display the item in trees, lists and navigation. - * - * ##What is the difference between entity and content/media/etc...? - * the entity only contains the basic node data, name, id and guid, whereas content - * nodes fetched through the content service also contains additional all of the content property data, etc.. - * This is the same principal for all entity types. Any user that is logged in to the back office will have access - * to view the basic entity information for all entities since the basic entity information does not contain sensitive information. - * - * ##Entity object types? - * You need to specify the type of object you want returned. - * - * The core object types are: - * - * - Document - * - Media - * - Member - * - Template - * - DocumentType - * - MediaType - * - MemberType - * - Macro - * - User - * - Language - * - Domain - * - DataType - **/ -function entityResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - getSafeAlias: function (value, camelCase) { - - if (!value) { - return ""; - } - value = value.replace("#", ""); - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetSafeAlias", { value: value, camelCase: camelCase })), - 'Failed to retrieve content type scaffold'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPath - * @methodOf umbraco.resources.entityResource - * - * @description - * Returns a path, given a node ID and type - * - * ##usage - *
    -         * entityResource.getPath(id, type)
    -         *    .then(function(pathArray) {
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {Int} id Id of node to return the public url to - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the url. - * - */ - getPath: function (id, type) { - - if (id === -1 || id === "-1") { - return "-1"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetPath", - [{ id: id }, {type: type }])), - 'Failed to retrieve path for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getUrl - * @methodOf umbraco.resources.entityResource - * - * @description - * Returns a url, given a node ID and type - * - * ##usage - *
    -         * entityResource.getUrl(id, type)
    -         *    .then(function(url) {
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {Int} id Id of node to return the public url to - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the url. - * - */ - getUrl: function (id, type) { - - if (id === -1 || id === "-1") { - return ""; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetUrl", - [{ id: id }, {type: type }])), - 'Failed to retrieve url for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getById - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity with a given id - * - * ##usage - *
    -         * //get media by id
    -         * entityResource.getEntityById(0, "Media")
    -         *    .then(function(ent) {
    -         *        var myDoc = ent; 
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {Int} id id of entity to return - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getById: function (id, type) { - - if (id === -1 || id === "-1") { - return null; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetById", - [{ id: id }, { type: type }])), - 'Failed to retrieve entity data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getByIds - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities, given a collection of ids - * - * ##usage - *
    -         * //Get templates for ids
    -         * entityResource.getEntitiesByIds( [1234,2526,28262], "Template")
    -         *    .then(function(templateArray) {
    -         *        var myDoc = contentArray; 
    -         *        alert('they are here!');
    -         *    });
    -         * 
    - * - * @param {Array} ids ids of entities to return as an array - * @param {string} type type name - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - getByIds: function (ids, type) { - - var query = "type=" + type; - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByIds", - query), - { - ids: ids - }), - 'Failed to retrieve entity data for ids ' + ids); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getByQuery - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity from a given xpath - * - * ##usage - *
    -         * //get content by xpath
    -         * entityResource.getByQuery("$current", -1, "Document")
    -         *    .then(function(ent) {
    -         *        var myDoc = ent; 
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {string} query xpath to use in query - * @param {Int} nodeContextId id id to start from - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getByQuery: function (query, nodeContextId, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByQuery", - [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), - 'Failed to retrieve entity data for query ' + query); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getAll - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity with a given id - * - * ##usage - *
    -         *
    -         * //Only return media
    -         * entityResource.getAll("Media")
    -         *    .then(function(ent) {
    -         *        var myDoc = ent; 
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {string} type Object type name - * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server - * @param {string} postFilterParams optional parameters for the postFilter expression - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getAll: function (type, postFilter, postFilterParams) { - - //need to build the query string manually - var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : ""); - if (postFilter && postFilterParams) { - var counter = 0; - _.each(postFilterParams, function(val, key) { - query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val; - counter++; - }); - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetAll", - query)), - 'Failed to retrieve entity data for type ' + type); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getAncestors - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets ancestor entities for a given item - * - * - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getAncestors: function (id, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetAncestors", - [{id: id}, {type: type}])), - 'Failed to retrieve ancestor data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getChildren - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets children entities for a given item - * - * @param {Int} parentid id of content item to return children of - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getChildren: function (id, type) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetChildren", - [{ id: id }, { type: type }])), - 'Failed to retrieve child data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPagedChildren - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets paged children of a content item with a given id - * - * ##usage - *
    -          * entityResource.getPagedChildren(1234, "Content", {pageSize: 10, pageNumber: 2})
    -          *    .then(function(contentArray) {
    -          *        var children = contentArray; 
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Int} parentid id of content item to return children of - * @param {string} type Object type name - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 1 - * @param {Int} options.pageNumber if paging data, current page index, default = 100 - * @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 items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getPagedChildren: function (parentId, type, options) { - - var defaults = { - pageSize: 1, - pageNumber: 100, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetPagedChildren", - { - id: parentId, - type: type, - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) - } - )), - 'Failed to retrieve child data for id ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPagedDescendants - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets paged descendants of a content item with a given id - * - * ##usage - *
    -          * entityResource.getPagedDescendants(1234, "Content", {pageSize: 10, pageNumber: 2})
    -          *    .then(function(contentArray) {
    -          *        var children = contentArray; 
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Int} parentid id of content item to return descendants of - * @param {string} type Object type name - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 1 - * @param {Int} options.pageNumber if paging data, current page index, default = 100 - * @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 items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getPagedDescendants: function (parentId, type, options) { - - var defaults = { - pageSize: 1, - pageNumber: 100, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetPagedDescendants", - { - id: parentId, - type: type, - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) - } - )), - 'Failed to retrieve child data for id ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#search - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities, given a lucene query and a type - * - * ##usage - *
    -         * entityResource.search("news", "Media")
    -         *    .then(function(mediaArray) {
    -         *        var myDoc = mediaArray; 
    -         *        alert('they are here!');
    -         *    });
    -         * 
    - * - * @param {String} Query search query - * @param {String} Type type of conten to search - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - search: function (query, type, searchFrom, canceler) { - - var args = [{ query: query }, { type: type }]; - if (searchFrom) { - args.push({ searchFrom: searchFrom }); - } - - var httpConfig = {}; - if (canceler) { - httpConfig["timeout"] = canceler; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "Search", - args), - httpConfig), - 'Failed to retrieve entity data for query ' + query); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#searchAll - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities from all available search indexes, given a lucene query - * - * ##usage - *
    -         * entityResource.searchAll("bob")
    -         *    .then(function(array) {
    -         *        var myDoc = array; 
    -         *        alert('they are here!');
    -         *    });
    -         * 
    - * - * @param {String} Query search query - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - searchAll: function (query, canceler) { - - var httpConfig = {}; - if (canceler) { - httpConfig["timeout"] = canceler; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "SearchAll", - [{ query: query }]), - httpConfig), - 'Failed to retrieve entity data for query ' + query); - } - - }; -} - -angular.module('umbraco.resources').factory('entityResource', entityResource); +/** + * @ngdoc service + * @name umbraco.resources.entityResource + * @description Loads in basic data for all entities + * + * ##What is an entity? + * An entity is a basic **read-only** representation of an Umbraco node. It contains only the most + * basic properties used to display the item in trees, lists and navigation. + * + * ##What is the difference between entity and content/media/etc...? + * the entity only contains the basic node data, name, id and guid, whereas content + * nodes fetched through the content service also contains additional all of the content property data, etc.. + * This is the same principal for all entity types. Any user that is logged in to the back office will have access + * to view the basic entity information for all entities since the basic entity information does not contain sensitive information. + * + * ##Entity object types? + * You need to specify the type of object you want returned. + * + * The core object types are: + * + * - Document + * - Media + * - Member + * - Template + * - DocumentType + * - MediaType + * - MemberType + * - Macro + * - User + * - Language + * - Domain + * - DataType + **/ +function entityResource($q, $http, umbRequestHelper) { + + //the factory object returned + return { + + getSafeAlias: function (value, camelCase) { + + if (!value) { + return ""; + } + value = value.replace("#", ""); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetSafeAlias", { value: value, camelCase: camelCase })), + 'Failed to retrieve content type scaffold'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPath + * @methodOf umbraco.resources.entityResource + * + * @description + * Returns a path, given a node ID and type + * + * ##usage + *
    +         * entityResource.getPath(id, type)
    +         *    .then(function(pathArray) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {Int} id Id of node to return the public url to + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the url. + * + */ + getPath: function (id, type) { + + if (id === -1 || id === "-1") { + return "-1"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetPath", + [{ id: id }, {type: type }])), + 'Failed to retrieve path for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getUrl + * @methodOf umbraco.resources.entityResource + * + * @description + * Returns a url, given a node ID and type + * + * ##usage + *
    +         * entityResource.getUrl(id, type)
    +         *    .then(function(url) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {Int} id Id of node to return the public url to + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the url. + * + */ + getUrl: function (id, type) { + + if (id === -1 || id === "-1") { + return ""; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrl", + [{ id: id }, {type: type }])), + 'Failed to retrieve url for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getById + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity with a given id + * + * ##usage + *
    +         * //get media by id
    +         * entityResource.getEntityById(0, "Media")
    +         *    .then(function(ent) {
    +         *        var myDoc = ent; 
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {Int} id id of entity to return + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getById: function (id, type) { + + if (id === -1 || id === "-1") { + return null; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetById", + [{ id: id }, { type: type }])), + 'Failed to retrieve entity data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getByIds + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities, given a collection of ids + * + * ##usage + *
    +         * //Get templates for ids
    +         * entityResource.getEntitiesByIds( [1234,2526,28262], "Template")
    +         *    .then(function(templateArray) {
    +         *        var myDoc = contentArray; 
    +         *        alert('they are here!');
    +         *    });
    +         * 
    + * + * @param {Array} ids ids of entities to return as an array + * @param {string} type type name + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + getByIds: function (ids, type) { + + var query = "type=" + type; + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByIds", + query), + { + ids: ids + }), + 'Failed to retrieve entity data for ids ' + ids); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getByQuery + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity from a given xpath + * + * ##usage + *
    +         * //get content by xpath
    +         * entityResource.getByQuery("$current", -1, "Document")
    +         *    .then(function(ent) {
    +         *        var myDoc = ent; 
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {string} query xpath to use in query + * @param {Int} nodeContextId id id to start from + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getByQuery: function (query, nodeContextId, type) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByQuery", + [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), + 'Failed to retrieve entity data for query ' + query); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getAll + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity with a given id + * + * ##usage + *
    +         *
    +         * //Only return media
    +         * entityResource.getAll("Media")
    +         *    .then(function(ent) {
    +         *        var myDoc = ent; 
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {string} type Object type name + * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server + * @param {string} postFilterParams optional parameters for the postFilter expression + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getAll: function (type, postFilter, postFilterParams) { + + //need to build the query string manually + var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : ""); + if (postFilter && postFilterParams) { + var counter = 0; + _.each(postFilterParams, function(val, key) { + query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val; + counter++; + }); + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetAll", + query)), + 'Failed to retrieve entity data for type ' + type); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getAncestors + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets ancestor entities for a given item + * + * + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getAncestors: function (id, type) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetAncestors", + [{id: id}, {type: type}])), + 'Failed to retrieve ancestor data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getChildren + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets children entities for a given item + * + * @param {Int} parentid id of content item to return children of + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getChildren: function (id, type) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetChildren", + [{ id: id }, { type: type }])), + 'Failed to retrieve child data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPagedChildren + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets paged children of a content item with a given id + * + * ##usage + *
    +          * entityResource.getPagedChildren(1234, "Content", {pageSize: 10, pageNumber: 2})
    +          *    .then(function(contentArray) {
    +          *        var children = contentArray; 
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Int} parentid id of content item to return children of + * @param {string} type Object type name + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 1 + * @param {Int} options.pageNumber if paging data, current page index, default = 100 + * @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 items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getPagedChildren: function (parentId, type, options) { + + var defaults = { + pageSize: 1, + pageNumber: 100, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder" + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetPagedChildren", + { + id: parentId, + type: type, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: encodeURIComponent(options.filter) + } + )), + 'Failed to retrieve child data for id ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPagedDescendants + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets paged descendants of a content item with a given id + * + * ##usage + *
    +          * entityResource.getPagedDescendants(1234, "Content", {pageSize: 10, pageNumber: 2})
    +          *    .then(function(contentArray) {
    +          *        var children = contentArray; 
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Int} parentid id of content item to return descendants of + * @param {string} type Object type name + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 1 + * @param {Int} options.pageNumber if paging data, current page index, default = 100 + * @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 items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getPagedDescendants: function (parentId, type, options) { + + var defaults = { + pageSize: 1, + pageNumber: 100, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder" + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetPagedDescendants", + { + id: parentId, + type: type, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: encodeURIComponent(options.filter) + } + )), + 'Failed to retrieve child data for id ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#search + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities, given a lucene query and a type + * + * ##usage + *
    +         * entityResource.search("news", "Media")
    +         *    .then(function(mediaArray) {
    +         *        var myDoc = mediaArray; 
    +         *        alert('they are here!');
    +         *    });
    +         * 
    + * + * @param {String} Query search query + * @param {String} Type type of conten to search + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + search: function (query, type, searchFrom, canceler) { + + var args = [{ query: query }, { type: type }]; + if (searchFrom) { + args.push({ searchFrom: searchFrom }); + } + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "Search", + args), + httpConfig), + 'Failed to retrieve entity data for query ' + query); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#searchAll + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities from all available search indexes, given a lucene query + * + * ##usage + *
    +         * entityResource.searchAll("bob")
    +         *    .then(function(array) {
    +         *        var myDoc = array; 
    +         *        alert('they are here!');
    +         *    });
    +         * 
    + * + * @param {String} Query search query + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + searchAll: function (query, canceler) { + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "SearchAll", + [{ query: query }]), + httpConfig), + 'Failed to retrieve entity data for query ' + query); + } + + }; +} + +angular.module('umbraco.resources').factory('entityResource', entityResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/legacy.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/legacy.resource.js index f2df220e3d..b42d80408f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/legacy.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/legacy.resource.js @@ -1,29 +1,29 @@ -/** - * @ngdoc service - * @name umbraco.resources.legacyResource - * @description Handles legacy dialog requests - **/ -function legacyResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - /** Loads in the data to display the section list */ - deleteItem: function (args) { - - if (!args.nodeId || !args.nodeType || !args.alias) { - throw "The args parameter is not formatted correct, it requires properties: nodeId, nodeType, alias"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "legacyApiBaseUrl", - "DeleteLegacyItem", - [{ nodeId: args.nodeId }, { nodeType: args.nodeType }, { alias: args.alias }])), - 'Failed to delete item ' + args.nodeId); - - } - }; -} - +/** + * @ngdoc service + * @name umbraco.resources.legacyResource + * @description Handles legacy dialog requests + **/ +function legacyResource($q, $http, umbRequestHelper) { + + //the factory object returned + return { + /** Loads in the data to display the section list */ + deleteItem: function (args) { + + if (!args.nodeId || !args.nodeType || !args.alias) { + throw "The args parameter is not formatted correct, it requires properties: nodeId, nodeType, alias"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "legacyApiBaseUrl", + "DeleteLegacyItem", + [{ nodeId: args.nodeId }, { nodeType: args.nodeType }, { alias: args.alias }])), + 'Failed to delete item ' + args.nodeId); + + } + }; +} + angular.module('umbraco.resources').factory('legacyResource', legacyResource); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js index cb676511a5..5eeaf5644b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js @@ -1,173 +1,173 @@ -/** - * @ngdoc service - * @name umbraco.resources.logResource - * @description Retrives log history from umbraco - * - * - **/ -function logResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - getPagedEntityLog: function (options) { - - var defaults = { - pageSize: 10, - pageNumber: 1, - orderDirection: "Descending" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } +/** + * @ngdoc service + * @name umbraco.resources.logResource + * @description Retrives log history from umbraco + * + * + **/ +function logResource($q, $http, umbRequestHelper) { - if (options.id === undefined || options.id === null) { - throw "options.id is required"; + //the factory object returned + return { + + getPagedEntityLog: function (options) { + + var defaults = { + pageSize: 10, + pageNumber: 1, + orderDirection: "Descending" + }; + if (options === undefined) { + options = {}; } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "logApiBaseUrl", - "GetPagedEntityLog", - options)), - 'Failed to retrieve log data for id'); - }, - - getPagedUserLog: function (options) { - - var defaults = { - pageSize: 10, - pageNumber: 1, - orderDirection: "Descending" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "logApiBaseUrl", - "GetPagedEntityLog", - options)), - 'Failed to retrieve log data for id'); + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + if (options.id === undefined || options.id === null) { + throw "options.id is required"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "logApiBaseUrl", + "GetPagedEntityLog", + options)), + 'Failed to retrieve log data for id'); }, - - /** - * @ngdoc method - * @name umbraco.resources.logResource#getEntityLog - * @methodOf umbraco.resources.logResource - * - * @description - * Gets the log history for a give entity id - * - * ##usage - *
    -         * logResource.getEntityLog(1234)
    -         *    .then(function(log) {
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {Int} id id of entity to return log history - * @returns {Promise} resourcePromise object containing the log. - * - */ - getEntityLog: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "logApiBaseUrl", - "GetEntityLog", - [{ id: id }])), - 'Failed to retrieve user data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.logResource#getUserLog - * @methodOf umbraco.resources.logResource - * - * @description - * Gets the current users' log history for a given type of log entry - * - * ##usage - *
    -         * logResource.getUserLog("save", new Date())
    -         *    .then(function(log) {
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {String} type logtype to query for - * @param {DateTime} since query the log back to this date, by defalt 7 days ago - * @returns {Promise} resourcePromise object containing the log. - * - */ - getUserLog: function (type, since) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "logApiBaseUrl", - "GetCurrentUserLog", - [{ logtype: type}, {sinceDate: since }])), - 'Failed to retrieve log data for current user of type ' + type + ' since ' + since); - }, - - /** - * @ngdoc method - * @name umbraco.resources.logResource#getLog - * @methodOf umbraco.resources.logResource - * - * @description - * Gets the log history for a given type of log entry - * - * ##usage - *
    -         * logResource.getLog("save", new Date())
    -         *    .then(function(log) {
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {String} type logtype to query for - * @param {DateTime} since query the log back to this date, by defalt 7 days ago - * @returns {Promise} resourcePromise object containing the log. - * - */ - getLog: function (type, since) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "logApiBaseUrl", - "GetLog", - [{ logtype: type}, {sinceDate: since }])), - 'Failed to retrieve log data of type ' + type + ' since ' + since); - } - }; -} - -angular.module('umbraco.resources').factory('logResource', logResource); + + getPagedUserLog: function (options) { + + var defaults = { + pageSize: 10, + pageNumber: 1, + orderDirection: "Descending" + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "logApiBaseUrl", + "GetPagedEntityLog", + options)), + 'Failed to retrieve log data for id'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.logResource#getEntityLog + * @methodOf umbraco.resources.logResource + * + * @description + * Gets the log history for a give entity id + * + * ##usage + *
    +         * logResource.getEntityLog(1234)
    +         *    .then(function(log) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {Int} id id of entity to return log history + * @returns {Promise} resourcePromise object containing the log. + * + */ + getEntityLog: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "logApiBaseUrl", + "GetEntityLog", + [{ id: id }])), + 'Failed to retrieve user data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.logResource#getUserLog + * @methodOf umbraco.resources.logResource + * + * @description + * Gets the current users' log history for a given type of log entry + * + * ##usage + *
    +         * logResource.getUserLog("save", new Date())
    +         *    .then(function(log) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {String} type logtype to query for + * @param {DateTime} since query the log back to this date, by defalt 7 days ago + * @returns {Promise} resourcePromise object containing the log. + * + */ + getUserLog: function (type, since) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "logApiBaseUrl", + "GetCurrentUserLog", + [{ logtype: type}, {sinceDate: since }])), + 'Failed to retrieve log data for current user of type ' + type + ' since ' + since); + }, + + /** + * @ngdoc method + * @name umbraco.resources.logResource#getLog + * @methodOf umbraco.resources.logResource + * + * @description + * Gets the log history for a given type of log entry + * + * ##usage + *
    +         * logResource.getLog("save", new Date())
    +         *    .then(function(log) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {String} type logtype to query for + * @param {DateTime} since query the log back to this date, by defalt 7 days ago + * @returns {Promise} resourcePromise object containing the log. + * + */ + getLog: function (type, since) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "logApiBaseUrl", + "GetLog", + [{ logtype: type}, {sinceDate: since }])), + 'Failed to retrieve log data of type ' + type + ' since ' + since); + } + }; +} + +angular.module('umbraco.resources').factory('logResource', logResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js index 5a489f0651..dde887776e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js @@ -1,83 +1,83 @@ -/** - * @ngdoc service - * @name umbraco.resources.macroResource - * @description Deals with data for macros - * - **/ -function macroResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - /** - * @ngdoc method - * @name umbraco.resources.macroResource#getMacroParameters - * @methodOf umbraco.resources.macroResource - * - * @description - * Gets the editable macro parameters for the specified macro alias - * - * @param {int} macroId The macro id to get parameters for - * - */ - getMacroParameters: function (macroId) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "macroApiBaseUrl", - "GetMacroParameters", - [{ macroId: macroId }])), - 'Failed to retrieve macro parameters for macro with id ' + macroId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.macroResource#getMacroResult - * @methodOf umbraco.resources.macroResource - * - * @description - * Gets the result of a macro as html to display in the rich text editor or in the Grid - * - * @param {int} macroId The macro id to get parameters for - * @param {int} pageId The current page id - * @param {Array} macroParamDictionary A dictionary of macro parameters - * - */ - getMacroResultAsHtmlForEditor: function (macroAlias, pageId, macroParamDictionary) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "macroApiBaseUrl", - "GetMacroResultAsHtmlForEditor"), { - macroAlias: macroAlias, - pageId: pageId, - macroParams: macroParamDictionary - }), - 'Failed to retrieve macro result for macro with alias ' + macroAlias); - }, - - /** - * - * @param {} filename - * @returns {} - */ - createPartialViewMacroWithFile: function(virtualPath, filename) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "macroApiBaseUrl", - "CreatePartialViewMacroWithFile"), { - virtualPath: virtualPath, - filename: filename - } - ), - 'Failed to create macro "' + filename + '"' - ); - - } - }; -} - -angular.module('umbraco.resources').factory('macroResource', macroResource); +/** + * @ngdoc service + * @name umbraco.resources.macroResource + * @description Deals with data for macros + * + **/ +function macroResource($q, $http, umbRequestHelper) { + + //the factory object returned + return { + + /** + * @ngdoc method + * @name umbraco.resources.macroResource#getMacroParameters + * @methodOf umbraco.resources.macroResource + * + * @description + * Gets the editable macro parameters for the specified macro alias + * + * @param {int} macroId The macro id to get parameters for + * + */ + getMacroParameters: function (macroId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "macroApiBaseUrl", + "GetMacroParameters", + [{ macroId: macroId }])), + 'Failed to retrieve macro parameters for macro with id ' + macroId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.macroResource#getMacroResult + * @methodOf umbraco.resources.macroResource + * + * @description + * Gets the result of a macro as html to display in the rich text editor or in the Grid + * + * @param {int} macroId The macro id to get parameters for + * @param {int} pageId The current page id + * @param {Array} macroParamDictionary A dictionary of macro parameters + * + */ + getMacroResultAsHtmlForEditor: function (macroAlias, pageId, macroParamDictionary) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "macroApiBaseUrl", + "GetMacroResultAsHtmlForEditor"), { + macroAlias: macroAlias, + pageId: pageId, + macroParams: macroParamDictionary + }), + 'Failed to retrieve macro result for macro with alias ' + macroAlias); + }, + + /** + * + * @param {} filename + * @returns {} + */ + createPartialViewMacroWithFile: function(virtualPath, filename) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "macroApiBaseUrl", + "CreatePartialViewMacroWithFile"), { + virtualPath: virtualPath, + filename: filename + } + ), + 'Failed to create macro "' + filename + '"' + ); + + } + }; +} + +angular.module('umbraco.resources').factory('macroResource', macroResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 93a92db1ec..9137017128 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -1,534 +1,534 @@ -/** - * @ngdoc service - * @name umbraco.resources.mediaResource - * @description Loads in data for media - **/ -function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveMediaItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMediaPostData(c, a); - } - }); - } - - return { - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#sort - * @methodOf umbraco.resources.mediaResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
    -          * var ids = [123,34533,2334,23434];
    -          * mediaResource.sort({ sortedIds: ids })
    -          *    .then(function() {
    -          *        $scope.complete = true;
    -          *    });
    -          * 
    - * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#move - * @methodOf umbraco.resources.mediaResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
    -          * mediaResource.move({ parentId: 1244, id: 123 })
    -          *    .then(function() {
    -          *        alert("node was moved");
    -          *    }, function(err){
    -          *      alert("node didnt move:" + err.data.Message); 
    -          *    });
    -          * 
    - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move media'); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets a media item with a given id - * - * ##usage - *
    -          * mediaResource.getById(1234)
    -          *    .then(function(media) {
    -          *        var myMedia = media; 
    -          *        alert('its here!');
    -          *    });
    -          * 
    - * - * @param {Int} id id of media item to return - * @returns {Promise} resourcePromise object containing the media item. - * - */ - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for media id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#deleteById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Deletes a media item with a given id - * - * ##usage - *
    -          * mediaResource.deleteById(1234)
    -          *    .then(function() {
    -          *        alert('its gone!');
    -          *    });
    -          * 
    - * - * @param {Int} id id of media item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getByIds - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets an array of media items, given a collection of ids - * - * ##usage - *
    -          * mediaResource.getByIds( [1234,2526,28262])
    -          *    .then(function(mediaArray) {
    -          *        var myDoc = contentArray; 
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Array} ids ids of media items to return as an array - * @returns {Promise} resourcePromise object containing the media items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getScaffold - * @methodOf umbraco.resources.mediaResource - * - * @description - * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * - * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * - * The scaffold is used to build editors for media that has not yet been populated with data. - * - * ##usage - *
    -          * mediaResource.getScaffold(1234, 'folder')
    -          *    .then(function(scaffold) {
    -          *        var myDoc = scaffold;
    -          *        myDoc.name = "My new media item"; 
    -          *
    -          *        mediaResource.save(myDoc, true)
    -          *            .then(function(media){
    -          *                alert("Retrieved, updated and saved again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the media scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty media item type ' + alias); - - }, - - rootMedia: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); - - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildren - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets children of a media item with a given id - * - * ##usage - *
    -          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
    -          *    .then(function(contentArray) {
    -          *        var children = contentArray; 
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @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 items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { - - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - //converts the value to a js bool - function toBool(v) { - if (angular.isNumber(v)) { - return v > 0; - } - if (angular.isString(v)) { - return v === "true"; - } - if (typeof v === "boolean") { - return v; - } - return false; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter } - ])), - 'Failed to retrieve children for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#save - * @methodOf umbraco.resources.mediaResource - * - * @description - * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
    -          * mediaResource.getById(1234)
    -          *    .then(function(media) {
    -          *          media.name = "I want a new name!";
    -          *          mediaResource.save(media, false)
    -          *            .then(function(media){
    -          *                alert("Retrieved, updated and saved again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function (media, isNew, files) { - return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#addFolder - * @methodOf umbraco.resources.mediaResource - * - * @description - * Shorthand for adding a media item of the type "Folder" under a given parent ID - * - * ##usage - *
    -          * mediaResource.addFolder("My gallery", 1234)
    -          *    .then(function(folder) {
    -          *        alert('New folder');
    -          *    });
    -          * 
    - * - * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath - * @returns {Promise} resourcePromise object. - * - */ - addFolder: function (name, parentId) { - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper - .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), - { - name: name, - parentId: parentId - }), - 'Failed to add folder'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildFolders - * @methodOf umbraco.resources.mediaResource - * - * @description - * Retrieves all media children with types used as folders. - * Uses the convention of looking for media items with mediaTypes ending in - * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * - * NOTE: This will return a max of 500 folders, if more is required it needs to be paged - * - * ##usage - *
    -          * mediaResource.getChildFolders(1234)
    -          *    .then(function(data) {
    -          *        alert('folders');
    -          *    });
    -          * 
    - * - * @param {int} parentId Id of the media item to query for child folders - * @returns {Promise} resourcePromise object. - * - */ - getChildFolders: function (parentId) { - if (!parentId) { - parentId = -1; - } - - //NOTE: This will return a max of 500 folders, if more is required it needs to be paged - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - { - id: parentId - })), - 'Failed to retrieve child folders for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#emptyRecycleBin - * @methodOf umbraco.resources.mediaResource - * - * @description - * Empties the media recycle bin - * - * ##usage - *
    -          * mediaResource.emptyRecycleBin()
    -          *    .then(function() {
    -          *        alert('its empty!');
    -          *    });
    -          * 
    - * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#search - * @methodOf umbraco.resources.mediaResource - * - * @description - * Paginated search for media items starting on the supplied nodeId - * - * ##usage - *
    -          * mediaResource.search("my search", 1, 100, -1)
    -          *    .then(function(searchResult) {
    -          *        alert('it's here!');
    -          *    });
    -          * 
    - * - * @param {string} query The search query - * @param {int} pageNumber The page number - * @param {int} pageSize The number of media items on a page - * @param {int} searchFrom NodeId to search from (-1 for root) - * @returns {Promise} resourcePromise object. - * - */ - search: function (query, pageNumber, pageSize, searchFrom) { - - var args = [ - { "query": query }, - { "pageNumber": pageNumber }, - { "pageSize": pageSize }, - { "searchFrom": searchFrom } - ]; - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "Search", - args)), - 'Failed to retrieve media items for search: ' + query); - } - - }; -} - -angular.module('umbraco.resources').factory('mediaResource', mediaResource); +/** + * @ngdoc service + * @name umbraco.resources.mediaResource + * @description Loads in data for media + **/ +function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { + + /** internal method process the saving of data and post processing the result */ + function saveMediaItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMediaPostData(c, a); + } + }); + } + + return { + + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for media recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#sort + * @methodOf umbraco.resources.mediaResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
    +          * var ids = [123,34533,2334,23434];
    +          * mediaResource.sort({ sortedIds: ids })
    +          *    .then(function() {
    +          *        $scope.complete = true;
    +          *    });
    +          * 
    + * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort media'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#move + * @methodOf umbraco.resources.mediaResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
    +          * mediaResource.move({ parentId: 1244, id: 123 })
    +          *    .then(function() {
    +          *        alert("node was moved");
    +          *    }, function(err){
    +          *      alert("node didnt move:" + err.data.Message); 
    +          *    });
    +          * 
    + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move media'); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets a media item with a given id + * + * ##usage + *
    +          * mediaResource.getById(1234)
    +          *    .then(function(media) {
    +          *        var myMedia = media; 
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} id id of media item to return + * @returns {Promise} resourcePromise object containing the media item. + * + */ + getById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for media id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#deleteById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Deletes a media item with a given id + * + * ##usage + *
    +          * mediaResource.deleteById(1234)
    +          *    .then(function() {
    +          *        alert('its gone!');
    +          *    });
    +          * 
    + * + * @param {Int} id id of media item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getByIds + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets an array of media items, given a collection of ids + * + * ##usage + *
    +          * mediaResource.getByIds( [1234,2526,28262])
    +          *    .then(function(mediaArray) {
    +          *        var myDoc = contentArray; 
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Array} ids ids of media items to return as an array + * @returns {Promise} resourcePromise object containing the media items array. + * + */ + getByIds: function (ids) { + + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for media ids ' + ids); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getScaffold + * @methodOf umbraco.resources.mediaResource + * + * @description + * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. + * + * - Parent Id must be provided so umbraco knows where to store the media + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * + * The scaffold is used to build editors for media that has not yet been populated with data. + * + * ##usage + *
    +          * mediaResource.getScaffold(1234, 'folder')
    +          *    .then(function(scaffold) {
    +          *        var myDoc = scaffold;
    +          *        myDoc.name = "My new media item"; 
    +          *
    +          *        mediaResource.save(myDoc, true)
    +          *            .then(function(media){
    +          *                alert("Retrieved, updated and saved again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Int} parentId id of media item to return + * @param {String} alias mediatype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the media scaffold. + * + */ + getScaffold: function (parentId, alias) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty media item type ' + alias); + + }, + + rootMedia: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRootMedia")), + 'Failed to retrieve data for root media'); + + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildren + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets children of a media item with a given id + * + * ##usage + *
    +          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
    +          *    .then(function(contentArray) {
    +          *        var children = contentArray; 
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @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 items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { + + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } + ])), + 'Failed to retrieve children for media item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#save + * @methodOf umbraco.resources.mediaResource + * + * @description + * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
    +          * mediaResource.getById(1234)
    +          *    .then(function(media) {
    +          *          media.name = "I want a new name!";
    +          *          mediaResource.save(media, false)
    +          *            .then(function(media){
    +          *                alert("Retrieved, updated and saved again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Object} media The media item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function (media, isNew, files) { + return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#addFolder + * @methodOf umbraco.resources.mediaResource + * + * @description + * Shorthand for adding a media item of the type "Folder" under a given parent ID + * + * ##usage + *
    +          * mediaResource.addFolder("My gallery", 1234)
    +          *    .then(function(folder) {
    +          *        alert('New folder');
    +          *    });
    +          * 
    + * + * @param {string} name Name of the folder to create + * @param {int} parentId Id of the media item to create the folder underneath + * @returns {Promise} resourcePromise object. + * + */ + addFolder: function (name, parentId) { + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper + .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), + { + name: name, + parentId: parentId + }), + 'Failed to add folder'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildFolders + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves all media children with types used as folders. + * Uses the convention of looking for media items with mediaTypes ending in + * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, + * + * NOTE: This will return a max of 500 folders, if more is required it needs to be paged + * + * ##usage + *
    +          * mediaResource.getChildFolders(1234)
    +          *    .then(function(data) {
    +          *        alert('folders');
    +          *    });
    +          * 
    + * + * @param {int} parentId Id of the media item to query for child folders + * @returns {Promise} resourcePromise object. + * + */ + getChildFolders: function (parentId) { + if (!parentId) { + parentId = -1; + } + + //NOTE: This will return a max of 500 folders, if more is required it needs to be paged + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + { + id: parentId + })), + 'Failed to retrieve child folders for media item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#emptyRecycleBin + * @methodOf umbraco.resources.mediaResource + * + * @description + * Empties the media recycle bin + * + * ##usage + *
    +          * mediaResource.emptyRecycleBin()
    +          *    .then(function() {
    +          *        alert('its empty!');
    +          *    });
    +          * 
    + * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#search + * @methodOf umbraco.resources.mediaResource + * + * @description + * Paginated search for media items starting on the supplied nodeId + * + * ##usage + *
    +          * mediaResource.search("my search", 1, 100, -1)
    +          *    .then(function(searchResult) {
    +          *        alert('it's here!');
    +          *    });
    +          * 
    + * + * @param {string} query The search query + * @param {int} pageNumber The page number + * @param {int} pageSize The number of media items on a page + * @param {int} searchFrom NodeId to search from (-1 for root) + * @returns {Promise} resourcePromise object. + * + */ + search: function (query, pageNumber, pageSize, searchFrom) { + + var args = [ + { "query": query }, + { "pageNumber": pageNumber }, + { "pageSize": pageSize }, + { "searchFrom": searchFrom } + ]; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "Search", + args)), + 'Failed to retrieve media items for search: ' + query); + } + + }; +} + +angular.module('umbraco.resources').factory('mediaResource', mediaResource); 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 572b2ba3fd..a98398f3ab 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 @@ -1,264 +1,264 @@ -/** - * @ngdoc service - * @name umbraco.resources.mediaTypeResource - * @description Loads in data for media types - **/ -function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { - - return { - - getCount: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetCount")), - 'Failed to retrieve count'); - }, - - getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { - if (!filterContentTypes) { - filterContentTypes = []; - } - if (!filterPropertyTypes) { - filterPropertyTypes = []; - } - - var query = { - contentTypeId: contentTypeId, - filterContentTypes: filterContentTypes, - filterPropertyTypes: filterPropertyTypes - }; - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetAvailableCompositeMediaTypes"), - query), - 'Failed to retrieve data for content type id ' + contentTypeId); - }, - /** - * @ngdoc method - * @name umbraco.resources.mediaTypeResource#getWhereCompositionIsUsedInContentTypes - * @methodOf umbraco.resources.mediaTypeResource - * - * @description - * Returns a list of media types which use a specific composition with a given id - * - * ##usage - *
    -         * mediaTypeResource.getWhereCompositionIsUsedInContentTypes(1234)
    -         *    .then(function(mediaTypeList) {
    -         *        console.log(mediaTypeList);
    -         *    });
    -         * 
    - * @param {Int} contentTypeId id of the composition content type to retrieve the list of the media types where it has been used - * @returns {Promise} resourcePromise object. - * - */ - getWhereCompositionIsUsedInContentTypes: function (contentTypeId) { - var query = { - contentTypeId: contentTypeId - }; - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetWhereCompositionIsUsedInContentTypes"), - query), - 'Failed to retrieve data for content type id ' + contentTypeId); - }, - /** - * @ngdoc method - * @name umbraco.resources.mediaTypeResource#getAllowedTypes - * @methodOf umbraco.resources.mediaTypeResource - * - * @description - * Returns a list of allowed media types underneath a media item with a given ID - * - * ##usage - *
    -         * mediaTypeResource.getAllowedTypes(1234)
    -         *    .then(function(array) {
    -         *        $scope.type = type;
    -         *    });
    -         * 
    - * @param {Int} mediaId id of the media item to retrive allowed child types for - * @returns {Promise} resourcePromise object. - * - */ - getAllowedTypes: function (mediaId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetAllowedChildren", - [{ contentId: mediaId }])), - 'Failed to retrieve allowed types for media id ' + mediaId); - }, - - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve content type'); - }, - - getAll: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetAll")), - 'Failed to retrieve all content types'); - }, - - getScaffold: function (parentId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetEmpty", { parentId: parentId })), - 'Failed to retrieve content type scaffold'); - }, - - deleteById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to retrieve content type'); - }, - - deleteContainerById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "DeleteContainer", - [{ id: id }])), - 'Failed to delete content type contaier'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaTypeResource#save - * @methodOf umbraco.resources.mediaTypeResource - * - * @description - * Saves or update a media type - * - * @param {Object} content data type object to create/update - * @returns {Promise} resourcePromise object. - * - */ - save: function (contentType) { - - var saveModel = umbDataFormatter.formatContentTypePostData(contentType); - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostSave"), saveModel), - 'Failed to save data for content type id ' + contentType.id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaTypeResource#move - * @methodOf umbraco.resources.mediaTypeResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
    -         * mediaTypeResource.move({ parentId: 1244, id: 123 })
    -         *    .then(function() {
    -         *        alert("node was moved");
    -         *    }, function(err){
    -         *      alert("node didnt move:" + err.data.Message);
    -         *    });
    -         * 
    - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, - - copy: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostCopy"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to copy content'); - }, - - createContainer: function(parentId, name) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "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 - ); - - } - - }; -} -angular.module('umbraco.resources').factory('mediaTypeResource', mediaTypeResource); +/** + * @ngdoc service + * @name umbraco.resources.mediaTypeResource + * @description Loads in data for media types + **/ +function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { + + return { + + getCount: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetCount")), + 'Failed to retrieve count'); + }, + + getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { + if (!filterContentTypes) { + filterContentTypes = []; + } + if (!filterPropertyTypes) { + filterPropertyTypes = []; + } + + var query = { + contentTypeId: contentTypeId, + filterContentTypes: filterContentTypes, + filterPropertyTypes: filterPropertyTypes + }; + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetAvailableCompositeMediaTypes"), + query), + 'Failed to retrieve data for content type id ' + contentTypeId); + }, + /** + * @ngdoc method + * @name umbraco.resources.mediaTypeResource#getWhereCompositionIsUsedInContentTypes + * @methodOf umbraco.resources.mediaTypeResource + * + * @description + * Returns a list of media types which use a specific composition with a given id + * + * ##usage + *
    +         * mediaTypeResource.getWhereCompositionIsUsedInContentTypes(1234)
    +         *    .then(function(mediaTypeList) {
    +         *        console.log(mediaTypeList);
    +         *    });
    +         * 
    + * @param {Int} contentTypeId id of the composition content type to retrieve the list of the media types where it has been used + * @returns {Promise} resourcePromise object. + * + */ + getWhereCompositionIsUsedInContentTypes: function (contentTypeId) { + var query = { + contentTypeId: contentTypeId + }; + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetWhereCompositionIsUsedInContentTypes"), + query), + 'Failed to retrieve data for content type id ' + contentTypeId); + }, + /** + * @ngdoc method + * @name umbraco.resources.mediaTypeResource#getAllowedTypes + * @methodOf umbraco.resources.mediaTypeResource + * + * @description + * Returns a list of allowed media types underneath a media item with a given ID + * + * ##usage + *
    +         * mediaTypeResource.getAllowedTypes(1234)
    +         *    .then(function(array) {
    +         *        $scope.type = type;
    +         *    });
    +         * 
    + * @param {Int} mediaId id of the media item to retrive allowed child types for + * @returns {Promise} resourcePromise object. + * + */ + getAllowedTypes: function (mediaId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetAllowedChildren", + [{ contentId: mediaId }])), + 'Failed to retrieve allowed types for media id ' + mediaId); + }, + + getById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve content type'); + }, + + getAll: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetAll")), + 'Failed to retrieve all content types'); + }, + + getScaffold: function (parentId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetEmpty", { parentId: parentId })), + 'Failed to retrieve content type scaffold'); + }, + + deleteById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to retrieve content type'); + }, + + deleteContainerById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete content type contaier'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaTypeResource#save + * @methodOf umbraco.resources.mediaTypeResource + * + * @description + * Saves or update a media type + * + * @param {Object} content data type object to create/update + * @returns {Promise} resourcePromise object. + * + */ + save: function (contentType) { + + var saveModel = umbDataFormatter.formatContentTypePostData(contentType); + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostSave"), saveModel), + 'Failed to save data for content type id ' + contentType.id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaTypeResource#move + * @methodOf umbraco.resources.mediaTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
    +         * mediaTypeResource.move({ parentId: 1244, id: 123 })
    +         *    .then(function() {
    +         *        alert("node was moved");
    +         *    }, function(err){
    +         *      alert("node didnt move:" + err.data.Message);
    +         *    });
    +         * 
    + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + + copy: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostCopy"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to copy content'); + }, + + createContainer: function(parentId, name) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "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 + ); + + } + + }; +} +angular.module('umbraco.resources').factory('mediaTypeResource', mediaTypeResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js index 353fe12cb8..d21edbbab8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js @@ -1,248 +1,248 @@ -/** - * @ngdoc service - * @name umbraco.resources.memberResource - * @description Loads in data for members - **/ -function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveMember(content, action, files) { - - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMemberPostData(c, a); - } - }); - } - - return { - getPagedResults: function(memberTypeAlias, options) { - - if (memberTypeAlias === 'all-members') { - memberTypeAlias = null; - } - - var defaults = { - pageSize: 25, - pageNumber: 1, - filter: '', - orderDirection: "Ascending", - orderBy: "LoginName", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - //converts the value to a js bool - function toBool(v) { - if (angular.isNumber(v)) { - return v > 0; - } - if (angular.isString(v)) { - return v === "true"; - } - if (typeof v === "boolean") { - return v; - } - return false; - } - - var params = [ - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter } - ]; - if (memberTypeAlias != null) { - params.push({ memberTypeAlias: memberTypeAlias }); - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetPagedResults", - params)), - 'Failed to retrieve member paged result'); - }, - - getListNode: function(listName) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetListNodeDisplay", - [{ listName: listName }])), - 'Failed to retrieve data for member list ' + listName); - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#getByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Gets a member item with a given key - * - * ##usage - *
    -          * memberResource.getByKey("0000-0000-000-00000-000")
    -          *    .then(function(member) {
    -          *        var mymember = member; 
    -          *        alert('its here!');
    -          *    });
    -          * 
    - * - * @param {Guid} key key of member item to return - * @returns {Promise} resourcePromise object containing the member item. - * - */ - getByKey: function(key) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetByKey", - [{ key: key }])), - 'Failed to retrieve data for member id ' + key); - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#deleteByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Deletes a member item with a given key - * - * ##usage - *
    -          * memberResource.deleteByKey("0000-0000-000-00000-000")
    -          *    .then(function() {
    -          *        alert('its gone!');
    -          *    });
    -          * 
    - * - * @param {Guid} key id of member item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteByKey: function(key) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "DeleteByKey", - [{ key: key }])), - 'Failed to delete item ' + key); - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#getScaffold - * @methodOf umbraco.resources.memberResource - * - * @description - * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. - * - * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold - * - * The scaffold is used to build editors for member that has not yet been populated with data. - * - * ##usage - *
    -          * memberResource.getScaffold('client')
    -          *    .then(function(scaffold) {
    -          *        var myDoc = scaffold;
    -          *        myDoc.name = "My new member item"; 
    -          *
    -          *        memberResource.save(myDoc, true)
    -          *            .then(function(member){
    -          *                alert("Retrieved, updated and saved again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {String} alias membertype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the member scaffold. - * - */ - getScaffold: function(alias) { - - if (alias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }])), - 'Failed to retrieve data for empty member item type ' + alias); - } - else { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty")), - 'Failed to retrieve data for empty member item type ' + alias); - } - - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#save - * @methodOf umbraco.resources.memberResource - * - * @description - * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation - * if the member needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
    -          * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
    -          *    .then(function(member) {
    -          *          member.name = "Bob";
    -          *          memberResource.save(member, false)
    -          *            .then(function(member){
    -          *                alert("Retrieved, updated and saved again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Object} media The member item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function(member, isNew, files) { - return saveMember(member, "save" + (isNew ? "New" : ""), files); - } - }; -} - -angular.module('umbraco.resources').factory('memberResource', memberResource); +/** + * @ngdoc service + * @name umbraco.resources.memberResource + * @description Loads in data for members + **/ +function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { + + /** internal method process the saving of data and post processing the result */ + function saveMember(content, action, files) { + + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMemberPostData(c, a); + } + }); + } + + return { + getPagedResults: function(memberTypeAlias, options) { + + if (memberTypeAlias === 'all-members') { + memberTypeAlias = null; + } + + var defaults = { + pageSize: 25, + pageNumber: 1, + filter: '', + orderDirection: "Ascending", + orderBy: "LoginName", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } + + var params = [ + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } + ]; + if (memberTypeAlias != null) { + params.push({ memberTypeAlias: memberTypeAlias }); + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetPagedResults", + params)), + 'Failed to retrieve member paged result'); + }, + + getListNode: function(listName) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetListNodeDisplay", + [{ listName: listName }])), + 'Failed to retrieve data for member list ' + listName); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#getByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Gets a member item with a given key + * + * ##usage + *
    +          * memberResource.getByKey("0000-0000-000-00000-000")
    +          *    .then(function(member) {
    +          *        var mymember = member; 
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Guid} key key of member item to return + * @returns {Promise} resourcePromise object containing the member item. + * + */ + getByKey: function(key) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetByKey", + [{ key: key }])), + 'Failed to retrieve data for member id ' + key); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#deleteByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Deletes a member item with a given key + * + * ##usage + *
    +          * memberResource.deleteByKey("0000-0000-000-00000-000")
    +          *    .then(function() {
    +          *        alert('its gone!');
    +          *    });
    +          * 
    + * + * @param {Guid} key id of member item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteByKey: function(key) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "DeleteByKey", + [{ key: key }])), + 'Failed to delete item ' + key); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#getScaffold + * @methodOf umbraco.resources.memberResource + * + * @description + * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. + * + * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold + * + * The scaffold is used to build editors for member that has not yet been populated with data. + * + * ##usage + *
    +          * memberResource.getScaffold('client')
    +          *    .then(function(scaffold) {
    +          *        var myDoc = scaffold;
    +          *        myDoc.name = "My new member item"; 
    +          *
    +          *        memberResource.save(myDoc, true)
    +          *            .then(function(member){
    +          *                alert("Retrieved, updated and saved again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {String} alias membertype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the member scaffold. + * + */ + getScaffold: function(alias) { + + if (alias) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }])), + 'Failed to retrieve data for empty member item type ' + alias); + } + else { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty")), + 'Failed to retrieve data for empty member item type ' + alias); + } + + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#save + * @methodOf umbraco.resources.memberResource + * + * @description + * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation + * if the member needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
    +          * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
    +          *    .then(function(member) {
    +          *          member.name = "Bob";
    +          *          memberResource.save(member, false)
    +          *            .then(function(member){
    +          *                alert("Retrieved, updated and saved again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Object} media The member item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function(member, isNew, files) { + return saveMember(member, "save" + (isNew ? "New" : ""), files); + } + }; +} + +angular.module('umbraco.resources').factory('memberResource', memberResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js index eba0b41fe0..6c15b89c0e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js @@ -1,110 +1,110 @@ -/** - * @ngdoc service - * @name umbraco.resources.memberTypeResource - * @description Loads in data for member types - **/ -function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { - - return { - - getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { - if (!filterContentTypes) { - filterContentTypes = []; - } - if (!filterPropertyTypes) { - filterPropertyTypes = []; - } - - var query = ""; - _.each(filterContentTypes, function (item) { - query += "filterContentTypes=" + item + "&"; - }); - // if filterContentTypes array is empty we need a empty variable in the querystring otherwise the service returns a error - if (filterContentTypes.length === 0) { - query += "filterContentTypes=&"; - } - _.each(filterPropertyTypes, function (item) { - query += "filterPropertyTypes=" + item + "&"; - }); - // if filterPropertyTypes array is empty we need a empty variable in the querystring otherwise the service returns a error - if (filterPropertyTypes.length === 0) { - query += "filterPropertyTypes=&"; - } - query += "contentTypeId=" + contentTypeId; - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberTypeApiBaseUrl", - "GetAvailableCompositeMemberTypes", - query)), - 'Failed to retrieve data for content type id ' + contentTypeId); - }, - - //return all member types - getTypes: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberTypeApiBaseUrl", - "GetAllTypes")), - 'Failed to retrieve data for member types id'); - }, - - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberTypeApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve content type'); - }, - - deleteById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "memberTypeApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete member type'); - }, - - getScaffold: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberTypeApiBaseUrl", - "GetEmpty")), - 'Failed to retrieve content type scaffold'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberTypeResource#save - * @methodOf umbraco.resources.memberTypeResource - * - * @description - * Saves or update a member type - * - * @param {Object} content data type object to create/update - * @returns {Promise} resourcePromise object. - * - */ - save: function (contentType) { - - var saveModel = umbDataFormatter.formatContentTypePostData(contentType); - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("memberTypeApiBaseUrl", "PostSave"), saveModel), - 'Failed to save data for member type id ' + contentType.id); - } - - }; -} -angular.module('umbraco.resources').factory('memberTypeResource', memberTypeResource); +/** + * @ngdoc service + * @name umbraco.resources.memberTypeResource + * @description Loads in data for member types + **/ +function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { + + return { + + getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { + if (!filterContentTypes) { + filterContentTypes = []; + } + if (!filterPropertyTypes) { + filterPropertyTypes = []; + } + + var query = ""; + _.each(filterContentTypes, function (item) { + query += "filterContentTypes=" + item + "&"; + }); + // if filterContentTypes array is empty we need a empty variable in the querystring otherwise the service returns a error + if (filterContentTypes.length === 0) { + query += "filterContentTypes=&"; + } + _.each(filterPropertyTypes, function (item) { + query += "filterPropertyTypes=" + item + "&"; + }); + // if filterPropertyTypes array is empty we need a empty variable in the querystring otherwise the service returns a error + if (filterPropertyTypes.length === 0) { + query += "filterPropertyTypes=&"; + } + query += "contentTypeId=" + contentTypeId; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberTypeApiBaseUrl", + "GetAvailableCompositeMemberTypes", + query)), + 'Failed to retrieve data for content type id ' + contentTypeId); + }, + + //return all member types + getTypes: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberTypeApiBaseUrl", + "GetAllTypes")), + 'Failed to retrieve data for member types id'); + }, + + getById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberTypeApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve content type'); + }, + + deleteById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "memberTypeApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete member type'); + }, + + getScaffold: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberTypeApiBaseUrl", + "GetEmpty")), + 'Failed to retrieve content type scaffold'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberTypeResource#save + * @methodOf umbraco.resources.memberTypeResource + * + * @description + * Saves or update a member type + * + * @param {Object} content data type object to create/update + * @returns {Promise} resourcePromise object. + * + */ + save: function (contentType) { + + var saveModel = umbDataFormatter.formatContentTypePostData(contentType); + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("memberTypeApiBaseUrl", "PostSave"), saveModel), + 'Failed to save data for member type id ' + contentType.id); + } + + }; +} +angular.module('umbraco.resources').factory('memberTypeResource', memberTypeResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/section.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/section.resource.js index 23fab36897..bcd922112e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/section.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/section.resource.js @@ -1,37 +1,37 @@ -/** - * @ngdoc service - * @name umbraco.resources.sectionResource - * @description Loads in data for section - **/ -function sectionResource($q, $http, umbRequestHelper) { - - /** internal method to get the tree app url */ - function getSectionsUrl(section) { - return Umbraco.Sys.ServerVariables.sectionApiBaseUrl + "GetSections"; - } - - //the factory object returned - return { - /** Loads in the data to display the section list */ - getSections: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "sectionApiBaseUrl", - "GetSections")), - 'Failed to retrieve data for sections'); - }, - - /** Loads in all available sections */ - getAllSections: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "sectionApiBaseUrl", - "GetAllSections")), - 'Failed to retrieve data for sections'); - } - }; -} - -angular.module('umbraco.resources').factory('sectionResource', sectionResource); +/** + * @ngdoc service + * @name umbraco.resources.sectionResource + * @description Loads in data for section + **/ +function sectionResource($q, $http, umbRequestHelper) { + + /** internal method to get the tree app url */ + function getSectionsUrl(section) { + return Umbraco.Sys.ServerVariables.sectionApiBaseUrl + "GetSections"; + } + + //the factory object returned + return { + /** Loads in the data to display the section list */ + getSections: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "sectionApiBaseUrl", + "GetSections")), + 'Failed to retrieve data for sections'); + }, + + /** Loads in all available sections */ + getAllSections: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "sectionApiBaseUrl", + "GetAllSections")), + 'Failed to retrieve data for sections'); + } + }; +} + +angular.module('umbraco.resources').factory('sectionResource', sectionResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js index dd25018191..0e08ac6518 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js @@ -1,91 +1,91 @@ -/** - * @ngdoc service - * @name umbraco.resources.treeResource - * @description Loads in data for trees - **/ -function treeResource($q, $http, umbRequestHelper) { - - /** internal method to get the tree node's children url */ - function getTreeNodesUrl(node) { - if (!node.childNodesUrl) { - throw "No childNodesUrl property found on the tree node, cannot load child nodes"; - } - return node.childNodesUrl; - } - - /** internal method to get the tree menu url */ - function getTreeMenuUrl(node) { - if (!node.menuUrl) { - return null; - } - return node.menuUrl; - } - - //the factory object returned - return { - - /** Loads in the data to display the nodes menu */ - loadMenu: function (node) { - var treeMenuUrl = getTreeMenuUrl(node); - if (treeMenuUrl !== undefined && treeMenuUrl !== null && treeMenuUrl.length > 0) { - return umbRequestHelper.resourcePromise( - $http.get(getTreeMenuUrl(node)), - "Failed to retrieve data for a node's menu " + node.id); - } else { - return $q.reject({ - errorMsg: "No tree menu url defined for node " + node.id - }); - } - }, - - /** Loads in the data to display the nodes for an application */ - loadApplication: function (options) { - - if (!options || !options.section) { - throw "The object specified for does not contain a 'section' property"; - } - - if(!options.tree){ - options.tree = ""; - } - if (!options.isDialog) { - options.isDialog = false; - } - - //create the query string for the tree request, these are the mandatory options: - var query = "application=" + options.section + "&tree=" + options.tree + "&isDialog=" + options.isDialog; - - //if you need to load a not initialized tree set this value to false - default is true - if (options.onlyinitialized) { - query += "&onlyInitialized=" + options.onlyinitialized; - } - - //the options can contain extra query string parameters - if (options.queryString) { - query += "&" + options.queryString; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "treeApplicationApiBaseUrl", - "GetApplicationTrees", - query)), - 'Failed to retrieve data for application tree ' + options.section); - }, - - /** Loads in the data to display the child nodes for a given node */ - loadNodes: function (options) { - - if (!options || !options.node) { - throw "The options parameter object does not contain the required properties: 'node'"; - } - - return umbRequestHelper.resourcePromise( - $http.get(getTreeNodesUrl(options.node)), - 'Failed to retrieve data for child nodes ' + options.node.nodeId); - } - }; -} - -angular.module('umbraco.resources').factory('treeResource', treeResource); +/** + * @ngdoc service + * @name umbraco.resources.treeResource + * @description Loads in data for trees + **/ +function treeResource($q, $http, umbRequestHelper) { + + /** internal method to get the tree node's children url */ + function getTreeNodesUrl(node) { + if (!node.childNodesUrl) { + throw "No childNodesUrl property found on the tree node, cannot load child nodes"; + } + return node.childNodesUrl; + } + + /** internal method to get the tree menu url */ + function getTreeMenuUrl(node) { + if (!node.menuUrl) { + return null; + } + return node.menuUrl; + } + + //the factory object returned + return { + + /** Loads in the data to display the nodes menu */ + loadMenu: function (node) { + var treeMenuUrl = getTreeMenuUrl(node); + if (treeMenuUrl !== undefined && treeMenuUrl !== null && treeMenuUrl.length > 0) { + return umbRequestHelper.resourcePromise( + $http.get(getTreeMenuUrl(node)), + "Failed to retrieve data for a node's menu " + node.id); + } else { + return $q.reject({ + errorMsg: "No tree menu url defined for node " + node.id + }); + } + }, + + /** Loads in the data to display the nodes for an application */ + loadApplication: function (options) { + + if (!options || !options.section) { + throw "The object specified for does not contain a 'section' property"; + } + + if(!options.tree){ + options.tree = ""; + } + if (!options.isDialog) { + options.isDialog = false; + } + + //create the query string for the tree request, these are the mandatory options: + var query = "application=" + options.section + "&tree=" + options.tree + "&isDialog=" + options.isDialog; + + //if you need to load a not initialized tree set this value to false - default is true + if (options.onlyinitialized) { + query += "&onlyInitialized=" + options.onlyinitialized; + } + + //the options can contain extra query string parameters + if (options.queryString) { + query += "&" + options.queryString; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "treeApplicationApiBaseUrl", + "GetApplicationTrees", + query)), + 'Failed to retrieve data for application tree ' + options.section); + }, + + /** Loads in the data to display the child nodes for a given node */ + loadNodes: function (options) { + + if (!options || !options.node) { + throw "The options parameter object does not contain the required properties: 'node'"; + } + + return umbRequestHelper.resourcePromise( + $http.get(getTreeNodesUrl(options.node)), + 'Failed to retrieve data for child nodes ' + options.node.nodeId); + } + }; +} + +angular.module('umbraco.resources').factory('treeResource', treeResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/_module.js b/src/Umbraco.Web.UI.Client/src/common/services/_module.js index 87f6bcec4b..120f267c59 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/_module.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/_module.js @@ -1 +1 @@ -angular.module("umbraco.services", ["umbraco.interceptors", "umbraco.resources"]); +angular.module("umbraco.services", ["umbraco.interceptors", "umbraco.resources"]); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js index 5abacc2a80..d04cfb1911 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js @@ -1,177 +1,177 @@ -/** - * @ngdoc service - * @name umbraco.services.angularHelper - * @function - * - * @description - * Some angular helper/extension methods - */ -function angularHelper($log, $q) { - return { - - /** - * Execute a list of promises sequentially. Unlike $q.all which executes all promises at once, this will execute them in sequence. - * @param {} promises - * @returns {} - */ - executeSequentialPromises: function (promises) { - - //this is sequential promise chaining, it's not pretty but we need to do it this way. - //$q.all doesn't execute promises in sequence but that's what we want to do here. - - if (!angular.isArray(promises)) { - throw "promises must be an array"; - } - - //now execute them in sequence... sorry there's no other good way to do it with angular promises - var j = 0; - function pExec(promise) { - j++; - return promise.then(function (data) { - if (j === promises.length) { - return $q.when(data); //exit - } - else { - return pExec(promises[j]); //recurse - } - }); - } - if (promises.length > 0) { - return pExec(promises[0]); //start the promise chain - } - else { - return $q.when(true); // just exit, no promises to execute - } - }, - - /** - * @ngdoc function - * @name safeApply - * @methodOf umbraco.services.angularHelper - * @function - * - * @description - * This checks if a digest/apply is already occuring, if not it will force an apply call - */ - safeApply: function (scope, fn) { - if (scope.$$phase || (scope.$root && scope.$root.$$phase)) { - if (angular.isFunction(fn)) { - fn(); - } - } - else { - if (angular.isFunction(fn)) { - scope.$apply(fn); - } - else { - scope.$apply(); - } - } - }, - - /** - * @ngdoc function - * @name getCurrentForm - * @methodOf umbraco.services.angularHelper - * @function - * - * @description - * Returns the current form object applied to the scope or null if one is not found - */ - getCurrentForm: function (scope) { - - //NOTE: There isn't a way in angular to get a reference to the current form object since the form object - // is just defined as a property of the scope when it is named but you'll always need to know the name which - // isn't very convenient. If we want to watch for validation changes we need to get a form reference. - // The way that we detect the form object is a bit hackerific in that we detect all of the required properties - // that exist on a form object. - // - //The other way to do it in a directive is to require "^form", but in a controller the only other way to do it - // is to inject the $element object and use: $element.inheritedData('$formController'); - - var form = null; - var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$submitted", "$pending"]; - - // a method to check that the collection of object prop names contains the property name expected - function propertyExists(objectPropNames) { - //ensure that every required property name exists on the current scope property - return _.every(requiredFormProps, function (item) { - - return _.contains(objectPropNames, item); - }); - } - - for (var p in scope) { - - if (_.isObject(scope[p]) && p !== "this" && p.substr(0, 1) !== "$") { - //get the keys of the property names for the current property - var props = _.keys(scope[p]); - //if the length isn't correct, try the next prop - if (props.length < requiredFormProps.length) { - continue; - } - - //ensure that every required property name exists on the current scope property - var containProperty = propertyExists(props); - - if (containProperty) { - form = scope[p]; - break; - } - } - } - - return form; - }, - - /** - * @ngdoc function - * @name validateHasForm - * @methodOf umbraco.services.angularHelper - * @function - * - * @description - * This will validate that the current scope has an assigned form object, if it doesn't an exception is thrown, if - * it does we return the form object. - */ - getRequiredCurrentForm: function (scope) { - var currentForm = this.getCurrentForm(scope); - if (!currentForm || !currentForm.$name) { - throw "The current scope requires a current form object (or ng-form) with a name assigned to it"; - } - return currentForm; - }, - - /** - * @ngdoc function - * @name getNullForm - * @methodOf umbraco.services.angularHelper - * @function - * - * @description - * Returns a null angular FormController, mostly for use in unit tests - * NOTE: This is actually the same construct as angular uses internally for creating a null form but they don't expose - * any of this publicly to us, so we need to create our own. - * NOTE: The properties has been added to the null form because we use them to get a form on a scope. - * - * @param {string} formName The form name to assign - */ - getNullForm: function (formName) { - return { - $error: {}, - $dirty: false, - $pristine: true, - $valid: true, - $submitted: false, - $pending: undefined, - $addControl: angular.noop, - $removeControl: angular.noop, - $setValidity: angular.noop, - $setDirty: angular.noop, - $setPristine: angular.noop, - $name: formName - }; - } - }; -} -angular.module('umbraco.services').factory('angularHelper', angularHelper); +/** + * @ngdoc service + * @name umbraco.services.angularHelper + * @function + * + * @description + * Some angular helper/extension methods + */ +function angularHelper($log, $q) { + return { + + /** + * Execute a list of promises sequentially. Unlike $q.all which executes all promises at once, this will execute them in sequence. + * @param {} promises + * @returns {} + */ + executeSequentialPromises: function (promises) { + + //this is sequential promise chaining, it's not pretty but we need to do it this way. + //$q.all doesn't execute promises in sequence but that's what we want to do here. + + if (!angular.isArray(promises)) { + throw "promises must be an array"; + } + + //now execute them in sequence... sorry there's no other good way to do it with angular promises + var j = 0; + function pExec(promise) { + j++; + return promise.then(function (data) { + if (j === promises.length) { + return $q.when(data); //exit + } + else { + return pExec(promises[j]); //recurse + } + }); + } + if (promises.length > 0) { + return pExec(promises[0]); //start the promise chain + } + else { + return $q.when(true); // just exit, no promises to execute + } + }, + + /** + * @ngdoc function + * @name safeApply + * @methodOf umbraco.services.angularHelper + * @function + * + * @description + * This checks if a digest/apply is already occuring, if not it will force an apply call + */ + safeApply: function (scope, fn) { + if (scope.$$phase || (scope.$root && scope.$root.$$phase)) { + if (angular.isFunction(fn)) { + fn(); + } + } + else { + if (angular.isFunction(fn)) { + scope.$apply(fn); + } + else { + scope.$apply(); + } + } + }, + + /** + * @ngdoc function + * @name getCurrentForm + * @methodOf umbraco.services.angularHelper + * @function + * + * @description + * Returns the current form object applied to the scope or null if one is not found + */ + getCurrentForm: function (scope) { + + //NOTE: There isn't a way in angular to get a reference to the current form object since the form object + // is just defined as a property of the scope when it is named but you'll always need to know the name which + // isn't very convenient. If we want to watch for validation changes we need to get a form reference. + // The way that we detect the form object is a bit hackerific in that we detect all of the required properties + // that exist on a form object. + // + //The other way to do it in a directive is to require "^form", but in a controller the only other way to do it + // is to inject the $element object and use: $element.inheritedData('$formController'); + + var form = null; + var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$submitted", "$pending"]; + + // a method to check that the collection of object prop names contains the property name expected + function propertyExists(objectPropNames) { + //ensure that every required property name exists on the current scope property + return _.every(requiredFormProps, function (item) { + + return _.contains(objectPropNames, item); + }); + } + + for (var p in scope) { + + if (_.isObject(scope[p]) && p !== "this" && p.substr(0, 1) !== "$") { + //get the keys of the property names for the current property + var props = _.keys(scope[p]); + //if the length isn't correct, try the next prop + if (props.length < requiredFormProps.length) { + continue; + } + + //ensure that every required property name exists on the current scope property + var containProperty = propertyExists(props); + + if (containProperty) { + form = scope[p]; + break; + } + } + } + + return form; + }, + + /** + * @ngdoc function + * @name validateHasForm + * @methodOf umbraco.services.angularHelper + * @function + * + * @description + * This will validate that the current scope has an assigned form object, if it doesn't an exception is thrown, if + * it does we return the form object. + */ + getRequiredCurrentForm: function (scope) { + var currentForm = this.getCurrentForm(scope); + if (!currentForm || !currentForm.$name) { + throw "The current scope requires a current form object (or ng-form) with a name assigned to it"; + } + return currentForm; + }, + + /** + * @ngdoc function + * @name getNullForm + * @methodOf umbraco.services.angularHelper + * @function + * + * @description + * Returns a null angular FormController, mostly for use in unit tests + * NOTE: This is actually the same construct as angular uses internally for creating a null form but they don't expose + * any of this publicly to us, so we need to create our own. + * NOTE: The properties has been added to the null form because we use them to get a form on a scope. + * + * @param {string} formName The form name to assign + */ + getNullForm: function (formName) { + return { + $error: {}, + $dirty: false, + $pristine: true, + $valid: true, + $submitted: false, + $pending: undefined, + $addControl: angular.noop, + $removeControl: angular.noop, + $setValidity: angular.noop, + $setDirty: angular.noop, + $setPristine: angular.noop, + $name: formName + }; + } + }; +} +angular.module('umbraco.services').factory('angularHelper', angularHelper); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js index a1257c00b3..39d9b686c7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js @@ -1,311 +1,311 @@ -/** - * @ngdoc service - * @name umbraco.services.assetsService - * - * @requires $q - * @requires angularHelper - * - * @description - * Promise-based utillity service to lazy-load client-side dependencies inside angular controllers. - * - * ##usage - * To use, simply inject the assetsService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *      angular.module("umbraco").controller("my.controller". function(assetsService){
    - *          assetsService.load(["script.js", "styles.css"], $scope).then(function(){
    - *                 //this code executes when the dependencies are done loading
    - *          });
    - *      });
    - * 
    - * - * You can also load individual files, which gives you greater control over what attibutes are passed to the file, as well as timeout - * - *
    - *      angular.module("umbraco").controller("my.controller". function(assetsService){
    - *          assetsService.loadJs("script.js", $scope, {charset: 'utf-8'}, 10000 }).then(function(){
    - *                 //this code executes when the script is done loading
    - *          });
    - *      });
    - * 
    - * - * For these cases, there are 2 individual methods, one for javascript, and one for stylesheets: - * - *
    - *      angular.module("umbraco").controller("my.controller". function(assetsService){
    - *          assetsService.loadCss("stye.css", $scope, {media: 'print'}, 10000 }).then(function(){
    - *                 //loadcss cannot determine when the css is done loading, so this will trigger instantly
    - *          });
    - *      });
    - * 
    - */ -angular.module('umbraco.services') - .factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope, $http, userService, javascriptLibraryResource) { - - var initAssetsLoaded = false; - - function appendRnd(url) { - //if we don't have a global umbraco obj yet, the app is bootstrapping - if (!Umbraco.Sys.ServerVariables.application) { - return url; - } - - var rnd = Umbraco.Sys.ServerVariables.application.cacheBuster; - var _op = (url.indexOf("?") > 0) ? "&" : "?"; - url = url + _op + "umb__rnd=" + rnd; - return url; - }; - - function convertVirtualPath(path) { - //make this work for virtual paths - if (path.startsWith("~/")) { - path = umbRequestHelper.convertVirtualToAbsolutePath(path); - } - return path; - } +/** + * @ngdoc service + * @name umbraco.services.assetsService + * + * @requires $q + * @requires angularHelper + * + * @description + * Promise-based utillity service to lazy-load client-side dependencies inside angular controllers. + * + * ##usage + * To use, simply inject the assetsService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *      angular.module("umbraco").controller("my.controller". function(assetsService){
    + *          assetsService.load(["script.js", "styles.css"], $scope).then(function(){
    + *                 //this code executes when the dependencies are done loading
    + *          });
    + *      });
    + * 
    + * + * You can also load individual files, which gives you greater control over what attibutes are passed to the file, as well as timeout + * + *
    + *      angular.module("umbraco").controller("my.controller". function(assetsService){
    + *          assetsService.loadJs("script.js", $scope, {charset: 'utf-8'}, 10000 }).then(function(){
    + *                 //this code executes when the script is done loading
    + *          });
    + *      });
    + * 
    + * + * For these cases, there are 2 individual methods, one for javascript, and one for stylesheets: + * + *
    + *      angular.module("umbraco").controller("my.controller". function(assetsService){
    + *          assetsService.loadCss("stye.css", $scope, {media: 'print'}, 10000 }).then(function(){
    + *                 //loadcss cannot determine when the css is done loading, so this will trigger instantly
    + *          });
    + *      });
    + * 
    + */ +angular.module('umbraco.services') + .factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope, $http, userService, javascriptLibraryResource) { - /** - * Loads in moment.js requirements during the _loadInitAssets call + var initAssetsLoaded = false; + + function appendRnd(url) { + //if we don't have a global umbraco obj yet, the app is bootstrapping + if (!Umbraco.Sys.ServerVariables.application) { + return url; + } + + var rnd = Umbraco.Sys.ServerVariables.application.cacheBuster; + var _op = (url.indexOf("?") > 0) ? "&" : "?"; + url = url + _op + "umb__rnd=" + rnd; + return url; + }; + + function convertVirtualPath(path) { + //make this work for virtual paths + if (path.startsWith("~/")) { + path = umbRequestHelper.convertVirtualToAbsolutePath(path); + } + return path; + } + + /** + * Loads in moment.js requirements during the _loadInitAssets call */ - function loadMomentLocaleForCurrentUser() { + function loadMomentLocaleForCurrentUser() { var self = this; - function loadLocales(currentUser, supportedLocales) { - var locale = currentUser.locale.toLowerCase(); - if (locale !== 'en-us') { - var localeUrls = []; - if (supportedLocales.indexOf(locale + '.js') > -1) { - localeUrls.push('lib/moment/' + locale + '.js'); - } - if (locale.indexOf('-') > -1) { - var majorLocale = locale.split('-')[0] + '.js'; - if (supportedLocales.indexOf(majorLocale) > -1) { - localeUrls.push('lib/moment/' + majorLocale); - } - } - return self.load(localeUrls, $rootScope); - } - else { - $q.when(true); - } + function loadLocales(currentUser, supportedLocales) { + var locale = currentUser.locale.toLowerCase(); + if (locale !== 'en-us') { + var localeUrls = []; + if (supportedLocales.indexOf(locale + '.js') > -1) { + localeUrls.push('lib/moment/' + locale + '.js'); + } + if (locale.indexOf('-') > -1) { + var majorLocale = locale.split('-')[0] + '.js'; + if (supportedLocales.indexOf(majorLocale) > -1) { + localeUrls.push('lib/moment/' + majorLocale); + } + } + return self.load(localeUrls, $rootScope); + } + else { + $q.when(true); + } } userService.getCurrentUser().then(function (currentUser) { - return javascriptLibraryResource.getSupportedLocalesForMoment().then(function (supportedLocales) { - return loadLocales(currentUser, supportedLocales); - }); - }); - - } - - var service = { - loadedAssets: {}, - - _getAssetPromise: function (path) { - - if (this.loadedAssets[path]) { - return this.loadedAssets[path]; - } else { - var deferred = $q.defer(); - this.loadedAssets[path] = { deferred: deferred, state: "new", path: path }; - return this.loadedAssets[path]; - } - }, - - /** - Internal method. This is called when the application is loading and the user is already authenticated, or once the user is authenticated. - There's a few assets the need to be loaded for the application to function but these assets require authentication to load. - */ - _loadInitAssets: function () { - - //here we need to ensure the required application assets are loaded - if (initAssetsLoaded === false) { - var self = this; - return self.loadJs(umbRequestHelper.getApiUrl("serverVarsJs", "", ""), $rootScope).then(function () { - initAssetsLoaded = true; - - //now we need to go get the legacyTreeJs - but this can be done async without waiting. - self.loadJs(umbRequestHelper.getApiUrl("legacyTreeJs", "", ""), $rootScope); - - return loadMomentLocaleForCurrentUser(); - }); - } - else { - return $q.when(true); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.assetsService#loadCss - * @methodOf umbraco.services.assetsService - * - * @description - * Injects a file as a stylesheet into the document head - * - * @param {String} path path to the css file to load - * @param {Scope} scope optional scope to pass into the loader - * @param {Object} keyvalue collection of attributes to pass to the stylesheet element - * @param {Number} timeout in milliseconds - * @returns {Promise} Promise object which resolves when the file has loaded - */ - loadCss: function (path, scope, attributes, timeout) { - - path = convertVirtualPath(path); - - var asset = this._getAssetPromise(path); // $q.defer(); - var t = timeout || 5000; - var a = attributes || undefined; - - if (asset.state === "new") { - asset.state = "loading"; - LazyLoad.css(appendRnd(path), function () { - if (!scope) { - scope = $rootScope; - } - asset.state = "loaded"; - angularHelper.safeApply(scope, function () { - asset.deferred.resolve(true); - }); - }); - } else if (asset.state === "loaded") { - asset.deferred.resolve(true); - } - return asset.deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.assetsService#loadJs - * @methodOf umbraco.services.assetsService - * - * @description - * Injects a file as a javascript into the document - * - * @param {String} path path to the js file to load - * @param {Scope} scope optional scope to pass into the loader - * @param {Object} keyvalue collection of attributes to pass to the script element - * @param {Number} timeout in milliseconds - * @returns {Promise} Promise object which resolves when the file has loaded - */ - loadJs: function (path, scope, attributes, timeout) { - - path = convertVirtualPath(path); - - var asset = this._getAssetPromise(path); // $q.defer(); - var t = timeout || 5000; - var a = attributes || undefined; - - if (asset.state === "new") { - asset.state = "loading"; - - LazyLoad.js(appendRnd(path), function () { - if (!scope) { - scope = $rootScope; - } - asset.state = "loaded"; - angularHelper.safeApply(scope, function () { - asset.deferred.resolve(true); - }); - }); - - } else if (asset.state === "loaded") { - asset.deferred.resolve(true); - } - - return asset.deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.assetsService#load - * @methodOf umbraco.services.assetsService - * - * @description - * Injects a collection of css and js files - * - * - * @param {Array} pathArray string array of paths to the files to load - * @param {Scope} scope optional scope to pass into the loader - * @returns {Promise} Promise object which resolves when all the files has loaded - */ - load: function (pathArray, scope) { - var promise; - - if (!angular.isArray(pathArray)) { - throw "pathArray must be an array"; - } - - // Check to see if there's anything to load, resolve promise if not - var nonEmpty = _.reject(pathArray, function (item) { - return item === undefined || item === ""; + return javascriptLibraryResource.getSupportedLocalesForMoment().then(function (supportedLocales) { + return loadLocales(currentUser, supportedLocales); }); - - if (nonEmpty.length === 0) { - var deferred = $q.defer(); - promise = deferred.promise; - deferred.resolve(true); - return promise; - } - - //compile a list of promises - //blocking - var promises = []; - var assets = []; - _.each(nonEmpty, function (path) { - path = convertVirtualPath(path); - var asset = service._getAssetPromise(path); - //if not previously loaded, add to list of promises - if (asset.state !== "loaded") { - if (asset.state === "new") { - asset.state = "loading"; - assets.push(asset); - } - - //we need to always push to the promises collection to monitor correct execution - promises.push(asset.deferred.promise); - } - }); - - //gives a central monitoring of all assets to load - promise = $q.all(promises); - - // Split into css and js asset arrays, and use LazyLoad on each array - var cssAssets = _.filter(assets, - function (asset) { - return asset.path.match(/(\.css$|\.css\?)/ig); - }); - var jsAssets = _.filter(assets, - function (asset) { - return asset.path.match(/(\.js$|\.js\?)/ig); + }); + + } + + var service = { + loadedAssets: {}, + + _getAssetPromise: function (path) { + + if (this.loadedAssets[path]) { + return this.loadedAssets[path]; + } else { + var deferred = $q.defer(); + this.loadedAssets[path] = { deferred: deferred, state: "new", path: path }; + return this.loadedAssets[path]; + } + }, + + /** + Internal method. This is called when the application is loading and the user is already authenticated, or once the user is authenticated. + There's a few assets the need to be loaded for the application to function but these assets require authentication to load. + */ + _loadInitAssets: function () { + + //here we need to ensure the required application assets are loaded + if (initAssetsLoaded === false) { + var self = this; + return self.loadJs(umbRequestHelper.getApiUrl("serverVarsJs", "", ""), $rootScope).then(function () { + initAssetsLoaded = true; + + //now we need to go get the legacyTreeJs - but this can be done async without waiting. + self.loadJs(umbRequestHelper.getApiUrl("legacyTreeJs", "", ""), $rootScope); + + return loadMomentLocaleForCurrentUser(); + }); + } + else { + return $q.when(true); + } + }, + + /** + * @ngdoc method + * @name umbraco.services.assetsService#loadCss + * @methodOf umbraco.services.assetsService + * + * @description + * Injects a file as a stylesheet into the document head + * + * @param {String} path path to the css file to load + * @param {Scope} scope optional scope to pass into the loader + * @param {Object} keyvalue collection of attributes to pass to the stylesheet element + * @param {Number} timeout in milliseconds + * @returns {Promise} Promise object which resolves when the file has loaded + */ + loadCss: function (path, scope, attributes, timeout) { + + path = convertVirtualPath(path); + + var asset = this._getAssetPromise(path); // $q.defer(); + var t = timeout || 5000; + var a = attributes || undefined; + + if (asset.state === "new") { + asset.state = "loading"; + LazyLoad.css(appendRnd(path), function () { + if (!scope) { + scope = $rootScope; + } + asset.state = "loaded"; + angularHelper.safeApply(scope, function () { + asset.deferred.resolve(true); + }); + }); + } else if (asset.state === "loaded") { + asset.deferred.resolve(true); + } + return asset.deferred.promise; + }, + + /** + * @ngdoc method + * @name umbraco.services.assetsService#loadJs + * @methodOf umbraco.services.assetsService + * + * @description + * Injects a file as a javascript into the document + * + * @param {String} path path to the js file to load + * @param {Scope} scope optional scope to pass into the loader + * @param {Object} keyvalue collection of attributes to pass to the script element + * @param {Number} timeout in milliseconds + * @returns {Promise} Promise object which resolves when the file has loaded + */ + loadJs: function (path, scope, attributes, timeout) { + + path = convertVirtualPath(path); + + var asset = this._getAssetPromise(path); // $q.defer(); + var t = timeout || 5000; + var a = attributes || undefined; + + if (asset.state === "new") { + asset.state = "loading"; + + LazyLoad.js(appendRnd(path), function () { + if (!scope) { + scope = $rootScope; + } + asset.state = "loaded"; + angularHelper.safeApply(scope, function () { + asset.deferred.resolve(true); + }); }); - function assetLoaded(asset) { - asset.state = "loaded"; - if (!scope) { - scope = $rootScope; - } - angularHelper.safeApply(scope, - function () { - asset.deferred.resolve(true); - }); + } else if (asset.state === "loaded") { + asset.deferred.resolve(true); } - if (cssAssets.length > 0) { - var cssPaths = _.map(cssAssets, function (asset) { return appendRnd(asset.path) }); - LazyLoad.css(cssPaths, function () { _.each(cssAssets, assetLoaded); }); - } - - if (jsAssets.length > 0) { - var jsPaths = _.map(jsAssets, function (asset) { return appendRnd(asset.path) }); - LazyLoad.js(jsPaths, function () { _.each(jsAssets, assetLoaded); }); + return asset.deferred.promise; + }, + + /** + * @ngdoc method + * @name umbraco.services.assetsService#load + * @methodOf umbraco.services.assetsService + * + * @description + * Injects a collection of css and js files + * + * + * @param {Array} pathArray string array of paths to the files to load + * @param {Scope} scope optional scope to pass into the loader + * @returns {Promise} Promise object which resolves when all the files has loaded + */ + load: function (pathArray, scope) { + var promise; + + if (!angular.isArray(pathArray)) { + throw "pathArray must be an array"; } - - return promise; - } - }; - - return service; - }); + + // Check to see if there's anything to load, resolve promise if not + var nonEmpty = _.reject(pathArray, function (item) { + return item === undefined || item === ""; + }); + + if (nonEmpty.length === 0) { + var deferred = $q.defer(); + promise = deferred.promise; + deferred.resolve(true); + return promise; + } + + //compile a list of promises + //blocking + var promises = []; + var assets = []; + _.each(nonEmpty, function (path) { + path = convertVirtualPath(path); + var asset = service._getAssetPromise(path); + //if not previously loaded, add to list of promises + if (asset.state !== "loaded") { + if (asset.state === "new") { + asset.state = "loading"; + assets.push(asset); + } + + //we need to always push to the promises collection to monitor correct execution + promises.push(asset.deferred.promise); + } + }); + + //gives a central monitoring of all assets to load + promise = $q.all(promises); + + // Split into css and js asset arrays, and use LazyLoad on each array + var cssAssets = _.filter(assets, + function (asset) { + return asset.path.match(/(\.css$|\.css\?)/ig); + }); + var jsAssets = _.filter(assets, + function (asset) { + return asset.path.match(/(\.js$|\.js\?)/ig); + }); + + function assetLoaded(asset) { + asset.state = "loaded"; + if (!scope) { + scope = $rootScope; + } + angularHelper.safeApply(scope, + function () { + asset.deferred.resolve(true); + }); + } + + if (cssAssets.length > 0) { + var cssPaths = _.map(cssAssets, function (asset) { return appendRnd(asset.path) }); + LazyLoad.css(cssPaths, function () { _.each(cssAssets, assetLoaded); }); + } + + if (jsAssets.length > 0) { + var jsPaths = _.map(jsAssets, function (asset) { return appendRnd(asset.path) }); + LazyLoad.js(jsPaths, function () { _.each(jsAssets, assetLoaded); }); + } + + return promise; + } + }; + + return service; + }); 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 4b77d28aee..8c1e42b0b6 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 @@ -1,632 +1,632 @@ - -/** -* @ngdoc service -* @name umbraco.services.contentEditingHelper -* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by -* all editors to share logic and reduce the amount of replicated code among editors. -**/ -function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, navigationService, localizationService, serverValidationManager, dialogService, formHelper, appState) { - - function isValidIdentifier(id){ - //empty id <= 0 - if(angular.isNumber(id) && id > 0){ - return true; - } - - //empty guid - if(id === "00000000-0000-0000-0000-000000000000"){ - return false; - } - - //empty string / alias - if(id === ""){ - return false; - } - - return true; - } - - return { - - /** Used by the content editor and mini content editor to perform saving operations */ + +/** +* @ngdoc service +* @name umbraco.services.contentEditingHelper +* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by +* all editors to share logic and reduce the amount of replicated code among editors. +**/ +function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, navigationService, localizationService, serverValidationManager, dialogService, formHelper, appState) { + + function isValidIdentifier(id){ + //empty id <= 0 + if(angular.isNumber(id) && id > 0){ + return true; + } + + //empty guid + if(id === "00000000-0000-0000-0000-000000000000"){ + return false; + } + + //empty string / alias + if(id === ""){ + return false; + } + + return true; + } + + return { + + /** Used by the content editor and mini content editor to perform saving operations */ //TODO: Make this a more helpful/reusable method for other form operations! we can simplify this form most forms - // = this is already done in the formhelper service - contentEditorPerformSave: function (args) { - if (!angular.isObject(args)) { - throw "args must be an object"; - } - if (!args.scope) { - throw "args.scope is not defined"; - } - if (!args.content) { - throw "args.content is not defined"; - } - if (!args.saveMethod) { - throw "args.saveMethod is not defined"; - } - - var redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; - var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true; - - var self = this; - - //we will use the default one for content if not specified - var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback; - - if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, action: args.action })) { - - args.scope.busy = true; - - return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles()) - .then(function (data) { - - formHelper.resetForm({ scope: args.scope }); - - self.handleSuccessfulSave({ - scope: args.scope, - savedContent: data, - redirectOnSuccess: redirectOnSuccess, - rebindCallback: function() { - rebindCallback.apply(self, [args.content, data]); - } - }); - - args.scope.busy = false; - return $q.resolve(data); - - }, function (err) { - self.handleSaveError({ - redirectOnFailure: redirectOnFailure, - err: err, - rebindCallback: function() { - rebindCallback.apply(self, [args.content, err.data]); - } - }); - - args.scope.busy = false; - return $q.reject(err); - }); - } - else { - return $q.reject(); - } - - }, - - /** Used by the content editor and media editor to add an info tab to the tabs array (normally known as the properties tab) */ - addInfoTab: function (tabs) { - - var infoTab = { - "alias": "_umb_infoTab", - "id": -1, - "label": "Info", - "properties": [] - }; - - // first check if tab is already added - var foundInfoTab = false; - - angular.forEach(tabs, function (tab) { - if (tab.id === infoTab.id && tab.alias === infoTab.alias) { - foundInfoTab = true; - } - }); - - // add info tab if is is not found - if (!foundInfoTab) { - localizationService.localize("general_info").then(function (value) { - infoTab.label = value; - tabs.push(infoTab); - }); - } - - }, - - /** Returns the action button definitions based on what permissions the user has. - The content.allowedActions parameter contains a list of chars, each represents a button by permission so - here we'll build the buttons according to the chars of the user. */ - configureContentEditorButtons: function (args) { - - if (!angular.isObject(args)) { - throw "args must be an object"; - } - if (!args.content) { - throw "args.content is not defined"; - } - if (!args.methods) { - throw "args.methods is not defined"; - } - if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.save || !args.methods.unPublish) { - throw "args.methods does not contain all required defined methods"; - } - - var buttons = { - defaultButton: null, - subButtons: [] - }; - - function createButtonDefinition(ch) { - switch (ch) { - case "U": - //publish action - return { - letter: ch, - labelKey: args.content.variants && args.content.variants.length > 1 ? "buttons_saveAndPublishMany" : "buttons_saveAndPublish", - handler: args.methods.saveAndPublish, - hotKey: "ctrl+p", - hotKeyWhenHidden: true, - alias: "saveAndPublish" - }; - case "H": - //send to publish - return { - letter: ch, - labelKey: "buttons_saveToPublish", - handler: args.methods.sendToPublish, - hotKey: "ctrl+p", - hotKeyWhenHidden: true, - alias: "sendToPublish" - }; - case "A": - //save - return { - letter: ch, - labelKey: "buttons_save", - handler: args.methods.save, - hotKey: "ctrl+s", - hotKeyWhenHidden: true, - alias: "save" - }; - case "Z": - //unpublish - return { - letter: ch, - labelKey: "content_unPublish", - handler: args.methods.unPublish, - hotKey: "ctrl+u", - hotKeyWhenHidden: true, - alias: "unpublish" - }; - default: - return null; - } - } - - //reset - buttons.subButtons = []; - - //This is the ideal button order but depends on circumstance, we'll use this array to create the button list - // Publish, SendToPublish, Save - 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. - //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"); - } - } - - //Now we need to make the drop down button list, this is also slightly tricky because: - //We cannot have any buttons if there's no default button above. - //We cannot have the unpublish button (Z) when there's no publish permission. - //We cannot have the unpublish button (Z) when the item is not published. - if (buttons.defaultButton) { - - //get the last index of the button order - var lastIndex = _.indexOf(buttonOrder, buttons.defaultButton.letter); - //add the remaining - for (var i = lastIndex + 1; i < buttonOrder.length; i++) { - if (_.contains(args.content.allowedActions, buttonOrder[i])) { - buttons.subButtons.push(createButtonDefinition(buttonOrder[i])); - } - } - - - // if we are not creating, then we should add unpublish too, - // so long as it's already published and if the user has access to publish - // and the user has access to unpublish (may have been removed via Event) - if (!args.create) { - if (args.content.publishDate && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) { - buttons.subButtons.push(createButtonDefinition("Z")); - } - } - } - - // If we have a scheduled publish or unpublish date change the default button to - // "save" and update the label to "save and schedule - if(args.content.releaseDate || args.content.removeDate) { - - // if save button is alread the default don't change it just update the label - if (buttons.defaultButton && buttons.defaultButton.letter === "A") { - buttons.defaultButton.labelKey = "buttons_saveAndSchedule"; - return; - } - - if(buttons.defaultButton && buttons.subButtons && buttons.subButtons.length > 0) { - // save a copy of the default so we can push it to the sub buttons later - var defaultButtonCopy = angular.copy(buttons.defaultButton); - var newSubButtons = []; - - // if save button is not the default button - find it and make it the default - angular.forEach(buttons.subButtons, function (subButton) { - - if (subButton.letter === "A") { - buttons.defaultButton = subButton; - buttons.defaultButton.labelKey = "buttons_saveAndSchedule"; - } else { - newSubButtons.push(subButton); - } - - }); - - // push old default button into subbuttons - newSubButtons.push(defaultButtonCopy); - buttons.subButtons = newSubButtons; - } - - } - - return buttons; - }, - - /** - * @ngdoc method - * @name umbraco.services.contentEditingHelper#getAllProps - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * Returns all propertes contained for the content item (since the normal model has properties contained inside of tabs) - */ - getAllProps: function (content) { - var allProps = []; - - for (var i = 0; i < content.tabs.length; i++) { - for (var p = 0; p < content.tabs[i].properties.length; p++) { - allProps.push(content.tabs[i].properties[p]); - } - } - - return allProps; - }, - - - /** - * @ngdoc method - * @name umbraco.services.contentEditingHelper#configureButtons - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * Returns a letter array for buttons, with the primary one first based on content model, permissions and editor state - */ - getAllowedActions : function(content, creating){ - - //This is the ideal button order but depends on circumstance, we'll use this array to create the button list - // Publish, SendToPublish, Save - var actionOrder = ["U", "H", "A"]; - var defaultActions; - var actions = []; - - //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. - if (!creating || _.contains(content.allowedActions, "C")) { - for (var b in actionOrder) { - if (_.contains(content.allowedActions, actionOrder[b])) { - defaultAction = actionOrder[b]; - break; - } - } - } - - actions.push(defaultAction); - - //Now we need to make the drop down button list, this is also slightly tricky because: - //We cannot have any buttons if there's no default button above. - //We cannot have the unpublish button (Z) when there's no publish permission. - //We cannot have the unpublish button (Z) when the item is not published. - if (defaultAction) { - //get the last index of the button order - var lastIndex = _.indexOf(actionOrder, defaultAction); - - //add the remaining - for (var i = lastIndex + 1; i < actionOrder.length; i++) { - if (_.contains(content.allowedActions, actionOrder[i])) { - actions.push(actionOrder[i]); - } - } - - //if we are not creating, then we should add unpublish too, - // so long as it's already published and if the user has access to publish - if (!creating) { - if (content.publishDate && _.contains(content.allowedActions,"U")) { - actions.push("Z"); - } - } - } - - return actions; - }, - - /** - * @ngdoc method - * @name umbraco.services.contentEditingHelper#getButtonFromAction - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * Returns a button object to render a button for the tabbed editor - * currently only returns built in system buttons for content and media actions - * returns label, alias, action char and hot-key - */ - getButtonFromAction : function(ch){ - switch (ch) { - case "U": - return { - letter: ch, - labelKey: "buttons_saveAndPublish", - handler: "saveAndPublish", - hotKey: "ctrl+p" - }; - case "H": - //send to publish - return { - letter: ch, - labelKey: "buttons_saveToPublish", - handler: "sendToPublish", - hotKey: "ctrl+p" - }; - case "A": - return { - letter: ch, - labelKey: "buttons_save", - handler: "save", - hotKey: "ctrl+s" - }; - case "Z": - return { - letter: ch, - labelKey: "content_unPublish", - handler: "unPublish" - }; - - default: - return null; - } - - }, - /** - * @ngdoc method - * @name umbraco.services.contentEditingHelper#reBindChangedProperties - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * re-binds all changed property values to the origContent object from the savedContent object and returns an array of changed properties. - */ - reBindChangedProperties: function (origContent, savedContent) { - - var changed = []; - - //get a list of properties since they are contained in tabs - var allOrigProps = this.getAllProps(origContent); - var allNewProps = this.getAllProps(savedContent); - - function getNewProp(alias) { - return _.find(allNewProps, function (item) { - return item.alias === alias; - }); - } - - //a method to ignore built-in prop changes - var shouldIgnore = function(propName) { - return _.some([ - "tabs", - "notifications", - "ModelState", - "tabs", - "properties", - "apps", - "createDateFormatted", - "releaseDateYear", - "releaseDateMonth", - "releaseDateDayNumber", - "releaseDateDay", - "releaseDateTime", - "removeDateYear", - "removeDateMonth", - "removeDateDayNumber", - "removeDateDay", - "removeDateTime", - ], function (i) { - return i === propName; - }); - }; - //check for changed built-in properties of the content - for (var o in origContent) { - - //ignore the ones listed in the array - if (shouldIgnore(o)) { - continue; - } - - if (!_.isEqual(origContent[o], savedContent[o])) { - origContent[o] = savedContent[o]; - } - } - - //check for changed properties of the content - for (var p in allOrigProps) { - var newProp = getNewProp(allOrigProps[p].alias); - if (newProp && !_.isEqual(allOrigProps[p].value, newProp.value)) { - - //they have changed so set the origContent prop to the new one - var origVal = allOrigProps[p].value; - allOrigProps[p].value = newProp.value; - - //instead of having a property editor $watch their expression to check if it has - // been updated, instead we'll check for the existence of a special method on their model - // and just call it. - if (angular.isFunction(allOrigProps[p].onValueChanged)) { - //send the newVal + oldVal - allOrigProps[p].onValueChanged(allOrigProps[p].value, origVal); - } - - changed.push(allOrigProps[p]); - } - } - - return changed; - }, - - /** - * @ngdoc function - * @name umbraco.services.contentEditingHelper#handleSaveError - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * A function to handle what happens when we have validation issues from the server side - * - */ - handleSaveError: function (args) { - - if (!args.err) { - throw "args.err cannot be null"; - } - if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) { - throw "args.redirectOnFailure must be set to true or false"; - } - - //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. - //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). - //Or, some strange server error - if (args.err.status === 400) { - //now we need to look through all the validation errors - if (args.err.data && (args.err.data.ModelState)) { - - //wire up the server validation errs - formHelper.handleServerValidation(args.err.data.ModelState); - - if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. Then we need - // to re-bind any server validation errors after the digest takes place. - - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { - args.rebindCallback(); - } - - serverValidationManager.executeAndClearAllSubscriptions(); - } - - //indicates we've handled the server result - return true; - } - } - return false; - }, - - /** - * @ngdoc function - * @name umbraco.services.contentEditingHelper#handleSuccessfulSave - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * A function to handle when saving a content item is successful. This will rebind the values of the model that have changed - * ensure the notifications are displayed and that the appropriate events are fired. This will also check if we need to redirect - * when we're creating new content. - */ - handleSuccessfulSave: function (args) { - - if (!args) { - throw "args cannot be null"; - } - if (!args.savedContent) { - throw "args.savedContent cannot be null"; - } - - // the default behaviour is to redirect on success. This adds option to prevent when false - args.redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; - - if (!args.redirectOnSuccess || !this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) { - - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. - //call the callback - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { - args.rebindCallback(); - } - } - }, - - /** - * @ngdoc function - * @name umbraco.services.contentEditingHelper#redirectToCreatedContent - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * Changes the location to be editing the newly created content after create was successful. - * We need to decide if we need to redirect to edito mode or if we will remain in create mode. - * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID - */ - redirectToCreatedContent: function (id, modelState) { - - //only continue if we are currently in create mode and not in infinite mode and if there is no 'Name' modelstate errors - // since we need at least a name to create content. - if ($routeParams.create && (isValidIdentifier(id) && (!modelState || !modelState["Name"]))) { - - //need to change the location to not be in 'create' mode. Currently the route will be something like: - // /belle/#/content/edit/1234?doctype=newsArticle&create=true - // but we need to remove everything after the query so that it is just: - // /belle/#/content/edit/9876 (where 9876 is the new id) - - //clear the query strings - navigationService.clearSearch(); - - //change to new path - $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); - //don't add a browser history for this - $location.replace(); - return true; - } - return false; - }, - - /** - * @ngdoc function - * @name umbraco.services.contentEditingHelper#redirectToRenamedContent - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * For some editors like scripts or entites that have names as ids, these names can change and we need to redirect - * to their new paths, this is helper method to do that. - */ - redirectToRenamedContent: function (id) { - //clear the query strings - navigationService.clearSearch(); - //change to new path - $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); - //don't add a browser history for this - $location.replace(); - return true; - } - }; -} -angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper); + // = this is already done in the formhelper service + contentEditorPerformSave: function (args) { + if (!angular.isObject(args)) { + throw "args must be an object"; + } + if (!args.scope) { + throw "args.scope is not defined"; + } + if (!args.content) { + throw "args.content is not defined"; + } + if (!args.saveMethod) { + throw "args.saveMethod is not defined"; + } + + var redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; + var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true; + + var self = this; + + //we will use the default one for content if not specified + var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback; + + if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, action: args.action })) { + + args.scope.busy = true; + + return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles()) + .then(function (data) { + + formHelper.resetForm({ scope: args.scope }); + + self.handleSuccessfulSave({ + scope: args.scope, + savedContent: data, + redirectOnSuccess: redirectOnSuccess, + rebindCallback: function() { + rebindCallback.apply(self, [args.content, data]); + } + }); + + args.scope.busy = false; + return $q.resolve(data); + + }, function (err) { + self.handleSaveError({ + redirectOnFailure: redirectOnFailure, + err: err, + rebindCallback: function() { + rebindCallback.apply(self, [args.content, err.data]); + } + }); + + args.scope.busy = false; + return $q.reject(err); + }); + } + else { + return $q.reject(); + } + + }, + + /** Used by the content editor and media editor to add an info tab to the tabs array (normally known as the properties tab) */ + addInfoTab: function (tabs) { + + var infoTab = { + "alias": "_umb_infoTab", + "id": -1, + "label": "Info", + "properties": [] + }; + + // first check if tab is already added + var foundInfoTab = false; + + angular.forEach(tabs, function (tab) { + if (tab.id === infoTab.id && tab.alias === infoTab.alias) { + foundInfoTab = true; + } + }); + + // add info tab if is is not found + if (!foundInfoTab) { + localizationService.localize("general_info").then(function (value) { + infoTab.label = value; + tabs.push(infoTab); + }); + } + + }, + + /** Returns the action button definitions based on what permissions the user has. + The content.allowedActions parameter contains a list of chars, each represents a button by permission so + here we'll build the buttons according to the chars of the user. */ + configureContentEditorButtons: function (args) { + + if (!angular.isObject(args)) { + throw "args must be an object"; + } + if (!args.content) { + throw "args.content is not defined"; + } + if (!args.methods) { + throw "args.methods is not defined"; + } + if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.save || !args.methods.unPublish) { + throw "args.methods does not contain all required defined methods"; + } + + var buttons = { + defaultButton: null, + subButtons: [] + }; + + function createButtonDefinition(ch) { + switch (ch) { + case "U": + //publish action + return { + letter: ch, + labelKey: args.content.variants && args.content.variants.length > 1 ? "buttons_saveAndPublishMany" : "buttons_saveAndPublish", + handler: args.methods.saveAndPublish, + hotKey: "ctrl+p", + hotKeyWhenHidden: true, + alias: "saveAndPublish" + }; + case "H": + //send to publish + return { + letter: ch, + labelKey: "buttons_saveToPublish", + handler: args.methods.sendToPublish, + hotKey: "ctrl+p", + hotKeyWhenHidden: true, + alias: "sendToPublish" + }; + case "A": + //save + return { + letter: ch, + labelKey: "buttons_save", + handler: args.methods.save, + hotKey: "ctrl+s", + hotKeyWhenHidden: true, + alias: "save" + }; + case "Z": + //unpublish + return { + letter: ch, + labelKey: "content_unPublish", + handler: args.methods.unPublish, + hotKey: "ctrl+u", + hotKeyWhenHidden: true, + alias: "unpublish" + }; + default: + return null; + } + } + + //reset + buttons.subButtons = []; + + //This is the ideal button order but depends on circumstance, we'll use this array to create the button list + // Publish, SendToPublish, Save + 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. + //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"); + } + } + + //Now we need to make the drop down button list, this is also slightly tricky because: + //We cannot have any buttons if there's no default button above. + //We cannot have the unpublish button (Z) when there's no publish permission. + //We cannot have the unpublish button (Z) when the item is not published. + if (buttons.defaultButton) { + + //get the last index of the button order + var lastIndex = _.indexOf(buttonOrder, buttons.defaultButton.letter); + //add the remaining + for (var i = lastIndex + 1; i < buttonOrder.length; i++) { + if (_.contains(args.content.allowedActions, buttonOrder[i])) { + buttons.subButtons.push(createButtonDefinition(buttonOrder[i])); + } + } + + + // if we are not creating, then we should add unpublish too, + // so long as it's already published and if the user has access to publish + // and the user has access to unpublish (may have been removed via Event) + if (!args.create) { + if (args.content.publishDate && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) { + buttons.subButtons.push(createButtonDefinition("Z")); + } + } + } + + // If we have a scheduled publish or unpublish date change the default button to + // "save" and update the label to "save and schedule + if(args.content.releaseDate || args.content.removeDate) { + + // if save button is alread the default don't change it just update the label + if (buttons.defaultButton && buttons.defaultButton.letter === "A") { + buttons.defaultButton.labelKey = "buttons_saveAndSchedule"; + return; + } + + if(buttons.defaultButton && buttons.subButtons && buttons.subButtons.length > 0) { + // save a copy of the default so we can push it to the sub buttons later + var defaultButtonCopy = angular.copy(buttons.defaultButton); + var newSubButtons = []; + + // if save button is not the default button - find it and make it the default + angular.forEach(buttons.subButtons, function (subButton) { + + if (subButton.letter === "A") { + buttons.defaultButton = subButton; + buttons.defaultButton.labelKey = "buttons_saveAndSchedule"; + } else { + newSubButtons.push(subButton); + } + + }); + + // push old default button into subbuttons + newSubButtons.push(defaultButtonCopy); + buttons.subButtons = newSubButtons; + } + + } + + return buttons; + }, + + /** + * @ngdoc method + * @name umbraco.services.contentEditingHelper#getAllProps + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * Returns all propertes contained for the content item (since the normal model has properties contained inside of tabs) + */ + getAllProps: function (content) { + var allProps = []; + + for (var i = 0; i < content.tabs.length; i++) { + for (var p = 0; p < content.tabs[i].properties.length; p++) { + allProps.push(content.tabs[i].properties[p]); + } + } + + return allProps; + }, + + + /** + * @ngdoc method + * @name umbraco.services.contentEditingHelper#configureButtons + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * Returns a letter array for buttons, with the primary one first based on content model, permissions and editor state + */ + getAllowedActions : function(content, creating){ + + //This is the ideal button order but depends on circumstance, we'll use this array to create the button list + // Publish, SendToPublish, Save + var actionOrder = ["U", "H", "A"]; + var defaultActions; + var actions = []; + + //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. + if (!creating || _.contains(content.allowedActions, "C")) { + for (var b in actionOrder) { + if (_.contains(content.allowedActions, actionOrder[b])) { + defaultAction = actionOrder[b]; + break; + } + } + } + + actions.push(defaultAction); + + //Now we need to make the drop down button list, this is also slightly tricky because: + //We cannot have any buttons if there's no default button above. + //We cannot have the unpublish button (Z) when there's no publish permission. + //We cannot have the unpublish button (Z) when the item is not published. + if (defaultAction) { + //get the last index of the button order + var lastIndex = _.indexOf(actionOrder, defaultAction); + + //add the remaining + for (var i = lastIndex + 1; i < actionOrder.length; i++) { + if (_.contains(content.allowedActions, actionOrder[i])) { + actions.push(actionOrder[i]); + } + } + + //if we are not creating, then we should add unpublish too, + // so long as it's already published and if the user has access to publish + if (!creating) { + if (content.publishDate && _.contains(content.allowedActions,"U")) { + actions.push("Z"); + } + } + } + + return actions; + }, + + /** + * @ngdoc method + * @name umbraco.services.contentEditingHelper#getButtonFromAction + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * Returns a button object to render a button for the tabbed editor + * currently only returns built in system buttons for content and media actions + * returns label, alias, action char and hot-key + */ + getButtonFromAction : function(ch){ + switch (ch) { + case "U": + return { + letter: ch, + labelKey: "buttons_saveAndPublish", + handler: "saveAndPublish", + hotKey: "ctrl+p" + }; + case "H": + //send to publish + return { + letter: ch, + labelKey: "buttons_saveToPublish", + handler: "sendToPublish", + hotKey: "ctrl+p" + }; + case "A": + return { + letter: ch, + labelKey: "buttons_save", + handler: "save", + hotKey: "ctrl+s" + }; + case "Z": + return { + letter: ch, + labelKey: "content_unPublish", + handler: "unPublish" + }; + + default: + return null; + } + + }, + /** + * @ngdoc method + * @name umbraco.services.contentEditingHelper#reBindChangedProperties + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * re-binds all changed property values to the origContent object from the savedContent object and returns an array of changed properties. + */ + reBindChangedProperties: function (origContent, savedContent) { + + var changed = []; + + //get a list of properties since they are contained in tabs + var allOrigProps = this.getAllProps(origContent); + var allNewProps = this.getAllProps(savedContent); + + function getNewProp(alias) { + return _.find(allNewProps, function (item) { + return item.alias === alias; + }); + } + + //a method to ignore built-in prop changes + var shouldIgnore = function(propName) { + return _.some([ + "tabs", + "notifications", + "ModelState", + "tabs", + "properties", + "apps", + "createDateFormatted", + "releaseDateYear", + "releaseDateMonth", + "releaseDateDayNumber", + "releaseDateDay", + "releaseDateTime", + "removeDateYear", + "removeDateMonth", + "removeDateDayNumber", + "removeDateDay", + "removeDateTime", + ], function (i) { + return i === propName; + }); + }; + //check for changed built-in properties of the content + for (var o in origContent) { + + //ignore the ones listed in the array + if (shouldIgnore(o)) { + continue; + } + + if (!_.isEqual(origContent[o], savedContent[o])) { + origContent[o] = savedContent[o]; + } + } + + //check for changed properties of the content + for (var p in allOrigProps) { + var newProp = getNewProp(allOrigProps[p].alias); + if (newProp && !_.isEqual(allOrigProps[p].value, newProp.value)) { + + //they have changed so set the origContent prop to the new one + var origVal = allOrigProps[p].value; + allOrigProps[p].value = newProp.value; + + //instead of having a property editor $watch their expression to check if it has + // been updated, instead we'll check for the existence of a special method on their model + // and just call it. + if (angular.isFunction(allOrigProps[p].onValueChanged)) { + //send the newVal + oldVal + allOrigProps[p].onValueChanged(allOrigProps[p].value, origVal); + } + + changed.push(allOrigProps[p]); + } + } + + return changed; + }, + + /** + * @ngdoc function + * @name umbraco.services.contentEditingHelper#handleSaveError + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * A function to handle what happens when we have validation issues from the server side + * + */ + handleSaveError: function (args) { + + if (!args.err) { + throw "args.err cannot be null"; + } + if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) { + throw "args.redirectOnFailure must be set to true or false"; + } + + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. + //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). + //Or, some strange server error + if (args.err.status === 400) { + //now we need to look through all the validation errors + if (args.err.data && (args.err.data.ModelState)) { + + //wire up the server validation errs + formHelper.handleServerValidation(args.err.data.ModelState); + + if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { + //we are not redirecting because this is not new content, it is existing content. In this case + // we need to detect what properties have changed and re-bind them with the server data. Then we need + // to re-bind any server validation errors after the digest takes place. + + if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + args.rebindCallback(); + } + + serverValidationManager.executeAndClearAllSubscriptions(); + } + + //indicates we've handled the server result + return true; + } + } + return false; + }, + + /** + * @ngdoc function + * @name umbraco.services.contentEditingHelper#handleSuccessfulSave + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * A function to handle when saving a content item is successful. This will rebind the values of the model that have changed + * ensure the notifications are displayed and that the appropriate events are fired. This will also check if we need to redirect + * when we're creating new content. + */ + handleSuccessfulSave: function (args) { + + if (!args) { + throw "args cannot be null"; + } + if (!args.savedContent) { + throw "args.savedContent cannot be null"; + } + + // the default behaviour is to redirect on success. This adds option to prevent when false + args.redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; + + if (!args.redirectOnSuccess || !this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) { + + //we are not redirecting because this is not new content, it is existing content. In this case + // we need to detect what properties have changed and re-bind them with the server data. + //call the callback + if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + args.rebindCallback(); + } + } + }, + + /** + * @ngdoc function + * @name umbraco.services.contentEditingHelper#redirectToCreatedContent + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * Changes the location to be editing the newly created content after create was successful. + * We need to decide if we need to redirect to edito mode or if we will remain in create mode. + * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID + */ + redirectToCreatedContent: function (id, modelState) { + + //only continue if we are currently in create mode and not in infinite mode and if there is no 'Name' modelstate errors + // since we need at least a name to create content. + if ($routeParams.create && (isValidIdentifier(id) && (!modelState || !modelState["Name"]))) { + + //need to change the location to not be in 'create' mode. Currently the route will be something like: + // /belle/#/content/edit/1234?doctype=newsArticle&create=true + // but we need to remove everything after the query so that it is just: + // /belle/#/content/edit/9876 (where 9876 is the new id) + + //clear the query strings + navigationService.clearSearch(); + + //change to new path + $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); + //don't add a browser history for this + $location.replace(); + return true; + } + return false; + }, + + /** + * @ngdoc function + * @name umbraco.services.contentEditingHelper#redirectToRenamedContent + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * For some editors like scripts or entites that have names as ids, these names can change and we need to redirect + * to their new paths, this is helper method to do that. + */ + redirectToRenamedContent: function (id) { + //clear the query strings + navigationService.clearSearch(); + //change to new path + $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); + //don't add a browser history for this + $location.replace(); + return true; + } + }; +} +angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js b/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js index 6364cd8054..ccf198bcbf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js @@ -1,539 +1,539 @@ -/** - * @ngdoc service - * @name umbraco.services.dialogService - * - * @requires $rootScope - * @requires $compile - * @requires $http - * @requires $log - * @requires $q - * @requires $templateCache - * - * @description - * Application-wide service for handling modals, overlays and dialogs - * By default it injects the passed template url into a div to body of the document - * And renders it, but does also support rendering items in an iframe, incase - * serverside processing is needed, or its a non-angular page - * - * ##usage - * To use, simply inject the dialogService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *    var dialog = dialogService.open({template: 'path/to/page.html', show: true, callback: done});
    - *    functon done(data){
    - *      //The dialog has been submitted
    - *      //data contains whatever the dialog has selected / attached
    - *    }
    - * 
    - */ - -angular.module('umbraco.services') -.factory('dialogService', function ($rootScope, $compile, $http, $timeout, $q, $templateCache, appState, eventsService) { - - var dialogs = []; - - /** Internal method that removes all dialogs */ - function removeAllDialogs(args) { - for (var i = 0; i < dialogs.length; i++) { - var dialog = dialogs[i]; - - //very special flag which means that global events cannot close this dialog - currently only used on the login - // dialog since it's special and cannot be closed without logging in. - if (!dialog.manualClose) { - dialog.close(args); - } - - } - } - - /** Internal method that closes the dialog properly and cleans up resources */ - function closeDialog(dialog) { - - if (dialog.element) { - dialog.element.modal('hide'); - - //this is not entirely enough since the damn webforms scriploader still complains - if (dialog.iframe) { - dialog.element.find("iframe").attr("src", "about:blank"); - } - - dialog.scope.$destroy(); - - //we need to do more than just remove the element, this will not destroy the - // scope in angular 1.1x, in angular 1.2x this is taken care of but if we dont - // take care of this ourselves we have memory leaks. - dialog.element.remove(); - - //remove 'this' dialog from the dialogs array - dialogs = _.reject(dialogs, function (i) { return i === dialog; }); - } - } - - /** Internal method that handles opening all dialogs */ - function openDialog(options) { - var defaults = { - container: $("body"), - animation: "fade", - modalClass: "umb-modal", - width: "100%", - inline: false, - iframe: false, - show: true, - template: "views/common/notfound.html", - callback: undefined, - closeCallback: undefined, - element: undefined, - // It will set this value as a property on the dialog controller's scope as dialogData, - // used to pass in custom data to the dialog controller's $scope. Though this is near identical to - // the dialogOptions property that is also set the the dialog controller's $scope object. - // So there's basically 2 ways of doing the same thing which we're now stuck with and in fact - // dialogData has another specially attached property called .selection which gets used. - dialogData: undefined - }; - - var dialog = angular.extend(defaults, options); - - //NOTE: People should NOT pass in a scope object that is legacy functoinality and causes problems. We will ALWAYS - // destroy the scope when the dialog is closed regardless if it is in use elsewhere which is why it shouldn't be done. - var scope = options.scope || $rootScope.$new(); - - //Modal dom obj and set id to old-dialog-service - used until we get all dialogs moved the the new overlay directive - dialog.element = $('
    '); - var id = "old-dialog-service"; - - if (options.inline) { - dialog.animation = ""; - } - else { - dialog.element.addClass("modal"); - dialog.element.addClass("hide"); - } - - //set the id and add classes - dialog.element - .attr('id', id) - .addClass(dialog.animation) - .addClass(dialog.modalClass); - - //push the modal into the global modal collection - //we halt the .push because a link click will trigger a closeAll right away - $timeout(function () { - dialogs.push(dialog); - }, 500); - - - dialog.close = function (data) { - if (dialog.closeCallback) { - dialog.closeCallback(data); - } - - closeDialog(dialog); - }; - - //if iframe is enabled, inject that instead of a template - if (dialog.iframe) { - var html = $(""); - dialog.element.html(html); - - //append to body or whatever element is passed in as options.containerElement - dialog.container.append(dialog.element); - - // Compile modal content - $timeout(function () { - $compile(dialog.element)(dialog.scope); - }); - - dialog.element.css("width", dialog.width); - - //Autoshow - if (dialog.show) { - dialog.element.modal('show'); - } - - dialog.scope = scope; - return dialog; - } - else { - - //We need to load the template with an httpget and once it's loaded we'll compile and assign the result to the container - // object. However since the result could be a promise or just data we need to use a $q.when. We still need to return the - // $modal object so we'll actually return the modal object synchronously without waiting for the promise. Otherwise this openDialog - // method will always need to return a promise which gets nasty because of promises in promises plus the result just needs a reference - // to the $modal object which will not change (only it's contents will change). - $q.when($templateCache.get(dialog.template) || $http.get(dialog.template, { cache: true }).then(function (res) { return res.data; })) - .then(function onSuccess(template) { - - // Build modal object - dialog.element.html(template); - - //append to body or other container element - dialog.container.append(dialog.element); - - // Compile modal content - $timeout(function () { - $compile(dialog.element)(scope); - }); - - scope.dialogOptions = dialog; - - //Scope to handle data from the modal form - scope.dialogData = dialog.dialogData ? dialog.dialogData : {}; - scope.dialogData.selection = []; - - // Provide scope display functions - //this passes the modal to the current scope - scope.$modal = function (name) { - dialog.element.modal(name); - }; - - scope.swipeHide = function (e) { - - if (appState.getGlobalState("touchDevice")) { - var selection = window.getSelection(); - if (selection.type !== "Range") { - scope.hide(); - } - } - }; - - //NOTE: Same as 'close' without the callbacks - scope.hide = function () { - closeDialog(dialog); - }; - - //basic events for submitting and closing - scope.submit = function (data) { - if (dialog.callback) { - dialog.callback(data); - } - - closeDialog(dialog); - }; - - scope.close = function (data) { - dialog.close(data); - }; - - //NOTE: This can ONLY ever be used to show the dialog if dialog.show is false (autoshow). - // You CANNOT call show() after you call hide(). hide = close, they are the same thing and once - // a dialog is closed it's resources are disposed of. - scope.show = function () { - if (dialog.manualClose === true) { - //show and configure that the keyboard events are not enabled on this modal - dialog.element.modal({ keyboard: false }); - } - else { - //just show normally - dialog.element.modal('show'); - } - - }; - - scope.select = function (item) { - var i = scope.dialogData.selection.indexOf(item); - if (i < 0) { - scope.dialogData.selection.push(item); - } else { - scope.dialogData.selection.splice(i, 1); - } - }; - - //NOTE: Same as 'close' without the callbacks - scope.dismiss = scope.hide; - - // Emit modal events - angular.forEach(['show', 'shown', 'hide', 'hidden'], function (name) { - dialog.element.on(name, function (ev) { - scope.$emit('modal-' + name, ev); - }); - }); - - // Support autofocus attribute - dialog.element.on('shown', function (event) { - $('input[autofocus]', dialog.element).first().trigger('focus'); - }); - - dialog.scope = scope; - - //Autoshow - if (dialog.show) { - scope.show(); - } - - }); - - //Return the modal object outside of the promise! - return dialog; - } - } - - /** Handles the closeDialogs event */ - eventsService.on("app.closeDialogs", function (evt, args) { - removeAllDialogs(args); - }); - - return { - /** - * @ngdoc method - * @name umbraco.services.dialogService#open - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a modal rendering a given template url. - * - * @param {Object} options rendering options - * @param {DomElement} options.container the DOM element to inject the modal into, by default set to body - * @param {Function} options.callback function called when the modal is submitted - * @param {String} options.template the url of the template - * @param {String} options.animation animation csss class, by default set to "fade" - * @param {String} options.modalClass modal css class, by default "umb-modal" - * @param {Bool} options.show show the modal instantly - * @param {Bool} options.iframe load template in an iframe, only needed for serverside templates - * @param {Int} options.width set a width on the modal, only needed for iframes - * @param {Bool} options.inline strips the modal from any animation and wrappers, used when you want to inject a dialog into an existing container - * @returns {Object} modal object - */ - open: function (options) { - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#close - * @methodOf umbraco.services.dialogService - * - * @description - * Closes a specific dialog - * @param {Object} dialog the dialog object to close - * @param {Object} args if specified this object will be sent to any callbacks registered on the dialogs. - */ - close: function (dialog, args) { - if (dialog) { - dialog.close(args); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#closeAll - * @methodOf umbraco.services.dialogService - * - * @description - * Closes all dialogs - * @param {Object} args if specified this object will be sent to any callbacks registered on the dialogs. - */ - closeAll: function (args) { - removeAllDialogs(args); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#mediaPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a media picker in a modal, the callback returns an array of selected media items - * @param {Object} options mediapicker dialog options object - * @param {Boolean} options.onlyImages Only display files that have an image file-extension - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - mediaPicker: function (options) { - options.template = 'views/common/dialogs/mediaPicker.html'; - options.show = true; - return openDialog(options); - }, - - - /** - * @ngdoc method - * @name umbraco.services.dialogService#contentPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a content picker tree in a modal, the callback returns an array of selected documents - * @param {Object} options content picker dialog options object - * @param {Boolean} options.multiPicker should the picker return one or multiple items - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - contentPicker: function (options) { - - options.treeAlias = "content"; - options.section = "content"; - - return this.treePicker(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#linkPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a link picker tree in a modal, the callback returns a single link - * @param {Object} options content picker dialog options object - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - linkPicker: function (options) { - options.template = 'views/common/dialogs/linkPicker.html'; - options.show = true; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#macroPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a mcaro picker in a modal, the callback returns a object representing the macro and it's parameters - * @param {Object} options macropicker dialog options object - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - macroPicker: function (options) { - options.template = 'views/common/dialogs/insertmacro.html'; - options.show = true; - options.modalClass = "span7 umb-modal"; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#memberPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a member picker in a modal, the callback returns a object representing the selected member - * @param {Object} options member picker dialog options object - * @param {Boolean} options.multiPicker should the tree pick one or multiple members before returning - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - memberPicker: function (options) { - - options.treeAlias = "member"; - options.section = "member"; - - return this.treePicker(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#memberGroupPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a member group picker in a modal, the callback returns a object representing the selected member - * @param {Object} options member group picker dialog options object - * @param {Boolean} options.multiPicker should the tree pick one or multiple members before returning - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - memberGroupPicker: function (options) { - options.template = 'views/common/dialogs/memberGroupPicker.html'; - options.show = true; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#iconPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a icon picker in a modal, the callback returns a object representing the selected icon - * @param {Object} options iconpicker dialog options object - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - iconPicker: function (options) { - options.template = 'views/common/dialogs/iconPicker.html'; - options.show = true; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#treePicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a tree picker in a modal, the callback returns a object representing the selected tree item - * @param {Object} options iconpicker dialog options object - * @param {String} options.section tree section to display - * @param {String} options.treeAlias specific tree to display - * @param {Boolean} options.multiPicker should the tree pick one or multiple items before returning - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - treePicker: function (options) { - options.template = 'views/common/dialogs/treePicker.html'; - options.show = true; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#propertyDialog - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a dialog with a chosen property editor in, a value can be passed to the modal, and this value is returned in the callback - * @param {Object} options mediapicker dialog options object - * @param {Function} options.callback callback function - * @param {String} editor editor to use to edit a given value and return on callback - * @param {Object} value value sent to the property editor - * @returns {Object} modal object - */ - //TODO: Wtf does this do? I don't think anything! - propertyDialog: function (options) { - options.template = 'views/common/dialogs/property.html'; - options.show = true; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#embedDialog - * @methodOf umbraco.services.dialogService - * @description - * Opens a dialog to an embed dialog - */ - embedDialog: function (options) { - options.template = 'views/common/dialogs/rteembed.html'; - options.show = true; - return openDialog(options); - }, - /** - * @ngdoc method - * @name umbraco.services.dialogService#ysodDialog - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a dialog to show a custom YSOD - */ - ysodDialog: function (ysodError) { - - var newScope = $rootScope.$new(); - newScope.error = ysodError; - return openDialog({ - modalClass: "umb-modal wide ysod", - scope: newScope, - //callback: options.callback, - template: 'views/common/dialogs/ysod.html', - show: true - }); - }, - - confirmDialog: function (ysodError) { - - options.template = 'views/common/dialogs/confirm.html'; - options.show = true; - return openDialog(options); - } - }; -}); +/** + * @ngdoc service + * @name umbraco.services.dialogService + * + * @requires $rootScope + * @requires $compile + * @requires $http + * @requires $log + * @requires $q + * @requires $templateCache + * + * @description + * Application-wide service for handling modals, overlays and dialogs + * By default it injects the passed template url into a div to body of the document + * And renders it, but does also support rendering items in an iframe, incase + * serverside processing is needed, or its a non-angular page + * + * ##usage + * To use, simply inject the dialogService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *    var dialog = dialogService.open({template: 'path/to/page.html', show: true, callback: done});
    + *    functon done(data){
    + *      //The dialog has been submitted
    + *      //data contains whatever the dialog has selected / attached
    + *    }
    + * 
    + */ + +angular.module('umbraco.services') +.factory('dialogService', function ($rootScope, $compile, $http, $timeout, $q, $templateCache, appState, eventsService) { + + var dialogs = []; + + /** Internal method that removes all dialogs */ + function removeAllDialogs(args) { + for (var i = 0; i < dialogs.length; i++) { + var dialog = dialogs[i]; + + //very special flag which means that global events cannot close this dialog - currently only used on the login + // dialog since it's special and cannot be closed without logging in. + if (!dialog.manualClose) { + dialog.close(args); + } + + } + } + + /** Internal method that closes the dialog properly and cleans up resources */ + function closeDialog(dialog) { + + if (dialog.element) { + dialog.element.modal('hide'); + + //this is not entirely enough since the damn webforms scriploader still complains + if (dialog.iframe) { + dialog.element.find("iframe").attr("src", "about:blank"); + } + + dialog.scope.$destroy(); + + //we need to do more than just remove the element, this will not destroy the + // scope in angular 1.1x, in angular 1.2x this is taken care of but if we dont + // take care of this ourselves we have memory leaks. + dialog.element.remove(); + + //remove 'this' dialog from the dialogs array + dialogs = _.reject(dialogs, function (i) { return i === dialog; }); + } + } + + /** Internal method that handles opening all dialogs */ + function openDialog(options) { + var defaults = { + container: $("body"), + animation: "fade", + modalClass: "umb-modal", + width: "100%", + inline: false, + iframe: false, + show: true, + template: "views/common/notfound.html", + callback: undefined, + closeCallback: undefined, + element: undefined, + // It will set this value as a property on the dialog controller's scope as dialogData, + // used to pass in custom data to the dialog controller's $scope. Though this is near identical to + // the dialogOptions property that is also set the the dialog controller's $scope object. + // So there's basically 2 ways of doing the same thing which we're now stuck with and in fact + // dialogData has another specially attached property called .selection which gets used. + dialogData: undefined + }; + + var dialog = angular.extend(defaults, options); + + //NOTE: People should NOT pass in a scope object that is legacy functoinality and causes problems. We will ALWAYS + // destroy the scope when the dialog is closed regardless if it is in use elsewhere which is why it shouldn't be done. + var scope = options.scope || $rootScope.$new(); + + //Modal dom obj and set id to old-dialog-service - used until we get all dialogs moved the the new overlay directive + dialog.element = $('
    '); + var id = "old-dialog-service"; + + if (options.inline) { + dialog.animation = ""; + } + else { + dialog.element.addClass("modal"); + dialog.element.addClass("hide"); + } + + //set the id and add classes + dialog.element + .attr('id', id) + .addClass(dialog.animation) + .addClass(dialog.modalClass); + + //push the modal into the global modal collection + //we halt the .push because a link click will trigger a closeAll right away + $timeout(function () { + dialogs.push(dialog); + }, 500); + + + dialog.close = function (data) { + if (dialog.closeCallback) { + dialog.closeCallback(data); + } + + closeDialog(dialog); + }; + + //if iframe is enabled, inject that instead of a template + if (dialog.iframe) { + var html = $(""); + dialog.element.html(html); + + //append to body or whatever element is passed in as options.containerElement + dialog.container.append(dialog.element); + + // Compile modal content + $timeout(function () { + $compile(dialog.element)(dialog.scope); + }); + + dialog.element.css("width", dialog.width); + + //Autoshow + if (dialog.show) { + dialog.element.modal('show'); + } + + dialog.scope = scope; + return dialog; + } + else { + + //We need to load the template with an httpget and once it's loaded we'll compile and assign the result to the container + // object. However since the result could be a promise or just data we need to use a $q.when. We still need to return the + // $modal object so we'll actually return the modal object synchronously without waiting for the promise. Otherwise this openDialog + // method will always need to return a promise which gets nasty because of promises in promises plus the result just needs a reference + // to the $modal object which will not change (only it's contents will change). + $q.when($templateCache.get(dialog.template) || $http.get(dialog.template, { cache: true }).then(function (res) { return res.data; })) + .then(function onSuccess(template) { + + // Build modal object + dialog.element.html(template); + + //append to body or other container element + dialog.container.append(dialog.element); + + // Compile modal content + $timeout(function () { + $compile(dialog.element)(scope); + }); + + scope.dialogOptions = dialog; + + //Scope to handle data from the modal form + scope.dialogData = dialog.dialogData ? dialog.dialogData : {}; + scope.dialogData.selection = []; + + // Provide scope display functions + //this passes the modal to the current scope + scope.$modal = function (name) { + dialog.element.modal(name); + }; + + scope.swipeHide = function (e) { + + if (appState.getGlobalState("touchDevice")) { + var selection = window.getSelection(); + if (selection.type !== "Range") { + scope.hide(); + } + } + }; + + //NOTE: Same as 'close' without the callbacks + scope.hide = function () { + closeDialog(dialog); + }; + + //basic events for submitting and closing + scope.submit = function (data) { + if (dialog.callback) { + dialog.callback(data); + } + + closeDialog(dialog); + }; + + scope.close = function (data) { + dialog.close(data); + }; + + //NOTE: This can ONLY ever be used to show the dialog if dialog.show is false (autoshow). + // You CANNOT call show() after you call hide(). hide = close, they are the same thing and once + // a dialog is closed it's resources are disposed of. + scope.show = function () { + if (dialog.manualClose === true) { + //show and configure that the keyboard events are not enabled on this modal + dialog.element.modal({ keyboard: false }); + } + else { + //just show normally + dialog.element.modal('show'); + } + + }; + + scope.select = function (item) { + var i = scope.dialogData.selection.indexOf(item); + if (i < 0) { + scope.dialogData.selection.push(item); + } else { + scope.dialogData.selection.splice(i, 1); + } + }; + + //NOTE: Same as 'close' without the callbacks + scope.dismiss = scope.hide; + + // Emit modal events + angular.forEach(['show', 'shown', 'hide', 'hidden'], function (name) { + dialog.element.on(name, function (ev) { + scope.$emit('modal-' + name, ev); + }); + }); + + // Support autofocus attribute + dialog.element.on('shown', function (event) { + $('input[autofocus]', dialog.element).first().trigger('focus'); + }); + + dialog.scope = scope; + + //Autoshow + if (dialog.show) { + scope.show(); + } + + }); + + //Return the modal object outside of the promise! + return dialog; + } + } + + /** Handles the closeDialogs event */ + eventsService.on("app.closeDialogs", function (evt, args) { + removeAllDialogs(args); + }); + + return { + /** + * @ngdoc method + * @name umbraco.services.dialogService#open + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a modal rendering a given template url. + * + * @param {Object} options rendering options + * @param {DomElement} options.container the DOM element to inject the modal into, by default set to body + * @param {Function} options.callback function called when the modal is submitted + * @param {String} options.template the url of the template + * @param {String} options.animation animation csss class, by default set to "fade" + * @param {String} options.modalClass modal css class, by default "umb-modal" + * @param {Bool} options.show show the modal instantly + * @param {Bool} options.iframe load template in an iframe, only needed for serverside templates + * @param {Int} options.width set a width on the modal, only needed for iframes + * @param {Bool} options.inline strips the modal from any animation and wrappers, used when you want to inject a dialog into an existing container + * @returns {Object} modal object + */ + open: function (options) { + return openDialog(options); + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#close + * @methodOf umbraco.services.dialogService + * + * @description + * Closes a specific dialog + * @param {Object} dialog the dialog object to close + * @param {Object} args if specified this object will be sent to any callbacks registered on the dialogs. + */ + close: function (dialog, args) { + if (dialog) { + dialog.close(args); + } + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#closeAll + * @methodOf umbraco.services.dialogService + * + * @description + * Closes all dialogs + * @param {Object} args if specified this object will be sent to any callbacks registered on the dialogs. + */ + closeAll: function (args) { + removeAllDialogs(args); + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#mediaPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a media picker in a modal, the callback returns an array of selected media items + * @param {Object} options mediapicker dialog options object + * @param {Boolean} options.onlyImages Only display files that have an image file-extension + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + mediaPicker: function (options) { + options.template = 'views/common/dialogs/mediaPicker.html'; + options.show = true; + return openDialog(options); + }, + + + /** + * @ngdoc method + * @name umbraco.services.dialogService#contentPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a content picker tree in a modal, the callback returns an array of selected documents + * @param {Object} options content picker dialog options object + * @param {Boolean} options.multiPicker should the picker return one or multiple items + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + contentPicker: function (options) { + + options.treeAlias = "content"; + options.section = "content"; + + return this.treePicker(options); + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#linkPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a link picker tree in a modal, the callback returns a single link + * @param {Object} options content picker dialog options object + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + linkPicker: function (options) { + options.template = 'views/common/dialogs/linkPicker.html'; + options.show = true; + return openDialog(options); + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#macroPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a mcaro picker in a modal, the callback returns a object representing the macro and it's parameters + * @param {Object} options macropicker dialog options object + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + macroPicker: function (options) { + options.template = 'views/common/dialogs/insertmacro.html'; + options.show = true; + options.modalClass = "span7 umb-modal"; + return openDialog(options); + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#memberPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a member picker in a modal, the callback returns a object representing the selected member + * @param {Object} options member picker dialog options object + * @param {Boolean} options.multiPicker should the tree pick one or multiple members before returning + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + memberPicker: function (options) { + + options.treeAlias = "member"; + options.section = "member"; + + return this.treePicker(options); + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#memberGroupPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a member group picker in a modal, the callback returns a object representing the selected member + * @param {Object} options member group picker dialog options object + * @param {Boolean} options.multiPicker should the tree pick one or multiple members before returning + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + memberGroupPicker: function (options) { + options.template = 'views/common/dialogs/memberGroupPicker.html'; + options.show = true; + return openDialog(options); + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#iconPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a icon picker in a modal, the callback returns a object representing the selected icon + * @param {Object} options iconpicker dialog options object + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + iconPicker: function (options) { + options.template = 'views/common/dialogs/iconPicker.html'; + options.show = true; + return openDialog(options); + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#treePicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a tree picker in a modal, the callback returns a object representing the selected tree item + * @param {Object} options iconpicker dialog options object + * @param {String} options.section tree section to display + * @param {String} options.treeAlias specific tree to display + * @param {Boolean} options.multiPicker should the tree pick one or multiple items before returning + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + treePicker: function (options) { + options.template = 'views/common/dialogs/treePicker.html'; + options.show = true; + return openDialog(options); + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#propertyDialog + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a dialog with a chosen property editor in, a value can be passed to the modal, and this value is returned in the callback + * @param {Object} options mediapicker dialog options object + * @param {Function} options.callback callback function + * @param {String} editor editor to use to edit a given value and return on callback + * @param {Object} value value sent to the property editor + * @returns {Object} modal object + */ + //TODO: Wtf does this do? I don't think anything! + propertyDialog: function (options) { + options.template = 'views/common/dialogs/property.html'; + options.show = true; + return openDialog(options); + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#embedDialog + * @methodOf umbraco.services.dialogService + * @description + * Opens a dialog to an embed dialog + */ + embedDialog: function (options) { + options.template = 'views/common/dialogs/rteembed.html'; + options.show = true; + return openDialog(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#ysodDialog + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a dialog to show a custom YSOD + */ + ysodDialog: function (ysodError) { + + var newScope = $rootScope.$new(); + newScope.error = ysodError; + return openDialog({ + modalClass: "umb-modal wide ysod", + scope: newScope, + //callback: options.callback, + template: 'views/common/dialogs/ysod.html', + show: true + }); + }, + + confirmDialog: function (ysodError) { + + options.template = 'views/common/dialogs/confirm.html'; + options.show = true; + return openDialog(options); + } + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js index 74b9abdb6a..5f45a2d3e5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js @@ -1,46 +1,46 @@ -/** Used to broadcast and listen for global events and allow the ability to add async listeners to the callbacks */ - -/* - Core app events: - - app.ready - app.authenticated - app.notAuthenticated +/** Used to broadcast and listen for global events and allow the ability to add async listeners to the callbacks */ + +/* + Core app events: + + app.ready + app.authenticated + app.notAuthenticated app.closeDialogs - app.ysod - app.reInitialize + app.ysod + app.reInitialize app.userRefresh - app.navigationReady -*/ - -function eventsService($q, $rootScope) { - - return { - - /** raise an event with a given name */ - emit: function (name, args) { - - //there are no listeners - if (!$rootScope.$$listeners[name]) { - return; - } - - //send the event - $rootScope.$emit(name, args); - }, - - /** subscribe to a method, or use scope.$on = same thing */ - on: function(name, callback) { - return $rootScope.$on(name, callback); - }, - - /** pass in the result of 'on' to this method, or just call the method returned from 'on' to unsubscribe */ - unsubscribe: function(handle) { - if (angular.isFunction(handle)) { - handle(); - } - } - }; -} - + app.navigationReady +*/ + +function eventsService($q, $rootScope) { + + return { + + /** raise an event with a given name */ + emit: function (name, args) { + + //there are no listeners + if (!$rootScope.$$listeners[name]) { + return; + } + + //send the event + $rootScope.$emit(name, args); + }, + + /** subscribe to a method, or use scope.$on = same thing */ + on: function(name, callback) { + return $rootScope.$on(name, callback); + }, + + /** pass in the result of 'on' to this method, or just call the method returned from 'on' to unsubscribe */ + unsubscribe: function(handle) { + if (angular.isFunction(handle)) { + handle(); + } + } + }; +} + angular.module('umbraco.services').factory('eventsService', eventsService); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js index 4057623675..5983f82e0f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -1,65 +1,65 @@ -/** - * @ngdoc service - * @name umbraco.services.fileManager - * @function - * - * @description - * Used by editors to manage any files that require uploading with the posted data, normally called by property editors - * that need to attach files. - * When a route changes successfully, we ensure that the collection is cleared. - */ -function fileManager() { - - var fileCollection = []; - - return { - /** - * @ngdoc function - * @name umbraco.services.fileManager#addFiles - * @methodOf umbraco.services.fileManager - * @function - * - * @description - * Attaches files to the current manager for the current editor for a particular property, if an empty array is set - * for the files collection that effectively clears the files for the specified editor. - */ - setFiles: function(propertyAlias, files) { - //this will clear the files for the current property and then add the new ones for the current property - fileCollection = _.reject(fileCollection, function (item) { - return item.alias === propertyAlias; - }); - for (var i = 0; i < files.length; i++) { - //save the file object to the files collection - fileCollection.push({ alias: propertyAlias, file: files[i] }); - } - }, - - /** - * @ngdoc function - * @name umbraco.services.fileManager#getFiles - * @methodOf umbraco.services.fileManager - * @function - * - * @description - * Returns all of the files attached to the file manager - */ - getFiles: function() { - return fileCollection; - }, - - /** - * @ngdoc function - * @name umbraco.services.fileManager#clearFiles - * @methodOf umbraco.services.fileManager - * @function - * - * @description - * Removes all files from the manager - */ - clearFiles: function () { - fileCollection = []; - } -}; -} - +/** + * @ngdoc service + * @name umbraco.services.fileManager + * @function + * + * @description + * Used by editors to manage any files that require uploading with the posted data, normally called by property editors + * that need to attach files. + * When a route changes successfully, we ensure that the collection is cleared. + */ +function fileManager() { + + var fileCollection = []; + + return { + /** + * @ngdoc function + * @name umbraco.services.fileManager#addFiles + * @methodOf umbraco.services.fileManager + * @function + * + * @description + * Attaches files to the current manager for the current editor for a particular property, if an empty array is set + * for the files collection that effectively clears the files for the specified editor. + */ + setFiles: function(propertyAlias, files) { + //this will clear the files for the current property and then add the new ones for the current property + fileCollection = _.reject(fileCollection, function (item) { + return item.alias === propertyAlias; + }); + for (var i = 0; i < files.length; i++) { + //save the file object to the files collection + fileCollection.push({ alias: propertyAlias, file: files[i] }); + } + }, + + /** + * @ngdoc function + * @name umbraco.services.fileManager#getFiles + * @methodOf umbraco.services.fileManager + * @function + * + * @description + * Returns all of the files attached to the file manager + */ + getFiles: function() { + return fileCollection; + }, + + /** + * @ngdoc function + * @name umbraco.services.fileManager#clearFiles + * @methodOf umbraco.services.fileManager + * @function + * + * @description + * Removes all files from the manager + */ + clearFiles: function () { + fileCollection = []; + } +}; +} + angular.module('umbraco.services').factory('fileManager', fileManager); \ No newline at end of file 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 7d34309396..ac90a0d333 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 @@ -1,191 +1,191 @@ -/** - * @ngdoc service - * @name umbraco.services.formHelper - * @function - * - * @description - * A utility class used to streamline how forms are developed, to ensure that validation is check and displayed consistently and to ensure that the correct events - * fire when they need to. - */ -function formHelper(angularHelper, serverValidationManager, $timeout, notificationsService, dialogService) { - return { - - /** - * @ngdoc function - * @name umbraco.services.formHelper#submitForm - * @methodOf umbraco.services.formHelper - * @function - * - * @description - * Called by controllers when submitting a form - this ensures that all client validation is checked, - * server validation is cleared, that the correct events execute and status messages are displayed. - * This returns true if the form is valid, otherwise false if form submission cannot continue. - * - * @param {object} args An object containing arguments for form submission - */ - submitForm: function (args) { - - var currentForm; - - if (!args) { - throw "args cannot be null"; - } - if (!args.scope) { - throw "args.scope cannot be null"; - } - if (!args.formCtrl) { - //try to get the closest form controller - currentForm = angularHelper.getRequiredCurrentForm(args.scope); - } - else { - currentForm = args.formCtrl; - } - - //the first thing any form must do is broadcast the formSubmitting event - args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); - - //then check if the form is valid - if (!args.skipValidation) { - if (currentForm.$invalid) { - return false; - } - } - - //reset the server validations - serverValidationManager.reset(); - - return true; - }, - - /** - * @ngdoc function - * @name umbraco.services.formHelper#submitForm - * @methodOf umbraco.services.formHelper - * @function - * - * @description - * Called by controllers when a form has been successfully submitted, this ensures the correct events are raised. - * - * @param {object} args An object containing arguments for form submission - */ - resetForm: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.scope) { - throw "args.scope cannot be null"; - } - - args.scope.$broadcast("formSubmitted", { scope: args.scope }); - }, - - showNotifications: function (args) { - if (!args || !args.notifications) { - return false; - } - if (angular.isArray(args.notifications)) { - for (var i = 0; i < args.notifications.length; i++) { - notificationsService.showNotification(args.notifications[i]); +/** + * @ngdoc service + * @name umbraco.services.formHelper + * @function + * + * @description + * A utility class used to streamline how forms are developed, to ensure that validation is check and displayed consistently and to ensure that the correct events + * fire when they need to. + */ +function formHelper(angularHelper, serverValidationManager, $timeout, notificationsService, dialogService) { + return { + + /** + * @ngdoc function + * @name umbraco.services.formHelper#submitForm + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * Called by controllers when submitting a form - this ensures that all client validation is checked, + * server validation is cleared, that the correct events execute and status messages are displayed. + * This returns true if the form is valid, otherwise false if form submission cannot continue. + * + * @param {object} args An object containing arguments for form submission + */ + submitForm: function (args) { + + var currentForm; + + if (!args) { + throw "args cannot be null"; } - return true; - } - return false; + if (!args.scope) { + throw "args.scope cannot be null"; + } + if (!args.formCtrl) { + //try to get the closest form controller + currentForm = angularHelper.getRequiredCurrentForm(args.scope); + } + else { + currentForm = args.formCtrl; + } + + //the first thing any form must do is broadcast the formSubmitting event + args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); + + //then check if the form is valid + if (!args.skipValidation) { + if (currentForm.$invalid) { + return false; + } + } + + //reset the server validations + serverValidationManager.reset(); + + return true; }, - - /** - * @ngdoc function - * @name umbraco.services.formHelper#handleError - * @methodOf umbraco.services.formHelper - * @function - * - * @description - * Needs to be called when a form submission fails, this will wire up all server validation errors in ModelState and - * add the correct messages to the notifications. If a server error has occurred this will show a ysod. - * - * @param {object} err The error object returned from the http promise - */ - handleError: function (err) { - //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. - //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). - //Or, some strange server error - if (err.status === 400) { - //now we need to look through all the validation errors - if (err.data && (err.data.ModelState)) { - - //wire up the server validation errs - this.handleServerValidation(err.data.ModelState); - - //execute all server validation events and subscribers - serverValidationManager.executeAndClearAllSubscriptions(); - } - else { - dialogService.ysodDialog(err); - } - } - - }, - - /** - * @ngdoc function - * @name umbraco.services.formHelper#handleServerValidation - * @methodOf umbraco.services.formHelper - * @function - * - * @description - * This wires up all of the server validation model state so that valServer and valServerField directives work - * - * @param {object} err The error object returned from the http promise - */ - handleServerValidation: function (modelState) { - for (var e in modelState) { - - //This is where things get interesting.... - // We need to support validation for all editor types such as both the content and content type editors. - // The Content editor ModelState is quite specific with the way that Properties are validated especially considering - // that each property is a User Developer property editor. - // The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations - // system. - // So, to do this (since we need to support backwards compat), we need to hack a little bit. For Content Properties, - // which are user defined, we know that they will exist with a prefixed ModelState of "_Properties.", so if we detect - // this, then we know it's a Property. - - //the alias in model state can be in dot notation which indicates - // * the first part is the content property alias - // * the second part is the field to which the valiation msg is associated with - //There will always be at least 2 parts for properties since all model errors for properties are prefixed with "Properties" - //If it is not prefixed with "Properties" that means the error is for a field of the object directly. - - var parts = e.split("."); - - //Check if this is for content properties - specific to content/media/member editors because those are special - // user defined properties with custom controls. - if (parts.length > 1 && parts[0] === "_Properties") { - - var propertyAlias = parts[1]; - - //if it contains 2 '.' then we will wire it up to a property's field - if (parts.length > 2) { - //add an error with a reference to the field for which the validation belongs too - serverValidationManager.addPropertyError(propertyAlias, parts[2], modelState[e][0]); - } - else { - //add a generic error for the property, no reference to a specific field - serverValidationManager.addPropertyError(propertyAlias, "", modelState[e][0]); - } - - } - else { - - //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: - // Groups[0].Properties[2].Alias - serverValidationManager.addFieldError(e, modelState[e][0]); - } - - //add to notifications - notificationsService.error("Validation", modelState[e][0]); - - } - } - }; -} + + /** + * @ngdoc function + * @name umbraco.services.formHelper#submitForm + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * Called by controllers when a form has been successfully submitted, this ensures the correct events are raised. + * + * @param {object} args An object containing arguments for form submission + */ + resetForm: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.scope) { + throw "args.scope cannot be null"; + } + + args.scope.$broadcast("formSubmitted", { scope: args.scope }); + }, + + showNotifications: function (args) { + if (!args || !args.notifications) { + return false; + } + if (angular.isArray(args.notifications)) { + for (var i = 0; i < args.notifications.length; i++) { + notificationsService.showNotification(args.notifications[i]); + } + return true; + } + return false; + }, + + /** + * @ngdoc function + * @name umbraco.services.formHelper#handleError + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * Needs to be called when a form submission fails, this will wire up all server validation errors in ModelState and + * add the correct messages to the notifications. If a server error has occurred this will show a ysod. + * + * @param {object} err The error object returned from the http promise + */ + handleError: function (err) { + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. + //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). + //Or, some strange server error + if (err.status === 400) { + //now we need to look through all the validation errors + if (err.data && (err.data.ModelState)) { + + //wire up the server validation errs + this.handleServerValidation(err.data.ModelState); + + //execute all server validation events and subscribers + serverValidationManager.executeAndClearAllSubscriptions(); + } + else { + dialogService.ysodDialog(err); + } + } + + }, + + /** + * @ngdoc function + * @name umbraco.services.formHelper#handleServerValidation + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * This wires up all of the server validation model state so that valServer and valServerField directives work + * + * @param {object} err The error object returned from the http promise + */ + handleServerValidation: function (modelState) { + for (var e in modelState) { + + //This is where things get interesting.... + // We need to support validation for all editor types such as both the content and content type editors. + // The Content editor ModelState is quite specific with the way that Properties are validated especially considering + // that each property is a User Developer property editor. + // The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations + // system. + // So, to do this (since we need to support backwards compat), we need to hack a little bit. For Content Properties, + // which are user defined, we know that they will exist with a prefixed ModelState of "_Properties.", so if we detect + // this, then we know it's a Property. + + //the alias in model state can be in dot notation which indicates + // * the first part is the content property alias + // * the second part is the field to which the valiation msg is associated with + //There will always be at least 2 parts for properties since all model errors for properties are prefixed with "Properties" + //If it is not prefixed with "Properties" that means the error is for a field of the object directly. + + var parts = e.split("."); + + //Check if this is for content properties - specific to content/media/member editors because those are special + // user defined properties with custom controls. + if (parts.length > 1 && parts[0] === "_Properties") { + + var propertyAlias = parts[1]; + + //if it contains 2 '.' then we will wire it up to a property's field + if (parts.length > 2) { + //add an error with a reference to the field for which the validation belongs too + serverValidationManager.addPropertyError(propertyAlias, parts[2], modelState[e][0]); + } + else { + //add a generic error for the property, no reference to a specific field + serverValidationManager.addPropertyError(propertyAlias, "", modelState[e][0]); + } + + } + else { + + //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: + // Groups[0].Properties[2].Alias + serverValidationManager.addFieldError(e, modelState[e][0]); + } + + //add to notifications + notificationsService.error("Validation", modelState[e][0]); + + } + } + }; +} angular.module('umbraco.services').factory('formHelper', formHelper); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/history.service.js b/src/Umbraco.Web.UI.Client/src/common/services/history.service.js index ee02efc4a0..114d002a4d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/history.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/history.service.js @@ -1,141 +1,141 @@ -/** - * @ngdoc service - * @name umbraco.services.historyService - * - * @requires $rootScope - * @requires $timeout - * @requires angularHelper - * - * @description - * Service to handle the main application navigation history. Responsible for keeping track - * of where a user navigates to, stores an icon, url and name in a collection, to make it easy - * for the user to go back to a previous editor / action - * - * **Note:** only works with new angular-based editors, not legacy ones - * - * ##usage - * To use, simply inject the historyService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *      angular.module("umbraco").controller("my.controller". function(historyService){
    - *         historyService.add({
    - *								icon: "icon-class",
    - *								name: "Editing 'articles',
    - *								link: "/content/edit/1234"}
    - *							);
    - *      }); 
    - * 
    - */ -angular.module('umbraco.services') -.factory('historyService', function ($rootScope, $timeout, angularHelper, eventsService) { - - var nArray = []; - - function add(item) { - - if (!item) { - return null; - } - - var listWithoutThisItem = _.reject(nArray, function(i) { - return i.link === item.link; - }); - - //put it at the top and reassign - listWithoutThisItem.splice(0, 0, item); - nArray = listWithoutThisItem; - return nArray[0]; - - } - - return { - /** - * @ngdoc method - * @name umbraco.services.historyService#add - * @methodOf umbraco.services.historyService - * - * @description - * Adds a given history item to the users history collection. - * - * @param {Object} item the history item - * @param {String} item.icon icon css class for the list, ex: "icon-image", "icon-doc" - * @param {String} item.link route to the editor, ex: "/content/edit/1234" - * @param {String} item.name friendly name for the history listing - * @returns {Object} history item object - */ - add: function (item) { - var icon = item.icon || "icon-file"; - angularHelper.safeApply($rootScope, function () { - var result = add({ name: item.name, icon: icon, link: item.link, time: new Date() }); - eventsService.emit("historyService.add", {added: result, all: nArray}); - return result; - }); - }, - /** - * @ngdoc method - * @name umbraco.services.historyService#remove - * @methodOf umbraco.services.historyService - * - * @description - * Removes a history item from the users history collection, given an index to remove from. - * - * @param {Int} index index to remove item from - */ - remove: function (index) { - angularHelper.safeApply($rootScope, function() { - var result = nArray.splice(index, 1); - eventsService.emit("historyService.remove", { removed: result, all: nArray }); - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.historyService#removeAll - * @methodOf umbraco.services.historyService - * - * @description - * Removes all history items from the users history collection - */ - removeAll: function () { - angularHelper.safeApply($rootScope, function() { - nArray = []; - eventsService.emit("historyService.removeAll"); - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.historyService#getCurrent - * @methodOf umbraco.services.historyService - * - * @description - * Method to return the current history collection. - * - */ - getCurrent: function(){ - return nArray; +/** + * @ngdoc service + * @name umbraco.services.historyService + * + * @requires $rootScope + * @requires $timeout + * @requires angularHelper + * + * @description + * Service to handle the main application navigation history. Responsible for keeping track + * of where a user navigates to, stores an icon, url and name in a collection, to make it easy + * for the user to go back to a previous editor / action + * + * **Note:** only works with new angular-based editors, not legacy ones + * + * ##usage + * To use, simply inject the historyService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *      angular.module("umbraco").controller("my.controller". function(historyService){
    + *         historyService.add({
    + *								icon: "icon-class",
    + *								name: "Editing 'articles',
    + *								link: "/content/edit/1234"}
    + *							);
    + *      }); 
    + * 
    + */ +angular.module('umbraco.services') +.factory('historyService', function ($rootScope, $timeout, angularHelper, eventsService) { + + var nArray = []; + + function add(item) { + + if (!item) { + return null; + } + + var listWithoutThisItem = _.reject(nArray, function(i) { + return i.link === item.link; + }); + + //put it at the top and reassign + listWithoutThisItem.splice(0, 0, item); + nArray = listWithoutThisItem; + return nArray[0]; + + } + + return { + /** + * @ngdoc method + * @name umbraco.services.historyService#add + * @methodOf umbraco.services.historyService + * + * @description + * Adds a given history item to the users history collection. + * + * @param {Object} item the history item + * @param {String} item.icon icon css class for the list, ex: "icon-image", "icon-doc" + * @param {String} item.link route to the editor, ex: "/content/edit/1234" + * @param {String} item.name friendly name for the history listing + * @returns {Object} history item object + */ + add: function (item) { + var icon = item.icon || "icon-file"; + angularHelper.safeApply($rootScope, function () { + var result = add({ name: item.name, icon: icon, link: item.link, time: new Date() }); + eventsService.emit("historyService.add", {added: result, all: nArray}); + return result; + }); + }, + /** + * @ngdoc method + * @name umbraco.services.historyService#remove + * @methodOf umbraco.services.historyService + * + * @description + * Removes a history item from the users history collection, given an index to remove from. + * + * @param {Int} index index to remove item from + */ + remove: function (index) { + angularHelper.safeApply($rootScope, function() { + var result = nArray.splice(index, 1); + eventsService.emit("historyService.remove", { removed: result, all: nArray }); + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.historyService#removeAll + * @methodOf umbraco.services.historyService + * + * @description + * Removes all history items from the users history collection + */ + removeAll: function () { + angularHelper.safeApply($rootScope, function() { + nArray = []; + eventsService.emit("historyService.removeAll"); + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.historyService#getCurrent + * @methodOf umbraco.services.historyService + * + * @description + * Method to return the current history collection. + * + */ + getCurrent: function(){ + return nArray; }, - /** - * @ngdoc method - * @name umbraco.services.historyService#getLastAccessedItemForSection - * @methodOf umbraco.services.historyService - * - * @description - * Method to return the item that was last accessed in the given section + /** + * @ngdoc method + * @name umbraco.services.historyService#getLastAccessedItemForSection + * @methodOf umbraco.services.historyService * - * @param {string} sectionAlias Alias of the section to return the last accessed item for. + * @description + * Method to return the item that was last accessed in the given section + * + * @param {string} sectionAlias Alias of the section to return the last accessed item for. */ getLastAccessedItemForSection: function (sectionAlias) { for (var i = 0, len = nArray.length; i < len; i++) { var item = nArray[i]; if (item.link.indexOf(sectionAlias + "/") === 0) { - return item; - } + return item; + } } - return null; - } - }; + return null; + } + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index 8d1d274e85..1b7a6da764 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -1,301 +1,301 @@ -/** - * @ngdoc service - * @name umbraco.services.localizationService - * - * @requires $http - * @requires $q - * @requires $window - * @requires $filter - * - * @description - * Application-wide service for handling localization - * - * ##usage - * To use, simply inject the localizationService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *    localizationService.localize("area_key").then(function(value){
    - *        element.html(value);
    - *    });
    - * 
    - */ - -angular.module('umbraco.services') -.factory('localizationService', function ($http, $q, eventsService, $window, $filter, userService) { - - //TODO: This should be injected as server vars - var url = "LocalizedText"; - var resourceFileLoadStatus = "none"; - var resourceLoadingPromise = []; - - function _lookup(value, tokens, dictionary) { - - //strip the key identifier if its there - if (value && value[0] === "@") { - value = value.substring(1); - } - - //if no area specified, add general_ - if (value && value.indexOf("_") < 0) { - value = "general_" + value; - } - - var entry = dictionary[value]; - if (entry) { - if (tokens) { - for (var i = 0; i < tokens.length; i++) { - entry = entry.replace("%" + i + "%", tokens[i]); - } - } - return entry; - } - return "[" + value + "]"; - } - - var service = { - // array to hold the localized resource string entries - dictionary: [], - - // loads the language resource file from the server - initLocalizedResources: function () { - var deferred = $q.defer(); - - if (resourceFileLoadStatus === "loaded") { - deferred.resolve(service.dictionary); - return deferred.promise; - } - - //if the resource is already loading, we don't want to force it to load another one in tandem, we'd rather - // wait for that initial http promise to finish and then return this one with the dictionary loaded - if (resourceFileLoadStatus === "loading") { - //add to the list of promises waiting - resourceLoadingPromise.push(deferred); - - //exit now it's already loading - return deferred.promise; - } - - resourceFileLoadStatus = "loading"; - - // build the url to retrieve the localized resource file - $http({ method: "GET", url: url, cache: false }) - .then(function (response) { - resourceFileLoadStatus = "loaded"; - service.dictionary = response.data; - - eventsService.emit("localizationService.updated", response.data); - - deferred.resolve(response.data); - //ensure all other queued promises are resolved - for (var p in resourceLoadingPromise) { - resourceLoadingPromise[p].resolve(response.data); - } - }, function (err) { - deferred.reject("Something broke"); - //ensure all other queued promises are resolved - for (var p in resourceLoadingPromise) { - resourceLoadingPromise[p].reject("Something broke"); - } - }); - return deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.localizationService#tokenize - * @methodOf umbraco.services.localizationService - * - * @description - * Helper to tokenize and compile a localization string - * @param {String} value the value to tokenize - * @param {Object} scope the $scope object - * @returns {String} tokenized resource string - */ - tokenize: function (value, scope) { - if (value) { - var localizer = value.split(':'); - var retval = { tokens: undefined, key: localizer[0].substring(0) }; - if (localizer.length > 1) { - retval.tokens = localizer[1].split(','); - for (var x = 0; x < retval.tokens.length; x++) { - retval.tokens[x] = scope.$eval(retval.tokens[x]); - } - } - - return retval; - } - return value; - }, - - /** - * @ngdoc method - * @name umbraco.services.localizationService#localize - * @methodOf umbraco.services.localizationService - * - * @description - * Checks the dictionary for a localized resource string - * @param {String} value the area/key to localize in the format of 'section_key' - * alternatively if no section is set such as 'key' then we assume the key is to be looked in - * the 'general' section - * - * @param {Array} tokens if specified this array will be sent as parameter values - * This replaces %0% and %1% etc in the dictionary key value with the passed in strings - * - * @returns {String} localized resource string - */ - localize: function (value, tokens) { - return service.initLocalizedResources().then(function (dic) { - var val = _lookup(value, tokens, dic); - return val; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.localizationService#localizeMany - * @methodOf umbraco.services.localizationService - * - * @description - * Checks the dictionary for multipe localized resource strings at once, preventing the need for nested promises - * with localizationService.localize - * - * ##Usage - *
    -         * localizationService.localizeMany(["speechBubbles_templateErrorHeader", "speechBubbles_templateErrorText"]).then(function(data){
    -         *      var header = data[0];
    -         *      var message = data[1];
    -         *      notificationService.error(header, message);
    -         * });
    -         * 
    - * - * @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key' - * alternatively if no section is set such as 'key' then we assume the key is to be looked in - * the 'general' section - * - * @returns {Array} An array of localized resource string in the same order - */ - localizeMany: function(keys) { - if(keys){ - - //The LocalizationService.localize promises we want to resolve - var promises = []; - - for(var i = 0; i < keys.length; i++){ - promises.push(service.localize(keys[i], undefined)); - } - - return $q.all(promises).then(function(localizedValues){ - return localizedValues; - }); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.localizationService#concat - * @methodOf umbraco.services.localizationService - * - * @description - * Checks the dictionary for multipe localized resource strings at once & concats them to a single string - * Which was not possible with localizationSerivce.localize() due to returning a promise - * - * ##Usage - *
    -         * localizationService.concat(["speechBubbles_templateErrorHeader", "speechBubbles_templateErrorText"]).then(function(data){
    -         *      var combinedText = data;
    -         * });
    -         * 
    - * - * @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key' - * alternatively if no section is set such as 'key' then we assume the key is to be looked in - * the 'general' section - * - * @returns {String} An concatenated string of localized resource string passed into the function in the same order - */ - concat: function(keys) { - if(keys){ - - //The LocalizationService.localize promises we want to resolve - var promises = []; - - for(var i = 0; i < keys.length; i++){ - promises.push(service.localize(keys[i], undefined)); - } - - return $q.all(promises).then(function(localizedValues){ - - //Build a concat string by looping over the array of resolved promises/translations - var returnValue = ""; - - for(var i = 0; i < localizedValues.length; i++){ - returnValue += localizedValues[i]; - } - - return returnValue; - }); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.localizationService#format - * @methodOf umbraco.services.localizationService - * - * @description - * Checks the dictionary for multipe localized resource strings at once & formats a tokenized message - * Which was not possible with localizationSerivce.localize() due to returning a promise - * - * ##Usage - *
    -         * localizationService.format(["template_insert", "template_insertSections"], "%0% %1%").then(function(data){
    -         *      //Will return 'Insert Sections'
    -         *      var formattedResult = data;
    -         * });
    -         * 
    - * - * @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key' - * alternatively if no section is set such as 'key' then we assume the key is to be looked in - * the 'general' section - * - * @param {String} message is the string you wish to replace containing tokens in the format of %0% and %1% - * with the localized resource strings - * - * @returns {String} An concatenated string of localized resource string passed into the function in the same order - */ - format: function(keys, message){ - if(keys){ - - //The LocalizationService.localize promises we want to resolve - var promises = []; - - for(var i = 0; i < keys.length; i++){ - promises.push(service.localize(keys[i], undefined)); - } - - return $q.all(promises).then(function(localizedValues){ - - //Replace {0} and {1} etc in message with the localized values - for(var i = 0; i < localizedValues.length; i++){ - var token = "%" + i + "%"; - var regex = new RegExp(token, "g"); - - message = message.replace(regex, localizedValues[i]); - } - - return message; - }); - } - } - - }; - - //This happens after login / auth and assets loading - eventsService.on("app.authenticated", function () { - resourceFileLoadStatus = "none"; - resourceLoadingPromise = []; - }); - - // return the local instance when called - return service; -}); +/** + * @ngdoc service + * @name umbraco.services.localizationService + * + * @requires $http + * @requires $q + * @requires $window + * @requires $filter + * + * @description + * Application-wide service for handling localization + * + * ##usage + * To use, simply inject the localizationService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *    localizationService.localize("area_key").then(function(value){
    + *        element.html(value);
    + *    });
    + * 
    + */ + +angular.module('umbraco.services') +.factory('localizationService', function ($http, $q, eventsService, $window, $filter, userService) { + + //TODO: This should be injected as server vars + var url = "LocalizedText"; + var resourceFileLoadStatus = "none"; + var resourceLoadingPromise = []; + + function _lookup(value, tokens, dictionary) { + + //strip the key identifier if its there + if (value && value[0] === "@") { + value = value.substring(1); + } + + //if no area specified, add general_ + if (value && value.indexOf("_") < 0) { + value = "general_" + value; + } + + var entry = dictionary[value]; + if (entry) { + if (tokens) { + for (var i = 0; i < tokens.length; i++) { + entry = entry.replace("%" + i + "%", tokens[i]); + } + } + return entry; + } + return "[" + value + "]"; + } + + var service = { + // array to hold the localized resource string entries + dictionary: [], + + // loads the language resource file from the server + initLocalizedResources: function () { + var deferred = $q.defer(); + + if (resourceFileLoadStatus === "loaded") { + deferred.resolve(service.dictionary); + return deferred.promise; + } + + //if the resource is already loading, we don't want to force it to load another one in tandem, we'd rather + // wait for that initial http promise to finish and then return this one with the dictionary loaded + if (resourceFileLoadStatus === "loading") { + //add to the list of promises waiting + resourceLoadingPromise.push(deferred); + + //exit now it's already loading + return deferred.promise; + } + + resourceFileLoadStatus = "loading"; + + // build the url to retrieve the localized resource file + $http({ method: "GET", url: url, cache: false }) + .then(function (response) { + resourceFileLoadStatus = "loaded"; + service.dictionary = response.data; + + eventsService.emit("localizationService.updated", response.data); + + deferred.resolve(response.data); + //ensure all other queued promises are resolved + for (var p in resourceLoadingPromise) { + resourceLoadingPromise[p].resolve(response.data); + } + }, function (err) { + deferred.reject("Something broke"); + //ensure all other queued promises are resolved + for (var p in resourceLoadingPromise) { + resourceLoadingPromise[p].reject("Something broke"); + } + }); + return deferred.promise; + }, + + /** + * @ngdoc method + * @name umbraco.services.localizationService#tokenize + * @methodOf umbraco.services.localizationService + * + * @description + * Helper to tokenize and compile a localization string + * @param {String} value the value to tokenize + * @param {Object} scope the $scope object + * @returns {String} tokenized resource string + */ + tokenize: function (value, scope) { + if (value) { + var localizer = value.split(':'); + var retval = { tokens: undefined, key: localizer[0].substring(0) }; + if (localizer.length > 1) { + retval.tokens = localizer[1].split(','); + for (var x = 0; x < retval.tokens.length; x++) { + retval.tokens[x] = scope.$eval(retval.tokens[x]); + } + } + + return retval; + } + return value; + }, + + /** + * @ngdoc method + * @name umbraco.services.localizationService#localize + * @methodOf umbraco.services.localizationService + * + * @description + * Checks the dictionary for a localized resource string + * @param {String} value the area/key to localize in the format of 'section_key' + * alternatively if no section is set such as 'key' then we assume the key is to be looked in + * the 'general' section + * + * @param {Array} tokens if specified this array will be sent as parameter values + * This replaces %0% and %1% etc in the dictionary key value with the passed in strings + * + * @returns {String} localized resource string + */ + localize: function (value, tokens) { + return service.initLocalizedResources().then(function (dic) { + var val = _lookup(value, tokens, dic); + return val; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.localizationService#localizeMany + * @methodOf umbraco.services.localizationService + * + * @description + * Checks the dictionary for multipe localized resource strings at once, preventing the need for nested promises + * with localizationService.localize + * + * ##Usage + *
    +         * localizationService.localizeMany(["speechBubbles_templateErrorHeader", "speechBubbles_templateErrorText"]).then(function(data){
    +         *      var header = data[0];
    +         *      var message = data[1];
    +         *      notificationService.error(header, message);
    +         * });
    +         * 
    + * + * @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key' + * alternatively if no section is set such as 'key' then we assume the key is to be looked in + * the 'general' section + * + * @returns {Array} An array of localized resource string in the same order + */ + localizeMany: function(keys) { + if(keys){ + + //The LocalizationService.localize promises we want to resolve + var promises = []; + + for(var i = 0; i < keys.length; i++){ + promises.push(service.localize(keys[i], undefined)); + } + + return $q.all(promises).then(function(localizedValues){ + return localizedValues; + }); + } + }, + + /** + * @ngdoc method + * @name umbraco.services.localizationService#concat + * @methodOf umbraco.services.localizationService + * + * @description + * Checks the dictionary for multipe localized resource strings at once & concats them to a single string + * Which was not possible with localizationSerivce.localize() due to returning a promise + * + * ##Usage + *
    +         * localizationService.concat(["speechBubbles_templateErrorHeader", "speechBubbles_templateErrorText"]).then(function(data){
    +         *      var combinedText = data;
    +         * });
    +         * 
    + * + * @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key' + * alternatively if no section is set such as 'key' then we assume the key is to be looked in + * the 'general' section + * + * @returns {String} An concatenated string of localized resource string passed into the function in the same order + */ + concat: function(keys) { + if(keys){ + + //The LocalizationService.localize promises we want to resolve + var promises = []; + + for(var i = 0; i < keys.length; i++){ + promises.push(service.localize(keys[i], undefined)); + } + + return $q.all(promises).then(function(localizedValues){ + + //Build a concat string by looping over the array of resolved promises/translations + var returnValue = ""; + + for(var i = 0; i < localizedValues.length; i++){ + returnValue += localizedValues[i]; + } + + return returnValue; + }); + } + }, + + /** + * @ngdoc method + * @name umbraco.services.localizationService#format + * @methodOf umbraco.services.localizationService + * + * @description + * Checks the dictionary for multipe localized resource strings at once & formats a tokenized message + * Which was not possible with localizationSerivce.localize() due to returning a promise + * + * ##Usage + *
    +         * localizationService.format(["template_insert", "template_insertSections"], "%0% %1%").then(function(data){
    +         *      //Will return 'Insert Sections'
    +         *      var formattedResult = data;
    +         * });
    +         * 
    + * + * @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key' + * alternatively if no section is set such as 'key' then we assume the key is to be looked in + * the 'general' section + * + * @param {String} message is the string you wish to replace containing tokens in the format of %0% and %1% + * with the localized resource strings + * + * @returns {String} An concatenated string of localized resource string passed into the function in the same order + */ + format: function(keys, message){ + if(keys){ + + //The LocalizationService.localize promises we want to resolve + var promises = []; + + for(var i = 0; i < keys.length; i++){ + promises.push(service.localize(keys[i], undefined)); + } + + return $q.all(promises).then(function(localizedValues){ + + //Replace {0} and {1} etc in message with the localized values + for(var i = 0; i < localizedValues.length; i++){ + var token = "%" + i + "%"; + var regex = new RegExp(token, "g"); + + message = message.replace(regex, localizedValues[i]); + } + + return message; + }); + } + } + + }; + + //This happens after login / auth and assets loading + eventsService.on("app.authenticated", function () { + resourceFileLoadStatus = "none"; + resourceLoadingPromise = []; + }); + + // return the local instance when called + return service; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js b/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js index 6e78d71e85..77ed6e821e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/macro.service.js @@ -1,207 +1,207 @@ -/** - * @ngdoc service - * @name umbraco.services.macroService - * - * - * @description - * A service to return macro information such as generating syntax to insert a macro into an editor - */ -function macroService() { - - return { - - /** parses the special macro syntax like and returns an object with the macro alias and it's parameters */ - parseMacroSyntax: function (syntax) { - - //This regex will match an alias of anything except characters that are quotes or new lines (for legacy reasons, when new macros are created - // their aliases are cleaned an invalid chars are stripped) - var expression = /(<\?UMBRACO_MACRO (?:.+?)?macroAlias=["']([^\"\'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i; - var match = expression.exec(syntax); - if (!match || match.length < 3) { - return null; - } - var alias = match[2]; - - //this will leave us with just the parameters - var paramsChunk = match[1].trim().replace(new RegExp("UMBRACO_MACRO macroAlias=[\"']" + alias + "[\"']"), "").trim(); - - var paramExpression = /(\w+?)=['\"]([\s\S]*?)['\"]/g; - - var paramMatch; - var returnVal = { - macroAlias: alias, - macroParamsDictionary: {} - }; - while (paramMatch = paramExpression.exec(paramsChunk)) { - returnVal.macroParamsDictionary[paramMatch[1]] = paramMatch[2]; - } - return returnVal; - }, - - /** - * @ngdoc function - * @name umbraco.services.macroService#generateWebFormsSyntax - * @methodOf umbraco.services.macroService - * @function - * - * @description - * generates the syntax for inserting a macro into a rich text editor - this is the very old umbraco style syntax - * - * @param {object} args an object containing the macro alias and it's parameter values - */ - generateMacroSyntax: function (args) { - - // - - var macroString = '"; - - return macroString; - }, - - /** - * @ngdoc function - * @name umbraco.services.macroService#generateWebFormsSyntax - * @methodOf umbraco.services.macroService - * @function - * - * @description - * generates the syntax for inserting a macro into a webforms templates - * - * @param {object} args an object containing the macro alias and it's parameter values - */ - generateWebFormsSyntax: function(args) { - - var macroString = '"; - - return macroString; - }, - - /** - * @ngdoc function - * @name umbraco.services.macroService#generateMvcSyntax - * @methodOf umbraco.services.macroService - * @function - * - * @description - * generates the syntax for inserting a macro into an mvc template - * - * @param {object} args an object containing the macro alias and it's parameter values - */ - generateMvcSyntax: function (args) { - - var macroString = "@Umbraco.RenderMacro(\"" + args.macroAlias + "\""; - - var hasParams = false; - var paramString; - if (args.macroParamsDictionary) { - - paramString = ", new {"; - - _.each(args.macroParamsDictionary, function(val, key) { - - hasParams = true; - - var keyVal = key + "=\"" + (val ? val : "") + "\", "; - - paramString += keyVal; - }); - - //remove the last , - paramString = paramString.trimEnd(", "); - - paramString += "}"; - } - if (hasParams) { - macroString += paramString; - } - - macroString += ")"; - return macroString; - }, - - collectValueData: function(macro, macroParams, renderingEngine) { - - var paramDictionary = {}; - var macroAlias = macro.alias; - var syntax; - - _.each(macroParams, function (item) { - - var val = item.value; - - if (item.value !== null && item.value !== undefined && !_.isString(item.value)) { - try { - val = angular.toJson(val); - } - catch (e) { - // not json - } - } - - //each value needs to be xml escaped!! since the value get's stored as an xml attribute - paramDictionary[item.alias] = _.escape(val); - - }); - - //get the syntax based on the rendering engine - if (renderingEngine && renderingEngine === "WebForms") { - syntax = this.generateWebFormsSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); - } - else if (renderingEngine && renderingEngine === "Mvc") { - syntax = this.generateMvcSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); - } - else { - syntax = this.generateMacroSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); - } - - var macroObject = { - "macroParamsDictionary": paramDictionary, - "macroAlias": macroAlias, - "syntax": syntax - }; - - return macroObject; - - } - - }; - -} - -angular.module('umbraco.services').factory('macroService', macroService); +/** + * @ngdoc service + * @name umbraco.services.macroService + * + * + * @description + * A service to return macro information such as generating syntax to insert a macro into an editor + */ +function macroService() { + + return { + + /** parses the special macro syntax like and returns an object with the macro alias and it's parameters */ + parseMacroSyntax: function (syntax) { + + //This regex will match an alias of anything except characters that are quotes or new lines (for legacy reasons, when new macros are created + // their aliases are cleaned an invalid chars are stripped) + var expression = /(<\?UMBRACO_MACRO (?:.+?)?macroAlias=["']([^\"\'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i; + var match = expression.exec(syntax); + if (!match || match.length < 3) { + return null; + } + var alias = match[2]; + + //this will leave us with just the parameters + var paramsChunk = match[1].trim().replace(new RegExp("UMBRACO_MACRO macroAlias=[\"']" + alias + "[\"']"), "").trim(); + + var paramExpression = /(\w+?)=['\"]([\s\S]*?)['\"]/g; + + var paramMatch; + var returnVal = { + macroAlias: alias, + macroParamsDictionary: {} + }; + while (paramMatch = paramExpression.exec(paramsChunk)) { + returnVal.macroParamsDictionary[paramMatch[1]] = paramMatch[2]; + } + return returnVal; + }, + + /** + * @ngdoc function + * @name umbraco.services.macroService#generateWebFormsSyntax + * @methodOf umbraco.services.macroService + * @function + * + * @description + * generates the syntax for inserting a macro into a rich text editor - this is the very old umbraco style syntax + * + * @param {object} args an object containing the macro alias and it's parameter values + */ + generateMacroSyntax: function (args) { + + // + + var macroString = '"; + + return macroString; + }, + + /** + * @ngdoc function + * @name umbraco.services.macroService#generateWebFormsSyntax + * @methodOf umbraco.services.macroService + * @function + * + * @description + * generates the syntax for inserting a macro into a webforms templates + * + * @param {object} args an object containing the macro alias and it's parameter values + */ + generateWebFormsSyntax: function(args) { + + var macroString = '"; + + return macroString; + }, + + /** + * @ngdoc function + * @name umbraco.services.macroService#generateMvcSyntax + * @methodOf umbraco.services.macroService + * @function + * + * @description + * generates the syntax for inserting a macro into an mvc template + * + * @param {object} args an object containing the macro alias and it's parameter values + */ + generateMvcSyntax: function (args) { + + var macroString = "@Umbraco.RenderMacro(\"" + args.macroAlias + "\""; + + var hasParams = false; + var paramString; + if (args.macroParamsDictionary) { + + paramString = ", new {"; + + _.each(args.macroParamsDictionary, function(val, key) { + + hasParams = true; + + var keyVal = key + "=\"" + (val ? val : "") + "\", "; + + paramString += keyVal; + }); + + //remove the last , + paramString = paramString.trimEnd(", "); + + paramString += "}"; + } + if (hasParams) { + macroString += paramString; + } + + macroString += ")"; + return macroString; + }, + + collectValueData: function(macro, macroParams, renderingEngine) { + + var paramDictionary = {}; + var macroAlias = macro.alias; + var syntax; + + _.each(macroParams, function (item) { + + var val = item.value; + + if (item.value !== null && item.value !== undefined && !_.isString(item.value)) { + try { + val = angular.toJson(val); + } + catch (e) { + // not json + } + } + + //each value needs to be xml escaped!! since the value get's stored as an xml attribute + paramDictionary[item.alias] = _.escape(val); + + }); + + //get the syntax based on the rendering engine + if (renderingEngine && renderingEngine === "WebForms") { + syntax = this.generateWebFormsSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); + } + else if (renderingEngine && renderingEngine === "Mvc") { + syntax = this.generateMvcSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); + } + else { + syntax = this.generateMacroSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); + } + + var macroObject = { + "macroParamsDictionary": paramDictionary, + "macroAlias": macroAlias, + "syntax": syntax + }; + + return macroObject; + + } + + }; + +} + +angular.module('umbraco.services').factory('macroService', macroService); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js index d9e95ff309..57c86cba90 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js @@ -1,106 +1,106 @@ -/** - * @ngdoc service - * @name umbraco.services.umbracoMenuActions - * - * @requires q - * @requires treeService - * - * @description - * Defines the methods that are called when menu items declare only an action to execute - */ -function umbracoMenuActions(treeService, $location, navigationService, appState, localizationService, usersResource, umbRequestHelper, notificationsService) { - - return { - - "ExportMember": function(args) { - var url = umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "ExportMemberData", - [{ key: args.entity.id }]); - - umbRequestHelper.downloadFile(url).then(function() { - localizationService.localize("speechBubbles_memberExportedSuccess").then(function (value) { - notificationsService.success(value); - }) - }, function(data) { - localizationService.localize("speechBubbles_memberExportedError").then(function (value) { - notificationsService.error(value); - }) - }); - - }, - - "DisableUser": function(args) { - localizationService.localize("defaultdialogs_confirmdisable").then(function (txtConfirmDisable) { - var currentMenuNode = UmbClientMgr.mainTree().getActionNode(); - if (confirm(txtConfirmDisable + ' "' + args.entity.name + '"?\n\n')) { - usersResource.disableUser(args.entity.id).then(function () { - navigationService.syncTree({ tree: args.treeAlias, path: [args.entity.parentId, args.entity.id], forceReload: true }); - }); - } - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.umbracoMenuActions#RefreshNode - * @methodOf umbraco.services.umbracoMenuActions - * @function - * - * @description - * Clears all node children and then gets it's up-to-date children from the server and re-assigns them - * @param {object} args An arguments object - * @param {object} args.entity The basic entity being acted upon - * @param {object} args.treeAlias The tree alias associated with this entity - * @param {object} args.section The current section - */ - "RefreshNode": function (args) { - - ////just in case clear any tree cache for this node/section - //treeService.clearCache({ - // cacheKey: "__" + args.section, //each item in the tree cache is cached by the section name - // childrenOf: args.entity.parentId //clear the children of the parent - //}); - - //since we're dealing with an entity, we need to attempt to find it's tree node, in the main tree - // this action is purely a UI thing so if for whatever reason there is no loaded tree node in the UI - // we can safely ignore this process. - - //to find a visible tree node, we'll go get the currently loaded root node from appState - var treeRoot = appState.getTreeState("currentRootNode"); - if (treeRoot && treeRoot.root) { - var treeNode = treeService.getDescendantNode(treeRoot.root, args.entity.id, args.treeAlias); - if (treeNode) { - treeService.loadNodeChildren({ node: treeNode, section: args.section }); - } - } - - - }, - - /** - * @ngdoc method - * @name umbraco.services.umbracoMenuActions#CreateChildEntity - * @methodOf umbraco.services.umbracoMenuActions - * @function - * - * @description - * This will re-route to a route for creating a new entity as a child of the current node - * @param {object} args An arguments object - * @param {object} args.entity The basic entity being acted upon - * @param {object} args.treeAlias The tree alias associated with this entity - * @param {object} args.section The current section - */ - "CreateChildEntity": function (args) { - - navigationService.hideNavigation(); - - var route = "/" + args.section + "/" + args.treeAlias + "/edit/" + args.entity.id; - //change to new path - $location.path(route).search({ create: true }); - - } - }; -} - +/** + * @ngdoc service + * @name umbraco.services.umbracoMenuActions + * + * @requires q + * @requires treeService + * + * @description + * Defines the methods that are called when menu items declare only an action to execute + */ +function umbracoMenuActions(treeService, $location, navigationService, appState, localizationService, usersResource, umbRequestHelper, notificationsService) { + + return { + + "ExportMember": function(args) { + var url = umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "ExportMemberData", + [{ key: args.entity.id }]); + + umbRequestHelper.downloadFile(url).then(function() { + localizationService.localize("speechBubbles_memberExportedSuccess").then(function (value) { + notificationsService.success(value); + }) + }, function(data) { + localizationService.localize("speechBubbles_memberExportedError").then(function (value) { + notificationsService.error(value); + }) + }); + + }, + + "DisableUser": function(args) { + localizationService.localize("defaultdialogs_confirmdisable").then(function (txtConfirmDisable) { + var currentMenuNode = UmbClientMgr.mainTree().getActionNode(); + if (confirm(txtConfirmDisable + ' "' + args.entity.name + '"?\n\n')) { + usersResource.disableUser(args.entity.id).then(function () { + navigationService.syncTree({ tree: args.treeAlias, path: [args.entity.parentId, args.entity.id], forceReload: true }); + }); + } + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.umbracoMenuActions#RefreshNode + * @methodOf umbraco.services.umbracoMenuActions + * @function + * + * @description + * Clears all node children and then gets it's up-to-date children from the server and re-assigns them + * @param {object} args An arguments object + * @param {object} args.entity The basic entity being acted upon + * @param {object} args.treeAlias The tree alias associated with this entity + * @param {object} args.section The current section + */ + "RefreshNode": function (args) { + + ////just in case clear any tree cache for this node/section + //treeService.clearCache({ + // cacheKey: "__" + args.section, //each item in the tree cache is cached by the section name + // childrenOf: args.entity.parentId //clear the children of the parent + //}); + + //since we're dealing with an entity, we need to attempt to find it's tree node, in the main tree + // this action is purely a UI thing so if for whatever reason there is no loaded tree node in the UI + // we can safely ignore this process. + + //to find a visible tree node, we'll go get the currently loaded root node from appState + var treeRoot = appState.getTreeState("currentRootNode"); + if (treeRoot && treeRoot.root) { + var treeNode = treeService.getDescendantNode(treeRoot.root, args.entity.id, args.treeAlias); + if (treeNode) { + treeService.loadNodeChildren({ node: treeNode, section: args.section }); + } + } + + + }, + + /** + * @ngdoc method + * @name umbraco.services.umbracoMenuActions#CreateChildEntity + * @methodOf umbraco.services.umbracoMenuActions + * @function + * + * @description + * This will re-route to a route for creating a new entity as a child of the current node + * @param {object} args An arguments object + * @param {object} args.entity The basic entity being acted upon + * @param {object} args.treeAlias The tree alias associated with this entity + * @param {object} args.section The current section + */ + "CreateChildEntity": function (args) { + + navigationService.hideNavigation(); + + var route = "/" + args.section + "/" + args.treeAlias + "/edit/" + args.entity.id; + //change to new path + $location.path(route).search({ create: true }); + + } + }; +} + angular.module('umbraco.services').factory('umbracoMenuActions', umbracoMenuActions); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js index 8bd1f87c3b..2575b05bb6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js @@ -1,300 +1,300 @@ -/** - * @ngdoc service - * @name umbraco.services.notificationsService - * - * @requires $rootScope - * @requires $timeout - * @requires angularHelper - * - * @description - * Application-wide service for handling notifications, the umbraco application - * maintains a single collection of notications, which the UI watches for changes. - * By default when a notication is added, it is automaticly removed 7 seconds after - * This can be changed on add() - * - * ##usage - * To use, simply inject the notificationsService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *		notificationsService.success("Document Published", "hooraaaay for you!");
    - *      notificationsService.error("Document Failed", "booooh");
    - * 
    - */ -angular.module('umbraco.services') -.factory('notificationsService', function ($rootScope, $timeout, angularHelper) { - - var nArray = []; - function setViewPath(view){ - if(view.indexOf('/') < 0) - { - view = "views/common/notifications/" + view; - } - - if(view.indexOf('.html') < 0) - { - view = view + ".html"; - } - return view; - } - - var service = { - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#add - * @methodOf umbraco.services.notificationsService - * - * @description - * Lower level api for adding notifcations, support more advanced options - * @param {Object} item The notification item - * @param {String} item.headline Short headline - * @param {String} item.message longer text for the notication, trimmed after 200 characters, which can then be exanded - * @param {String} item.type Notification type, can be: "success","warning","error" or "info" - * @param {String} item.url url to open when notification is clicked - * @param {String} item.view path to custom view to load into the notification box - * @param {Array} item.actions Collection of button actions to append (label, func, cssClass) - * @param {Boolean} item.sticky if set to true, the notification will not auto-close - * @returns {Object} args notification object - */ - - add: function(item) { - angularHelper.safeApply($rootScope, function () { - - if(item.view){ - item.view = setViewPath(item.view); - item.sticky = true; - item.type = "form"; - item.headline = null; - } - - - //add a colon after the headline if there is a message as well - if (item.message) { - item.headline += ": "; - if(item.message.length > 200) { - item.sticky = true; - } - } - - //we need to ID the item, going by index isn't good enough because people can remove at different indexes - // whenever they want. Plus once we remove one, then the next index will be different. The only way to - // effectively remove an item is by an Id. - item.id = String.CreateGuid(); - - nArray.push(item); - - if(!item.sticky) { - $timeout(function() { - var found = _.find(nArray, function(i) { - return i.id === item.id; - }); - - if (found) { - var index = nArray.indexOf(found); - nArray.splice(index, 1); - } - - }, 7000); - } - - return item; - }); - - }, - - hasView : function(view){ - if(!view){ - return _.find(nArray, function(notification){ return notification.view;}); - }else{ - view = setViewPath(view).toLowerCase(); - return _.find(nArray, function(notification){ return notification.view.toLowerCase() === view;}); - } - }, - addView: function(view, args){ - var item = { - args: args, - view: view - }; - - service.add(item); - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#showNotification - * @methodOf umbraco.services.notificationsService - * - * @description - * Shows a notification based on the object passed in, normally used to render notifications sent back from the server - * - * @returns {Object} args notification object - */ - showNotification: function(args) { - if (!args) { - throw "args cannot be null"; - } - if (args.type === undefined || args.type === null) { - throw "args.type cannot be null"; - } - if (!args.header) { - throw "args.header cannot be null"; - } - - switch(args.type) { - case 0: - //save - this.success(args.header, args.message); - break; - case 1: - //info - this.success(args.header, args.message); - break; - case 2: - //error - this.error(args.header, args.message); - break; - case 3: - //success - this.success(args.header, args.message); - break; - case 4: - //warning - this.warning(args.header, args.message); - break; - } - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#success - * @methodOf umbraco.services.notificationsService - * - * @description - * Adds a green success notication to the notications collection - * This should be used when an operations *completes* without errors - * - * @param {String} headline Headline of the notification - * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded - * @returns {Object} notification object - */ - success: function (headline, message) { - return service.add({ headline: headline, message: message, type: 'success', time: new Date() }); - }, - /** - * @ngdoc method - * @name umbraco.services.notificationsService#error - * @methodOf umbraco.services.notificationsService - * - * @description - * Adds a red error notication to the notications collection - * This should be used when an operations *fails* and could not complete - * - * @param {String} headline Headline of the notification - * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded - * @returns {Object} notification object - */ - error: function (headline, message) { - return service.add({ headline: headline, message: message, type: 'error', time: new Date() }); - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#warning - * @methodOf umbraco.services.notificationsService - * - * @description - * Adds a yellow warning notication to the notications collection - * This should be used when an operations *completes* but something was not as expected - * - * - * @param {String} headline Headline of the notification - * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded - * @returns {Object} notification object - */ - warning: function (headline, message) { - return service.add({ headline: headline, message: message, type: 'warning', time: new Date() }); - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#warning - * @methodOf umbraco.services.notificationsService - * - * @description - * Adds a yellow warning notication to the notications collection - * This should be used when an operations *completes* but something was not as expected - * - * - * @param {String} headline Headline of the notification - * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded - * @returns {Object} notification object - */ - info: function (headline, message) { - return service.add({ headline: headline, message: message, type: 'info', time: new Date() }); - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#remove - * @methodOf umbraco.services.notificationsService - * - * @description - * Removes a notification from the notifcations collection at a given index - * - * @param {Int} index index where the notication should be removed from - */ - remove: function (index) { - if(angular.isObject(index)){ - var i = nArray.indexOf(index); - angularHelper.safeApply($rootScope, function() { - nArray.splice(i, 1); - }); - }else{ - angularHelper.safeApply($rootScope, function() { - nArray.splice(index, 1); - }); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#removeAll - * @methodOf umbraco.services.notificationsService - * - * @description - * Removes all notifications from the notifcations collection - */ - removeAll: function () { - angularHelper.safeApply($rootScope, function() { - nArray = []; - }); - }, - - /** - * @ngdoc property - * @name umbraco.services.notificationsService#current - * @propertyOf umbraco.services.notificationsService - * - * @description - * Returns an array of current notifications to display - * - * @returns {string} returns an array - */ - current: nArray, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#getCurrent - * @methodOf umbraco.services.notificationsService - * - * @description - * Method to return all notifications from the notifcations collection - */ - getCurrent: function(){ - return nArray; - } - }; - - return service; +/** + * @ngdoc service + * @name umbraco.services.notificationsService + * + * @requires $rootScope + * @requires $timeout + * @requires angularHelper + * + * @description + * Application-wide service for handling notifications, the umbraco application + * maintains a single collection of notications, which the UI watches for changes. + * By default when a notication is added, it is automaticly removed 7 seconds after + * This can be changed on add() + * + * ##usage + * To use, simply inject the notificationsService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *		notificationsService.success("Document Published", "hooraaaay for you!");
    + *      notificationsService.error("Document Failed", "booooh");
    + * 
    + */ +angular.module('umbraco.services') +.factory('notificationsService', function ($rootScope, $timeout, angularHelper) { + + var nArray = []; + function setViewPath(view){ + if(view.indexOf('/') < 0) + { + view = "views/common/notifications/" + view; + } + + if(view.indexOf('.html') < 0) + { + view = view + ".html"; + } + return view; + } + + var service = { + + /** + * @ngdoc method + * @name umbraco.services.notificationsService#add + * @methodOf umbraco.services.notificationsService + * + * @description + * Lower level api for adding notifcations, support more advanced options + * @param {Object} item The notification item + * @param {String} item.headline Short headline + * @param {String} item.message longer text for the notication, trimmed after 200 characters, which can then be exanded + * @param {String} item.type Notification type, can be: "success","warning","error" or "info" + * @param {String} item.url url to open when notification is clicked + * @param {String} item.view path to custom view to load into the notification box + * @param {Array} item.actions Collection of button actions to append (label, func, cssClass) + * @param {Boolean} item.sticky if set to true, the notification will not auto-close + * @returns {Object} args notification object + */ + + add: function(item) { + angularHelper.safeApply($rootScope, function () { + + if(item.view){ + item.view = setViewPath(item.view); + item.sticky = true; + item.type = "form"; + item.headline = null; + } + + + //add a colon after the headline if there is a message as well + if (item.message) { + item.headline += ": "; + if(item.message.length > 200) { + item.sticky = true; + } + } + + //we need to ID the item, going by index isn't good enough because people can remove at different indexes + // whenever they want. Plus once we remove one, then the next index will be different. The only way to + // effectively remove an item is by an Id. + item.id = String.CreateGuid(); + + nArray.push(item); + + if(!item.sticky) { + $timeout(function() { + var found = _.find(nArray, function(i) { + return i.id === item.id; + }); + + if (found) { + var index = nArray.indexOf(found); + nArray.splice(index, 1); + } + + }, 7000); + } + + return item; + }); + + }, + + hasView : function(view){ + if(!view){ + return _.find(nArray, function(notification){ return notification.view;}); + }else{ + view = setViewPath(view).toLowerCase(); + return _.find(nArray, function(notification){ return notification.view.toLowerCase() === view;}); + } + }, + addView: function(view, args){ + var item = { + args: args, + view: view + }; + + service.add(item); + }, + + /** + * @ngdoc method + * @name umbraco.services.notificationsService#showNotification + * @methodOf umbraco.services.notificationsService + * + * @description + * Shows a notification based on the object passed in, normally used to render notifications sent back from the server + * + * @returns {Object} args notification object + */ + showNotification: function(args) { + if (!args) { + throw "args cannot be null"; + } + if (args.type === undefined || args.type === null) { + throw "args.type cannot be null"; + } + if (!args.header) { + throw "args.header cannot be null"; + } + + switch(args.type) { + case 0: + //save + this.success(args.header, args.message); + break; + case 1: + //info + this.success(args.header, args.message); + break; + case 2: + //error + this.error(args.header, args.message); + break; + case 3: + //success + this.success(args.header, args.message); + break; + case 4: + //warning + this.warning(args.header, args.message); + break; + } + }, + + /** + * @ngdoc method + * @name umbraco.services.notificationsService#success + * @methodOf umbraco.services.notificationsService + * + * @description + * Adds a green success notication to the notications collection + * This should be used when an operations *completes* without errors + * + * @param {String} headline Headline of the notification + * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded + * @returns {Object} notification object + */ + success: function (headline, message) { + return service.add({ headline: headline, message: message, type: 'success', time: new Date() }); + }, + /** + * @ngdoc method + * @name umbraco.services.notificationsService#error + * @methodOf umbraco.services.notificationsService + * + * @description + * Adds a red error notication to the notications collection + * This should be used when an operations *fails* and could not complete + * + * @param {String} headline Headline of the notification + * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded + * @returns {Object} notification object + */ + error: function (headline, message) { + return service.add({ headline: headline, message: message, type: 'error', time: new Date() }); + }, + + /** + * @ngdoc method + * @name umbraco.services.notificationsService#warning + * @methodOf umbraco.services.notificationsService + * + * @description + * Adds a yellow warning notication to the notications collection + * This should be used when an operations *completes* but something was not as expected + * + * + * @param {String} headline Headline of the notification + * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded + * @returns {Object} notification object + */ + warning: function (headline, message) { + return service.add({ headline: headline, message: message, type: 'warning', time: new Date() }); + }, + + /** + * @ngdoc method + * @name umbraco.services.notificationsService#warning + * @methodOf umbraco.services.notificationsService + * + * @description + * Adds a yellow warning notication to the notications collection + * This should be used when an operations *completes* but something was not as expected + * + * + * @param {String} headline Headline of the notification + * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded + * @returns {Object} notification object + */ + info: function (headline, message) { + return service.add({ headline: headline, message: message, type: 'info', time: new Date() }); + }, + + /** + * @ngdoc method + * @name umbraco.services.notificationsService#remove + * @methodOf umbraco.services.notificationsService + * + * @description + * Removes a notification from the notifcations collection at a given index + * + * @param {Int} index index where the notication should be removed from + */ + remove: function (index) { + if(angular.isObject(index)){ + var i = nArray.indexOf(index); + angularHelper.safeApply($rootScope, function() { + nArray.splice(i, 1); + }); + }else{ + angularHelper.safeApply($rootScope, function() { + nArray.splice(index, 1); + }); + } + }, + + /** + * @ngdoc method + * @name umbraco.services.notificationsService#removeAll + * @methodOf umbraco.services.notificationsService + * + * @description + * Removes all notifications from the notifcations collection + */ + removeAll: function () { + angularHelper.safeApply($rootScope, function() { + nArray = []; + }); + }, + + /** + * @ngdoc property + * @name umbraco.services.notificationsService#current + * @propertyOf umbraco.services.notificationsService + * + * @description + * Returns an array of current notifications to display + * + * @returns {string} returns an array + */ + current: nArray, + + /** + * @ngdoc method + * @name umbraco.services.notificationsService#getCurrent + * @methodOf umbraco.services.notificationsService + * + * @description + * Method to return all notifications from the notifcations collection + */ + getCurrent: function(){ + return nArray; + } + }; + + return service; }); \ No newline at end of file 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 8738c1011e..f0a3239602 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 @@ -1,160 +1,160 @@ -/** - * @ngdoc service - * @name umbraco.services.searchService - * - * - * @description - * Service for handling the main application search, can currently search content, media and members - * - * ##usage - * To use, simply inject the searchService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *      searchService.searchMembers({term: 'bob'}).then(function(results){
    - *          angular.forEach(results, function(result){
    - *                  //returns:
    - *                  {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
    - *           })          
    - *           var result = 
    - *       }) 
    - * 
    - */ -angular.module('umbraco.services') - .factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { - - return { - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchMembers - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default member search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching members - */ - searchMembers: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureMemberResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchContent - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default internal content search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching content items - */ - searchContent: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureContentResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchMedia - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default media search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching media items - */ - searchMedia: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureMediaResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchAll - * @methodOf umbraco.services.searchService - * - * @description - * Searches all available indexes and returns all results in one collection - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching items - */ - searchAll: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - return entityResource.searchAll(args.term, args.canceler).then(function (data) { - - _.each(data, function (resultByType) { +/** + * @ngdoc service + * @name umbraco.services.searchService + * + * + * @description + * Service for handling the main application search, can currently search content, media and members + * + * ##usage + * To use, simply inject the searchService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *      searchService.searchMembers({term: 'bob'}).then(function(results){
    + *          angular.forEach(results, function(result){
    + *                  //returns:
    + *                  {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
    + *           })          
    + *           var result = 
    + *       }) 
    + * 
    + */ +angular.module('umbraco.services') + .factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { + + return { + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchMembers + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default member search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching members + */ + searchMembers: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureMemberResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchContent + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default internal content search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching content items + */ + searchContent: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureContentResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchMedia + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default media search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching media items + */ + searchMedia: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureMediaResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchAll + * @methodOf umbraco.services.searchService + * + * @description + * Searches all available indexes and returns all results in one collection + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching items + */ + searchAll: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + return entityResource.searchAll(args.term, args.canceler).then(function (data) { + + _.each(data, function (resultByType) { //we need to format the search result data to include things like the subtitle, urls, etc... // this is done with registered angular services as part of the SearchableTreeAttribute, if that - // is not found, than we format with the default formatter + // is not found, than we format with the default formatter var formatterMethod = searchResultFormatter.configureDefaultResult; //check if a custom formatter is specified... - if (resultByType.jsSvc) { - var searchFormatterService = $injector.get(resultByType.jsSvc); - if (searchFormatterService) { - if (!resultByType.jsMethod) { - resultByType.jsMethod = "format"; - } - formatterMethod = searchFormatterService[resultByType.jsMethod]; - - if (!formatterMethod) { - throw "The method " + resultByType.jsMethod + " on the angular service " + resultByType.jsSvc + " could not be found"; - } - } - } - //now apply the formatter for each result - _.each(resultByType.results, function (item) { - formatterMethod.apply(this, [item, resultByType.treeAlias, resultByType.appAlias]); + if (resultByType.jsSvc) { + var searchFormatterService = $injector.get(resultByType.jsSvc); + if (searchFormatterService) { + if (!resultByType.jsMethod) { + resultByType.jsMethod = "format"; + } + formatterMethod = searchFormatterService[resultByType.jsMethod]; + + if (!formatterMethod) { + throw "The method " + resultByType.jsMethod + " on the angular service " + resultByType.jsSvc + " could not be found"; + } + } + } + //now apply the formatter for each result + _.each(resultByType.results, function (item) { + formatterMethod.apply(this, [item, resultByType.treeAlias, resultByType.appAlias]); }); - - }); - - return data; - }); - - }, - - //TODO: This doesn't do anything! - setCurrent: function (sectionAlias) { - - var currentSection = sectionAlias; - } - }; + + }); + + return data; + }); + + }, + + //TODO: This doesn't do anything! + setCurrent: function (sectionAlias) { + + var currentSection = sectionAlias; + } + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js index fdc81a3989..d7e34d0f1c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js @@ -1,383 +1,383 @@ -/** - * @ngdoc service - * @name umbraco.services.serverValidationManager - * @function - * - * @description - * Used to handle server side validation and wires up the UI with the messages. There are 2 types of validation messages, one - * is for user defined properties (called Properties) and the other is for field properties which are attached to the native - * model objects (not user defined). The methods below are named according to these rules: Properties vs Fields. - */ -function serverValidationManager($timeout) { - - var callbacks = []; - - /** calls the callback specified with the errors specified, used internally */ - function executeCallback(self, errorsForCallback, callback) { - - callback.apply(self, [ - false, //pass in a value indicating it is invalid - errorsForCallback, //pass in the errors for this item - self.items]); //pass in all errors in total - } - - function getFieldErrors(self, fieldName) { - if (!angular.isString(fieldName)) { - throw "fieldName must be a string"; - } - - //find errors for this field name - return _.filter(self.items, function (item) { - return (item.propertyAlias === null && item.fieldName === fieldName); - }); - } - - function getPropertyErrors(self, propertyAlias, fieldName) { - if (!angular.isString(propertyAlias)) { - throw "propertyAlias must be a string"; - } - if (fieldName && !angular.isString(fieldName)) { - throw "fieldName must be a string"; - } - - //find all errors for this property - return _.filter(self.items, function (item) { - return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - } - - return { - - /** - * @ngdoc function - * @name umbraco.services.serverValidationManager#subscribe - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * This method needs to be called once all field and property errors are wired up. - * - * In some scenarios where the error collection needs to be persisted over a route change - * (i.e. when a content item (or any item) is created and the route redirects to the editor) - * the controller should call this method once the data is bound to the scope - * so that any persisted validation errors are re-bound to their controls. Once they are re-binded this then clears the validation - * colleciton so that if another route change occurs, the previously persisted validation errors are not re-bound to the new item. - */ - executeAndClearAllSubscriptions: function() { - - var self = this; - - $timeout(function () { - - for (var cb in callbacks) { - if (callbacks[cb].propertyAlias === null) { - //its a field error callback - var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName); - if (fieldErrors.length > 0) { - executeCallback(self, fieldErrors, callbacks[cb].callback); - } - } - else { - //its a property error - var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].fieldName); - if (propErrors.length > 0) { - executeCallback(self, propErrors, callbacks[cb].callback); - } - } - } - //now that they are all executed, we're gonna clear all of the errors we have - self.clear(); - }); - }, - - /** - * @ngdoc function - * @name umbraco.services.serverValidationManager#subscribe - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Adds a callback method that is executed whenever validation changes for the field name + property specified. - * This is generally used for server side validation in order to match up a server side validation error with - * a particular field, otherwise we can only pinpoint that there is an error for a content property, not the - * property's specific field. This is used with the val-server directive in which the directive specifies the - * field alias to listen for. - * If propertyAlias is null, then this subscription is for a field property (not a user defined property). - */ - subscribe: function (propertyAlias, fieldName, callback) { - if (!callback) { - return; - } - - if (propertyAlias === null) { - //don't add it if it already exists - var exists1 = _.find(callbacks, function (item) { - return item.propertyAlias === null && item.fieldName === fieldName; - }); - if (!exists1) { - callbacks.push({ propertyAlias: null, fieldName: fieldName, callback: callback }); - } - } - else if (propertyAlias !== undefined) { - //don't add it if it already exists - var exists2 = _.find(callbacks, function (item) { - return item.propertyAlias === propertyAlias && item.fieldName === fieldName; - }); - if (!exists2) { - callbacks.push({ propertyAlias: propertyAlias, fieldName: fieldName, callback: callback }); - } - } - }, - - unsubscribe: function (propertyAlias, fieldName) { - - if (propertyAlias === null) { - - //remove all callbacks for the content field - callbacks = _.reject(callbacks, function (item) { - return item.propertyAlias === null && item.fieldName === fieldName; - }); - - } - else if (propertyAlias !== undefined) { - - //remove all callbacks for the content property - callbacks = _.reject(callbacks, function (item) { - return item.propertyAlias === propertyAlias && - (item.fieldName === fieldName || - ((item.fieldName === undefined || item.fieldName === "") && (fieldName === undefined || fieldName === ""))); - }); - } - - - }, - - - /** - * @ngdoc function - * @name getPropertyCallbacks - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets all callbacks that has been registered using the subscribe method for the propertyAlias + fieldName combo. - * This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an - * explicit field name set. - */ - getPropertyCallbacks: function (propertyAlias, fieldName) { - var found = _.filter(callbacks, function (item) { - //returns any callback that have been registered directly against the field and for only the property - return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === ""))); - }); - return found; - }, - - /** - * @ngdoc function - * @name getFieldCallbacks - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets all callbacks that has been registered using the subscribe method for the field. - */ - getFieldCallbacks: function (fieldName) { - var found = _.filter(callbacks, function (item) { - //returns any callback that have been registered directly against the field - return (item.propertyAlias === null && item.fieldName === fieldName); - }); - return found; - }, - - /** - * @ngdoc function - * @name addFieldError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Adds an error message for a native content item field (not a user defined property, for Example, 'Name') - */ - addFieldError: function(fieldName, errorMsg) { - if (!fieldName) { - return; - } - - //only add the item if it doesn't exist - if (!this.hasFieldError(fieldName)) { - this.items.push({ - propertyAlias: null, - fieldName: fieldName, - errorMsg: errorMsg - }); - } - - //find all errors for this item - var errorsForCallback = getFieldErrors(this, fieldName); - //we should now call all of the call backs registered for this error - var cbs = this.getFieldCallbacks(fieldName); - //call each callback for this error - for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback); - } - }, - - /** - * @ngdoc function - * @name addPropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Adds an error message for the content property - */ - addPropertyError: function (propertyAlias, fieldName, errorMsg) { - if (!propertyAlias) { - return; - } - - //only add the item if it doesn't exist - if (!this.hasPropertyError(propertyAlias, fieldName)) { - this.items.push({ - propertyAlias: propertyAlias, - fieldName: fieldName, - errorMsg: errorMsg - }); - } - - //find all errors for this item - var errorsForCallback = getPropertyErrors(this, propertyAlias, fieldName); - //we should now call all of the call backs registered for this error - var cbs = this.getPropertyCallbacks(propertyAlias, fieldName); - //call each callback for this error - for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback); - } - }, - - /** - * @ngdoc function - * @name removePropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Removes an error message for the content property - */ - removePropertyError: function (propertyAlias, fieldName) { - - if (!propertyAlias) { - return; - } - //remove the item - this.items = _.reject(this.items, function (item) { - return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - }, - - /** - * @ngdoc function - * @name reset - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Clears all errors and notifies all callbacks that all server errros are now valid - used when submitting a form - */ - reset: function () { - this.clear(); - for (var cb in callbacks) { - callbacks[cb].callback.apply(this, [ - true, //pass in a value indicating it is VALID - [], //pass in empty collection - []]); //pass in empty collection - } - }, - - /** - * @ngdoc function - * @name clear - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Clears all errors - */ - clear: function() { - this.items = []; - }, - - /** - * @ngdoc function - * @name getPropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets the error message for the content property - */ - getPropertyError: function (propertyAlias, fieldName) { - var err = _.find(this.items, function (item) { - //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - return err; - }, - - /** - * @ngdoc function - * @name getFieldError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets the error message for a content field - */ - getFieldError: function (fieldName) { - var err = _.find(this.items, function (item) { - //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.fieldName === fieldName); - }); - return err; - }, - - /** - * @ngdoc function - * @name hasPropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Checks if the content property + field name combo has an error - */ - hasPropertyError: function (propertyAlias, fieldName) { - var err = _.find(this.items, function (item) { - //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - return err ? true : false; - }, - - /** - * @ngdoc function - * @name hasFieldError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Checks if a content field has an error - */ - hasFieldError: function (fieldName) { - var err = _.find(this.items, function (item) { - //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.fieldName === fieldName); - }); - return err ? true : false; - }, - - /** The array of error messages */ - items: [] - }; -} - +/** + * @ngdoc service + * @name umbraco.services.serverValidationManager + * @function + * + * @description + * Used to handle server side validation and wires up the UI with the messages. There are 2 types of validation messages, one + * is for user defined properties (called Properties) and the other is for field properties which are attached to the native + * model objects (not user defined). The methods below are named according to these rules: Properties vs Fields. + */ +function serverValidationManager($timeout) { + + var callbacks = []; + + /** calls the callback specified with the errors specified, used internally */ + function executeCallback(self, errorsForCallback, callback) { + + callback.apply(self, [ + false, //pass in a value indicating it is invalid + errorsForCallback, //pass in the errors for this item + self.items]); //pass in all errors in total + } + + function getFieldErrors(self, fieldName) { + if (!angular.isString(fieldName)) { + throw "fieldName must be a string"; + } + + //find errors for this field name + return _.filter(self.items, function (item) { + return (item.propertyAlias === null && item.fieldName === fieldName); + }); + } + + function getPropertyErrors(self, propertyAlias, fieldName) { + if (!angular.isString(propertyAlias)) { + throw "propertyAlias must be a string"; + } + if (fieldName && !angular.isString(fieldName)) { + throw "fieldName must be a string"; + } + + //find all errors for this property + return _.filter(self.items, function (item) { + return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + }); + } + + return { + + /** + * @ngdoc function + * @name umbraco.services.serverValidationManager#subscribe + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * This method needs to be called once all field and property errors are wired up. + * + * In some scenarios where the error collection needs to be persisted over a route change + * (i.e. when a content item (or any item) is created and the route redirects to the editor) + * the controller should call this method once the data is bound to the scope + * so that any persisted validation errors are re-bound to their controls. Once they are re-binded this then clears the validation + * colleciton so that if another route change occurs, the previously persisted validation errors are not re-bound to the new item. + */ + executeAndClearAllSubscriptions: function() { + + var self = this; + + $timeout(function () { + + for (var cb in callbacks) { + if (callbacks[cb].propertyAlias === null) { + //its a field error callback + var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName); + if (fieldErrors.length > 0) { + executeCallback(self, fieldErrors, callbacks[cb].callback); + } + } + else { + //its a property error + var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].fieldName); + if (propErrors.length > 0) { + executeCallback(self, propErrors, callbacks[cb].callback); + } + } + } + //now that they are all executed, we're gonna clear all of the errors we have + self.clear(); + }); + }, + + /** + * @ngdoc function + * @name umbraco.services.serverValidationManager#subscribe + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Adds a callback method that is executed whenever validation changes for the field name + property specified. + * This is generally used for server side validation in order to match up a server side validation error with + * a particular field, otherwise we can only pinpoint that there is an error for a content property, not the + * property's specific field. This is used with the val-server directive in which the directive specifies the + * field alias to listen for. + * If propertyAlias is null, then this subscription is for a field property (not a user defined property). + */ + subscribe: function (propertyAlias, fieldName, callback) { + if (!callback) { + return; + } + + if (propertyAlias === null) { + //don't add it if it already exists + var exists1 = _.find(callbacks, function (item) { + return item.propertyAlias === null && item.fieldName === fieldName; + }); + if (!exists1) { + callbacks.push({ propertyAlias: null, fieldName: fieldName, callback: callback }); + } + } + else if (propertyAlias !== undefined) { + //don't add it if it already exists + var exists2 = _.find(callbacks, function (item) { + return item.propertyAlias === propertyAlias && item.fieldName === fieldName; + }); + if (!exists2) { + callbacks.push({ propertyAlias: propertyAlias, fieldName: fieldName, callback: callback }); + } + } + }, + + unsubscribe: function (propertyAlias, fieldName) { + + if (propertyAlias === null) { + + //remove all callbacks for the content field + callbacks = _.reject(callbacks, function (item) { + return item.propertyAlias === null && item.fieldName === fieldName; + }); + + } + else if (propertyAlias !== undefined) { + + //remove all callbacks for the content property + callbacks = _.reject(callbacks, function (item) { + return item.propertyAlias === propertyAlias && + (item.fieldName === fieldName || + ((item.fieldName === undefined || item.fieldName === "") && (fieldName === undefined || fieldName === ""))); + }); + } + + + }, + + + /** + * @ngdoc function + * @name getPropertyCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the propertyAlias + fieldName combo. + * This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an + * explicit field name set. + */ + getPropertyCallbacks: function (propertyAlias, fieldName) { + var found = _.filter(callbacks, function (item) { + //returns any callback that have been registered directly against the field and for only the property + return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === ""))); + }); + return found; + }, + + /** + * @ngdoc function + * @name getFieldCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the field. + */ + getFieldCallbacks: function (fieldName) { + var found = _.filter(callbacks, function (item) { + //returns any callback that have been registered directly against the field + return (item.propertyAlias === null && item.fieldName === fieldName); + }); + return found; + }, + + /** + * @ngdoc function + * @name addFieldError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Adds an error message for a native content item field (not a user defined property, for Example, 'Name') + */ + addFieldError: function(fieldName, errorMsg) { + if (!fieldName) { + return; + } + + //only add the item if it doesn't exist + if (!this.hasFieldError(fieldName)) { + this.items.push({ + propertyAlias: null, + fieldName: fieldName, + errorMsg: errorMsg + }); + } + + //find all errors for this item + var errorsForCallback = getFieldErrors(this, fieldName); + //we should now call all of the call backs registered for this error + var cbs = this.getFieldCallbacks(fieldName); + //call each callback for this error + for (var cb in cbs) { + executeCallback(this, errorsForCallback, cbs[cb].callback); + } + }, + + /** + * @ngdoc function + * @name addPropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Adds an error message for the content property + */ + addPropertyError: function (propertyAlias, fieldName, errorMsg) { + if (!propertyAlias) { + return; + } + + //only add the item if it doesn't exist + if (!this.hasPropertyError(propertyAlias, fieldName)) { + this.items.push({ + propertyAlias: propertyAlias, + fieldName: fieldName, + errorMsg: errorMsg + }); + } + + //find all errors for this item + var errorsForCallback = getPropertyErrors(this, propertyAlias, fieldName); + //we should now call all of the call backs registered for this error + var cbs = this.getPropertyCallbacks(propertyAlias, fieldName); + //call each callback for this error + for (var cb in cbs) { + executeCallback(this, errorsForCallback, cbs[cb].callback); + } + }, + + /** + * @ngdoc function + * @name removePropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Removes an error message for the content property + */ + removePropertyError: function (propertyAlias, fieldName) { + + if (!propertyAlias) { + return; + } + //remove the item + this.items = _.reject(this.items, function (item) { + return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + }); + }, + + /** + * @ngdoc function + * @name reset + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Clears all errors and notifies all callbacks that all server errros are now valid - used when submitting a form + */ + reset: function () { + this.clear(); + for (var cb in callbacks) { + callbacks[cb].callback.apply(this, [ + true, //pass in a value indicating it is VALID + [], //pass in empty collection + []]); //pass in empty collection + } + }, + + /** + * @ngdoc function + * @name clear + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Clears all errors + */ + clear: function() { + this.items = []; + }, + + /** + * @ngdoc function + * @name getPropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets the error message for the content property + */ + getPropertyError: function (propertyAlias, fieldName) { + var err = _.find(this.items, function (item) { + //return true if the property alias matches and if an empty field name is specified or the field name matches + return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + }); + return err; + }, + + /** + * @ngdoc function + * @name getFieldError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets the error message for a content field + */ + getFieldError: function (fieldName) { + var err = _.find(this.items, function (item) { + //return true if the property alias matches and if an empty field name is specified or the field name matches + return (item.propertyAlias === null && item.fieldName === fieldName); + }); + return err; + }, + + /** + * @ngdoc function + * @name hasPropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if the content property + field name combo has an error + */ + hasPropertyError: function (propertyAlias, fieldName) { + var err = _.find(this.items, function (item) { + //return true if the property alias matches and if an empty field name is specified or the field name matches + return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + }); + return err ? true : false; + }, + + /** + * @ngdoc function + * @name hasFieldError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if a content field has an error + */ + hasFieldError: function (fieldName) { + var err = _.find(this.items, function (item) { + //return true if the property alias matches and if an empty field name is specified or the field name matches + return (item.propertyAlias === null && item.fieldName === fieldName); + }); + return err ? true : false; + }, + + /** The array of error messages */ + items: [] + }; +} + angular.module('umbraco.services').factory('serverValidationManager', serverValidationManager); \ No newline at end of file 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 86de5b586d..15953c0438 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 @@ -1,816 +1,816 @@ -/** - * @ngdoc service - * @name umbraco.services.tinyMceService - * - * - * @description - * A service containing all logic for all of the Umbraco TinyMCE plugins - */ -function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService) { - return { - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#configuration - * @methodOf umbraco.services.tinyMceService - * - * @description - * Returns a collection of plugins available to the tinyMCE editor - * - */ - configuration: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "rteApiBaseUrl", - "GetConfiguration"), { cache: true }), - 'Failed to retrieve tinymce configuration'); - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#defaultPrevalues - * @methodOf umbraco.services.tinyMceService - * - * @description - * Returns a default configration to fallback on in case none is provided - * - */ - defaultPrevalues: function () { - var cfg = {}; - cfg.toolbar = ["code", "bold", "italic", "styleselect","alignleft", "aligncenter", "alignright", "bullist","numlist", "outdent", "indent", "link", "image", "umbmediapicker", "umbembeddialog", "umbmacro"]; - cfg.stylesheets = []; - cfg.dimensions = { height: 500 }; - cfg.maxImageSize = 500; - return cfg; - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createInsertEmbeddedMedia - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the umbrco insert embedded media tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createInsertEmbeddedMedia: function (editor, scope, callback) { - editor.addButton('umbembeddialog', { - icon: 'custom icon-tv', - tooltip: 'Embed', - onclick: function () { - if (callback) { - callback(); - } - } - }); - }, - - insertEmbeddedMediaInEditor: function(editor, preview) { - editor.insertContent(preview); - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createMediaPicker - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the umbrco insert media tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createMediaPicker: function (editor, scope, callback) { - editor.addButton('umbmediapicker', { - icon: 'custom icon-picture', - tooltip: 'Media Picker', - stateSelector: 'img', - onclick: function () { - - var selectedElm = editor.selection.getNode(), - currentTarget; - - - if(selectedElm.nodeName === 'IMG'){ - var img = $(selectedElm); - - var hasUdi = img.attr("data-udi") ? true : false; - - currentTarget = { - altText: img.attr("alt"), - url: img.attr("src") - }; - - if (hasUdi) { - currentTarget["udi"] = img.attr("data-udi"); - } - else { - currentTarget["id"] = img.attr("rel"); - } - } - - userService.getCurrentUser().then(function(userData) { - if(callback) { - callback(currentTarget, userData); - } - }); - - } - }); - }, - - insertMediaInEditor: function(editor, img) { - if(img) { - - var hasUdi = img.udi ? true : false; - - var data = { - alt: img.altText || "", - src: (img.url) ? img.url : "nothing.jpg", - id: '__mcenew' - }; - - if (hasUdi) { - data["data-udi"] = img.udi; - } - else { - //Considering these fixed because UDI will now be used and thus - // we have no need for rel http://issues.umbraco.org/issue/U4-6228, http://issues.umbraco.org/issue/U4-6595 - data["rel"] = img.id; - data["data-id"] = img.id; - } - - editor.insertContent(editor.dom.createHTML('img', data)); - - $timeout(function () { - var imgElm = editor.dom.get('__mcenew'); - var size = editor.dom.getSize(imgElm); - - if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) { - var newSize = imageHelper.scaleToMaxSize(editor.settings.maxImageSize, size.w, size.h); - - var s = "width: " + newSize.width + "px; height:" + newSize.height + "px;"; - editor.dom.setAttrib(imgElm, 'style', s); - editor.dom.setAttrib(imgElm, 'id', null); - - if (img.url) { - var src = img.url + "?width=" + newSize.width + "&height=" + newSize.height; - editor.dom.setAttrib(imgElm, 'data-mce-src', src); - } - } - }, 500); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createUmbracoMacro - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the insert umbrco macro tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createInsertMacro: function (editor, $scope, callback) { - - var createInsertMacroScope = this; - - /** Adds custom rules for the macro plugin and custom serialization */ - editor.on('preInit', function (args) { - //this is requires so that we tell the serializer that a 'div' is actually allowed in the root, otherwise the cleanup will strip it out - editor.serializer.addRules('div'); - - /** This checks if the div is a macro container, if so, checks if its wrapped in a p tag and then unwraps it (removes p tag) */ - editor.serializer.addNodeFilter('div', function (nodes, name) { - for (var i = 0; i < nodes.length; i++) { - if (nodes[i].attr("class") === "umb-macro-holder" && nodes[i].parent && nodes[i].parent.name.toUpperCase() === "P") { - nodes[i].parent.unwrap(); - } - } - }); - - }); - - /** - * Because the macro gets wrapped in a P tag because of the way 'enter' works, this - * method will return the macro element if not wrapped in a p, or the p if the macro - * element is the only one inside of it even if we are deep inside an element inside the macro - */ - function getRealMacroElem(element) { - var e = $(element).closest(".umb-macro-holder"); - if (e.length > 0) { - if (e.get(0).parentNode.nodeName === "P") { - //now check if we're the only element - if (element.parentNode.childNodes.length === 1) { - return e.get(0).parentNode; - } - } - return e.get(0); - } - return null; - } - - /** Adds the button instance */ - editor.addButton('umbmacro', { - icon: 'custom icon-settings-alt', - tooltip: 'Insert macro', - onPostRender: function () { - - var ctrl = this; - var isOnMacroElement = false; - - /** - if the selection comes from a different element that is not the macro's - we need to check if the selection includes part of the macro, if so we'll force the selection - to clear to the next element since if people can select part of the macro markup they can then modify it. - */ - function handleSelectionChange() { - - if (!editor.selection.isCollapsed()) { - var endSelection = tinymce.activeEditor.selection.getEnd(); - var startSelection = tinymce.activeEditor.selection.getStart(); - //don't proceed if it's an entire element selected - if (endSelection !== startSelection) { - - //if the end selection is a macro then move the cursor - //NOTE: we don't have to handle when the selection comes from a previous parent because - // that is automatically taken care of with the normal onNodeChanged logic since the - // evt.element will be the macro once it becomes part of the selection. - var $testForMacro = $(endSelection).closest(".umb-macro-holder"); - if ($testForMacro.length > 0) { - - //it came from before so move after, if there is no after then select ourselves - var next = $testForMacro.next(); - if (next.length > 0) { - editor.selection.setCursorLocation($testForMacro.next().get(0)); - } - else { - selectMacroElement($testForMacro.get(0)); - } - - } - } - } - } - - /** helper method to select the macro element */ - function selectMacroElement(macroElement) { - - // move selection to top element to ensure we can't edit this - editor.selection.select(macroElement); - - // check if the current selection *is* the element (ie bug) - var currentSelection = editor.selection.getStart(); - if (tinymce.isIE) { - if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) { - while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) { - currentSelection = currentSelection.parentNode; - } - editor.selection.select(currentSelection); - } - } - } - - /** - * Add a node change handler, test if we're editing a macro and select the whole thing, then set our isOnMacroElement flag. - * If we change the selection inside this method, then we end up in an infinite loop, so we have to remove ourselves - * from the event listener before changing selection, however, it seems that putting a break point in this method - * will always cause an 'infinite' loop as the caret keeps changing. - */ - function onNodeChanged(evt) { - - //set our macro button active when on a node of class umb-macro-holder - var $macroElement = $(evt.element).closest(".umb-macro-holder"); - - handleSelectionChange(); - - //set the button active - ctrl.active($macroElement.length !== 0); - - if ($macroElement.length > 0) { - var macroElement = $macroElement.get(0); - - //remove the event listener before re-selecting - editor.off('NodeChange', onNodeChanged); - - selectMacroElement(macroElement); - - //set the flag - isOnMacroElement = true; - - //re-add the event listener - editor.on('NodeChange', onNodeChanged); - } - else { - isOnMacroElement = false; - } - - } - - /** when the contents load we need to find any macros declared and load in their content */ - editor.on("LoadContent", function (o) { - - //get all macro divs and load their content - $(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function() { - createInsertMacroScope.loadMacroContent($(this), null, $scope); - }); - - }); - - /** This prevents any other commands from executing when the current element is the macro so the content cannot be edited */ - editor.on('BeforeExecCommand', function (o) { - if (isOnMacroElement) { - if (o.preventDefault) { - o.preventDefault(); - } - if (o.stopImmediatePropagation) { - o.stopImmediatePropagation(); - } - return; - } - }); - - /** This double checks and ensures you can't paste content into the rendered macro */ - editor.on("Paste", function (o) { - if (isOnMacroElement) { - if (o.preventDefault) { - o.preventDefault(); - } - if (o.stopImmediatePropagation) { - o.stopImmediatePropagation(); - } - return; - } - }); - - //set onNodeChanged event listener - editor.on('NodeChange', onNodeChanged); - - /** - * Listen for the keydown in the editor, we'll check if we are currently on a macro element, if so - * we'll check if the key down is a supported key which requires an action, otherwise we ignore the request - * so the macro cannot be edited. - */ - editor.on('KeyDown', function (e) { - if (isOnMacroElement) { - var macroElement = editor.selection.getNode(); - - //get the 'real' element (either p or the real one) - macroElement = getRealMacroElem(macroElement); - - //prevent editing - e.preventDefault(); - e.stopPropagation(); - - var moveSibling = function (element, isNext) { - var $e = $(element); - var $sibling = isNext ? $e.next() : $e.prev(); - if ($sibling.length > 0) { - editor.selection.select($sibling.get(0)); - editor.selection.collapse(true); - } - else { - //if we're moving previous and there is no sibling, then lets recurse and just select the next one - if (!isNext) { - moveSibling(element, true); - return; - } - - //if there is no sibling we'll generate a new p at the end and select it - editor.setContent(editor.getContent() + "

     

    "); - editor.selection.select($(editor.dom.getRoot()).children().last().get(0)); - editor.selection.collapse(true); - - } - }; - - //supported keys to move to the next or prev element (13-enter, 27-esc, 38-up, 40-down, 39-right, 37-left) - //supported keys to remove the macro (8-backspace, 46-delete) - //TODO: Should we make the enter key insert a line break before or leave it as moving to the next element? - if ($.inArray(e.keyCode, [13, 40, 39]) !== -1) { - //move to next element - moveSibling(macroElement, true); - } - else if ($.inArray(e.keyCode, [27, 38, 37]) !== -1) { - //move to prev element - moveSibling(macroElement, false); - } - else if ($.inArray(e.keyCode, [8, 46]) !== -1) { - //delete macro element - - //move first, then delete - moveSibling(macroElement, false); - editor.dom.remove(macroElement); - } - return ; - } - }); - - }, - - /** The insert macro button click event handler */ - onclick: function () { - - var dialogData = { - //flag for use in rte so we only show macros flagged for the editor - richTextEditor: true - }; - - //when we click we could have a macro already selected and in that case we'll want to edit the current parameters - //so we'll need to extract them and submit them to the dialog. - var macroElement = editor.selection.getNode(); - macroElement = getRealMacroElem(macroElement); - if (macroElement) { - //we have a macro selected so we'll need to parse it's alias and parameters - var contents = $(macroElement).contents(); - var comment = _.find(contents, function(item) { - return item.nodeType === 8; - }); - if (!comment) { - throw "Cannot parse the current macro, the syntax in the editor is invalid"; - } - var syntax = comment.textContent.trim(); - var parsed = macroService.parseMacroSyntax(syntax); - dialogData = { - macroData: parsed - }; - } - - if(callback) { - callback(dialogData); - } - - } - }); - }, - - insertMacroInEditor: function(editor, macroObject, $scope) { - - //put the macro syntax in comments, we will parse this out on the server side to be used - //for persisting. - var macroSyntaxComment = ""; - //create an id class for this element so we can re-select it after inserting - var uniqueId = "umb-macro-" + editor.dom.uniqueId(); - var macroDiv = editor.dom.create('div', - { - 'class': 'umb-macro-holder ' + macroObject.macroAlias + ' mceNonEditable ' + uniqueId - }, - macroSyntaxComment + 'Macro alias: ' + macroObject.macroAlias + ''); - - editor.selection.setNode(macroDiv); - - var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId)); - - //async load the macro content - this.loadMacroContent($macroDiv, macroObject, $scope); - - }, - - /** loads in the macro content async from the server */ - loadMacroContent: function($macroDiv, macroData, $scope) { - - //if we don't have the macroData, then we'll need to parse it from the macro div - if (!macroData) { - var contents = $macroDiv.contents(); - var comment = _.find(contents, function (item) { - return item.nodeType === 8; - }); - if (!comment) { - throw "Cannot parse the current macro, the syntax in the editor is invalid"; - } - var syntax = comment.textContent.trim(); - var parsed = macroService.parseMacroSyntax(syntax); - macroData = parsed; - } - - var $ins = $macroDiv.find("ins"); - - //show the throbber - $macroDiv.addClass("loading"); - - var contentId = $routeParams.id; - - //need to wrap in safe apply since this might be occuring outside of angular - angularHelper.safeApply($scope, function() { - macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary) - .then(function (htmlResult) { - - $macroDiv.removeClass("loading"); - htmlResult = htmlResult.trim(); - if (htmlResult !== "") { - $ins.html(htmlResult); - } - }); - }); - - }, - - createLinkPicker: function(editor, $scope, onClick) { - - function createLinkList(callback) { - return function() { - var linkList = editor.settings.link_list; - - if (typeof(linkList) === "string") { - tinymce.util.XHR.send({ - url: linkList, - success: function(text) { - callback(tinymce.util.JSON.parse(text)); - } - }); - } else { - callback(linkList); - } - }; - } - - function showDialog(linkList) { - var data = {}, selection = editor.selection, dom = editor.dom, selectedElm, anchorElm, initialText; - var win, linkListCtrl, relListCtrl, targetListCtrl; - - function linkListChangeHandler(e) { - var textCtrl = win.find('#text'); - - if (!textCtrl.value() || (e.lastControl && textCtrl.value() === e.lastControl.text())) { - textCtrl.value(e.control.text()); - } - - win.find('#href').value(e.control.value()); - } - - function buildLinkList() { - var linkListItems = [{ - text: 'None', - value: '' - }]; - - tinymce.each(linkList, function(link) { - linkListItems.push({ - text: link.text || link.title, - value: link.value || link.url, - menu: link.menu - }); - }); - - return linkListItems; - } - - function buildRelList(relValue) { - var relListItems = [{ - text: 'None', - value: '' - }]; - - tinymce.each(editor.settings.rel_list, function(rel) { - relListItems.push({ - text: rel.text || rel.title, - value: rel.value, - selected: relValue === rel.value - }); - }); - - return relListItems; - } - - function buildTargetList(targetValue) { - var targetListItems = [{ - text: 'None', - value: '' - }]; - - if (!editor.settings.target_list) { - targetListItems.push({ - text: 'New window', - value: '_blank' - }); - } - - tinymce.each(editor.settings.target_list, function(target) { - targetListItems.push({ - text: target.text || target.title, - value: target.value, - selected: targetValue === target.value - }); - }); - - return targetListItems; - } - - function buildAnchorListControl(url) { - var anchorList = []; - - tinymce.each(editor.dom.select('a:not([href])'), function(anchor) { - var id = anchor.name || anchor.id; - - if (id) { - anchorList.push({ - text: id, - value: '#' + id, - selected: url.indexOf('#' + id) !== -1 - }); - } - }); - - if (anchorList.length) { - anchorList.unshift({ - text: 'None', - value: '' - }); - - return { - name: 'anchor', - type: 'listbox', - label: 'Anchors', - values: anchorList, - onselect: linkListChangeHandler - }; - } - } - - function updateText() { - if (!initialText && data.text.length === 0) { - this.parent().parent().find('#text')[0].value(this.value()); - } - } - - selectedElm = selection.getNode(); - anchorElm = dom.getParent(selectedElm, 'a[href]'); - - data.text = initialText = anchorElm ? (anchorElm.innerText || anchorElm.textContent) : selection.getContent({format: 'text'}); - data.href = anchorElm ? dom.getAttrib(anchorElm, 'href') : ''; - data.target = anchorElm ? dom.getAttrib(anchorElm, 'target') : ''; - data.rel = anchorElm ? dom.getAttrib(anchorElm, 'rel') : ''; - - if (selectedElm.nodeName === "IMG") { - data.text = initialText = " "; - } - - if (linkList) { - linkListCtrl = { - type: 'listbox', - label: 'Link list', - values: buildLinkList(), - onselect: linkListChangeHandler - }; - } - - if (editor.settings.target_list !== false) { - targetListCtrl = { - name: 'target', - type: 'listbox', - label: 'Target', - values: buildTargetList(data.target) - }; - } - - if (editor.settings.rel_list) { - relListCtrl = { - name: 'rel', - type: 'listbox', - label: 'Rel', - values: buildRelList(data.rel) - }; - } - - var injector = angular.element(document.getElementById("umbracoMainPageBody")).injector(); - var dialogService = injector.get("dialogService"); - var currentTarget = null; - - //if we already have a link selected, we want to pass that data over to the dialog - if(anchorElm){ - var anchor = $(anchorElm); - currentTarget = { - name: anchor.attr("title"), - url: anchor.attr("href"), - target: anchor.attr("target") - }; - - //locallink detection, we do this here, to avoid poluting the dialogservice - //so the dialog service can just expect to get a node-like structure - if (currentTarget.url.indexOf("localLink:") > 0) { - var linkId = currentTarget.url.substring(currentTarget.url.indexOf(":") + 1, currentTarget.url.length - 1); - //we need to check if this is an INT or a UDI - var parsedIntId = parseInt(linkId, 10); - if (isNaN(parsedIntId)) { - //it's a UDI - currentTarget.udi = linkId; - } - else { - currentTarget.id = linkId; - } - } - } - - if(onClick) { - onClick(currentTarget, anchorElm); - } - - } - - editor.addButton('link', { - icon: 'link', - tooltip: 'Insert/edit link', - shortcut: 'Ctrl+K', - onclick: createLinkList(showDialog), - stateSelector: 'a[href]' - }); - - editor.addButton('unlink', { - icon: 'unlink', - tooltip: 'Remove link', - cmd: 'unlink', - stateSelector: 'a[href]' - }); - - editor.addShortcut('Ctrl+K', '', createLinkList(showDialog)); - this.showDialog = showDialog; - - editor.addMenuItem('link', { - icon: 'link', - text: 'Insert link', - shortcut: 'Ctrl+K', - onclick: createLinkList(showDialog), - stateSelector: 'a[href]', - context: 'insert', - prependToContext: true - }); - - }, - - insertLinkInEditor: function(editor, target, anchorElm) { - - var href = target.url; - // We want to use the Udi. If it is set, we use it, else fallback to id, and finally to null - var hasUdi = target.udi ? true : false; - var id = hasUdi ? target.udi : (target.id ? target.id : null); - - //Create a json obj used to create the attributes for the tag - function createElemAttributes() { - var a = { - href: href, - title: target.name, - target: target.target ? target.target : null, - rel: target.rel ? target.rel : null - }; - if (hasUdi) { - a["data-udi"] = target.udi; - } - else if (target.id) { - a["data-id"] = target.id; - } - return a; - } - - function insertLink() { - if (anchorElm) { - editor.dom.setAttribs(anchorElm, createElemAttributes()); - - editor.selection.select(anchorElm); - editor.execCommand('mceEndTyping'); - } - else { - editor.execCommand('mceInsertLink', false, createElemAttributes()); - } - } - - if (!href) { - editor.execCommand('unlink'); - return; - } - - //if we have an id, it must be a locallink:id, aslong as the isMedia flag is not set - if(id && (angular.isUndefined(target.isMedia) || !target.isMedia)){ - - href = "/{localLink:" + id + "}"; - - insertLink(); - return; - } - - // Is email and not //user@domain.com - if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf('mailto:') === -1) { - href = 'mailto:' + href; - insertLink(); - return; - } - - // Is www. prefixed - if (/^\s*www\./i.test(href)) { - href = 'http://' + href; - insertLink(); - return; - } - - insertLink(); - - } - - }; -} - -angular.module('umbraco.services').factory('tinyMceService', tinyMceService); +/** + * @ngdoc service + * @name umbraco.services.tinyMceService + * + * + * @description + * A service containing all logic for all of the Umbraco TinyMCE plugins + */ +function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService) { + return { + + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#configuration + * @methodOf umbraco.services.tinyMceService + * + * @description + * Returns a collection of plugins available to the tinyMCE editor + * + */ + configuration: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "rteApiBaseUrl", + "GetConfiguration"), { cache: true }), + 'Failed to retrieve tinymce configuration'); + }, + + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#defaultPrevalues + * @methodOf umbraco.services.tinyMceService + * + * @description + * Returns a default configration to fallback on in case none is provided + * + */ + defaultPrevalues: function () { + var cfg = {}; + cfg.toolbar = ["code", "bold", "italic", "styleselect","alignleft", "aligncenter", "alignright", "bullist","numlist", "outdent", "indent", "link", "image", "umbmediapicker", "umbembeddialog", "umbmacro"]; + cfg.stylesheets = []; + cfg.dimensions = { height: 500 }; + cfg.maxImageSize = 500; + return cfg; + }, + + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#createInsertEmbeddedMedia + * @methodOf umbraco.services.tinyMceService + * + * @description + * Creates the umbrco insert embedded media tinymce plugin + * + * @param {Object} editor the TinyMCE editor instance + * @param {Object} $scope the current controller scope + */ + createInsertEmbeddedMedia: function (editor, scope, callback) { + editor.addButton('umbembeddialog', { + icon: 'custom icon-tv', + tooltip: 'Embed', + onclick: function () { + if (callback) { + callback(); + } + } + }); + }, + + insertEmbeddedMediaInEditor: function(editor, preview) { + editor.insertContent(preview); + }, + + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#createMediaPicker + * @methodOf umbraco.services.tinyMceService + * + * @description + * Creates the umbrco insert media tinymce plugin + * + * @param {Object} editor the TinyMCE editor instance + * @param {Object} $scope the current controller scope + */ + createMediaPicker: function (editor, scope, callback) { + editor.addButton('umbmediapicker', { + icon: 'custom icon-picture', + tooltip: 'Media Picker', + stateSelector: 'img', + onclick: function () { + + var selectedElm = editor.selection.getNode(), + currentTarget; + + + if(selectedElm.nodeName === 'IMG'){ + var img = $(selectedElm); + + var hasUdi = img.attr("data-udi") ? true : false; + + currentTarget = { + altText: img.attr("alt"), + url: img.attr("src") + }; + + if (hasUdi) { + currentTarget["udi"] = img.attr("data-udi"); + } + else { + currentTarget["id"] = img.attr("rel"); + } + } + + userService.getCurrentUser().then(function(userData) { + if(callback) { + callback(currentTarget, userData); + } + }); + + } + }); + }, + + insertMediaInEditor: function(editor, img) { + if(img) { + + var hasUdi = img.udi ? true : false; + + var data = { + alt: img.altText || "", + src: (img.url) ? img.url : "nothing.jpg", + id: '__mcenew' + }; + + if (hasUdi) { + data["data-udi"] = img.udi; + } + else { + //Considering these fixed because UDI will now be used and thus + // we have no need for rel http://issues.umbraco.org/issue/U4-6228, http://issues.umbraco.org/issue/U4-6595 + data["rel"] = img.id; + data["data-id"] = img.id; + } + + editor.insertContent(editor.dom.createHTML('img', data)); + + $timeout(function () { + var imgElm = editor.dom.get('__mcenew'); + var size = editor.dom.getSize(imgElm); + + if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) { + var newSize = imageHelper.scaleToMaxSize(editor.settings.maxImageSize, size.w, size.h); + + var s = "width: " + newSize.width + "px; height:" + newSize.height + "px;"; + editor.dom.setAttrib(imgElm, 'style', s); + editor.dom.setAttrib(imgElm, 'id', null); + + if (img.url) { + var src = img.url + "?width=" + newSize.width + "&height=" + newSize.height; + editor.dom.setAttrib(imgElm, 'data-mce-src', src); + } + } + }, 500); + } + }, + + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#createUmbracoMacro + * @methodOf umbraco.services.tinyMceService + * + * @description + * Creates the insert umbrco macro tinymce plugin + * + * @param {Object} editor the TinyMCE editor instance + * @param {Object} $scope the current controller scope + */ + createInsertMacro: function (editor, $scope, callback) { + + var createInsertMacroScope = this; + + /** Adds custom rules for the macro plugin and custom serialization */ + editor.on('preInit', function (args) { + //this is requires so that we tell the serializer that a 'div' is actually allowed in the root, otherwise the cleanup will strip it out + editor.serializer.addRules('div'); + + /** This checks if the div is a macro container, if so, checks if its wrapped in a p tag and then unwraps it (removes p tag) */ + editor.serializer.addNodeFilter('div', function (nodes, name) { + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].attr("class") === "umb-macro-holder" && nodes[i].parent && nodes[i].parent.name.toUpperCase() === "P") { + nodes[i].parent.unwrap(); + } + } + }); + + }); + + /** + * Because the macro gets wrapped in a P tag because of the way 'enter' works, this + * method will return the macro element if not wrapped in a p, or the p if the macro + * element is the only one inside of it even if we are deep inside an element inside the macro + */ + function getRealMacroElem(element) { + var e = $(element).closest(".umb-macro-holder"); + if (e.length > 0) { + if (e.get(0).parentNode.nodeName === "P") { + //now check if we're the only element + if (element.parentNode.childNodes.length === 1) { + return e.get(0).parentNode; + } + } + return e.get(0); + } + return null; + } + + /** Adds the button instance */ + editor.addButton('umbmacro', { + icon: 'custom icon-settings-alt', + tooltip: 'Insert macro', + onPostRender: function () { + + var ctrl = this; + var isOnMacroElement = false; + + /** + if the selection comes from a different element that is not the macro's + we need to check if the selection includes part of the macro, if so we'll force the selection + to clear to the next element since if people can select part of the macro markup they can then modify it. + */ + function handleSelectionChange() { + + if (!editor.selection.isCollapsed()) { + var endSelection = tinymce.activeEditor.selection.getEnd(); + var startSelection = tinymce.activeEditor.selection.getStart(); + //don't proceed if it's an entire element selected + if (endSelection !== startSelection) { + + //if the end selection is a macro then move the cursor + //NOTE: we don't have to handle when the selection comes from a previous parent because + // that is automatically taken care of with the normal onNodeChanged logic since the + // evt.element will be the macro once it becomes part of the selection. + var $testForMacro = $(endSelection).closest(".umb-macro-holder"); + if ($testForMacro.length > 0) { + + //it came from before so move after, if there is no after then select ourselves + var next = $testForMacro.next(); + if (next.length > 0) { + editor.selection.setCursorLocation($testForMacro.next().get(0)); + } + else { + selectMacroElement($testForMacro.get(0)); + } + + } + } + } + } + + /** helper method to select the macro element */ + function selectMacroElement(macroElement) { + + // move selection to top element to ensure we can't edit this + editor.selection.select(macroElement); + + // check if the current selection *is* the element (ie bug) + var currentSelection = editor.selection.getStart(); + if (tinymce.isIE) { + if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) { + while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) { + currentSelection = currentSelection.parentNode; + } + editor.selection.select(currentSelection); + } + } + } + + /** + * Add a node change handler, test if we're editing a macro and select the whole thing, then set our isOnMacroElement flag. + * If we change the selection inside this method, then we end up in an infinite loop, so we have to remove ourselves + * from the event listener before changing selection, however, it seems that putting a break point in this method + * will always cause an 'infinite' loop as the caret keeps changing. + */ + function onNodeChanged(evt) { + + //set our macro button active when on a node of class umb-macro-holder + var $macroElement = $(evt.element).closest(".umb-macro-holder"); + + handleSelectionChange(); + + //set the button active + ctrl.active($macroElement.length !== 0); + + if ($macroElement.length > 0) { + var macroElement = $macroElement.get(0); + + //remove the event listener before re-selecting + editor.off('NodeChange', onNodeChanged); + + selectMacroElement(macroElement); + + //set the flag + isOnMacroElement = true; + + //re-add the event listener + editor.on('NodeChange', onNodeChanged); + } + else { + isOnMacroElement = false; + } + + } + + /** when the contents load we need to find any macros declared and load in their content */ + editor.on("LoadContent", function (o) { + + //get all macro divs and load their content + $(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function() { + createInsertMacroScope.loadMacroContent($(this), null, $scope); + }); + + }); + + /** This prevents any other commands from executing when the current element is the macro so the content cannot be edited */ + editor.on('BeforeExecCommand', function (o) { + if (isOnMacroElement) { + if (o.preventDefault) { + o.preventDefault(); + } + if (o.stopImmediatePropagation) { + o.stopImmediatePropagation(); + } + return; + } + }); + + /** This double checks and ensures you can't paste content into the rendered macro */ + editor.on("Paste", function (o) { + if (isOnMacroElement) { + if (o.preventDefault) { + o.preventDefault(); + } + if (o.stopImmediatePropagation) { + o.stopImmediatePropagation(); + } + return; + } + }); + + //set onNodeChanged event listener + editor.on('NodeChange', onNodeChanged); + + /** + * Listen for the keydown in the editor, we'll check if we are currently on a macro element, if so + * we'll check if the key down is a supported key which requires an action, otherwise we ignore the request + * so the macro cannot be edited. + */ + editor.on('KeyDown', function (e) { + if (isOnMacroElement) { + var macroElement = editor.selection.getNode(); + + //get the 'real' element (either p or the real one) + macroElement = getRealMacroElem(macroElement); + + //prevent editing + e.preventDefault(); + e.stopPropagation(); + + var moveSibling = function (element, isNext) { + var $e = $(element); + var $sibling = isNext ? $e.next() : $e.prev(); + if ($sibling.length > 0) { + editor.selection.select($sibling.get(0)); + editor.selection.collapse(true); + } + else { + //if we're moving previous and there is no sibling, then lets recurse and just select the next one + if (!isNext) { + moveSibling(element, true); + return; + } + + //if there is no sibling we'll generate a new p at the end and select it + editor.setContent(editor.getContent() + "

     

    "); + editor.selection.select($(editor.dom.getRoot()).children().last().get(0)); + editor.selection.collapse(true); + + } + }; + + //supported keys to move to the next or prev element (13-enter, 27-esc, 38-up, 40-down, 39-right, 37-left) + //supported keys to remove the macro (8-backspace, 46-delete) + //TODO: Should we make the enter key insert a line break before or leave it as moving to the next element? + if ($.inArray(e.keyCode, [13, 40, 39]) !== -1) { + //move to next element + moveSibling(macroElement, true); + } + else if ($.inArray(e.keyCode, [27, 38, 37]) !== -1) { + //move to prev element + moveSibling(macroElement, false); + } + else if ($.inArray(e.keyCode, [8, 46]) !== -1) { + //delete macro element + + //move first, then delete + moveSibling(macroElement, false); + editor.dom.remove(macroElement); + } + return ; + } + }); + + }, + + /** The insert macro button click event handler */ + onclick: function () { + + var dialogData = { + //flag for use in rte so we only show macros flagged for the editor + richTextEditor: true + }; + + //when we click we could have a macro already selected and in that case we'll want to edit the current parameters + //so we'll need to extract them and submit them to the dialog. + var macroElement = editor.selection.getNode(); + macroElement = getRealMacroElem(macroElement); + if (macroElement) { + //we have a macro selected so we'll need to parse it's alias and parameters + var contents = $(macroElement).contents(); + var comment = _.find(contents, function(item) { + return item.nodeType === 8; + }); + if (!comment) { + throw "Cannot parse the current macro, the syntax in the editor is invalid"; + } + var syntax = comment.textContent.trim(); + var parsed = macroService.parseMacroSyntax(syntax); + dialogData = { + macroData: parsed + }; + } + + if(callback) { + callback(dialogData); + } + + } + }); + }, + + insertMacroInEditor: function(editor, macroObject, $scope) { + + //put the macro syntax in comments, we will parse this out on the server side to be used + //for persisting. + var macroSyntaxComment = ""; + //create an id class for this element so we can re-select it after inserting + var uniqueId = "umb-macro-" + editor.dom.uniqueId(); + var macroDiv = editor.dom.create('div', + { + 'class': 'umb-macro-holder ' + macroObject.macroAlias + ' mceNonEditable ' + uniqueId + }, + macroSyntaxComment + 'Macro alias: ' + macroObject.macroAlias + ''); + + editor.selection.setNode(macroDiv); + + var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId)); + + //async load the macro content + this.loadMacroContent($macroDiv, macroObject, $scope); + + }, + + /** loads in the macro content async from the server */ + loadMacroContent: function($macroDiv, macroData, $scope) { + + //if we don't have the macroData, then we'll need to parse it from the macro div + if (!macroData) { + var contents = $macroDiv.contents(); + var comment = _.find(contents, function (item) { + return item.nodeType === 8; + }); + if (!comment) { + throw "Cannot parse the current macro, the syntax in the editor is invalid"; + } + var syntax = comment.textContent.trim(); + var parsed = macroService.parseMacroSyntax(syntax); + macroData = parsed; + } + + var $ins = $macroDiv.find("ins"); + + //show the throbber + $macroDiv.addClass("loading"); + + var contentId = $routeParams.id; + + //need to wrap in safe apply since this might be occuring outside of angular + angularHelper.safeApply($scope, function() { + macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary) + .then(function (htmlResult) { + + $macroDiv.removeClass("loading"); + htmlResult = htmlResult.trim(); + if (htmlResult !== "") { + $ins.html(htmlResult); + } + }); + }); + + }, + + createLinkPicker: function(editor, $scope, onClick) { + + function createLinkList(callback) { + return function() { + var linkList = editor.settings.link_list; + + if (typeof(linkList) === "string") { + tinymce.util.XHR.send({ + url: linkList, + success: function(text) { + callback(tinymce.util.JSON.parse(text)); + } + }); + } else { + callback(linkList); + } + }; + } + + function showDialog(linkList) { + var data = {}, selection = editor.selection, dom = editor.dom, selectedElm, anchorElm, initialText; + var win, linkListCtrl, relListCtrl, targetListCtrl; + + function linkListChangeHandler(e) { + var textCtrl = win.find('#text'); + + if (!textCtrl.value() || (e.lastControl && textCtrl.value() === e.lastControl.text())) { + textCtrl.value(e.control.text()); + } + + win.find('#href').value(e.control.value()); + } + + function buildLinkList() { + var linkListItems = [{ + text: 'None', + value: '' + }]; + + tinymce.each(linkList, function(link) { + linkListItems.push({ + text: link.text || link.title, + value: link.value || link.url, + menu: link.menu + }); + }); + + return linkListItems; + } + + function buildRelList(relValue) { + var relListItems = [{ + text: 'None', + value: '' + }]; + + tinymce.each(editor.settings.rel_list, function(rel) { + relListItems.push({ + text: rel.text || rel.title, + value: rel.value, + selected: relValue === rel.value + }); + }); + + return relListItems; + } + + function buildTargetList(targetValue) { + var targetListItems = [{ + text: 'None', + value: '' + }]; + + if (!editor.settings.target_list) { + targetListItems.push({ + text: 'New window', + value: '_blank' + }); + } + + tinymce.each(editor.settings.target_list, function(target) { + targetListItems.push({ + text: target.text || target.title, + value: target.value, + selected: targetValue === target.value + }); + }); + + return targetListItems; + } + + function buildAnchorListControl(url) { + var anchorList = []; + + tinymce.each(editor.dom.select('a:not([href])'), function(anchor) { + var id = anchor.name || anchor.id; + + if (id) { + anchorList.push({ + text: id, + value: '#' + id, + selected: url.indexOf('#' + id) !== -1 + }); + } + }); + + if (anchorList.length) { + anchorList.unshift({ + text: 'None', + value: '' + }); + + return { + name: 'anchor', + type: 'listbox', + label: 'Anchors', + values: anchorList, + onselect: linkListChangeHandler + }; + } + } + + function updateText() { + if (!initialText && data.text.length === 0) { + this.parent().parent().find('#text')[0].value(this.value()); + } + } + + selectedElm = selection.getNode(); + anchorElm = dom.getParent(selectedElm, 'a[href]'); + + data.text = initialText = anchorElm ? (anchorElm.innerText || anchorElm.textContent) : selection.getContent({format: 'text'}); + data.href = anchorElm ? dom.getAttrib(anchorElm, 'href') : ''; + data.target = anchorElm ? dom.getAttrib(anchorElm, 'target') : ''; + data.rel = anchorElm ? dom.getAttrib(anchorElm, 'rel') : ''; + + if (selectedElm.nodeName === "IMG") { + data.text = initialText = " "; + } + + if (linkList) { + linkListCtrl = { + type: 'listbox', + label: 'Link list', + values: buildLinkList(), + onselect: linkListChangeHandler + }; + } + + if (editor.settings.target_list !== false) { + targetListCtrl = { + name: 'target', + type: 'listbox', + label: 'Target', + values: buildTargetList(data.target) + }; + } + + if (editor.settings.rel_list) { + relListCtrl = { + name: 'rel', + type: 'listbox', + label: 'Rel', + values: buildRelList(data.rel) + }; + } + + var injector = angular.element(document.getElementById("umbracoMainPageBody")).injector(); + var dialogService = injector.get("dialogService"); + var currentTarget = null; + + //if we already have a link selected, we want to pass that data over to the dialog + if(anchorElm){ + var anchor = $(anchorElm); + currentTarget = { + name: anchor.attr("title"), + url: anchor.attr("href"), + target: anchor.attr("target") + }; + + //locallink detection, we do this here, to avoid poluting the dialogservice + //so the dialog service can just expect to get a node-like structure + if (currentTarget.url.indexOf("localLink:") > 0) { + var linkId = currentTarget.url.substring(currentTarget.url.indexOf(":") + 1, currentTarget.url.length - 1); + //we need to check if this is an INT or a UDI + var parsedIntId = parseInt(linkId, 10); + if (isNaN(parsedIntId)) { + //it's a UDI + currentTarget.udi = linkId; + } + else { + currentTarget.id = linkId; + } + } + } + + if(onClick) { + onClick(currentTarget, anchorElm); + } + + } + + editor.addButton('link', { + icon: 'link', + tooltip: 'Insert/edit link', + shortcut: 'Ctrl+K', + onclick: createLinkList(showDialog), + stateSelector: 'a[href]' + }); + + editor.addButton('unlink', { + icon: 'unlink', + tooltip: 'Remove link', + cmd: 'unlink', + stateSelector: 'a[href]' + }); + + editor.addShortcut('Ctrl+K', '', createLinkList(showDialog)); + this.showDialog = showDialog; + + editor.addMenuItem('link', { + icon: 'link', + text: 'Insert link', + shortcut: 'Ctrl+K', + onclick: createLinkList(showDialog), + stateSelector: 'a[href]', + context: 'insert', + prependToContext: true + }); + + }, + + insertLinkInEditor: function(editor, target, anchorElm) { + + var href = target.url; + // We want to use the Udi. If it is set, we use it, else fallback to id, and finally to null + var hasUdi = target.udi ? true : false; + var id = hasUdi ? target.udi : (target.id ? target.id : null); + + //Create a json obj used to create the attributes for the tag + function createElemAttributes() { + var a = { + href: href, + title: target.name, + target: target.target ? target.target : null, + rel: target.rel ? target.rel : null + }; + if (hasUdi) { + a["data-udi"] = target.udi; + } + else if (target.id) { + a["data-id"] = target.id; + } + return a; + } + + function insertLink() { + if (anchorElm) { + editor.dom.setAttribs(anchorElm, createElemAttributes()); + + editor.selection.select(anchorElm); + editor.execCommand('mceEndTyping'); + } + else { + editor.execCommand('mceInsertLink', false, createElemAttributes()); + } + } + + if (!href) { + editor.execCommand('unlink'); + return; + } + + //if we have an id, it must be a locallink:id, aslong as the isMedia flag is not set + if(id && (angular.isUndefined(target.isMedia) || !target.isMedia)){ + + href = "/{localLink:" + id + "}"; + + insertLink(); + return; + } + + // Is email and not //user@domain.com + if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf('mailto:') === -1) { + href = 'mailto:' + href; + insertLink(); + return; + } + + // Is www. prefixed + if (/^\s*www\./i.test(href)) { + href = 'http://' + href; + insertLink(); + return; + } + + insertLink(); + + } + + }; +} + +angular.module('umbraco.services').factory('tinyMceService', tinyMceService); 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 a0cfa504f4..ea3ed45175 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 @@ -1,817 +1,817 @@ - -/** - * @ngdoc service - * @name umbraco.services.treeService - * @function - * - * @description - * The tree service factory, used internally by the umbTree and umbTreeItem directives - */ -function treeService($q, treeResource, iconHelper, notificationsService, eventsService) { - - //SD: Have looked at putting this in sessionStorage (not localStorage since that means you wouldn't be able to work - // in multiple tabs) - however our tree structure is cyclical, meaning a node has a reference to it's parent and it's children - // which you cannot serialize to sessionStorage. There's really no benefit of session storage except that you could refresh - // a tab and have the trees where they used to be - supposed that is kind of nice but would mean we'd have to store the parent - // as a nodeid reference instead of a variable with a getParent() method. - var treeCache = {}; - - var standardCssClass = 'icon umb-tree-icon sprTree'; - - function getCacheKey(args) { - //if there is no cache key they return null - it won't be cached. - if (!args || !args.cacheKey) { - return null; - } - - var cacheKey = args.cacheKey; - cacheKey += "_" + args.section; - return cacheKey; - } - - return { - - /** Internal method to return the tree cache */ - _getTreeCache: function() { - return treeCache; - }, - - /** Internal method to track expanded paths on a tree */ - _trackExpandedPaths: function (node, expandedPaths) { - if (!node.children || !angular.isArray(node.children) || node.children.length == 0) { - return; - } - - //take the last child - var childPath = this.getPath(node.children[node.children.length - 1]).join(","); - //check if this already exists, if so exit - if (expandedPaths.indexOf(childPath) !== -1) { - return; - } - - if (expandedPaths.length === 0) { - expandedPaths.push(childPath); //track it - return; - } - - var clonedPaths = expandedPaths.slice(0); //make a copy to iterate over so we can modify the original in the iteration - - _.each(clonedPaths, function (p) { - if (childPath.startsWith(p + ",")) { - //this means that the node's path supercedes this path stored so we can remove the current 'p' and replace it with node.path - expandedPaths.splice(expandedPaths.indexOf(p), 1); //remove it - expandedPaths.push(childPath); //replace it - } - else if (p.startsWith(childPath + ",")) { - //this means we've already tracked a deeper node so we shouldn't track this one - } - else { - expandedPaths.push(childPath); //track it - } - }); - }, - - /** Internal method that ensures there's a routePath, parent and level property on each tree node and adds some icon specific properties so that the nodes display properly */ - _formatNodeDataForUseInUI: function (parentNode, treeNodes, section, level) { - //if no level is set, then we make it 1 - var childLevel = (level ? level : 1); - //set the section if it's not already set - 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++) { - - 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. - treeNode.parent = funcParent; - - //set the section for each tree node - this allows us to reference this easily when accessing tree nodes - 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 (!treeNode.routePath) { - - if (treeNode.metaData && treeNode.metaData["treeAlias"]) { - //this is a root node - treeNode.routePath = section; - } - else { - var treeAlias = this.getTreeAlias(treeNode); - treeNode.routePath = section + "/" + treeAlias + "/edit/" + treeNode.id; - } - } - - //now, format the icon data - 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 - treeNode.style = "height:16px;width:16px;"; - } - else { - treeNode.style = ""; - } - } - else { - 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 - treeNode.cssClass = standardCssClass + " legacy-custom-file"; - } - - if (treeNode.metaData && treeNode.metaData.noAccess === true) { - if (!treeNode.cssClasses) { - treeNode.cssClasses = []; - } - treeNode.cssClasses.push("no-access"); - } - } - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getTreePackageFolder - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Determines if the current tree is a plugin tree and if so returns the package folder it has declared - * so we know where to find it's views, otherwise it will just return undefined. - * - * @param {String} treeAlias The tree alias to check - */ - getTreePackageFolder: function(treeAlias) { - //we determine this based on the server variables - if (Umbraco.Sys.ServerVariables.umbracoPlugins && - Umbraco.Sys.ServerVariables.umbracoPlugins.trees && - angular.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) { - - var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function(item) { - return item.alias === treeAlias; - }); - - return found ? found.packageFolder : undefined; - } - return undefined; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#clearCache - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Clears the tree cache - with optional cacheKey, optional section or optional filter. - * - * @param {Object} args arguments - * @param {String} args.cacheKey optional cachekey - this is used to clear specific trees in dialogs - * @param {String} args.section optional section alias - clear tree for a given section - * @param {String} args.childrenOf optional parent ID - only clear the cache below a specific node - */ - clearCache: function (args) { - //clear all if not specified - if (!args) { - treeCache = {}; - } - else { - //if section and cache key specified just clear that cache - if (args.section && args.cacheKey) { - var cacheKey = getCacheKey(args); - if (cacheKey && treeCache && treeCache[cacheKey] != null) { - treeCache = _.omit(treeCache, cacheKey); - } - } - else if (args.childrenOf) { - //if childrenOf is supplied a cacheKey must be supplied as well - if (!args.cacheKey) { - throw "args.cacheKey is required if args.childrenOf is supplied"; - } - //this will clear out all children for the parentId passed in to this parameter, we'll - // do this by recursing and specifying a filter - var self = this; - this.clearCache({ - cacheKey: args.cacheKey, - filter: function(cc) { - //get the new parent node from the tree cache - var parent = self.getDescendantNode(cc.root, args.childrenOf); - if (parent) { - //clear it's children and set to not expanded - parent.children = null; - parent.expanded = false; - } - //return the cache to be saved - return cc; - } - }); - } - else if (args.filter && angular.isFunction(args.filter)) { - //if a filter is supplied a cacheKey must be supplied as well - if (!args.cacheKey) { - throw "args.cacheKey is required if args.filter is supplied"; - } - - //if a filter is supplied the function needs to return the data to keep - var byKey = treeCache[args.cacheKey]; - if (byKey) { - var result = args.filter(byKey); - - if (result) { - //set the result to the filtered data - treeCache[args.cacheKey] = result; - } - else { - //remove the cache - treeCache = _.omit(treeCache, args.cacheKey); - } - - } - - } - else if (args.cacheKey) { - //if only the cache key is specified, then clear all cache starting with that key - var allKeys1 = _.keys(treeCache); - var toRemove1 = _.filter(allKeys1, function (k) { - return k.startsWith(args.cacheKey + "_"); - }); - treeCache = _.omit(treeCache, toRemove1); - } - else if (args.section) { - //if only the section is specified then clear all cache regardless of cache key by that section - var allKeys2 = _.keys(treeCache); - var toRemove2 = _.filter(allKeys2, function (k) { - return k.endsWith("_" + args.section); - }); - treeCache = _.omit(treeCache, toRemove2); - } - } - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#loadNodeChildren - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Clears all node children, gets it's up-to-date children from the server and re-assigns them and then - * returns them in a promise. - * @param {object} args An arguments object - * @param {object} args.node The tree node - * @param {object} args.section The current section - */ - loadNodeChildren: function(args) { - if (!args) { - throw "No args object defined for loadNodeChildren"; - } - if (!args.node) { - throw "No node defined on args object for loadNodeChildren"; - } - - this.removeChildNodes(args.node); - args.node.loading = true; - - return this.getChildren(args) - .then(function(data) { - - //set state to done and expand (only if there actually are children!) - args.node.loading = false; - args.node.children = data; - if (args.node.children && args.node.children.length > 0) { - args.node.expanded = true; - args.node.hasChildren = true; - } - - //Since we've removed the children & reloaded them, we need to refresh the UI now because the tree node UI doesn't operate on normal angular $watch since that will be pretty slow - if (angular.isFunction(args.node.updateNodeData)) { - args.node.updateNodeData(args.node); - } - - return $q.when(data); - - }, function(reason) { - - //in case of error, emit event - eventsService.emit("treeService.treeNodeLoadError", {error: reason } ); - - //stop show the loading indicator - args.node.loading = false; - - //tell notications about the error - notificationsService.error(reason); - - return $q.reject(reason); - }); - - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#removeNode - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Removes a given node from the tree - * @param {object} treeNode the node to remove - */ - removeNode: function(treeNode) { - if (!angular.isFunction(treeNode.parent)) { - return; - } - - if (treeNode.parent() == null) { - throw "Cannot remove a node that doesn't have a parent"; - } - //remove the current item from it's siblings - treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1); - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#removeChildNodes - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Removes all child nodes from a given tree node - * @param {object} treeNode the node to remove children from - */ - removeChildNodes : function(treeNode) { - treeNode.expanded = false; - treeNode.children = []; - treeNode.hasChildren = false; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getChildNode - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Gets a child node with a given ID, from a specific treeNode - * @param {object} treeNode to retrive child node from - * @param {int} id id of child node - */ - getChildNode: function (treeNode, id) { - if (!treeNode.children) { - return null; - } - var found = _.find(treeNode.children, function (child) { - return String(child.id).toLowerCase() === String(id).toLowerCase(); - }); - return found === undefined ? null : found; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getDescendantNode - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Gets a descendant node by id - * @param {object} treeNode to retrive descendant node from - * @param {int} id id of descendant node - * @param {string} treeAlias - optional tree alias, if fetching descendant node from a child of a listview document - */ - getDescendantNode: function(treeNode, id, treeAlias) { - - //validate if it is a section container since we'll need a treeAlias if it is one - if (treeNode.isContainer === true && !treeAlias) { - throw "Cannot get a descendant node from a section container node without a treeAlias specified"; - } - - //if it is a section container, we need to find the tree to be searched - if (treeNode.isContainer) { - var foundRoot = null; - for (var c = 0; c < treeNode.children.length; c++) { - if (this.getTreeAlias(treeNode.children[c]) === treeAlias) { - foundRoot = treeNode.children[c]; - break; - } - } - if (!foundRoot) { - throw "Could not find a tree in the current section with alias " + treeAlias; - } - treeNode = foundRoot; - } - - //check this node - if (treeNode.id === id) { - return treeNode; - } - - //check the first level - var found = this.getChildNode(treeNode, id); - if (found) { - return found; - } - - //check each child of this node - if (!treeNode.children) { - return null; - } - - for (var i = 0; i < treeNode.children.length; i++) { - var child = treeNode.children[i]; - if (child.children && angular.isArray(child.children) && child.children.length > 0) { - //recurse - found = this.getDescendantNode(child, id); - if (found) { - return found; - } - } - } - - //not found - return found === undefined ? null : found; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getTreeRoot - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Gets the root node of the current tree type for a given tree node - * @param {object} treeNode to retrive tree root node from - */ - getTreeRoot: function (treeNode) { - if (!treeNode) { - throw "treeNode cannot be null"; - } - - //all root nodes have metadata key 'treeAlias' - var root = null; - var current = treeNode; - while (root === null && current) { - - if (current.metaData && current.metaData["treeAlias"]) { - root = current; - } - else if (angular.isFunction(current.parent)) { - //we can only continue if there is a parent() method which means this - // tree node was loaded in as part of a real tree, not just as a single tree - // node from the server. - current = current.parent(); - } - else { - current = null; - } - } - return root; - }, - - /** Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node */ - /** - * @ngdoc method - * @name umbraco.services.treeService#getTreeAlias - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node - * @param {object} treeNode to retrive tree alias from - */ - getTreeAlias : function(treeNode) { - var root = this.getTreeRoot(treeNode); - if (root) { - return root.metaData["treeAlias"]; - } - return ""; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getTree - * @methodOf umbraco.services.treeService - * @function - * - * @description - * gets the tree, returns a promise - * @param {object} args Arguments - * @param {string} args.section Section alias - * @param {string} args.cacheKey Optional cachekey - */ - getTree: function (args) { - - //set defaults - if (!args) { - args = { section: 'content', cacheKey: null }; - } - else if (!args.section) { - args.section = 'content'; - } - - var cacheKey = getCacheKey(args); - - //return the cache if it exists - if (cacheKey && treeCache[cacheKey] !== undefined) { - return $q.when(treeCache[cacheKey]); - } - - var self = this; - return treeResource.loadApplication(args) - .then(function(data) { - //this will be called once the tree app data has loaded - var result = { - name: data.name, - alias: args.section, - root: data - }; - //we need to format/modify some of the node data to be used in our app. - self._formatNodeDataForUseInUI(result.root, result.root.children, args.section); - - //cache this result if a cache key is specified - generally a cache key should ONLY - // be specified for application trees, dialog trees should not be cached. - if (cacheKey) { - treeCache[cacheKey] = result; - return $q.when(treeCache[cacheKey]); - } - - //return un-cached - return $q.when(result); - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getMenu - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Returns available menu actions for a given tree node - * @param {object} args Arguments - * @param {string} args.treeNode tree node object to retrieve the menu for - */ - getMenu: function (args) { - - if (!args) { - throw "args cannot be null"; - } - if (!args.treeNode) { - throw "args.treeNode cannot be null"; - } - - return treeResource.loadMenu(args.treeNode) - .then(function(data) { - //need to convert the icons to new ones - for (var i = 0; i < data.length; i++) { - data[i].cssclass = iconHelper.convertFromLegacyIcon(data[i].cssclass); - } - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getChildren - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Gets the children from the server for a given node - * @param {object} args Arguments - * @param {object} args.node tree node object to retrieve the children for - * @param {string} args.section current section alias - */ - getChildren: function (args) { - - if (!args) { - throw "No args object defined for getChildren"; - } - if (!args.node) { - throw "No node defined on args object for getChildren"; - } - - var section = args.section || 'content'; - var treeItem = args.node; - - var self = this; - - return treeResource.loadNodes({ node: treeItem }) - .then(function (data) { - //now that we have the data, we need to add the level property to each item and the view - self._formatNodeDataForUseInUI(treeItem, data, section, treeItem.level + 1); - return $q.when(data); - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#reloadNode - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Re-loads the single node from the server - * @param {object} node Tree node to reload - */ - reloadNode: function(node) { - if (!node) { - throw "node cannot be null"; - } - if (!node.parent()) { - throw "cannot reload a single node without a parent"; - } - if (!node.section) { - throw "cannot reload a single node without an assigned node.section"; - } - - //set the node to loading - node.loading = true; - - return this.getChildren({ node: node.parent(), section: node.section }).then(function(data) { - - //ok, now that we have the children, find the node we're reloading - var found = _.find(data, function(item) { - return item.id === node.id; - }); - if (found) { - //now we need to find the node in the parent.children collection to replace - var index = _.indexOf(node.parent().children, node); - //the trick here is to not actually replace the node - this would cause the delete animations - //to fire, instead we're just going to replace all the properties of this node. - - //there should always be a method assigned but we'll check anyways - if (angular.isFunction(node.parent().children[index].updateNodeData)) { - node.parent().children[index].updateNodeData(found); - } - else { - //just update as per normal - this means styles, etc.. won't be applied - _.extend(node.parent().children[index], found); - } - - //set the node loading - node.parent().children[index].loading = false; - //return - return $q.when(node.parent().children[index]); - } - else { - return $q.reject(); - } - }, function() { - return $q.reject(); - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getPath - * @methodOf umbraco.services.treeService - * @function - * - * @description - * This will return the current node's path by walking up the tree - * @param {object} node Tree node to retrieve path for - */ - getPath: function(node) { - if (!node) { - throw "node cannot be null"; - } - if (!angular.isFunction(node.parent)) { - throw "node.parent is not a function, the path cannot be resolved"; - } - //all root nodes have metadata key 'treeAlias' - var reversePath = []; - var current = node; - while (current != null) { - reversePath.push(current.id); - if (current.metaData && current.metaData["treeAlias"]) { - current = null; - } - else { - current = current.parent(); - } - } - return reversePath.reverse(); - }, - - syncTree: function(args) { - - if (!args) { - throw "No args object defined for syncTree"; - } - if (!args.node) { - throw "No node defined on args object for syncTree"; - } - if (!args.path) { - throw "No path defined on args object for syncTree"; - } - if (!angular.isArray(args.path)) { - throw "Path must be an array"; - } - if (args.path.length < 1) { - //if there is no path, make -1 the path, and that should sync the tree root - args.path.push("-1"); - } - - //get the rootNode for the current node, we'll sync based on that - var root = this.getTreeRoot(args.node); - if (!root) { - throw "Could not get the root tree node based on the node passed in"; - } - - //now we want to loop through the ids in the path, first we'll check if the first part - //of the path is the root node, otherwise we'll search it's children. - var currPathIndex = 0; - //if the first id is the root node and there's only one... then consider it synced - if (String(args.path[currPathIndex]).toLowerCase() === String(args.node.id).toLowerCase()) { - if (args.path.length === 1) { - //return the root - return $q.when(root); - } - else { - //move to the next path part and continue - currPathIndex = 1; - } - } - - //now that we have the first id to lookup, we can start the process - - var self = this; - var node = args.node; - - var doSync = function () { - //check if it exists in the already loaded children - var child = self.getChildNode(node, args.path[currPathIndex]); - if (child) { - if (args.path.length === (currPathIndex + 1)) { - //woot! synced the node - if (!args.forceReload) { - return $q.when(child); - } - else { - //even though we've found the node if forceReload is specified - //we want to go update this single node from the server - return self.reloadNode(child); - } - } - else { - //now we need to recurse with the updated node/currPathIndex - currPathIndex++; - node = child; - //recurse - return doSync(); - } - } - else { - //couldn't find it in the - return self.loadNodeChildren({ node: node, section: node.section }).then(function (children) { - //ok, got the children, let's find it - var found = self.getChildNode(node, args.path[currPathIndex]); - if (found) { - if (args.path.length === (currPathIndex + 1)) { - //woot! synced the node - return $q.when(found); - } - else { - //now we need to recurse with the updated node/currPathIndex - currPathIndex++; - node = found; - //recurse - return doSync(); - } - } - else { - //fail! - return $q.reject(); - } - }, function () { - //fail! - return $q.reject(); - }); - } - }; - - //start - return doSync(); - - } - - }; -} - -angular.module('umbraco.services').factory('treeService', treeService); + +/** + * @ngdoc service + * @name umbraco.services.treeService + * @function + * + * @description + * The tree service factory, used internally by the umbTree and umbTreeItem directives + */ +function treeService($q, treeResource, iconHelper, notificationsService, eventsService) { + + //SD: Have looked at putting this in sessionStorage (not localStorage since that means you wouldn't be able to work + // in multiple tabs) - however our tree structure is cyclical, meaning a node has a reference to it's parent and it's children + // which you cannot serialize to sessionStorage. There's really no benefit of session storage except that you could refresh + // a tab and have the trees where they used to be - supposed that is kind of nice but would mean we'd have to store the parent + // as a nodeid reference instead of a variable with a getParent() method. + var treeCache = {}; + + var standardCssClass = 'icon umb-tree-icon sprTree'; + + function getCacheKey(args) { + //if there is no cache key they return null - it won't be cached. + if (!args || !args.cacheKey) { + return null; + } + + var cacheKey = args.cacheKey; + cacheKey += "_" + args.section; + return cacheKey; + } + + return { + + /** Internal method to return the tree cache */ + _getTreeCache: function() { + return treeCache; + }, + + /** Internal method to track expanded paths on a tree */ + _trackExpandedPaths: function (node, expandedPaths) { + if (!node.children || !angular.isArray(node.children) || node.children.length == 0) { + return; + } + + //take the last child + var childPath = this.getPath(node.children[node.children.length - 1]).join(","); + //check if this already exists, if so exit + if (expandedPaths.indexOf(childPath) !== -1) { + return; + } + + if (expandedPaths.length === 0) { + expandedPaths.push(childPath); //track it + return; + } + + var clonedPaths = expandedPaths.slice(0); //make a copy to iterate over so we can modify the original in the iteration + + _.each(clonedPaths, function (p) { + if (childPath.startsWith(p + ",")) { + //this means that the node's path supercedes this path stored so we can remove the current 'p' and replace it with node.path + expandedPaths.splice(expandedPaths.indexOf(p), 1); //remove it + expandedPaths.push(childPath); //replace it + } + else if (p.startsWith(childPath + ",")) { + //this means we've already tracked a deeper node so we shouldn't track this one + } + else { + expandedPaths.push(childPath); //track it + } + }); + }, + + /** Internal method that ensures there's a routePath, parent and level property on each tree node and adds some icon specific properties so that the nodes display properly */ + _formatNodeDataForUseInUI: function (parentNode, treeNodes, section, level) { + //if no level is set, then we make it 1 + var childLevel = (level ? level : 1); + //set the section if it's not already set + 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++) { + + 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. + treeNode.parent = funcParent; + + //set the section for each tree node - this allows us to reference this easily when accessing tree nodes + 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 (!treeNode.routePath) { + + if (treeNode.metaData && treeNode.metaData["treeAlias"]) { + //this is a root node + treeNode.routePath = section; + } + else { + var treeAlias = this.getTreeAlias(treeNode); + treeNode.routePath = section + "/" + treeAlias + "/edit/" + treeNode.id; + } + } + + //now, format the icon data + 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 + treeNode.style = "height:16px;width:16px;"; + } + else { + treeNode.style = ""; + } + } + else { + 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 + treeNode.cssClass = standardCssClass + " legacy-custom-file"; + } + + if (treeNode.metaData && treeNode.metaData.noAccess === true) { + if (!treeNode.cssClasses) { + treeNode.cssClasses = []; + } + treeNode.cssClasses.push("no-access"); + } + } + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#getTreePackageFolder + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Determines if the current tree is a plugin tree and if so returns the package folder it has declared + * so we know where to find it's views, otherwise it will just return undefined. + * + * @param {String} treeAlias The tree alias to check + */ + getTreePackageFolder: function(treeAlias) { + //we determine this based on the server variables + if (Umbraco.Sys.ServerVariables.umbracoPlugins && + Umbraco.Sys.ServerVariables.umbracoPlugins.trees && + angular.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) { + + var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function(item) { + return item.alias === treeAlias; + }); + + return found ? found.packageFolder : undefined; + } + return undefined; + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#clearCache + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Clears the tree cache - with optional cacheKey, optional section or optional filter. + * + * @param {Object} args arguments + * @param {String} args.cacheKey optional cachekey - this is used to clear specific trees in dialogs + * @param {String} args.section optional section alias - clear tree for a given section + * @param {String} args.childrenOf optional parent ID - only clear the cache below a specific node + */ + clearCache: function (args) { + //clear all if not specified + if (!args) { + treeCache = {}; + } + else { + //if section and cache key specified just clear that cache + if (args.section && args.cacheKey) { + var cacheKey = getCacheKey(args); + if (cacheKey && treeCache && treeCache[cacheKey] != null) { + treeCache = _.omit(treeCache, cacheKey); + } + } + else if (args.childrenOf) { + //if childrenOf is supplied a cacheKey must be supplied as well + if (!args.cacheKey) { + throw "args.cacheKey is required if args.childrenOf is supplied"; + } + //this will clear out all children for the parentId passed in to this parameter, we'll + // do this by recursing and specifying a filter + var self = this; + this.clearCache({ + cacheKey: args.cacheKey, + filter: function(cc) { + //get the new parent node from the tree cache + var parent = self.getDescendantNode(cc.root, args.childrenOf); + if (parent) { + //clear it's children and set to not expanded + parent.children = null; + parent.expanded = false; + } + //return the cache to be saved + return cc; + } + }); + } + else if (args.filter && angular.isFunction(args.filter)) { + //if a filter is supplied a cacheKey must be supplied as well + if (!args.cacheKey) { + throw "args.cacheKey is required if args.filter is supplied"; + } + + //if a filter is supplied the function needs to return the data to keep + var byKey = treeCache[args.cacheKey]; + if (byKey) { + var result = args.filter(byKey); + + if (result) { + //set the result to the filtered data + treeCache[args.cacheKey] = result; + } + else { + //remove the cache + treeCache = _.omit(treeCache, args.cacheKey); + } + + } + + } + else if (args.cacheKey) { + //if only the cache key is specified, then clear all cache starting with that key + var allKeys1 = _.keys(treeCache); + var toRemove1 = _.filter(allKeys1, function (k) { + return k.startsWith(args.cacheKey + "_"); + }); + treeCache = _.omit(treeCache, toRemove1); + } + else if (args.section) { + //if only the section is specified then clear all cache regardless of cache key by that section + var allKeys2 = _.keys(treeCache); + var toRemove2 = _.filter(allKeys2, function (k) { + return k.endsWith("_" + args.section); + }); + treeCache = _.omit(treeCache, toRemove2); + } + } + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#loadNodeChildren + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Clears all node children, gets it's up-to-date children from the server and re-assigns them and then + * returns them in a promise. + * @param {object} args An arguments object + * @param {object} args.node The tree node + * @param {object} args.section The current section + */ + loadNodeChildren: function(args) { + if (!args) { + throw "No args object defined for loadNodeChildren"; + } + if (!args.node) { + throw "No node defined on args object for loadNodeChildren"; + } + + this.removeChildNodes(args.node); + args.node.loading = true; + + return this.getChildren(args) + .then(function(data) { + + //set state to done and expand (only if there actually are children!) + args.node.loading = false; + args.node.children = data; + if (args.node.children && args.node.children.length > 0) { + args.node.expanded = true; + args.node.hasChildren = true; + } + + //Since we've removed the children & reloaded them, we need to refresh the UI now because the tree node UI doesn't operate on normal angular $watch since that will be pretty slow + if (angular.isFunction(args.node.updateNodeData)) { + args.node.updateNodeData(args.node); + } + + return $q.when(data); + + }, function(reason) { + + //in case of error, emit event + eventsService.emit("treeService.treeNodeLoadError", {error: reason } ); + + //stop show the loading indicator + args.node.loading = false; + + //tell notications about the error + notificationsService.error(reason); + + return $q.reject(reason); + }); + + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#removeNode + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Removes a given node from the tree + * @param {object} treeNode the node to remove + */ + removeNode: function(treeNode) { + if (!angular.isFunction(treeNode.parent)) { + return; + } + + if (treeNode.parent() == null) { + throw "Cannot remove a node that doesn't have a parent"; + } + //remove the current item from it's siblings + treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1); + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#removeChildNodes + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Removes all child nodes from a given tree node + * @param {object} treeNode the node to remove children from + */ + removeChildNodes : function(treeNode) { + treeNode.expanded = false; + treeNode.children = []; + treeNode.hasChildren = false; + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#getChildNode + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Gets a child node with a given ID, from a specific treeNode + * @param {object} treeNode to retrive child node from + * @param {int} id id of child node + */ + getChildNode: function (treeNode, id) { + if (!treeNode.children) { + return null; + } + var found = _.find(treeNode.children, function (child) { + return String(child.id).toLowerCase() === String(id).toLowerCase(); + }); + return found === undefined ? null : found; + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#getDescendantNode + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Gets a descendant node by id + * @param {object} treeNode to retrive descendant node from + * @param {int} id id of descendant node + * @param {string} treeAlias - optional tree alias, if fetching descendant node from a child of a listview document + */ + getDescendantNode: function(treeNode, id, treeAlias) { + + //validate if it is a section container since we'll need a treeAlias if it is one + if (treeNode.isContainer === true && !treeAlias) { + throw "Cannot get a descendant node from a section container node without a treeAlias specified"; + } + + //if it is a section container, we need to find the tree to be searched + if (treeNode.isContainer) { + var foundRoot = null; + for (var c = 0; c < treeNode.children.length; c++) { + if (this.getTreeAlias(treeNode.children[c]) === treeAlias) { + foundRoot = treeNode.children[c]; + break; + } + } + if (!foundRoot) { + throw "Could not find a tree in the current section with alias " + treeAlias; + } + treeNode = foundRoot; + } + + //check this node + if (treeNode.id === id) { + return treeNode; + } + + //check the first level + var found = this.getChildNode(treeNode, id); + if (found) { + return found; + } + + //check each child of this node + if (!treeNode.children) { + return null; + } + + for (var i = 0; i < treeNode.children.length; i++) { + var child = treeNode.children[i]; + if (child.children && angular.isArray(child.children) && child.children.length > 0) { + //recurse + found = this.getDescendantNode(child, id); + if (found) { + return found; + } + } + } + + //not found + return found === undefined ? null : found; + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#getTreeRoot + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Gets the root node of the current tree type for a given tree node + * @param {object} treeNode to retrive tree root node from + */ + getTreeRoot: function (treeNode) { + if (!treeNode) { + throw "treeNode cannot be null"; + } + + //all root nodes have metadata key 'treeAlias' + var root = null; + var current = treeNode; + while (root === null && current) { + + if (current.metaData && current.metaData["treeAlias"]) { + root = current; + } + else if (angular.isFunction(current.parent)) { + //we can only continue if there is a parent() method which means this + // tree node was loaded in as part of a real tree, not just as a single tree + // node from the server. + current = current.parent(); + } + else { + current = null; + } + } + return root; + }, + + /** Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node */ + /** + * @ngdoc method + * @name umbraco.services.treeService#getTreeAlias + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node + * @param {object} treeNode to retrive tree alias from + */ + getTreeAlias : function(treeNode) { + var root = this.getTreeRoot(treeNode); + if (root) { + return root.metaData["treeAlias"]; + } + return ""; + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#getTree + * @methodOf umbraco.services.treeService + * @function + * + * @description + * gets the tree, returns a promise + * @param {object} args Arguments + * @param {string} args.section Section alias + * @param {string} args.cacheKey Optional cachekey + */ + getTree: function (args) { + + //set defaults + if (!args) { + args = { section: 'content', cacheKey: null }; + } + else if (!args.section) { + args.section = 'content'; + } + + var cacheKey = getCacheKey(args); + + //return the cache if it exists + if (cacheKey && treeCache[cacheKey] !== undefined) { + return $q.when(treeCache[cacheKey]); + } + + var self = this; + return treeResource.loadApplication(args) + .then(function(data) { + //this will be called once the tree app data has loaded + var result = { + name: data.name, + alias: args.section, + root: data + }; + //we need to format/modify some of the node data to be used in our app. + self._formatNodeDataForUseInUI(result.root, result.root.children, args.section); + + //cache this result if a cache key is specified - generally a cache key should ONLY + // be specified for application trees, dialog trees should not be cached. + if (cacheKey) { + treeCache[cacheKey] = result; + return $q.when(treeCache[cacheKey]); + } + + //return un-cached + return $q.when(result); + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#getMenu + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Returns available menu actions for a given tree node + * @param {object} args Arguments + * @param {string} args.treeNode tree node object to retrieve the menu for + */ + getMenu: function (args) { + + if (!args) { + throw "args cannot be null"; + } + if (!args.treeNode) { + throw "args.treeNode cannot be null"; + } + + return treeResource.loadMenu(args.treeNode) + .then(function(data) { + //need to convert the icons to new ones + for (var i = 0; i < data.length; i++) { + data[i].cssclass = iconHelper.convertFromLegacyIcon(data[i].cssclass); + } + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#getChildren + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Gets the children from the server for a given node + * @param {object} args Arguments + * @param {object} args.node tree node object to retrieve the children for + * @param {string} args.section current section alias + */ + getChildren: function (args) { + + if (!args) { + throw "No args object defined for getChildren"; + } + if (!args.node) { + throw "No node defined on args object for getChildren"; + } + + var section = args.section || 'content'; + var treeItem = args.node; + + var self = this; + + return treeResource.loadNodes({ node: treeItem }) + .then(function (data) { + //now that we have the data, we need to add the level property to each item and the view + self._formatNodeDataForUseInUI(treeItem, data, section, treeItem.level + 1); + return $q.when(data); + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#reloadNode + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Re-loads the single node from the server + * @param {object} node Tree node to reload + */ + reloadNode: function(node) { + if (!node) { + throw "node cannot be null"; + } + if (!node.parent()) { + throw "cannot reload a single node without a parent"; + } + if (!node.section) { + throw "cannot reload a single node without an assigned node.section"; + } + + //set the node to loading + node.loading = true; + + return this.getChildren({ node: node.parent(), section: node.section }).then(function(data) { + + //ok, now that we have the children, find the node we're reloading + var found = _.find(data, function(item) { + return item.id === node.id; + }); + if (found) { + //now we need to find the node in the parent.children collection to replace + var index = _.indexOf(node.parent().children, node); + //the trick here is to not actually replace the node - this would cause the delete animations + //to fire, instead we're just going to replace all the properties of this node. + + //there should always be a method assigned but we'll check anyways + if (angular.isFunction(node.parent().children[index].updateNodeData)) { + node.parent().children[index].updateNodeData(found); + } + else { + //just update as per normal - this means styles, etc.. won't be applied + _.extend(node.parent().children[index], found); + } + + //set the node loading + node.parent().children[index].loading = false; + //return + return $q.when(node.parent().children[index]); + } + else { + return $q.reject(); + } + }, function() { + return $q.reject(); + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.treeService#getPath + * @methodOf umbraco.services.treeService + * @function + * + * @description + * This will return the current node's path by walking up the tree + * @param {object} node Tree node to retrieve path for + */ + getPath: function(node) { + if (!node) { + throw "node cannot be null"; + } + if (!angular.isFunction(node.parent)) { + throw "node.parent is not a function, the path cannot be resolved"; + } + //all root nodes have metadata key 'treeAlias' + var reversePath = []; + var current = node; + while (current != null) { + reversePath.push(current.id); + if (current.metaData && current.metaData["treeAlias"]) { + current = null; + } + else { + current = current.parent(); + } + } + return reversePath.reverse(); + }, + + syncTree: function(args) { + + if (!args) { + throw "No args object defined for syncTree"; + } + if (!args.node) { + throw "No node defined on args object for syncTree"; + } + if (!args.path) { + throw "No path defined on args object for syncTree"; + } + if (!angular.isArray(args.path)) { + throw "Path must be an array"; + } + if (args.path.length < 1) { + //if there is no path, make -1 the path, and that should sync the tree root + args.path.push("-1"); + } + + //get the rootNode for the current node, we'll sync based on that + var root = this.getTreeRoot(args.node); + if (!root) { + throw "Could not get the root tree node based on the node passed in"; + } + + //now we want to loop through the ids in the path, first we'll check if the first part + //of the path is the root node, otherwise we'll search it's children. + var currPathIndex = 0; + //if the first id is the root node and there's only one... then consider it synced + if (String(args.path[currPathIndex]).toLowerCase() === String(args.node.id).toLowerCase()) { + if (args.path.length === 1) { + //return the root + return $q.when(root); + } + else { + //move to the next path part and continue + currPathIndex = 1; + } + } + + //now that we have the first id to lookup, we can start the process + + var self = this; + var node = args.node; + + var doSync = function () { + //check if it exists in the already loaded children + var child = self.getChildNode(node, args.path[currPathIndex]); + if (child) { + if (args.path.length === (currPathIndex + 1)) { + //woot! synced the node + if (!args.forceReload) { + return $q.when(child); + } + else { + //even though we've found the node if forceReload is specified + //we want to go update this single node from the server + return self.reloadNode(child); + } + } + else { + //now we need to recurse with the updated node/currPathIndex + currPathIndex++; + node = child; + //recurse + return doSync(); + } + } + else { + //couldn't find it in the + return self.loadNodeChildren({ node: node, section: node.section }).then(function (children) { + //ok, got the children, let's find it + var found = self.getChildNode(node, args.path[currPathIndex]); + if (found) { + if (args.path.length === (currPathIndex + 1)) { + //woot! synced the node + return $q.when(found); + } + else { + //now we need to recurse with the updated node/currPathIndex + currPathIndex++; + node = found; + //recurse + return doSync(); + } + } + else { + //fail! + return $q.reject(); + } + }, function () { + //fail! + return $q.reject(); + }); + } + }; + + //start + return doSync(); + + } + + }; +} + +angular.module('umbraco.services').factory('treeService', treeService); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 26271ade2f..9eeadf7154 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -1,434 +1,434 @@ -/** -* @ngdoc service -* @name umbraco.services.umbRequestHelper -* @description A helper object used for sending requests to the server -**/ -function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService, formHelper) { - - return { - - /** - * @ngdoc method - * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath - * @methodOf umbraco.services.umbRequestHelper - * @function - * - * @description - * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path - * - * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown - */ - convertVirtualToAbsolutePath: function(virtualPath) { - if (virtualPath.startsWith("/")) { - return virtualPath; - } - if (!virtualPath.startsWith("~/")) { - throw "The path " + virtualPath + " is not a virtual path"; - } - if (!Umbraco.Sys.ServerVariables.application.applicationPath) { - throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath"; - } - return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/"); - }, - - /** - * @ngdoc method - * @name umbraco.services.umbRequestHelper#dictionaryToQueryString - * @methodOf umbraco.services.umbRequestHelper - * @function - * - * @description - * This will turn an array of key/value pairs or a standard dictionary into a query string - * - * @param {Array} queryStrings An array of key/value pairs - */ - dictionaryToQueryString: function (queryStrings) { - - if (angular.isArray(queryStrings)) { - return _.map(queryStrings, function (item) { - var key = null; - var val = null; - for (var k in item) { - key = k; - val = item[k]; - break; - } - if (key === null || val === null) { - throw "The object in the array was not formatted as a key/value pair"; - } - return encodeURIComponent(key) + "=" + encodeURIComponent(val); - }).join("&"); - } - else if (angular.isObject(queryStrings)) { - - //this allows for a normal object to be passed in (ie. a dictionary) - return decodeURIComponent($.param(queryStrings)); - } - - throw "The queryString parameter is not an array or object of key value pairs"; - }, - - /** - * @ngdoc method - * @name umbraco.services.umbRequestHelper#getApiUrl - * @methodOf umbraco.services.umbRequestHelper - * @function - * - * @description - * This will return the webapi Url for the requested key based on the servervariables collection - * - * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary - * @param {string} actionName The webapi action name - * @param {object} queryStrings Can be either a string or an array containing key/value pairs - */ - getApiUrl: function (apiName, actionName, queryStrings) { - if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) { - throw "No server variables defined!"; - } - - if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) { - throw "No url found for api name " + apiName; - } - - return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName + - (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings))); - - }, - - /** - * @ngdoc function - * @name umbraco.services.umbRequestHelper#resourcePromise - * @methodOf umbraco.services.umbRequestHelper - * @function - * - * @description - * This returns a promise with an underlying http call, it is a helper method to reduce - * the amount of duplicate code needed to query http resources and automatically handle any - * Http errors. See /docs/source/using-promises-resources.md - * - * @param {object} opts A mixed object which can either be a string representing the error message to be - * returned OR an object containing either: - * { success: successCallback, errorMsg: errorMessage } - * OR - * { success: successCallback, error: errorCallback } - * In both of the above, the successCallback must accept these parameters: data, status, headers, config - * If using the errorCallback it must accept these parameters: data, status, headers, config - * The success callback must return the data which will be resolved by the deferred object. - * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status } - */ - resourcePromise: function (httpPromise, opts) { - - /** The default success callback used if one is not supplied in the opts */ - function defaultSuccess(data, status, headers, config) { - //when it's successful, just return the data - return data; - } - - /** The default error callback used if one is not supplied in the opts */ - function defaultError(data, status, headers, config) { - return { - //NOTE: the default error message here should never be used based on the above docs! - errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'), - data: data, - status: status - }; - } - - //create the callbacs based on whats been passed in. - var callbacks = { - success: ((!opts || !opts.success) ? defaultSuccess : opts.success), - error: ((!opts || !opts.error) ? defaultError : opts.error) - }; - - return httpPromise.then(function (response) { - - //invoke the callback - var result = callbacks.success.apply(this, [response.data, response.status, response.headers, response.config]); +/** +* @ngdoc service +* @name umbraco.services.umbRequestHelper +* @description A helper object used for sending requests to the server +**/ +function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService, formHelper) { + + return { + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path + * + * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown + */ + convertVirtualToAbsolutePath: function(virtualPath) { + if (virtualPath.startsWith("/")) { + return virtualPath; + } + if (!virtualPath.startsWith("~/")) { + throw "The path " + virtualPath + " is not a virtual path"; + } + if (!Umbraco.Sys.ServerVariables.application.applicationPath) { + throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath"; + } + return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/"); + }, + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#dictionaryToQueryString + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will turn an array of key/value pairs or a standard dictionary into a query string + * + * @param {Array} queryStrings An array of key/value pairs + */ + dictionaryToQueryString: function (queryStrings) { + + if (angular.isArray(queryStrings)) { + return _.map(queryStrings, function (item) { + var key = null; + var val = null; + for (var k in item) { + key = k; + val = item[k]; + break; + } + if (key === null || val === null) { + throw "The object in the array was not formatted as a key/value pair"; + } + return encodeURIComponent(key) + "=" + encodeURIComponent(val); + }).join("&"); + } + else if (angular.isObject(queryStrings)) { + + //this allows for a normal object to be passed in (ie. a dictionary) + return decodeURIComponent($.param(queryStrings)); + } + + throw "The queryString parameter is not an array or object of key value pairs"; + }, + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#getApiUrl + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will return the webapi Url for the requested key based on the servervariables collection + * + * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary + * @param {string} actionName The webapi action name + * @param {object} queryStrings Can be either a string or an array containing key/value pairs + */ + getApiUrl: function (apiName, actionName, queryStrings) { + if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) { + throw "No server variables defined!"; + } + + if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) { + throw "No url found for api name " + apiName; + } + + return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName + + (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings))); + + }, + + /** + * @ngdoc function + * @name umbraco.services.umbRequestHelper#resourcePromise + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This returns a promise with an underlying http call, it is a helper method to reduce + * the amount of duplicate code needed to query http resources and automatically handle any + * Http errors. See /docs/source/using-promises-resources.md + * + * @param {object} opts A mixed object which can either be a string representing the error message to be + * returned OR an object containing either: + * { success: successCallback, errorMsg: errorMessage } + * OR + * { success: successCallback, error: errorCallback } + * In both of the above, the successCallback must accept these parameters: data, status, headers, config + * If using the errorCallback it must accept these parameters: data, status, headers, config + * The success callback must return the data which will be resolved by the deferred object. + * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status } + */ + resourcePromise: function (httpPromise, opts) { + + /** The default success callback used if one is not supplied in the opts */ + function defaultSuccess(data, status, headers, config) { + //when it's successful, just return the data + return data; + } + + /** The default error callback used if one is not supplied in the opts */ + function defaultError(data, status, headers, config) { + return { + //NOTE: the default error message here should never be used based on the above docs! + errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'), + data: data, + status: status + }; + } + + //create the callbacs based on whats been passed in. + var callbacks = { + success: ((!opts || !opts.success) ? defaultSuccess : opts.success), + error: ((!opts || !opts.error) ? defaultError : opts.error) + }; + + return httpPromise.then(function (response) { + + //invoke the callback + var result = callbacks.success.apply(this, [response.data, response.status, response.headers, response.config]); formHelper.showNotifications(response.data); - - //when it's successful, just return the data - return $q.resolve(result); - - }, function (response) { - - //invoke the callback - var result = callbacks.error.apply(this, [response.data, response.status, response.headers, response.config]); - - //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. - if (response.status >= 500 && response.status < 600) { - - //show a ysod dialog - if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { - eventsService.emit('app.ysod', - { - errorMsg: 'An error occured', - data: response.data - }); - } - else { - //show a simple error notification - notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + result.errorMsg + ""); - } - - } - else { - formHelper.showNotifications(result.data); - } - - //return an error object including the error message for UI - return $q.reject({ - errorMsg: result.errorMsg, - data: result.data, - status: result.status - }) - }); - - }, - - /** Used for saving content/media/members specifically */ - postSaveContent: function (args) { - - if (!args.restApiUrl) { - throw "args.restApiUrl is a required argument"; - } - if (!args.content) { - throw "args.content is a required argument"; - } - if (!args.action) { - throw "args.action is a required argument"; - } - if (!args.files) { - throw "args.files is a required argument"; - } - if (!args.dataFormatter) { - throw "args.dataFormatter is a required argument"; - } - - //save the active tab id so we can set it when the data is returned. - var activeTab = _.find(args.content.tabs, function (item) { - return item.active; - }); - var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); - - //save the data - return this.postMultiPartRequest( - args.restApiUrl, - { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, - //data transform callback: - function (data, formData) { - //now add all of the assigned files - for (var f in args.files) { - //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key - // so we know which property it belongs to on the server side - formData.append("file_" + args.files[f].alias, args.files[f].file); - } - }).then(function (response) { - //success callback - - //reset the tabs and set the active one - if (response.data.tabs && response.data.tabs.length > 0) { - _.each(response.data.tabs, function (item) { - item.active = false; - }); - response.data.tabs[activeTabIndex].active = true; - } + + //when it's successful, just return the data + return $q.resolve(result); + + }, function (response) { + + //invoke the callback + var result = callbacks.error.apply(this, [response.data, response.status, response.headers, response.config]); + + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (response.status >= 500 && response.status < 600) { + + //show a ysod dialog + if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', + data: response.data + }); + } + else { + //show a simple error notification + notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + result.errorMsg + ""); + } + + } + else { + formHelper.showNotifications(result.data); + } + + //return an error object including the error message for UI + return $q.reject({ + errorMsg: result.errorMsg, + data: result.data, + status: result.status + }) + }); + + }, + + /** Used for saving content/media/members specifically */ + postSaveContent: function (args) { + + if (!args.restApiUrl) { + throw "args.restApiUrl is a required argument"; + } + if (!args.content) { + throw "args.content is a required argument"; + } + if (!args.action) { + throw "args.action is a required argument"; + } + if (!args.files) { + throw "args.files is a required argument"; + } + if (!args.dataFormatter) { + throw "args.dataFormatter is a required argument"; + } + + //save the active tab id so we can set it when the data is returned. + var activeTab = _.find(args.content.tabs, function (item) { + return item.active; + }); + var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); + + //save the data + return this.postMultiPartRequest( + args.restApiUrl, + { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, + //data transform callback: + function (data, formData) { + //now add all of the assigned files + for (var f in args.files) { + //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key + // so we know which property it belongs to on the server side + formData.append("file_" + args.files[f].alias, args.files[f].file); + } + }).then(function (response) { + //success callback + + //reset the tabs and set the active one + if (response.data.tabs && response.data.tabs.length > 0) { + _.each(response.data.tabs, function (item) { + item.active = false; + }); + response.data.tabs[activeTabIndex].active = true; + } formHelper.showNotifications(response.data); - - //the data returned is the up-to-date data so the UI will refresh - return $q.resolve(response.data); - }, function (response) { - //failure callback - - //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. - if (response.status >= 500 && response.status < 600) { - - //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, - // we have to just check for the existence of a string value but currently that is the best way to - // do this since it's very hacky/difficult to catch this on the server - if (typeof response.data !== "undefined" && typeof response.data.indexOf === "function" && response.data.indexOf("Maximum request length exceeded") >= 0) { - notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); - } - else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { - //show a ysod dialog - eventsService.emit('app.ysod', - { - errorMsg: 'An error occured', - data: response.data - }); - } - else { - //show a simple error notification - notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + response.data.ExceptionMessage + ""); - } - - } - else { - formHelper.showNotifications(response.data); - } - - //return an error object including the error message for UI - return $q.reject({ - errorMsg: 'An error occurred', - data: response.data, - status: response.status - }); - - }); - }, - - /** Posts a multi-part mime request to the server */ - postMultiPartRequest: function (url, jsonData, transformCallback) { - - //validate input, jsonData can be an array of key/value pairs or just one key/value pair. - if (!jsonData) { throw "jsonData cannot be null"; } - - if (angular.isArray(jsonData)) { - _.each(jsonData, function (item) { - if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } - }); - } - else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } - - return $http({ - method: 'POST', - url: url, - //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files - // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request - // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'undefined' - // will force the request to automatically populate the headers properly including the boundary parameter. - headers: { 'Content-Type': undefined }, - transformRequest: function(data) { - var formData = new FormData(); - //add the json data - if (angular.isArray(data)) { - _.each(data, function(item) { - formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); - }); - } - else { - formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); - } - - //call the callback - if (transformCallback) { - transformCallback.apply(this, [data, formData]); - } - - return formData; - }, - data: jsonData - }).then(function(response) { - return $q.resolve(response); - }, function(response) { - return $q.reject(response); - }); - }, - - /** - * Downloads a file to the client using AJAX/XHR - * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html - * See https://stackoverflow.com/a/24129082/694494 - */ - downloadFile : function (httpPath) { - - // Use an arraybuffer - return $http.get(httpPath, { responseType: 'arraybuffer' }) - .then(function (response) { - - var octetStreamMime = 'application/octet-stream'; - var success = false; - - // Get the headers - var headers = response.headers(); - - // Get the filename from the x-filename header or default to "download.bin" - var filename = headers['x-filename'] || 'download.bin'; - - // Determine the content type from the header or default to "application/octet-stream" - var contentType = headers['content-type'] || octetStreamMime; - - try { - // Try using msSaveBlob if supported - var blob = new Blob([response.data], { type: contentType }); - if (navigator.msSaveBlob) - navigator.msSaveBlob(blob, filename); - else { - // Try using other saveBlob implementations, if available - var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; - if (saveBlob === undefined) throw "Not supported"; - saveBlob(blob, filename); - } - success = true; - } catch (ex) { - console.log("saveBlob method failed with the following exception:"); - console.log(ex); - } - - if (!success) { - // Get the blob url creator - var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; - if (urlCreator) { - // Try to use a download link - var link = document.createElement('a'); - if ('download' in link) { - // Try to simulate a click - try { - // Prepare a blob URL - var blob = new Blob([response.data], { type: contentType }); - var url = urlCreator.createObjectURL(blob); - link.setAttribute('href', url); - - // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) - link.setAttribute("download", filename); - - // Simulate clicking the download link - var event = document.createEvent('MouseEvents'); - event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); - link.dispatchEvent(event); - success = true; - - } catch (ex) { - console.log("Download link method with simulated click failed with the following exception:"); - console.log(ex); - } - } - - if (!success) { - // Fallback to window.location method - try { - // Prepare a blob URL - // Use application/octet-stream when using window.location to force download - var blob = new Blob([response.data], { type: octetStreamMime }); - var url = urlCreator.createObjectURL(blob); - window.location = url; - success = true; - } catch (ex) { - console.log("Download link method with window.location failed with the following exception:"); - console.log(ex); - } - } - - } - } - - if (!success) { - // Fallback to window.open method - window.open(httpPath, '_blank', ''); - } - - return $q.resolve(); - - }, function (response) { - - return $q.reject({ - errorMsg: "An error occurred downloading the file", - data: response.data, - status: response.status - }); - }); - } - }; -} -angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); + + //the data returned is the up-to-date data so the UI will refresh + return $q.resolve(response.data); + }, function (response) { + //failure callback + + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (response.status >= 500 && response.status < 600) { + + //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, + // we have to just check for the existence of a string value but currently that is the best way to + // do this since it's very hacky/difficult to catch this on the server + if (typeof response.data !== "undefined" && typeof response.data.indexOf === "function" && response.data.indexOf("Maximum request length exceeded") >= 0) { + notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); + } + else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { + //show a ysod dialog + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', + data: response.data + }); + } + else { + //show a simple error notification + notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + response.data.ExceptionMessage + ""); + } + + } + else { + formHelper.showNotifications(response.data); + } + + //return an error object including the error message for UI + return $q.reject({ + errorMsg: 'An error occurred', + data: response.data, + status: response.status + }); + + }); + }, + + /** Posts a multi-part mime request to the server */ + postMultiPartRequest: function (url, jsonData, transformCallback) { + + //validate input, jsonData can be an array of key/value pairs or just one key/value pair. + if (!jsonData) { throw "jsonData cannot be null"; } + + if (angular.isArray(jsonData)) { + _.each(jsonData, function (item) { + if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } + }); + } + else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } + + return $http({ + method: 'POST', + url: url, + //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files + // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request + // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'undefined' + // will force the request to automatically populate the headers properly including the boundary parameter. + headers: { 'Content-Type': undefined }, + transformRequest: function(data) { + var formData = new FormData(); + //add the json data + if (angular.isArray(data)) { + _.each(data, function(item) { + formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); + }); + } + else { + formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); + } + + //call the callback + if (transformCallback) { + transformCallback.apply(this, [data, formData]); + } + + return formData; + }, + data: jsonData + }).then(function(response) { + return $q.resolve(response); + }, function(response) { + return $q.reject(response); + }); + }, + + /** + * Downloads a file to the client using AJAX/XHR + * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html + * See https://stackoverflow.com/a/24129082/694494 + */ + downloadFile : function (httpPath) { + + // Use an arraybuffer + return $http.get(httpPath, { responseType: 'arraybuffer' }) + .then(function (response) { + + var octetStreamMime = 'application/octet-stream'; + var success = false; + + // Get the headers + var headers = response.headers(); + + // Get the filename from the x-filename header or default to "download.bin" + var filename = headers['x-filename'] || 'download.bin'; + + // Determine the content type from the header or default to "application/octet-stream" + var contentType = headers['content-type'] || octetStreamMime; + + try { + // Try using msSaveBlob if supported + var blob = new Blob([response.data], { type: contentType }); + if (navigator.msSaveBlob) + navigator.msSaveBlob(blob, filename); + else { + // Try using other saveBlob implementations, if available + var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; + if (saveBlob === undefined) throw "Not supported"; + saveBlob(blob, filename); + } + success = true; + } catch (ex) { + console.log("saveBlob method failed with the following exception:"); + console.log(ex); + } + + if (!success) { + // Get the blob url creator + var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; + if (urlCreator) { + // Try to use a download link + var link = document.createElement('a'); + if ('download' in link) { + // Try to simulate a click + try { + // Prepare a blob URL + var blob = new Blob([response.data], { type: contentType }); + var url = urlCreator.createObjectURL(blob); + link.setAttribute('href', url); + + // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) + link.setAttribute("download", filename); + + // Simulate clicking the download link + var event = document.createEvent('MouseEvents'); + event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); + link.dispatchEvent(event); + success = true; + + } catch (ex) { + console.log("Download link method with simulated click failed with the following exception:"); + console.log(ex); + } + } + + if (!success) { + // Fallback to window.location method + try { + // Prepare a blob URL + // Use application/octet-stream when using window.location to force download + var blob = new Blob([response.data], { type: octetStreamMime }); + var url = urlCreator.createObjectURL(blob); + window.location = url; + success = true; + } catch (ex) { + console.log("Download link method with window.location failed with the following exception:"); + console.log(ex); + } + } + + } + } + + if (!success) { + // Fallback to window.open method + window.open(httpPath, '_blank', ''); + } + + return $q.resolve(); + + }, function (response) { + + return $q.reject({ + errorMsg: "An error occurred downloading the file", + data: response.data, + status: response.status + }); + }); + } + }; +} +angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 13743e01f3..5b723d9328 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -1,282 +1,282 @@ -angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, $log, requestRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) { - - var currentUser = null; - var lastUserId = null; - var loginDialog = null; - - //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server - // this is used so that we know when to go and get the user's remaining seconds directly. - var lastServerTimeoutSet = null; - - function openLoginDialog(isTimedOut) { - if (!loginDialog) { - loginDialog = dialogService.open({ - - //very special flag which means that global events cannot close this dialog - manualClose: true, - - template: 'views/common/dialogs/login.html', - modalClass: "login-overlay", - animation: "slide", - show: true, - callback: onLoginDialogClose, - dialogData: { - isTimedOut: isTimedOut - } - }); - } - } - - function onLoginDialogClose(success) { - loginDialog = null; - - if (success) { - requestRetryQueue.retryAll(currentUser.name); - } - else { - requestRetryQueue.cancelAll(); - $location.path('/'); - } - } - - /** - This methods will set the current user when it is resolved and - will then start the counter to count in-memory how many seconds they have - remaining on the auth session - */ - function setCurrentUser(usr) { - if (!usr.remainingAuthSeconds) { - throw "The user object is invalid, the remainingAuthSeconds is required."; - } - currentUser = usr; - lastServerTimeoutSet = new Date(); - //start the timer - countdownUserTimeout(); - } - - /** - Method to count down the current user's timeout seconds, - this will continually count down their current remaining seconds every 5 seconds until - there are no more seconds remaining. - */ - function countdownUserTimeout() { - - $timeout(function () { - - if (currentUser) { - //countdown by 5 seconds since that is how long our timer is for. - currentUser.remainingAuthSeconds -= 5; - - //if there are more than 30 remaining seconds, recurse! - if (currentUser.remainingAuthSeconds > 30) { - - //we need to check when the last time the timeout was set from the server, if - // it has been more than 30 seconds then we'll manually go and retrieve it from the - // server - this helps to keep our local countdown in check with the true timeout. - if (lastServerTimeoutSet != null) { - var now = new Date(); - var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000; - - if (seconds > 30) { - - //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we - // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. - lastServerTimeoutSet = null; - - //now go get it from the server - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - authResource.getRemainingTimeoutSeconds().then(function (result) { - setUserTimeoutInternal(result); - }); - }); - } - } - - //recurse the countdown! - countdownUserTimeout(); - } - else { - - //we are either timed out or very close to timing out so we need to show the login dialog. - if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - try { - //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we - // don't actually care about this result. - authResource.getRemainingTimeoutSeconds(); - } - finally { - userAuthExpired(); - } - }); - } - else { - //we've got less than 30 seconds remaining so let's check the server - - if (lastServerTimeoutSet != null) { - //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we - // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. - lastServerTimeoutSet = null; - - //now go get it from the server - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - authResource.getRemainingTimeoutSeconds().then(function (result) { - setUserTimeoutInternal(result); - }); - }); - } - - //recurse the countdown! - countdownUserTimeout(); - - } - } - } - }, 5000, //every 5 seconds - false); //false = do NOT execute a digest for every iteration - } - - /** Called to update the current user's timeout */ - function setUserTimeoutInternal(newTimeout) { - - - var asNumber = parseFloat(newTimeout); - if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) { - currentUser.remainingAuthSeconds = newTimeout; - lastServerTimeoutSet = new Date(); - } - } - - /** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */ - function userAuthExpired(isLogout) { - //store the last user id and clear the user - if (currentUser && currentUser.id !== undefined) { - lastUserId = currentUser.id; - } - - if (currentUser) { - currentUser.remainingAuthSeconds = 0; - } - - lastServerTimeoutSet = null; - currentUser = null; - - //broadcast a global event that the user is no longer logged in - eventsService.emit("app.notAuthenticated"); - - openLoginDialog(isLogout === undefined ? true : !isLogout); - } - - // Register a handler for when an item is added to the retry queue - requestRetryQueue.onItemAddedCallbacks.push(function (retryItem) { - if (requestRetryQueue.hasMore()) { - userAuthExpired(); - } - }); - - return { - - /** Internal method to display the login dialog */ - _showLoginDialog: function () { - openLoginDialog(); - }, - /** Returns a promise, sends a request to the server to check if the current cookie is authorized */ - isAuthenticated: function () { - //if we've got a current user then just return true - if (currentUser) { - var deferred = $q.defer(); - deferred.resolve(true); - return deferred.promise; - } - return authResource.isAuthenticated(); - }, - - /** Returns a promise, sends a request to the server to validate the credentials */ - authenticate: function (login, password) { - - return authResource.performLogin(login, password) - .then(this.setAuthenticationSuccessful); - }, - setAuthenticationSuccessful: function (data) { - - //when it's successful, return the user data - setCurrentUser(data); - - var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" }; - - //broadcast a global event - eventsService.emit("app.authenticated", result); - return result; - }, - - /** Logs the user out - */ - logout: function () { - - return authResource.performLogout() - .then(function (data) { - userAuthExpired(); - //done! - return null; - }); - }, - - /** Refreshes the current user data with the data stored for the user on the server and returns it */ - refreshCurrentUser: function () { - var deferred = $q.defer(); - - authResource.getCurrentUser() - .then(function (data) { - - var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; - - setCurrentUser(data); - - deferred.resolve(currentUser); - }, function () { - //it failed, so they are not logged in - deferred.reject(); - }); - - return deferred.promise; - }, - - /** Returns the current user object in a promise */ - getCurrentUser: function (args) { - - if (!currentUser) { - return authResource.getCurrentUser() - .then(function (data) { - - var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; - - if (args && args.broadcastEvent) { - //broadcast a global event, will inform listening controllers to load in the user specific data - eventsService.emit("app.authenticated", result); - } - - setCurrentUser(data); - - return $q.when(currentUser); - }, function () { - //it failed, so they are not logged in - return $q.reject(currentUser); - }); - - } - else { - return $q.when(currentUser); - } - }, - - /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ - setUserTimeout: function (newTimeout) { - setUserTimeoutInternal(newTimeout); - } - }; - - }); +angular.module('umbraco.services') + .factory('userService', function ($rootScope, eventsService, $q, $location, $log, requestRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) { + + var currentUser = null; + var lastUserId = null; + var loginDialog = null; + + //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server + // this is used so that we know when to go and get the user's remaining seconds directly. + var lastServerTimeoutSet = null; + + function openLoginDialog(isTimedOut) { + if (!loginDialog) { + loginDialog = dialogService.open({ + + //very special flag which means that global events cannot close this dialog + manualClose: true, + + template: 'views/common/dialogs/login.html', + modalClass: "login-overlay", + animation: "slide", + show: true, + callback: onLoginDialogClose, + dialogData: { + isTimedOut: isTimedOut + } + }); + } + } + + function onLoginDialogClose(success) { + loginDialog = null; + + if (success) { + requestRetryQueue.retryAll(currentUser.name); + } + else { + requestRetryQueue.cancelAll(); + $location.path('/'); + } + } + + /** + This methods will set the current user when it is resolved and + will then start the counter to count in-memory how many seconds they have + remaining on the auth session + */ + function setCurrentUser(usr) { + if (!usr.remainingAuthSeconds) { + throw "The user object is invalid, the remainingAuthSeconds is required."; + } + currentUser = usr; + lastServerTimeoutSet = new Date(); + //start the timer + countdownUserTimeout(); + } + + /** + Method to count down the current user's timeout seconds, + this will continually count down their current remaining seconds every 5 seconds until + there are no more seconds remaining. + */ + function countdownUserTimeout() { + + $timeout(function () { + + if (currentUser) { + //countdown by 5 seconds since that is how long our timer is for. + currentUser.remainingAuthSeconds -= 5; + + //if there are more than 30 remaining seconds, recurse! + if (currentUser.remainingAuthSeconds > 30) { + + //we need to check when the last time the timeout was set from the server, if + // it has been more than 30 seconds then we'll manually go and retrieve it from the + // server - this helps to keep our local countdown in check with the true timeout. + if (lastServerTimeoutSet != null) { + var now = new Date(); + var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000; + + if (seconds > 30) { + + //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we + // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. + lastServerTimeoutSet = null; + + //now go get it from the server + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + }); + }); + } + } + + //recurse the countdown! + countdownUserTimeout(); + } + else { + + //we are either timed out or very close to timing out so we need to show the login dialog. + if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + try { + //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we + // don't actually care about this result. + authResource.getRemainingTimeoutSeconds(); + } + finally { + userAuthExpired(); + } + }); + } + else { + //we've got less than 30 seconds remaining so let's check the server + + if (lastServerTimeoutSet != null) { + //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we + // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. + lastServerTimeoutSet = null; + + //now go get it from the server + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + }); + }); + } + + //recurse the countdown! + countdownUserTimeout(); + + } + } + } + }, 5000, //every 5 seconds + false); //false = do NOT execute a digest for every iteration + } + + /** Called to update the current user's timeout */ + function setUserTimeoutInternal(newTimeout) { + + + var asNumber = parseFloat(newTimeout); + if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) { + currentUser.remainingAuthSeconds = newTimeout; + lastServerTimeoutSet = new Date(); + } + } + + /** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */ + function userAuthExpired(isLogout) { + //store the last user id and clear the user + if (currentUser && currentUser.id !== undefined) { + lastUserId = currentUser.id; + } + + if (currentUser) { + currentUser.remainingAuthSeconds = 0; + } + + lastServerTimeoutSet = null; + currentUser = null; + + //broadcast a global event that the user is no longer logged in + eventsService.emit("app.notAuthenticated"); + + openLoginDialog(isLogout === undefined ? true : !isLogout); + } + + // Register a handler for when an item is added to the retry queue + requestRetryQueue.onItemAddedCallbacks.push(function (retryItem) { + if (requestRetryQueue.hasMore()) { + userAuthExpired(); + } + }); + + return { + + /** Internal method to display the login dialog */ + _showLoginDialog: function () { + openLoginDialog(); + }, + /** Returns a promise, sends a request to the server to check if the current cookie is authorized */ + isAuthenticated: function () { + //if we've got a current user then just return true + if (currentUser) { + var deferred = $q.defer(); + deferred.resolve(true); + return deferred.promise; + } + return authResource.isAuthenticated(); + }, + + /** Returns a promise, sends a request to the server to validate the credentials */ + authenticate: function (login, password) { + + return authResource.performLogin(login, password) + .then(this.setAuthenticationSuccessful); + }, + setAuthenticationSuccessful: function (data) { + + //when it's successful, return the user data + setCurrentUser(data); + + var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" }; + + //broadcast a global event + eventsService.emit("app.authenticated", result); + return result; + }, + + /** Logs the user out + */ + logout: function () { + + return authResource.performLogout() + .then(function (data) { + userAuthExpired(); + //done! + return null; + }); + }, + + /** Refreshes the current user data with the data stored for the user on the server and returns it */ + refreshCurrentUser: function () { + var deferred = $q.defer(); + + authResource.getCurrentUser() + .then(function (data) { + + var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; + + setCurrentUser(data); + + deferred.resolve(currentUser); + }, function () { + //it failed, so they are not logged in + deferred.reject(); + }); + + return deferred.promise; + }, + + /** Returns the current user object in a promise */ + getCurrentUser: function (args) { + + if (!currentUser) { + return authResource.getCurrentUser() + .then(function (data) { + + var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; + + if (args && args.broadcastEvent) { + //broadcast a global event, will inform listening controllers to load in the user specific data + eventsService.emit("app.authenticated", result); + } + + setCurrentUser(data); + + return $q.when(currentUser); + }, function () { + //it failed, so they are not logged in + return $q.reject(currentUser); + }); + + } + else { + return $q.when(currentUser); + } + }, + + /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ + setUserTimeout: function (newTimeout) { + setUserTimeoutInternal(newTimeout); + } + }; + + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 79a42b92d4..a7ff9def21 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -1,298 +1,298 @@ -/*Contains multiple services for various helper tasks */ -function versionHelper() { - - return { - - //see: https://gist.github.com/TheDistantSea/8021359 - versionCompare: function (v1, v2, options) { - var lexicographical = options && options.lexicographical, - zeroExtend = options && options.zeroExtend, - v1parts = v1.split('.'), - v2parts = v2.split('.'); - - function isValidPart(x) { - return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); - } - - if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { - return NaN; - } - - if (zeroExtend) { - while (v1parts.length < v2parts.length) { - v1parts.push("0"); - } - while (v2parts.length < v1parts.length) { - v2parts.push("0"); - } - } - - if (!lexicographical) { - v1parts = v1parts.map(Number); - v2parts = v2parts.map(Number); - } - - for (var i = 0; i < v1parts.length; ++i) { - if (v2parts.length === i) { - return 1; - } - - if (v1parts[i] === v2parts[i]) { - continue; - } - else if (v1parts[i] > v2parts[i]) { - return 1; - } - else { - return -1; - } - } - - if (v1parts.length !== v2parts.length) { - return -1; - } - - return 0; - } - }; -} -angular.module('umbraco.services').factory('versionHelper', versionHelper); - -function dateHelper() { - - return { - - convertToServerStringTime: function (momentLocal, serverOffsetMinutes, format) { - - //get the formatted offset time in HH:mm (server time offset is in minutes) - var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + - moment() - .startOf('day') - .minutes(Math.abs(serverOffsetMinutes)) - .format('HH:mm'); - - var server = moment.utc(momentLocal).utcOffset(formattedOffset); - return server.format(format ? format : "YYYY-MM-DD HH:mm:ss"); - }, - - convertToLocalMomentTime: function (strVal, serverOffsetMinutes) { - - //get the formatted offset time in HH:mm (server time offset is in minutes) - var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + - moment() - .startOf('day') - .minutes(Math.abs(serverOffsetMinutes)) - .format('HH:mm'); - - //if the string format already denotes that it's in "Roundtrip UTC" format (i.e. "2018-02-07T00:20:38.173Z") - //otherwise known as https://en.wikipedia.org/wiki/ISO_8601. This is the default format returned from the server - //since that is the default formatter for newtonsoft.json. When it is in this format, we need to tell moment - //to load the date as UTC so it's not changed, otherwise load it normally - var isoFormat; - if (strVal.indexOf("T") > -1 && strVal.endsWith("Z")) { - isoFormat = moment.utc(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset; - } - else { - isoFormat = moment(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset; - } - - //create a moment with the iso format which will include the offset with the correct time - // then convert it to local time - return moment.parseZone(isoFormat).local(); - }, - - getLocalDate: function (date, culture, format) { - if (date) { - var dateVal; - var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; - var localOffset = new Date().getTimezoneOffset(); - var serverTimeNeedsOffsetting = -serverOffset !== localOffset; - if (serverTimeNeedsOffsetting) { - dateVal = this.convertToLocalMomentTime(date, serverOffset); - } else { - dateVal = moment(date, 'YYYY-MM-DD HH:mm:ss'); - } - return dateVal.locale(culture).format(format); - } - } - - }; -} -angular.module('umbraco.services').factory('dateHelper', dateHelper); - -function packageHelper(assetsService, treeService, eventsService, $templateCache) { - - return { - - /** Called when a package is installed, this resets a bunch of data and ensures the new package assets are loaded in */ - packageInstalled: function () { - - //clears the tree - treeService.clearCache(); - - //clears the template cache - $templateCache.removeAll(); - - //emit event to notify anything else - eventsService.emit("app.reInitialize"); - } - - }; -} -angular.module('umbraco.services').factory('packageHelper', packageHelper); - -/** - * @ngdoc function - * @name umbraco.services.umbModelMapper - * @function - * - * @description - * Utility class to map/convert models - */ -function umbModelMapper() { - - return { - - - /** - * @ngdoc function - * @name umbraco.services.umbModelMapper#convertToEntityBasic - * @methodOf umbraco.services.umbModelMapper - * @function - * - * @description - * Converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model. - * @param {Object} source The source model - * @param {Number} source.id The node id of the model - * @param {String} source.name The node name - * @param {String} source.icon The models icon as a css class (.icon-doc) - * @param {Number} source.parentId The parentID, if no parent, set to -1 - * @param {path} source.path comma-separated string of ancestor IDs (-1,1234,1782,1234) - */ - - /** This converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model */ - convertToEntityBasic: function (source) { - var required = ["id", "name", "icon", "parentId", "path"]; - _.each(required, function (k) { - if (!_.has(source, k)) { - throw "The source object does not contain the property " + k; - } - }); - var optional = ["metaData", "key", "alias"]; - //now get the basic object - var result = _.pick(source, required.concat(optional)); - return result; - } - - }; -} -angular.module('umbraco.services').factory('umbModelMapper', umbModelMapper); - -/** - * @ngdoc function - * @name umbraco.services.umbSessionStorage - * @function - * - * @description - * Used to get/set things in browser sessionStorage but always prefixes keys with "umb_" and converts json vals so there is no overlap - * with any sessionStorage created by a developer. - */ -function umbSessionStorage($window) { - - //gets the sessionStorage object if available, otherwise just uses a normal object - // - required for unit tests. - var storage = $window['sessionStorage'] ? $window['sessionStorage'] : {}; - - return { - - get: function (key) { - return angular.fromJson(storage["umb_" + key]); - }, - - set: function (key, value) { - storage["umb_" + key] = angular.toJson(value); - } - - }; -} -angular.module('umbraco.services').factory('umbSessionStorage', umbSessionStorage); - -/** - * @ngdoc function - * @name umbraco.services.updateChecker - * @function - * - * @description - * used to check for updates and display a notifcation - */ -function updateChecker($http, umbRequestHelper) { - return { - - /** - * @ngdoc function - * @name umbraco.services.updateChecker#check - * @methodOf umbraco.services.updateChecker - * @function - * - * @description - * Called to load in the legacy tree js which is required on startup if a user is logged in or - * after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded. - */ - check: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "updateCheckApiBaseUrl", - "GetCheck")), - 'Failed to retrieve update status'); - } - }; -} -angular.module('umbraco.services').factory('updateChecker', updateChecker); - -/** -* @ngdoc service -* @name umbraco.services.umbPropertyEditorHelper -* @description A helper object used for property editors -**/ -function umbPropEditorHelper() { - return { - /** - * @ngdoc function - * @name getImagePropertyValue - * @methodOf umbraco.services.umbPropertyEditorHelper - * @function - * - * @description - * Returns the correct view path for a property editor, it will detect if it is a full virtual path but if not then default to the internal umbraco one - * - * @param {string} input the view path currently stored for the property editor - */ - getViewPath: function (input, isPreValue) { - var path = String(input); - - if (path.startsWith('/')) { - - //This is an absolute path, so just leave it - return path; - } else { - - if (path.indexOf("/") >= 0) { - //This is a relative path, so just leave it - return path; - } else { - if (!isPreValue) { - //i.e. views/propertyeditors/fileupload/fileupload.html - return "views/propertyeditors/" + path + "/" + path + ".html"; - } else { - //i.e. views/prevalueeditors/requiredfield.html - return "views/prevalueeditors/" + path + ".html"; - } - } - - } - } - }; -} +/*Contains multiple services for various helper tasks */ +function versionHelper() { + + return { + + //see: https://gist.github.com/TheDistantSea/8021359 + versionCompare: function (v1, v2, options) { + var lexicographical = options && options.lexicographical, + zeroExtend = options && options.zeroExtend, + v1parts = v1.split('.'), + v2parts = v2.split('.'); + + function isValidPart(x) { + return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); + } + + if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { + return NaN; + } + + if (zeroExtend) { + while (v1parts.length < v2parts.length) { + v1parts.push("0"); + } + while (v2parts.length < v1parts.length) { + v2parts.push("0"); + } + } + + if (!lexicographical) { + v1parts = v1parts.map(Number); + v2parts = v2parts.map(Number); + } + + for (var i = 0; i < v1parts.length; ++i) { + if (v2parts.length === i) { + return 1; + } + + if (v1parts[i] === v2parts[i]) { + continue; + } + else if (v1parts[i] > v2parts[i]) { + return 1; + } + else { + return -1; + } + } + + if (v1parts.length !== v2parts.length) { + return -1; + } + + return 0; + } + }; +} +angular.module('umbraco.services').factory('versionHelper', versionHelper); + +function dateHelper() { + + return { + + convertToServerStringTime: function (momentLocal, serverOffsetMinutes, format) { + + //get the formatted offset time in HH:mm (server time offset is in minutes) + var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + + moment() + .startOf('day') + .minutes(Math.abs(serverOffsetMinutes)) + .format('HH:mm'); + + var server = moment.utc(momentLocal).utcOffset(formattedOffset); + return server.format(format ? format : "YYYY-MM-DD HH:mm:ss"); + }, + + convertToLocalMomentTime: function (strVal, serverOffsetMinutes) { + + //get the formatted offset time in HH:mm (server time offset is in minutes) + var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + + moment() + .startOf('day') + .minutes(Math.abs(serverOffsetMinutes)) + .format('HH:mm'); + + //if the string format already denotes that it's in "Roundtrip UTC" format (i.e. "2018-02-07T00:20:38.173Z") + //otherwise known as https://en.wikipedia.org/wiki/ISO_8601. This is the default format returned from the server + //since that is the default formatter for newtonsoft.json. When it is in this format, we need to tell moment + //to load the date as UTC so it's not changed, otherwise load it normally + var isoFormat; + if (strVal.indexOf("T") > -1 && strVal.endsWith("Z")) { + isoFormat = moment.utc(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset; + } + else { + isoFormat = moment(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset; + } + + //create a moment with the iso format which will include the offset with the correct time + // then convert it to local time + return moment.parseZone(isoFormat).local(); + }, + + getLocalDate: function (date, culture, format) { + if (date) { + var dateVal; + var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; + var localOffset = new Date().getTimezoneOffset(); + var serverTimeNeedsOffsetting = -serverOffset !== localOffset; + if (serverTimeNeedsOffsetting) { + dateVal = this.convertToLocalMomentTime(date, serverOffset); + } else { + dateVal = moment(date, 'YYYY-MM-DD HH:mm:ss'); + } + return dateVal.locale(culture).format(format); + } + } + + }; +} +angular.module('umbraco.services').factory('dateHelper', dateHelper); + +function packageHelper(assetsService, treeService, eventsService, $templateCache) { + + return { + + /** Called when a package is installed, this resets a bunch of data and ensures the new package assets are loaded in */ + packageInstalled: function () { + + //clears the tree + treeService.clearCache(); + + //clears the template cache + $templateCache.removeAll(); + + //emit event to notify anything else + eventsService.emit("app.reInitialize"); + } + + }; +} +angular.module('umbraco.services').factory('packageHelper', packageHelper); + +/** + * @ngdoc function + * @name umbraco.services.umbModelMapper + * @function + * + * @description + * Utility class to map/convert models + */ +function umbModelMapper() { + + return { + + + /** + * @ngdoc function + * @name umbraco.services.umbModelMapper#convertToEntityBasic + * @methodOf umbraco.services.umbModelMapper + * @function + * + * @description + * Converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model. + * @param {Object} source The source model + * @param {Number} source.id The node id of the model + * @param {String} source.name The node name + * @param {String} source.icon The models icon as a css class (.icon-doc) + * @param {Number} source.parentId The parentID, if no parent, set to -1 + * @param {path} source.path comma-separated string of ancestor IDs (-1,1234,1782,1234) + */ + + /** This converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model */ + convertToEntityBasic: function (source) { + var required = ["id", "name", "icon", "parentId", "path"]; + _.each(required, function (k) { + if (!_.has(source, k)) { + throw "The source object does not contain the property " + k; + } + }); + var optional = ["metaData", "key", "alias"]; + //now get the basic object + var result = _.pick(source, required.concat(optional)); + return result; + } + + }; +} +angular.module('umbraco.services').factory('umbModelMapper', umbModelMapper); + +/** + * @ngdoc function + * @name umbraco.services.umbSessionStorage + * @function + * + * @description + * Used to get/set things in browser sessionStorage but always prefixes keys with "umb_" and converts json vals so there is no overlap + * with any sessionStorage created by a developer. + */ +function umbSessionStorage($window) { + + //gets the sessionStorage object if available, otherwise just uses a normal object + // - required for unit tests. + var storage = $window['sessionStorage'] ? $window['sessionStorage'] : {}; + + return { + + get: function (key) { + return angular.fromJson(storage["umb_" + key]); + }, + + set: function (key, value) { + storage["umb_" + key] = angular.toJson(value); + } + + }; +} +angular.module('umbraco.services').factory('umbSessionStorage', umbSessionStorage); + +/** + * @ngdoc function + * @name umbraco.services.updateChecker + * @function + * + * @description + * used to check for updates and display a notifcation + */ +function updateChecker($http, umbRequestHelper) { + return { + + /** + * @ngdoc function + * @name umbraco.services.updateChecker#check + * @methodOf umbraco.services.updateChecker + * @function + * + * @description + * Called to load in the legacy tree js which is required on startup if a user is logged in or + * after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded. + */ + check: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "updateCheckApiBaseUrl", + "GetCheck")), + 'Failed to retrieve update status'); + } + }; +} +angular.module('umbraco.services').factory('updateChecker', updateChecker); + +/** +* @ngdoc service +* @name umbraco.services.umbPropertyEditorHelper +* @description A helper object used for property editors +**/ +function umbPropEditorHelper() { + return { + /** + * @ngdoc function + * @name getImagePropertyValue + * @methodOf umbraco.services.umbPropertyEditorHelper + * @function + * + * @description + * Returns the correct view path for a property editor, it will detect if it is a full virtual path but if not then default to the internal umbraco one + * + * @param {string} input the view path currently stored for the property editor + */ + getViewPath: function (input, isPreValue) { + var path = String(input); + + if (path.startsWith('/')) { + + //This is an absolute path, so just leave it + return path; + } else { + + if (path.indexOf("/") >= 0) { + //This is a relative path, so just leave it + return path; + } else { + if (!isPreValue) { + //i.e. views/propertyeditors/fileupload/fileupload.html + return "views/propertyeditors/" + path + "/" + path + ".html"; + } else { + //i.e. views/prevalueeditors/requiredfield.html + return "views/prevalueeditors/" + path + ".html"; + } + } + + } + } + }; +} angular.module('umbraco.services').factory('umbPropEditorHelper', umbPropEditorHelper); diff --git a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js index fe1148d6a8..bec3e97543 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js @@ -1,158 +1,158 @@ -/** - * @ngdoc controller - * @name Umbraco.SearchController - * @function - * - * @description - * Controls the search functionality in the site - * - */ -function SearchController($scope, searchService, $log, $location, navigationService, $q) { - - $scope.searchTerm = null; - $scope.searchResults = []; - $scope.isSearching = false; - $scope.selectedResult = -1; - - $scope.navigateResults = function (ev) { - //38: up 40: down, 13: enter - - switch (ev.keyCode) { - case 38: - iterateResults(true); - break; - case 40: - iterateResults(false); - break; - case 13: - if ($scope.selectedItem) { - $location.path($scope.selectedItem.editorPath); - navigationService.hideSearch(); - } - break; - } - }; - - var group = undefined; - var groupNames = []; - var groupIndex = -1; - var itemIndex = -1; - $scope.selectedItem = undefined; - - $scope.clearSearch = function () { - $scope.searchTerm = null; - }; - function iterateResults(up) { - //default group - if (!group) { - - for (var g in $scope.groups) { - if ($scope.groups.hasOwnProperty(g)) { - groupNames.push(g); - - } - } - - //Sorting to match the groups order - groupNames.sort(); - - group = $scope.groups[groupNames[0]]; - groupIndex = 0; - } - - if (up) { - if (itemIndex === 0) { - if (groupIndex === 0) { - gotoGroup(Object.keys($scope.groups).length - 1, true); - } else { - gotoGroup(groupIndex - 1, true); - } - } else { - gotoItem(itemIndex - 1); - } - } else { - if (itemIndex < group.results.length - 1) { - gotoItem(itemIndex + 1); - } else { - if (groupIndex === Object.keys($scope.groups).length - 1) { - gotoGroup(0); - } else { - gotoGroup(groupIndex + 1); - } - } - } - } - - function gotoGroup(index, up) { - groupIndex = index; - group = $scope.groups[groupNames[groupIndex]]; - - if (up) { - gotoItem(group.results.length - 1); - } else { - gotoItem(0); - } - } - - function gotoItem(index) { - itemIndex = index; - $scope.selectedItem = group.results[itemIndex]; - } - - //used to cancel any request in progress if another one needs to take it's place - var canceler = null; - - $scope.$watch("searchTerm", _.debounce(function (newVal, oldVal) { - $scope.$apply(function () { - $scope.hasResults = false; - if ($scope.searchTerm) { - if (newVal !== null && newVal !== undefined && newVal !== oldVal) { - - //Resetting for brand new search - group = undefined; - groupNames = []; - groupIndex = -1; - itemIndex = -1; - - $scope.isSearching = true; - navigationService.showSearch(); - $scope.selectedItem = undefined; - - //a canceler exists, so perform the cancelation operation and reset - if (canceler) { - canceler.resolve(); - canceler = $q.defer(); - } - else { - canceler = $q.defer(); - } - - searchService.searchAll({ term: $scope.searchTerm, canceler: canceler }).then(function (result) { - - //result is a dictionary of group Title and it's results - var filtered = {}; - _.each(result, function (value, key) { - if (value.results.length > 0) { - filtered[key] = value; - } - }); - $scope.groups = filtered; - // check if search has results - $scope.hasResults = Object.keys($scope.groups).length > 0; - //set back to null so it can be re-created - canceler = null; - $scope.isSearching = false; - }); - } - } - else { - $scope.isSearching = false; - navigationService.hideSearch(); - $scope.selectedItem = undefined; - } - }); - }, 200)); - -} -//register it +/** + * @ngdoc controller + * @name Umbraco.SearchController + * @function + * + * @description + * Controls the search functionality in the site + * + */ +function SearchController($scope, searchService, $log, $location, navigationService, $q) { + + $scope.searchTerm = null; + $scope.searchResults = []; + $scope.isSearching = false; + $scope.selectedResult = -1; + + $scope.navigateResults = function (ev) { + //38: up 40: down, 13: enter + + switch (ev.keyCode) { + case 38: + iterateResults(true); + break; + case 40: + iterateResults(false); + break; + case 13: + if ($scope.selectedItem) { + $location.path($scope.selectedItem.editorPath); + navigationService.hideSearch(); + } + break; + } + }; + + var group = undefined; + var groupNames = []; + var groupIndex = -1; + var itemIndex = -1; + $scope.selectedItem = undefined; + + $scope.clearSearch = function () { + $scope.searchTerm = null; + }; + function iterateResults(up) { + //default group + if (!group) { + + for (var g in $scope.groups) { + if ($scope.groups.hasOwnProperty(g)) { + groupNames.push(g); + + } + } + + //Sorting to match the groups order + groupNames.sort(); + + group = $scope.groups[groupNames[0]]; + groupIndex = 0; + } + + if (up) { + if (itemIndex === 0) { + if (groupIndex === 0) { + gotoGroup(Object.keys($scope.groups).length - 1, true); + } else { + gotoGroup(groupIndex - 1, true); + } + } else { + gotoItem(itemIndex - 1); + } + } else { + if (itemIndex < group.results.length - 1) { + gotoItem(itemIndex + 1); + } else { + if (groupIndex === Object.keys($scope.groups).length - 1) { + gotoGroup(0); + } else { + gotoGroup(groupIndex + 1); + } + } + } + } + + function gotoGroup(index, up) { + groupIndex = index; + group = $scope.groups[groupNames[groupIndex]]; + + if (up) { + gotoItem(group.results.length - 1); + } else { + gotoItem(0); + } + } + + function gotoItem(index) { + itemIndex = index; + $scope.selectedItem = group.results[itemIndex]; + } + + //used to cancel any request in progress if another one needs to take it's place + var canceler = null; + + $scope.$watch("searchTerm", _.debounce(function (newVal, oldVal) { + $scope.$apply(function () { + $scope.hasResults = false; + if ($scope.searchTerm) { + if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + + //Resetting for brand new search + group = undefined; + groupNames = []; + groupIndex = -1; + itemIndex = -1; + + $scope.isSearching = true; + navigationService.showSearch(); + $scope.selectedItem = undefined; + + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } + else { + canceler = $q.defer(); + } + + searchService.searchAll({ term: $scope.searchTerm, canceler: canceler }).then(function (result) { + + //result is a dictionary of group Title and it's results + var filtered = {}; + _.each(result, function (value, key) { + if (value.results.length > 0) { + filtered[key] = value; + } + }); + $scope.groups = filtered; + // check if search has results + $scope.hasResults = Object.keys($scope.groups).length > 0; + //set back to null so it can be re-created + canceler = null; + $scope.isSearching = false; + }); + } + } + else { + $scope.isSearching = false; + navigationService.hideSearch(); + $scope.selectedItem = undefined; + } + }); + }, 200)); + +} +//register it angular.module('umbraco').controller("Umbraco.SearchController", SearchController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/index.html b/src/Umbraco.Web.UI.Client/src/index.html index 50d78c315b..bd354efc90 100644 --- a/src/Umbraco.Web.UI.Client/src/index.html +++ b/src/Umbraco.Web.UI.Client/src/index.html @@ -1,27 +1,27 @@ - - - - - - - - Umbraco - - - - -
    - - -
    -
    -
    -
    -
    - - - - - - - + + + + + + + + Umbraco + + + + +
    + + +
    +
    +
    +
    +
    + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/less/alerts.less b/src/Umbraco.Web.UI.Client/src/less/alerts.less index 7973c6e207..3907b59f58 100644 --- a/src/Umbraco.Web.UI.Client/src/less/alerts.less +++ b/src/Umbraco.Web.UI.Client/src/less/alerts.less @@ -1,100 +1,100 @@ -// -// Alerts -// -------------------------------------------------- - - -// Base styles -// ------------------------- - -.alert { - padding: 8px 35px 8px 14px; - margin-bottom: @baseLineHeight; - background-color: @warningBackground; - border: 1px solid @warningBorder; - .border-radius(@alertBorderRadius); -} -.alert, -.alert h4, -.alert a { - // Specified for the h4 to prevent conflicts of changing @headingsColor - color: @warningText; -} -.alert h4 { - margin: 0; -} - -// Adjust close link position -.alert .close { - position: relative; - top: -2px; - right: -21px; - line-height: @baseLineHeight; -} - -.close.-align-right { - right: 0; -} - - -// Alternate styles -// ------------------------- - -.alert-success { - background-color: @successBackground; - border-color: @successBorder; - color: @successText; -} -.alert-success h4 { - color: @successText; -} -.alert-danger, -.alert-error { - background-color: @errorBackground; - border-color: @errorBorder; - color: @errorText; -} -.alert-danger h4, -.alert-error h4 { - color: @errorText; -} -.alert-info { - background-color: @infoBackground; - border-color: @infoBorder; - color: @infoText; -} -.alert-info h4 { - color: @infoText; -} - -.alert-form { - background-color: @white; - border: 1px solid @gray-3 !important; - color: @gray-3; - box-shadow: 0 -1px 6px 0 rgba(0,0,0,0.16); -} - -.alert-form.-no-border { - border: none !important; -} - -.alert-form h4 { - color: @black; - font-weight: bold; - margin-bottom: 5px; -} - - -// Block alerts -// ------------------------- - -.alert-block { - padding-top: 14px; - padding-bottom: 14px; -} -.alert-block > p, -.alert-block > ul { - margin-bottom: 0; -} -.alert-block p + p { - margin-top: 5px; -} +// +// Alerts +// -------------------------------------------------- + + +// Base styles +// ------------------------- + +.alert { + padding: 8px 35px 8px 14px; + margin-bottom: @baseLineHeight; + background-color: @warningBackground; + border: 1px solid @warningBorder; + .border-radius(@alertBorderRadius); +} +.alert, +.alert h4, +.alert a { + // Specified for the h4 to prevent conflicts of changing @headingsColor + color: @warningText; +} +.alert h4 { + margin: 0; +} + +// Adjust close link position +.alert .close { + position: relative; + top: -2px; + right: -21px; + line-height: @baseLineHeight; +} + +.close.-align-right { + right: 0; +} + + +// Alternate styles +// ------------------------- + +.alert-success { + background-color: @successBackground; + border-color: @successBorder; + color: @successText; +} +.alert-success h4 { + color: @successText; +} +.alert-danger, +.alert-error { + background-color: @errorBackground; + border-color: @errorBorder; + color: @errorText; +} +.alert-danger h4, +.alert-error h4 { + color: @errorText; +} +.alert-info { + background-color: @infoBackground; + border-color: @infoBorder; + color: @infoText; +} +.alert-info h4 { + color: @infoText; +} + +.alert-form { + background-color: @white; + border: 1px solid @gray-3 !important; + color: @gray-3; + box-shadow: 0 -1px 6px 0 rgba(0,0,0,0.16); +} + +.alert-form.-no-border { + border: none !important; +} + +.alert-form h4 { + color: @black; + font-weight: bold; + margin-bottom: 5px; +} + + +// Block alerts +// ------------------------- + +.alert-block { + padding-top: 14px; + padding-bottom: 14px; +} +.alert-block > p, +.alert-block > ul { + margin-bottom: 0; +} +.alert-block p + p { + margin-top: 5px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index e1021eff2d..b18f2c942f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -1,189 +1,189 @@ - -// Core variables and mixins -@import "fonts.less"; // Loading fonts -@import "variables.less"; // Modify this for custom colors, font-sizes, etc -@import "mixins.less"; - -// CSS Reset -@import "../../lib/bootstrap/less/reset.less"; - -// Grid system and page structure -@import "../../lib/bootstrap/less/scaffolding.less"; -@import "../../lib/bootstrap/less/grid.less"; -@import "../../lib/bootstrap/less/layouts.less"; - -// Base CSS -@import "../../lib/bootstrap/less/type.less"; -@import "../../lib/bootstrap/less/code.less"; -@import "tables.less"; - - -// Components: common -@import "../../lib/bootstrap/less/dropdowns.less"; -@import "../../lib/bootstrap/less/wells.less"; -@import "../../lib/bootstrap/less/component-animations.less"; -@import "../../lib/bootstrap/less/close.less"; - -// Components: Buttons & Alerts -@import "../../lib/bootstrap/less/button-groups.less"; -@import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less - -// Components: Nav -@import "navs.less"; -@import "../../lib/bootstrap/less/navbar.less"; -@import "../../lib/bootstrap/less/breadcrumbs.less"; -@import "../../lib/bootstrap/less/pagination.less"; -@import "../../lib/bootstrap/less/pager.less"; - -// Components: Popovers -@import "../../lib/bootstrap/less/modals.less"; -@import "../../lib/bootstrap/less/tooltip.less"; -@import "../../lib/bootstrap/less/popovers.less"; -@import "tipmenu.less"; - -// Components: Misc -@import "../../lib/bootstrap/less/thumbnails.less"; -@import "../../lib/bootstrap/less/media.less"; -@import "../../lib/bootstrap/less/labels-badges.less"; -@import "../../lib/bootstrap/less/progress-bars.less"; -@import "../../lib/bootstrap/less/accordion.less"; -@import "../../lib/bootstrap/less/carousel.less"; -@import "../../lib/bootstrap/less/hero-unit.less"; - - -// Utility classes -@import "../../lib/bootstrap/less/utilities.less"; // Has to be last to override when necessary - - -// Application wide styles (refactor is WIP) -@import "application/grid.less"; -@import "application/shadows.less"; -@import "application/animations.less"; - -// Utilities -@import "utilities/_font-weight.less"; - -// Belle styles -@import "buttons.less"; -@import "forms.less"; -@import "modals.less"; -@import "panel.less"; -@import "sections.less"; -@import "helveticons.less"; -@import "main.less"; -@import "tree.less"; -@import "listview.less"; -@import "gridview.less"; -@import "footer.less"; -@import "dragdrop.less"; -@import "dashboards.less"; - -@import "forms/umb-validation-label.less"; - -// Umbraco Components -@import "components/application/umb-app-header.less"; -@import "components/application/umb-app-content.less"; -@import "components/application/umb-tour.less"; -@import "components/application/umb-backdrop.less"; -@import "components/application/umb-drawer.less"; -@import "components/application/umb-language-picker.less"; -@import "components/application/umb-dashboard.less"; - -@import "components/html/umb-expansion-panel.less"; -@import "components/html/umb-alert.less"; - -@import "components/editor.less"; -@import "components/overlays.less"; -@import "components/card.less"; -@import "components/editor/umb-editor.less"; -@import "components/umb-sub-views.less"; -@import "components/umb-editor-navigation.less"; -@import "components/umb-editor-sub-views.less"; -@import "components/editor/subheader/umb-editor-sub-header.less"; -@import "components/umb-grid-selector.less"; -@import "components/umb-child-selector.less"; -@import "components/umb-group-builder.less"; -@import "components/umb-list-view-settings.less"; -@import "components/umb-table.less"; -@import "components/umb-confirm-action.less"; -@import "components/umb-keyboard-shortcuts-overview.less"; -@import "components/umb-checkbox-list.less"; -@import "components/umb-locked-field.less"; -@import "components/umb-tabs.less"; -@import "components/umb-load-indicator.less"; -@import "components/umb-breadcrumbs.less"; -@import "components/umb-media-grid.less"; -@import "components/umb-folder-grid.less"; -@import "components/umb-content-grid.less"; -@import "components/umb-layout-selector.less"; -@import "components/tooltip/umb-tooltip.less"; -@import "components/tooltip/umb-tooltip-list.less"; -@import "components/overlays/umb-overlay-backdrop.less"; -@import "components/umb-grid.less"; -@import "components/umb-empty-state.less"; -@import "components/umb-property-editor.less"; -@import "components/umb-iconpicker.less"; -@import "components/umb-insert-code-box.less"; -@import "components/umb-packages.less"; -@import "components/umb-package-local-install.less"; -@import "components/umb-lightbox.less"; -@import "components/umb-avatar.less"; -@import "components/umb-progress-bar.less"; -@import "components/umb-querybuilder.less"; -@import "components/umb-pagination.less"; -@import "components/umb-mini-list-view.less"; -@import "components/umb-badge.less"; -@import "components/umb-nested-content.less"; -@import "components/umb-checkmark.less"; -@import "components/umb-list.less"; -@import "components/umb-box.less"; -@import "components/umb-number-badge.less"; -@import "components/umb-progress-circle.less"; - -@import "components/buttons/umb-button.less"; -@import "components/buttons/umb-button-group.less"; -@import "components/buttons/umb-toggle.less"; - -@import "components/notifications/umb-notifications.less"; -@import "components/umb-file-dropzone.less"; -@import "components/umb-node-preview.less"; -@import "components/umb-mini-editor.less"; - -@import "components/users/umb-user-cards.less"; -@import "components/users/umb-user-group-picker-list.less"; -@import "components/users/umb-user-group-preview.less"; -@import "components/users/umb-user-preview.less"; -@import "components/users/umb-user-picker-list.less"; -@import "components/users/umb-permission.less"; - - -// Utilities -@import "utilities/layout/_display.less"; -@import "utilities/typography/_text-decoration.less"; -@import "utilities/typography/_white-space.less"; -@import "utilities/_flexbox.less"; -@import "utilities/_spacing.less"; -@import "utilities/_text-align.less"; -@import "utilities/_width.less"; - -//page specific styles -@import "pages/document-type-editor.less"; -@import "pages/login.less"; -@import "pages/welcome-dashboard.less"; - - -//used for property editors -@import "property-editors.less"; - -//used for prevalue editors -@import "components/prevalues/multivalues.less"; - - -@import "typeahead.less"; -@import "hacks.less"; - -@import "healthcheck.less"; -@import "getstarted.less"; - -// cleanup properties.less when it is done -@import "properties.less"; + +// Core variables and mixins +@import "fonts.less"; // Loading fonts +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "mixins.less"; + +// CSS Reset +@import "../../lib/bootstrap/less/reset.less"; + +// Grid system and page structure +@import "../../lib/bootstrap/less/scaffolding.less"; +@import "../../lib/bootstrap/less/grid.less"; +@import "../../lib/bootstrap/less/layouts.less"; + +// Base CSS +@import "../../lib/bootstrap/less/type.less"; +@import "../../lib/bootstrap/less/code.less"; +@import "tables.less"; + + +// Components: common +@import "../../lib/bootstrap/less/dropdowns.less"; +@import "../../lib/bootstrap/less/wells.less"; +@import "../../lib/bootstrap/less/component-animations.less"; +@import "../../lib/bootstrap/less/close.less"; + +// Components: Buttons & Alerts +@import "../../lib/bootstrap/less/button-groups.less"; +@import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less + +// Components: Nav +@import "navs.less"; +@import "../../lib/bootstrap/less/navbar.less"; +@import "../../lib/bootstrap/less/breadcrumbs.less"; +@import "../../lib/bootstrap/less/pagination.less"; +@import "../../lib/bootstrap/less/pager.less"; + +// Components: Popovers +@import "../../lib/bootstrap/less/modals.less"; +@import "../../lib/bootstrap/less/tooltip.less"; +@import "../../lib/bootstrap/less/popovers.less"; +@import "tipmenu.less"; + +// Components: Misc +@import "../../lib/bootstrap/less/thumbnails.less"; +@import "../../lib/bootstrap/less/media.less"; +@import "../../lib/bootstrap/less/labels-badges.less"; +@import "../../lib/bootstrap/less/progress-bars.less"; +@import "../../lib/bootstrap/less/accordion.less"; +@import "../../lib/bootstrap/less/carousel.less"; +@import "../../lib/bootstrap/less/hero-unit.less"; + + +// Utility classes +@import "../../lib/bootstrap/less/utilities.less"; // Has to be last to override when necessary + + +// Application wide styles (refactor is WIP) +@import "application/grid.less"; +@import "application/shadows.less"; +@import "application/animations.less"; + +// Utilities +@import "utilities/_font-weight.less"; + +// Belle styles +@import "buttons.less"; +@import "forms.less"; +@import "modals.less"; +@import "panel.less"; +@import "sections.less"; +@import "helveticons.less"; +@import "main.less"; +@import "tree.less"; +@import "listview.less"; +@import "gridview.less"; +@import "footer.less"; +@import "dragdrop.less"; +@import "dashboards.less"; + +@import "forms/umb-validation-label.less"; + +// Umbraco Components +@import "components/application/umb-app-header.less"; +@import "components/application/umb-app-content.less"; +@import "components/application/umb-tour.less"; +@import "components/application/umb-backdrop.less"; +@import "components/application/umb-drawer.less"; +@import "components/application/umb-language-picker.less"; +@import "components/application/umb-dashboard.less"; + +@import "components/html/umb-expansion-panel.less"; +@import "components/html/umb-alert.less"; + +@import "components/editor.less"; +@import "components/overlays.less"; +@import "components/card.less"; +@import "components/editor/umb-editor.less"; +@import "components/umb-sub-views.less"; +@import "components/umb-editor-navigation.less"; +@import "components/umb-editor-sub-views.less"; +@import "components/editor/subheader/umb-editor-sub-header.less"; +@import "components/umb-grid-selector.less"; +@import "components/umb-child-selector.less"; +@import "components/umb-group-builder.less"; +@import "components/umb-list-view-settings.less"; +@import "components/umb-table.less"; +@import "components/umb-confirm-action.less"; +@import "components/umb-keyboard-shortcuts-overview.less"; +@import "components/umb-checkbox-list.less"; +@import "components/umb-locked-field.less"; +@import "components/umb-tabs.less"; +@import "components/umb-load-indicator.less"; +@import "components/umb-breadcrumbs.less"; +@import "components/umb-media-grid.less"; +@import "components/umb-folder-grid.less"; +@import "components/umb-content-grid.less"; +@import "components/umb-layout-selector.less"; +@import "components/tooltip/umb-tooltip.less"; +@import "components/tooltip/umb-tooltip-list.less"; +@import "components/overlays/umb-overlay-backdrop.less"; +@import "components/umb-grid.less"; +@import "components/umb-empty-state.less"; +@import "components/umb-property-editor.less"; +@import "components/umb-iconpicker.less"; +@import "components/umb-insert-code-box.less"; +@import "components/umb-packages.less"; +@import "components/umb-package-local-install.less"; +@import "components/umb-lightbox.less"; +@import "components/umb-avatar.less"; +@import "components/umb-progress-bar.less"; +@import "components/umb-querybuilder.less"; +@import "components/umb-pagination.less"; +@import "components/umb-mini-list-view.less"; +@import "components/umb-badge.less"; +@import "components/umb-nested-content.less"; +@import "components/umb-checkmark.less"; +@import "components/umb-list.less"; +@import "components/umb-box.less"; +@import "components/umb-number-badge.less"; +@import "components/umb-progress-circle.less"; + +@import "components/buttons/umb-button.less"; +@import "components/buttons/umb-button-group.less"; +@import "components/buttons/umb-toggle.less"; + +@import "components/notifications/umb-notifications.less"; +@import "components/umb-file-dropzone.less"; +@import "components/umb-node-preview.less"; +@import "components/umb-mini-editor.less"; + +@import "components/users/umb-user-cards.less"; +@import "components/users/umb-user-group-picker-list.less"; +@import "components/users/umb-user-group-preview.less"; +@import "components/users/umb-user-preview.less"; +@import "components/users/umb-user-picker-list.less"; +@import "components/users/umb-permission.less"; + + +// Utilities +@import "utilities/layout/_display.less"; +@import "utilities/typography/_text-decoration.less"; +@import "utilities/typography/_white-space.less"; +@import "utilities/_flexbox.less"; +@import "utilities/_spacing.less"; +@import "utilities/_text-align.less"; +@import "utilities/_width.less"; + +//page specific styles +@import "pages/document-type-editor.less"; +@import "pages/login.less"; +@import "pages/welcome-dashboard.less"; + + +//used for property editors +@import "property-editors.less"; + +//used for prevalue editors +@import "components/prevalues/multivalues.less"; + + +@import "typeahead.less"; +@import "hacks.less"; + +@import "healthcheck.less"; +@import "getstarted.less"; + +// cleanup properties.less when it is done +@import "properties.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index 6a402c7946..daa6757f44 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -1,316 +1,316 @@ -// -// Buttons -// -------------------------------------------------- - - -// Base styles -// -------------------------------------------------- - -// Core -.btn { - display: inline-block; - padding: 6px 14px; - margin-bottom: 0; // For input.btn - font-size: @baseFontSize; - line-height: @baseLineHeight; - text-align: center; - vertical-align: middle; - cursor: pointer; - background: @btnBackground; - color: @black; - border: none; - box-shadow: none; - border-radius: 3px; - - // Hover/focus state - &:hover, - &:focus { - background: @btnBackgroundHighlight; - color: @gray-4; - background-position: 0 -15px; - text-decoration: none; - - // transition is only when going to hover/focus, otherwise the background - // behind the gradient (there for IE<=9 fallback) gets mismatched - .transition(background-position .1s linear); - } - - // Focus state for keyboard and accessibility - &:focus { - .tab-focus(); - } - - // Active state - &.active, - &:active { - background-image: none; - outline: 0; - .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); - } - - // Disabled state - &.disabled, - &[disabled], - &:disabled:hover { - cursor: default; - border-color: @btnBorder; - .opacity(65); - .box-shadow(none); - } - -} - -.btn-group>.btn+.dropdown-toggle { - box-shadow: none; - -webkit-box-shadow:none; -} - -.btn-group .btn.dropdown-toggle { - border-left-width: 1px; - border-left-style: solid; - border-color: rgba(0,0,0,0.09); -} - -// Button Sizes -// -------------------------------------------------- - -// Large -.btn-large { - padding: @paddingLarge; - font-size: @fontSizeLarge; -} -.btn-large [class^="icon-"], -.btn-large [class*=" icon-"] { - margin-top: 4px; - .border-radius(@borderRadiusLarge); -} - -// Small -.btn-small { - padding: @paddingSmall; - font-size: @fontSizeSmall; - .border-radius(@borderRadiusSmall); -} - -.btn-small [class^="icon-"], -.btn-small [class*=" icon-"] { - margin-top: 0; -} -.btn-mini [class^="icon-"], -.btn-mini [class*=" icon-"] { - margin-top: -1px; -} - -// Mini -.btn-mini { - padding: @paddingMini; - font-size: @fontSizeMini; - .border-radius(@borderRadiusSmall); -} - - -// Block button -// ------------------------- - -.btn-block { - display: block; - width: 100%; - padding-left: 0; - padding-right: 0; - .box-sizing(border-box); -} - -// Vertically space out multiple block buttons -.btn-block + .btn-block { - margin-top: 5px; -} - -// Specificity overrides -input[type="submit"], -input[type="reset"], -input[type="button"] { - &.btn-block { - width: 100%; - } -} - -// Round button -.btn-round{ - font-size: 24px; - color: @gray-3; - background: @white; - - line-height: 32px; - text-align: center; - -moz-border-radius: 15px; - border-radius: 15px; - height: 32px; - width: 32px; - overflow: hidden; - display: inline-block; - z-index: 6666; -} - - -// Alternate buttons -// -------------------------------------------------- - -// Provide *some* extra contrast for those who can get it -.btn-primary.active, -.btn-warning.active, -.btn-danger.active, -.btn-success.active, -.btn-info.active, -.btn-inverse.active, -.btn-neutral.active { - color: rgba(255,255,255,.75); -} - -.btn-primary, -.btn-warning, -.btn-danger, -.btn-success, -.btn-info, -.btn-inverse, -.btn-neutral { - font-weight: bold; -} - -// Set the backgrounds -// ------------------------- -.btn-primary { - .buttonBackground(@btnPrimaryBackground, @btnPrimaryBackgroundHighlight); -} - -// Warning appears are orange -.btn-warning { - .buttonBackground(@btnWarningBackground, @btnWarningBackgroundHighlight); -} -// Danger and error appear as red -.btn-danger { - .buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight); -} -// Success appears as green -.btn-success { - .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight); -} -// Info appears as a neutral blue -.btn-info { - .buttonBackground(@btnInfoBackground, @btnInfoBackgroundHighlight); -} -// Inverse appears as dark gray -.btn-inverse { - .buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight); -} -// Neutral appears as lighter gray -.btn-neutral { - .buttonBackground(@btnNeutralBackground, @btnNeutralBackgroundHighlight); - color: @gray-5; - // Hover/focus state - &:hover, - &:focus { - color: @gray-5; - } - - &.disabled, - &[disabled] { - color: @gray-5; - .opacity(65); - } - -} - -.btn-install { - margin: 40px auto; - display: block; - padding: 15px 50px; - font-size: 16px; - border: none; - background: @green; - color: white; - font-weight: bold; - &:hover { - background: @green-d1; - } -} - -// Cross-browser Jank -// -------------------------------------------------- - -button.btn, -input[type="submit"].btn { - - // Firefox 3.6 only I believe - &::-moz-focus-inner { - padding: 0; - border: 0; - } - - // IE7 has some default padding on button controls - *padding-top: 3px; - *padding-bottom: 3px; - - &.btn-large { - *padding-top: 7px; - *padding-bottom: 7px; - } - &.btn-small { - *padding-top: 3px; - *padding-bottom: 3px; - } - &.btn-mini { - *padding-top: 1px; - *padding-bottom: 1px; - } - - // Safari defaults to 1px for input. Ref U4-7721. - margin: 0px; -} - - -// Link buttons -// -------------------------------------------------- - -// Make a button look and behave like a link -.btn-link, -.btn-link:active, -.btn-link[disabled] { - background-color: transparent; - background-image: none; - .box-shadow(none); -} -.btn-link { - border-color: transparent; - cursor: pointer; - color: @linkColor; - .border-radius(0); -} -.btn-link:hover, -.btn-link:focus { - color: @linkColorHover; - text-decoration: underline; - background-color: transparent; -} -.btn-link[disabled]:hover, -.btn-link[disabled]:focus { - color: @gray-4; - text-decoration: none; -} - -// Make a reverse type of a button link -.btn-link-reverse{ - text-decoration:underline; - &:hover, - &:focus{ - text-decoration:none; - } -} - -.btn-link.-underline { - display: inline-block; - text-decoration: underline; - - &:hover { - text-decoration: none; - } -} +// +// Buttons +// -------------------------------------------------- + + +// Base styles +// -------------------------------------------------- + +// Core +.btn { + display: inline-block; + padding: 6px 14px; + margin-bottom: 0; // For input.btn + font-size: @baseFontSize; + line-height: @baseLineHeight; + text-align: center; + vertical-align: middle; + cursor: pointer; + background: @btnBackground; + color: @black; + border: none; + box-shadow: none; + border-radius: 3px; + + // Hover/focus state + &:hover, + &:focus { + background: @btnBackgroundHighlight; + color: @gray-4; + background-position: 0 -15px; + text-decoration: none; + + // transition is only when going to hover/focus, otherwise the background + // behind the gradient (there for IE<=9 fallback) gets mismatched + .transition(background-position .1s linear); + } + + // Focus state for keyboard and accessibility + &:focus { + .tab-focus(); + } + + // Active state + &.active, + &:active { + background-image: none; + outline: 0; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); + } + + // Disabled state + &.disabled, + &[disabled], + &:disabled:hover { + cursor: default; + border-color: @btnBorder; + .opacity(65); + .box-shadow(none); + } + +} + +.btn-group>.btn+.dropdown-toggle { + box-shadow: none; + -webkit-box-shadow:none; +} + +.btn-group .btn.dropdown-toggle { + border-left-width: 1px; + border-left-style: solid; + border-color: rgba(0,0,0,0.09); +} + +// Button Sizes +// -------------------------------------------------- + +// Large +.btn-large { + padding: @paddingLarge; + font-size: @fontSizeLarge; +} +.btn-large [class^="icon-"], +.btn-large [class*=" icon-"] { + margin-top: 4px; + .border-radius(@borderRadiusLarge); +} + +// Small +.btn-small { + padding: @paddingSmall; + font-size: @fontSizeSmall; + .border-radius(@borderRadiusSmall); +} + +.btn-small [class^="icon-"], +.btn-small [class*=" icon-"] { + margin-top: 0; +} +.btn-mini [class^="icon-"], +.btn-mini [class*=" icon-"] { + margin-top: -1px; +} + +// Mini +.btn-mini { + padding: @paddingMini; + font-size: @fontSizeMini; + .border-radius(@borderRadiusSmall); +} + + +// Block button +// ------------------------- + +.btn-block { + display: block; + width: 100%; + padding-left: 0; + padding-right: 0; + .box-sizing(border-box); +} + +// Vertically space out multiple block buttons +.btn-block + .btn-block { + margin-top: 5px; +} + +// Specificity overrides +input[type="submit"], +input[type="reset"], +input[type="button"] { + &.btn-block { + width: 100%; + } +} + +// Round button +.btn-round{ + font-size: 24px; + color: @gray-3; + background: @white; + + line-height: 32px; + text-align: center; + -moz-border-radius: 15px; + border-radius: 15px; + height: 32px; + width: 32px; + overflow: hidden; + display: inline-block; + z-index: 6666; +} + + +// Alternate buttons +// -------------------------------------------------- + +// Provide *some* extra contrast for those who can get it +.btn-primary.active, +.btn-warning.active, +.btn-danger.active, +.btn-success.active, +.btn-info.active, +.btn-inverse.active, +.btn-neutral.active { + color: rgba(255,255,255,.75); +} + +.btn-primary, +.btn-warning, +.btn-danger, +.btn-success, +.btn-info, +.btn-inverse, +.btn-neutral { + font-weight: bold; +} + +// Set the backgrounds +// ------------------------- +.btn-primary { + .buttonBackground(@btnPrimaryBackground, @btnPrimaryBackgroundHighlight); +} + +// Warning appears are orange +.btn-warning { + .buttonBackground(@btnWarningBackground, @btnWarningBackgroundHighlight); +} +// Danger and error appear as red +.btn-danger { + .buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight); +} +// Success appears as green +.btn-success { + .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight); +} +// Info appears as a neutral blue +.btn-info { + .buttonBackground(@btnInfoBackground, @btnInfoBackgroundHighlight); +} +// Inverse appears as dark gray +.btn-inverse { + .buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight); +} +// Neutral appears as lighter gray +.btn-neutral { + .buttonBackground(@btnNeutralBackground, @btnNeutralBackgroundHighlight); + color: @gray-5; + // Hover/focus state + &:hover, + &:focus { + color: @gray-5; + } + + &.disabled, + &[disabled] { + color: @gray-5; + .opacity(65); + } + +} + +.btn-install { + margin: 40px auto; + display: block; + padding: 15px 50px; + font-size: 16px; + border: none; + background: @green; + color: white; + font-weight: bold; + &:hover { + background: @green-d1; + } +} + +// Cross-browser Jank +// -------------------------------------------------- + +button.btn, +input[type="submit"].btn { + + // Firefox 3.6 only I believe + &::-moz-focus-inner { + padding: 0; + border: 0; + } + + // IE7 has some default padding on button controls + *padding-top: 3px; + *padding-bottom: 3px; + + &.btn-large { + *padding-top: 7px; + *padding-bottom: 7px; + } + &.btn-small { + *padding-top: 3px; + *padding-bottom: 3px; + } + &.btn-mini { + *padding-top: 1px; + *padding-bottom: 1px; + } + + // Safari defaults to 1px for input. Ref U4-7721. + margin: 0px; +} + + +// Link buttons +// -------------------------------------------------- + +// Make a button look and behave like a link +.btn-link, +.btn-link:active, +.btn-link[disabled] { + background-color: transparent; + background-image: none; + .box-shadow(none); +} +.btn-link { + border-color: transparent; + cursor: pointer; + color: @linkColor; + .border-radius(0); +} +.btn-link:hover, +.btn-link:focus { + color: @linkColorHover; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +.btn-link[disabled]:focus { + color: @gray-4; + text-decoration: none; +} + +// Make a reverse type of a button link +.btn-link-reverse{ + text-decoration:underline; + &:hover, + &:focus{ + text-decoration:none; + } +} + +.btn-link.-underline { + display: inline-block; + text-decoration: underline; + + &:hover { + text-decoration: none; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less index 47ed97fde2..0cb7bc9fe4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less @@ -1,44 +1,44 @@ -.umb-breadcrumbs { - list-style: none; - margin-bottom: 0; - margin-left: 0; - display: flex; - flex-wrap: wrap; -} - -.umb-breadcrumbs__ancestor { - display: flex; -} - -.umb-breadcrumbs__ancestor-link, -.umb-breadcrumbs__ancestor-text { - font-size: 13px; - color: @gray-3; - max-width: 150px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.umb-breadcrumbs__ancestor-link { - text-decoration: underline; -} - -.umb-breadcrumbs__ancestor-link:hover { - color: @black; -} - -.umb-breadcrumbs__seperator { - position: relative; - top: 1px; - margin-left: 5px; - margin-right: 5px; - color: @gray-7; -} - -input.umb-breadcrumbs__add-ancestor { - height: 25px; - margin-top: -2px; - margin-left: 3px; - width: 100px; +.umb-breadcrumbs { + list-style: none; + margin-bottom: 0; + margin-left: 0; + display: flex; + flex-wrap: wrap; +} + +.umb-breadcrumbs__ancestor { + display: flex; +} + +.umb-breadcrumbs__ancestor-link, +.umb-breadcrumbs__ancestor-text { + font-size: 13px; + color: @gray-3; + max-width: 150px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.umb-breadcrumbs__ancestor-link { + text-decoration: underline; +} + +.umb-breadcrumbs__ancestor-link:hover { + color: @black; +} + +.umb-breadcrumbs__seperator { + position: relative; + top: 1px; + margin-left: 5px; + margin-right: 5px; + color: @gray-7; +} + +input.umb-breadcrumbs__add-ancestor { + height: 25px; + margin-top: -2px; + margin-left: 3px; + width: 100px; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less index 107c14ad86..d09d170814 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -1,1103 +1,1103 @@ -// TODO General cleanup in !important (Round 2) - -// Gridview -// ------------------------- -.umb-grid IFRAME { - overflow: hidden; -} - - - -// Sortable -// ------------------------- - -// sortable-helper -.umb-grid .ui-sortable-helper { - position: absolute !important; - background-color: @turquoise !important; - height: 42px !important; - width: 42px !important; - overflow: hidden; - padding: 5px; - border-radius: 2px; - text-align: center; - font-family: "icomoon"; - box-shadow: 3px 3px 12px 0 rgba(50, 50, 50, 0.45); - - &:after { - line-height: 42px; - font-size: 22px; - content: "\e126"; - color: @white; - } - - * { - display: none; - } -} - -.umb-grid .ui-sortable-helper .umb-row-title-bar, -.umb-grid .ui-sortable-helper .cell-tools-add { - display: none !important; -} - -// sortable-placeholder -.umb-grid .ui-sortable-placeholder { - position: absolute; - left: 0; - right: 0; - background-color: @turquoise; - height: 2px; - margin-bottom: 20px; - - &:before, &:after { - position: absolute; - top: -9px; - font-family: "icomoon"; - font-size: 18px; - color: @turquoise; - } - - &:before { - left: -5px; - content: "\e0e9"; - } - - &:after { - right: -5px; - content: "\e0d7"; - } -} - -.umb-grid .umb-cell .ui-sortable-placeholder { - left: 10px; - right: 10px; -} - - - - -// utils -// ------------------------- -.umb-grid-width { - margin: 20px auto; - width: 100%; -} - -.umb-grid .right { - float: right; -} - - - -// general layout components -// ------------------------- -.umb-grid .tb { - width: 100%; -} - -.umb-grid .td { - width: 100%; - display: inline-block; - vertical-align: top; - box-sizing: border-box; -} - -.umb-grid .middle { - text-align: center; -} - -.umb-grid .mainTd { - position: relative; -} - - - -// COLUMN -// ------------------------- -.umb-grid .umb-column { - position: relative; -} - - - -// ROW -// ------------------------- -.umb-grid .umb-row { - position: relative; - margin-bottom: 40px; - padding-top: 10px; - - &:hover { - background-color: @grayLighter; - } - - &[ng-click], - &[data-ng-click], - &[x-ng-click] { - cursor: pointer; - } -} - -.umb-grid .row-tools a { - text-decoration: none; -} - - - -// CELL -// ------------------------- -.umb-grid .umb-cell { - position: relative; -} - -.umb-grid .umb-cell-content { - position: relative; - display: block; - box-sizing: border-box; - margin: 10px; - border: 1px solid transparent; -} - -.umb-grid .umb-row .umb-cell-placeholder { - min-height: 130px; - background-color: @gray-10; - border-width: 2px; - border-style: dashed; - border-color: @gray-8; - transition: border-color 100ms linear; - - &:hover { - border-color: @turquoise; - cursor: pointer; - } -} - -.umb-grid .umb-cell-content.-has-editors { - padding-top: 38px; - background-color: @white; - border-width: 1px; - border-style: solid; - border-color: @gray-8; - - &:hover { - cursor: auto; - } -} - -.umb-grid .umb-cell-content.-has-editors.-collapsed { - padding-top: 10px; -} - -.umb-grid .cell-tools { - position: absolute; - top: 10px; - right: 10px; - color: @gray-3; - font-size: 16px; -} - -.umb-grid .cell-tool { - cursor: pointer; - float: right; - &:hover { - color: @turquoise-d1; - } -} - -.umb-grid .cell-tools-add { - color: @turquoise-d1; - &:focus, &:hover { - text-decoration: none; - } -} - -.umb-grid .cell-tools-add.-center { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - color: @turquoise-d1; -} - -.umb-grid .cell-tools-add.-bar { - display: block; - background: @gray-10; - text-align: center; - padding: 5px; - border: 1px dashed @gray-7; - margin: 10px; -} - - -.umb-grid .cell-tools-remove { - display: inline-block; - position: relative; -} - -.umb-grid .cell-tools-remove .iconBox:hover, -.umb-grid .cell-tools-remove .iconBox:hover * { - background: @red !important; - border-color: @red !important; -} - -.umb-grid .cell-tools-move { - display: inline-block; -} - -.umb-grid .cell-tools-edit { - display: inline-block; - color: @white; -} - -.umb-grid .drop-overlay { - position: absolute; - z-index: 10; - top: 0; - left: 0; - background: @white; - opacity: 0.9; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - font-size: 14px; - box-sizing: border-box; - text-align: center; - line-height: 1.3em; - font-weight: bold; - flex-direction: column; -} - -.drop-overlay.-disable { - color: @red; -} - -.drop-overlay.-allow { - color: @green; -} - -.umb-grid .drop-overlay .drop-icon { - font-size: 40px; - margin-bottom: 20px; -} - - - -// CONTROL -// ------------------------- -.umb-grid .umb-control { - position: relative; - display: block; - margin-left: 10px; - margin-right: 10px; - margin-bottom: 10px; -} - -.umb-control-collapsed { - background-color: @gray-10; - padding: 5px 10px; - border-width: 1px; - border-style: solid; - border-color: transparent; - cursor: move; -} - -.umb-control-collapsed:hover { - border-color: @turquoise; -} - -.umb-grid .umb-control-click-overlay { - position: absolute; - width: 100%; - height: 100%; - z-index: 5; - top: 0; - left: 0; - opacity: 0; - cursor: pointer; - &:hover { - background-color: @turquoise; - opacity: 0.1; - transition: opacity 0.1s; - } -} - -.umb-grid .umb-row-title-bar { - display: flex; - padding-left: 10px; - padding-right: 10px; -} - -.umb-grid .umb-row-title { - display: inline-block; - cursor: pointer; - font-size: 15px; - font-weight: bold; - color: @black; - margin-right: 6px; -} - -.umb-grid .row-tools { - display: inline-block; - margin-left: 10px; - font-size: 18px; - color: @gray-3; -} - -.umb-grid .row-tool { - cursor: pointer; -} - -.umb-grid .umb-add-row { - text-align: center; -} - - - -// CONTROL PLACEHOLDER -// ------------------------- -.umb-grid .umb-control-placeholder { - min-height: 20px; - position: relative; - text-align: center; - text-align: -moz-center; - cursor: text; -} - -.umb-grid .umb-control-placeholder .placeholder { - font-size: 14px; - opacity: 0.7; - text-align: left; - padding: 5px; - border: 1px solid @gray-9; - height: 20px; -} - -.umb-grid .umb-control-placeholder:hover .placeholder { - border: 1px solid @gray-7; -} - - - -// EDITOR PLACEHOLDER -// ------------------------- -.umb-grid .umb-editor-placeholder { - min-height: 65px; - padding: 20px; - padding-bottom: 30px; - position: relative; - background-color: @white; - border: 4px dashed @gray-8; - text-align: center; - text-align: -moz-center; - cursor: pointer; -} - -.umb-grid .umb-editor-placeholder i { - color: @gray-8; - font-size: 85px; - line-height: 85px; - display: block; - margin-bottom: 10px; -} - - - -// Active states -// ------------------------- - -// Row states -.umb-grid .umb-row.-active { - background-color: @turquoise-d1; - - .umb-row-title-bar { - cursor: move; - } - - .row-tool, - .umb-row-title { - color: @white; - } - - .umb-grid-has-config { - color: fade(@white, 66); - } - - .umb-cell { - .umb-grid-has-config { - color: fade(@black, 44); - } - } - - .umb-cell .umb-cell-content { - border-color: transparent; - } -} - - -.umb-grid .umb-row.-active-child { - background-color: @gray-10; - - .umb-row-title-bar { - cursor: inherit; - } - - .umb-row-title { - color: @gray-3; - } - - .row-tool { - color: fade(@black, 23); - } - - .umb-grid-has-config { - color: fade(@black, 44); - } - - .umb-cell-content.-placeholder { - border-color: @gray-8; - - &:hover { - border-color: fade(@gray, 44); - } - } -} - - -// Cell states - -.umb-grid .umb-row .umb-cell.-active { - border-color: @gray-8; - - .umb-cell-content.-has-editors { - box-shadow: 3px 3px 6px rgba(0, 0, 0, .07); - border-color: @turquoise; - } -} - -.umb-grid .umb-row .umb-cell.-active-child { - - .cell-tool { - color: fade(@black, 23); - } - - .umb-cell-content.-has-editors { - border-color: rgba(113, 136, 160, .44); - } -} - - - - - -// Title bar and tools -.umb-grid .umb-row-title-bar { - display: flex; -} - -.umb-grid .umb-grid-right { - display: flex; - flex-direction: row; - justify-content: center; -} - -.umb-grid .umb-tools { - margin-left: auto; -} - - -// Add more content button -.umb-grid-add-more-content { - text-align: center; -} - -.umb-grid .newbtn { - width: auto; - padding: 6px 15px; - border-style: solid; - font-size: 12px; - font-weight: bold; - display: inline-block; - margin: 10px auto 20px; - border-color: @gray-9; - - &:hover { - cursor: pointer; - opacity: .77; - } -} - - - - -// Form elements -// ------------------------- -.umb-grid textarea.textstring { - display: block; - overflow: hidden; - border: none; - background: @white; - outline: none; - resize: none; - color: @gray-3; -} - -.umb-grid .umb-cell-rte textarea.mceNoEditor { - display: none !important; -} - -.umb-grid .umb-cell-media .caption { - display: block; - overflow: hidden; - border: none; - background: @white; - outline: none; - width: 98%; - resize: none; - font-style: italic; -} - -.umb-grid .cellPanelRte { - min-height: 60px; -} - -.umb-grid .umb-cell-embed iframe { - width: 100%; -} - -.umb-grid .umb-cell-rte { - border-color: transparent; -} - - - -// ICONS -// ------------------------- -.umb-grid .iconBox { - padding: 4px 6px; - display: inline-block; - cursor: pointer; - border-radius: 200px; - background: @gray-10; - border: 1px solid @gray-7; - margin: 2px; - - &:hover, &:hover * { - background: @turquoise !important; - color: @white !important; - border-color: @turquoise !important; - text-decoration: none; - } -} - -.umb-grid .iconBox span.prompt { - display: block; - white-space: nowrap; - text-align: center; -} - -.umb-grid .iconBox span.prompt > a { - text-decoration: underline; -} - -.umb-grid .iconBox a:hover { - text-decoration: none; - color: @white !important; -} - -.umb-grid .iconBox.selected { - -webkit-appearance: none; - 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); - box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); - border-radius: 3px; - background: transparent; -} - -.umb-grid .iconBox i { - font-size: 16px !important; - color: @gray-3 ; - display: block; -} - -.umb-grid .help-text { - color: @black; - font-size: 14px; - font-weight: bold; - display: inline-block; - clear: both; -} - - - -// TINYMCE EDITOR -// ------------------------- - -.umb-grid .mce-panel { - background: transparent !important; - border: none !important; - clear: both; -} - -.umb-grid .mce-btn { - button { - padding-top: 8px; - padding-bottom: 8px; - padding-left: 6px; - line-height: inherit; - } - - &:not(.mce-menubtn) { - button { - padding-right: 6px; - } - } -} - -.umb-grid .mce-toolbar { - border-bottom: 1px solid @gray-8; - background-color: rgba(250, 250, 250, 1); - display: none; -} - -.umb-grid .umb-control.-active .mce-toolbar { - display: block; -} - -.umb-grid .mce-flow-layout-item { - margin: 0; -} - -.umb-grid .mceContentBody { - overflow-y: hidden!important; -} - - -// MEDIA EDITOR -// ------------------------- -.umb-grid .fullSizeImage { - width: 100%; -} - - - -// Width -// ------------------------- -.umb-grid .boxWidth { - text-align: right; - margin-bottom: 10px; -} - -.umb-grid .boxWidth input { - text-align: center; - width: 40px; -} - -.umb-grid .boxWidth label { - font-size: 10px; - padding: 0; - margin: 5px 5px 0 0; - color: @gray-5; -} - - - -// Margin Control -// ------------------------- -.umb-grid .umb-control { - border-width: 1px; - border-style: solid; - border-color: transparent; -} - -.umb-grid .umb-control.-active { - border-color: @turquoise; -} - -.umb-grid .umb-templates-columns { - margin-top: 30px; -} - -.umb-grid .umb-control-inner { - position: relative; -} - -.umb-grid .umb-control-bar { - opacity: 0; - background: @turquoise; - padding: 2px 5px; - color: @white; - font-size: 12px; - height: 0; - display: flex; - transition: height 80ms linear, opacity 80ms linear; - align-items: center; -} - -.umb-grid .umb-control-title { - display: flex; - align-items: center; - font-weight: bold; -} - -.umb-grid .umb-control.-active .umb-control-bar { - opacity: 1; - height: 25px; - cursor: move; -} - -.umb-grid .umb-control-tools { - display: inline-block; - margin-left: 10px; -} - -.umb-grid .umb-control-tool { - font-size: 16px; - margin-right: 5px; - position: relative; - cursor: pointer; - display:inline-block; -} - - - -// Template -// ------------------------- -.umb-grid .umb-templates { - text-align: center; - overflow: hidden; - width: 100%; -} - -.umb-grid .umb-templates-template { - display: inline-block; - width: 100px; - padding-right: 30px; - margin: 20px; -} - -.umb-grid .umb-templates-template a.tb:hover { - border: 5px solid @turquoise; -} - -.umb-grid .umb-templates-template .tb { - width: 100%; - height: 150px; - padding: 10px; - background-color: @gray-10; - border: 5px solid @gray-8; - cursor: pointer; - position: relative; -} - -.umb-grid .umb-templates-template .tr { - height: 100%; - position: relative; -} - -.umb-grid .umb-templates-template .tb .umb-templates-column { - height: 100%; - border: 1px dashed @gray-8; - border-right: none; -} - -.umb-grid .umb-templates-template .tb .umb-templates-column.last { - border-right: 1px dashed @gray-8 !important; -} - -.umb-grid a.umb-templates-column:hover, -.umb-grid a.umb-templates-column.selected { - background-color: @turquoise; -} - - - -// Template Column -// ------------------------- -/* New template preview */ -.umb-grid { - .templates-preview { - display: inline-block; - width: 100%; - text-align: center; - - small { - position: absolute; - width: 100%; - left: 0; - bottom: -25px; - padding-top: 15px; - } - - .help-text { - margin: 35px 35px 0 0; - } - } - - .preview-rows { - display: inline-block; - position: relative; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 125px; - margin: 15px; - border: 3px solid @gray-8; - transition: border 100ms linear; - - &.prevalues-rows { - margin: 0 20px 20px 0; - width: 80px; - float: left; - } - - &.prevalues-templates { - margin: 0 20px 20px 0; - float: left; - } - - &:hover { - border-color: @turquoise; - cursor: pointer; - } - - .preview-row { - display: inline-block; - width: 100%; - vertical-align: bottom; - } - } - - .preview-rows.layout { - padding: 2px; - - .preview-row { - height: 100%; - } - - .preview-col { - height: 180px; - } - - .preview-cell { - background-color: @gray-10; - } - - .preview-overlay { - display: none; - } - } - - .preview-rows.columns { - min-height: 16px; - line-height: 11px; - padding: 1px; - - &.prevalues-rows { - min-height: 30px; - } - } - - .preview-rows { - .preview-col { - display: block; - float: left; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 33.3%; - - /* temp value */ - height: 10px; - margin: 0; - border: 1px solid @white; - - .preview-cell { - display: block; - width: 100%; - height: 100%; - background-color: @gray-8; - margin: 0 1px 1px 0; - } - } - - &.prevalues-templates { - .preview-col { - height: 80px; - } - } - } - - .preview-overlay { - display: block; - width: 100%; - position: absolute; - height: 100%; - top: 0; - box-sizing: border-box; - left: 0; - border: 3px solid @white; - } -} - -// Has Config -// ------------------------- - -.umb-grid .umb-grid-has-config { - display: inline; - font-size: 13px; - color: @gray-5; -} - -.umb-grid .umb-cell { - .umb-grid-has-config { - position: absolute; - top: 10px; - left: 10px; - } -} - - -// Overlay -// ------------------------- -.umb-grid .cell-tools-menu { - position: absolute; - width: 360px; - height: 380px; - overflow: auto; - border: 1px solid @gray-8; - margin-top: -270px; - margin-left: -150px; - background: @white; - padding: 7px; - top: 0; - left: 50%; - z-index: 6660; - box-shadow: 3px 3px 12px 0 rgba(50, 50, 50, 0.45); -} - -.umb-grid .cell-tools-menu h5 { - border-bottom: 1px solid @gray-8; - color: @gray-5; - padding: 10px; - margin-top: 0; -} - -.umb-grid .elements { - display: block; - padding: 0; - margin: 0; -} - -.umb-grid .elements li { - display: inline-block; - width: 90px; - height: 80px; - margin: 5px; - padding: 5px; - overflow: hidden; - font-size: 12px; - - &:hover, &:hover * { - background: @turquoise; - color: @white; - } -} - -.umb-grid .elements a { - color: @gray-1; - text-decoration: none; -} - -.umb-grid .elements i { - font-size: 30px; - line-height: 50px; - color: @gray-6; - display: block; -} - - - -// Configuration specific styles -// ------------------------- -.umb-grid-configuration .umb-templates { - text-align: left; -} - -.umb-grid-configuration ul { - display: block; -} - -.umb-grid-configuration ul li { - display: block; - width: auto; - text-align: left; -} - -.umb-grid-configuration .umb-templates .umb-templates-template .tb { - max-height: 50px; - border-width: 2px !important; - padding: 0; - border-spacing: 2px; - overflow: hidden; -} - -.umb-grid-configuration .umb-templates .umb-templates-template span { - background: @gray-8; - display: inline-block; -} - -.umb-grid-configuration .umb-templates .umb-templates-template .tb:hover { - border-width: 2px !important; -} - -.umb-grid-configuration .umb-templates-column { - display: block; - float: left; - margin-left: -1px; - border: 1px @white solid !important; - background: @gray-8; -} - -.umb-grid-configuration .umb-templates-column.last { - margin-right: -1px; -} - -.umb-grid-configuration .umb-templates-column.add { - text-align: center; - font-size: 20px; - line-height: 70px; - color: @gray-8; - text-decoration: none; - background: @white; -} - -.umb-grid-configuration .mainTdpt { - height: initial; - border: none; -} - -.umb-grid-configuration .umb-templates-rows .umb-templates-row { - margin: 0 50px 20px 0; - width: 60px; -} - -.umb-grid-configuration .umb-templates-rows .umb-templates-row .tb { - border-width: 2px !important; - padding: 0; - border-spacing: 2px; -} - -.umb-grid-configuration .umb-templates-rows .mainTdpt { - height: 10px !important; -} - -.umb-grid-configuration a.umb-templates-column { - height: 70px !important; -} +// TODO General cleanup in !important (Round 2) + +// Gridview +// ------------------------- +.umb-grid IFRAME { + overflow: hidden; +} + + + +// Sortable +// ------------------------- + +// sortable-helper +.umb-grid .ui-sortable-helper { + position: absolute !important; + background-color: @turquoise !important; + height: 42px !important; + width: 42px !important; + overflow: hidden; + padding: 5px; + border-radius: 2px; + text-align: center; + font-family: "icomoon"; + box-shadow: 3px 3px 12px 0 rgba(50, 50, 50, 0.45); + + &:after { + line-height: 42px; + font-size: 22px; + content: "\e126"; + color: @white; + } + + * { + display: none; + } +} + +.umb-grid .ui-sortable-helper .umb-row-title-bar, +.umb-grid .ui-sortable-helper .cell-tools-add { + display: none !important; +} + +// sortable-placeholder +.umb-grid .ui-sortable-placeholder { + position: absolute; + left: 0; + right: 0; + background-color: @turquoise; + height: 2px; + margin-bottom: 20px; + + &:before, &:after { + position: absolute; + top: -9px; + font-family: "icomoon"; + font-size: 18px; + color: @turquoise; + } + + &:before { + left: -5px; + content: "\e0e9"; + } + + &:after { + right: -5px; + content: "\e0d7"; + } +} + +.umb-grid .umb-cell .ui-sortable-placeholder { + left: 10px; + right: 10px; +} + + + + +// utils +// ------------------------- +.umb-grid-width { + margin: 20px auto; + width: 100%; +} + +.umb-grid .right { + float: right; +} + + + +// general layout components +// ------------------------- +.umb-grid .tb { + width: 100%; +} + +.umb-grid .td { + width: 100%; + display: inline-block; + vertical-align: top; + box-sizing: border-box; +} + +.umb-grid .middle { + text-align: center; +} + +.umb-grid .mainTd { + position: relative; +} + + + +// COLUMN +// ------------------------- +.umb-grid .umb-column { + position: relative; +} + + + +// ROW +// ------------------------- +.umb-grid .umb-row { + position: relative; + margin-bottom: 40px; + padding-top: 10px; + + &:hover { + background-color: @grayLighter; + } + + &[ng-click], + &[data-ng-click], + &[x-ng-click] { + cursor: pointer; + } +} + +.umb-grid .row-tools a { + text-decoration: none; +} + + + +// CELL +// ------------------------- +.umb-grid .umb-cell { + position: relative; +} + +.umb-grid .umb-cell-content { + position: relative; + display: block; + box-sizing: border-box; + margin: 10px; + border: 1px solid transparent; +} + +.umb-grid .umb-row .umb-cell-placeholder { + min-height: 130px; + background-color: @gray-10; + border-width: 2px; + border-style: dashed; + border-color: @gray-8; + transition: border-color 100ms linear; + + &:hover { + border-color: @turquoise; + cursor: pointer; + } +} + +.umb-grid .umb-cell-content.-has-editors { + padding-top: 38px; + background-color: @white; + border-width: 1px; + border-style: solid; + border-color: @gray-8; + + &:hover { + cursor: auto; + } +} + +.umb-grid .umb-cell-content.-has-editors.-collapsed { + padding-top: 10px; +} + +.umb-grid .cell-tools { + position: absolute; + top: 10px; + right: 10px; + color: @gray-3; + font-size: 16px; +} + +.umb-grid .cell-tool { + cursor: pointer; + float: right; + &:hover { + color: @turquoise-d1; + } +} + +.umb-grid .cell-tools-add { + color: @turquoise-d1; + &:focus, &:hover { + text-decoration: none; + } +} + +.umb-grid .cell-tools-add.-center { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: @turquoise-d1; +} + +.umb-grid .cell-tools-add.-bar { + display: block; + background: @gray-10; + text-align: center; + padding: 5px; + border: 1px dashed @gray-7; + margin: 10px; +} + + +.umb-grid .cell-tools-remove { + display: inline-block; + position: relative; +} + +.umb-grid .cell-tools-remove .iconBox:hover, +.umb-grid .cell-tools-remove .iconBox:hover * { + background: @red !important; + border-color: @red !important; +} + +.umb-grid .cell-tools-move { + display: inline-block; +} + +.umb-grid .cell-tools-edit { + display: inline-block; + color: @white; +} + +.umb-grid .drop-overlay { + position: absolute; + z-index: 10; + top: 0; + left: 0; + background: @white; + opacity: 0.9; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; + box-sizing: border-box; + text-align: center; + line-height: 1.3em; + font-weight: bold; + flex-direction: column; +} + +.drop-overlay.-disable { + color: @red; +} + +.drop-overlay.-allow { + color: @green; +} + +.umb-grid .drop-overlay .drop-icon { + font-size: 40px; + margin-bottom: 20px; +} + + + +// CONTROL +// ------------------------- +.umb-grid .umb-control { + position: relative; + display: block; + margin-left: 10px; + margin-right: 10px; + margin-bottom: 10px; +} + +.umb-control-collapsed { + background-color: @gray-10; + padding: 5px 10px; + border-width: 1px; + border-style: solid; + border-color: transparent; + cursor: move; +} + +.umb-control-collapsed:hover { + border-color: @turquoise; +} + +.umb-grid .umb-control-click-overlay { + position: absolute; + width: 100%; + height: 100%; + z-index: 5; + top: 0; + left: 0; + opacity: 0; + cursor: pointer; + &:hover { + background-color: @turquoise; + opacity: 0.1; + transition: opacity 0.1s; + } +} + +.umb-grid .umb-row-title-bar { + display: flex; + padding-left: 10px; + padding-right: 10px; +} + +.umb-grid .umb-row-title { + display: inline-block; + cursor: pointer; + font-size: 15px; + font-weight: bold; + color: @black; + margin-right: 6px; +} + +.umb-grid .row-tools { + display: inline-block; + margin-left: 10px; + font-size: 18px; + color: @gray-3; +} + +.umb-grid .row-tool { + cursor: pointer; +} + +.umb-grid .umb-add-row { + text-align: center; +} + + + +// CONTROL PLACEHOLDER +// ------------------------- +.umb-grid .umb-control-placeholder { + min-height: 20px; + position: relative; + text-align: center; + text-align: -moz-center; + cursor: text; +} + +.umb-grid .umb-control-placeholder .placeholder { + font-size: 14px; + opacity: 0.7; + text-align: left; + padding: 5px; + border: 1px solid @gray-9; + height: 20px; +} + +.umb-grid .umb-control-placeholder:hover .placeholder { + border: 1px solid @gray-7; +} + + + +// EDITOR PLACEHOLDER +// ------------------------- +.umb-grid .umb-editor-placeholder { + min-height: 65px; + padding: 20px; + padding-bottom: 30px; + position: relative; + background-color: @white; + border: 4px dashed @gray-8; + text-align: center; + text-align: -moz-center; + cursor: pointer; +} + +.umb-grid .umb-editor-placeholder i { + color: @gray-8; + font-size: 85px; + line-height: 85px; + display: block; + margin-bottom: 10px; +} + + + +// Active states +// ------------------------- + +// Row states +.umb-grid .umb-row.-active { + background-color: @turquoise-d1; + + .umb-row-title-bar { + cursor: move; + } + + .row-tool, + .umb-row-title { + color: @white; + } + + .umb-grid-has-config { + color: fade(@white, 66); + } + + .umb-cell { + .umb-grid-has-config { + color: fade(@black, 44); + } + } + + .umb-cell .umb-cell-content { + border-color: transparent; + } +} + + +.umb-grid .umb-row.-active-child { + background-color: @gray-10; + + .umb-row-title-bar { + cursor: inherit; + } + + .umb-row-title { + color: @gray-3; + } + + .row-tool { + color: fade(@black, 23); + } + + .umb-grid-has-config { + color: fade(@black, 44); + } + + .umb-cell-content.-placeholder { + border-color: @gray-8; + + &:hover { + border-color: fade(@gray, 44); + } + } +} + + +// Cell states + +.umb-grid .umb-row .umb-cell.-active { + border-color: @gray-8; + + .umb-cell-content.-has-editors { + box-shadow: 3px 3px 6px rgba(0, 0, 0, .07); + border-color: @turquoise; + } +} + +.umb-grid .umb-row .umb-cell.-active-child { + + .cell-tool { + color: fade(@black, 23); + } + + .umb-cell-content.-has-editors { + border-color: rgba(113, 136, 160, .44); + } +} + + + + + +// Title bar and tools +.umb-grid .umb-row-title-bar { + display: flex; +} + +.umb-grid .umb-grid-right { + display: flex; + flex-direction: row; + justify-content: center; +} + +.umb-grid .umb-tools { + margin-left: auto; +} + + +// Add more content button +.umb-grid-add-more-content { + text-align: center; +} + +.umb-grid .newbtn { + width: auto; + padding: 6px 15px; + border-style: solid; + font-size: 12px; + font-weight: bold; + display: inline-block; + margin: 10px auto 20px; + border-color: @gray-9; + + &:hover { + cursor: pointer; + opacity: .77; + } +} + + + + +// Form elements +// ------------------------- +.umb-grid textarea.textstring { + display: block; + overflow: hidden; + border: none; + background: @white; + outline: none; + resize: none; + color: @gray-3; +} + +.umb-grid .umb-cell-rte textarea.mceNoEditor { + display: none !important; +} + +.umb-grid .umb-cell-media .caption { + display: block; + overflow: hidden; + border: none; + background: @white; + outline: none; + width: 98%; + resize: none; + font-style: italic; +} + +.umb-grid .cellPanelRte { + min-height: 60px; +} + +.umb-grid .umb-cell-embed iframe { + width: 100%; +} + +.umb-grid .umb-cell-rte { + border-color: transparent; +} + + + +// ICONS +// ------------------------- +.umb-grid .iconBox { + padding: 4px 6px; + display: inline-block; + cursor: pointer; + border-radius: 200px; + background: @gray-10; + border: 1px solid @gray-7; + margin: 2px; + + &:hover, &:hover * { + background: @turquoise !important; + color: @white !important; + border-color: @turquoise !important; + text-decoration: none; + } +} + +.umb-grid .iconBox span.prompt { + display: block; + white-space: nowrap; + text-align: center; +} + +.umb-grid .iconBox span.prompt > a { + text-decoration: underline; +} + +.umb-grid .iconBox a:hover { + text-decoration: none; + color: @white !important; +} + +.umb-grid .iconBox.selected { + -webkit-appearance: none; + 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); + box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); + border-radius: 3px; + background: transparent; +} + +.umb-grid .iconBox i { + font-size: 16px !important; + color: @gray-3 ; + display: block; +} + +.umb-grid .help-text { + color: @black; + font-size: 14px; + font-weight: bold; + display: inline-block; + clear: both; +} + + + +// TINYMCE EDITOR +// ------------------------- + +.umb-grid .mce-panel { + background: transparent !important; + border: none !important; + clear: both; +} + +.umb-grid .mce-btn { + button { + padding-top: 8px; + padding-bottom: 8px; + padding-left: 6px; + line-height: inherit; + } + + &:not(.mce-menubtn) { + button { + padding-right: 6px; + } + } +} + +.umb-grid .mce-toolbar { + border-bottom: 1px solid @gray-8; + background-color: rgba(250, 250, 250, 1); + display: none; +} + +.umb-grid .umb-control.-active .mce-toolbar { + display: block; +} + +.umb-grid .mce-flow-layout-item { + margin: 0; +} + +.umb-grid .mceContentBody { + overflow-y: hidden!important; +} + + +// MEDIA EDITOR +// ------------------------- +.umb-grid .fullSizeImage { + width: 100%; +} + + + +// Width +// ------------------------- +.umb-grid .boxWidth { + text-align: right; + margin-bottom: 10px; +} + +.umb-grid .boxWidth input { + text-align: center; + width: 40px; +} + +.umb-grid .boxWidth label { + font-size: 10px; + padding: 0; + margin: 5px 5px 0 0; + color: @gray-5; +} + + + +// Margin Control +// ------------------------- +.umb-grid .umb-control { + border-width: 1px; + border-style: solid; + border-color: transparent; +} + +.umb-grid .umb-control.-active { + border-color: @turquoise; +} + +.umb-grid .umb-templates-columns { + margin-top: 30px; +} + +.umb-grid .umb-control-inner { + position: relative; +} + +.umb-grid .umb-control-bar { + opacity: 0; + background: @turquoise; + padding: 2px 5px; + color: @white; + font-size: 12px; + height: 0; + display: flex; + transition: height 80ms linear, opacity 80ms linear; + align-items: center; +} + +.umb-grid .umb-control-title { + display: flex; + align-items: center; + font-weight: bold; +} + +.umb-grid .umb-control.-active .umb-control-bar { + opacity: 1; + height: 25px; + cursor: move; +} + +.umb-grid .umb-control-tools { + display: inline-block; + margin-left: 10px; +} + +.umb-grid .umb-control-tool { + font-size: 16px; + margin-right: 5px; + position: relative; + cursor: pointer; + display:inline-block; +} + + + +// Template +// ------------------------- +.umb-grid .umb-templates { + text-align: center; + overflow: hidden; + width: 100%; +} + +.umb-grid .umb-templates-template { + display: inline-block; + width: 100px; + padding-right: 30px; + margin: 20px; +} + +.umb-grid .umb-templates-template a.tb:hover { + border: 5px solid @turquoise; +} + +.umb-grid .umb-templates-template .tb { + width: 100%; + height: 150px; + padding: 10px; + background-color: @gray-10; + border: 5px solid @gray-8; + cursor: pointer; + position: relative; +} + +.umb-grid .umb-templates-template .tr { + height: 100%; + position: relative; +} + +.umb-grid .umb-templates-template .tb .umb-templates-column { + height: 100%; + border: 1px dashed @gray-8; + border-right: none; +} + +.umb-grid .umb-templates-template .tb .umb-templates-column.last { + border-right: 1px dashed @gray-8 !important; +} + +.umb-grid a.umb-templates-column:hover, +.umb-grid a.umb-templates-column.selected { + background-color: @turquoise; +} + + + +// Template Column +// ------------------------- +/* New template preview */ +.umb-grid { + .templates-preview { + display: inline-block; + width: 100%; + text-align: center; + + small { + position: absolute; + width: 100%; + left: 0; + bottom: -25px; + padding-top: 15px; + } + + .help-text { + margin: 35px 35px 0 0; + } + } + + .preview-rows { + display: inline-block; + position: relative; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 125px; + margin: 15px; + border: 3px solid @gray-8; + transition: border 100ms linear; + + &.prevalues-rows { + margin: 0 20px 20px 0; + width: 80px; + float: left; + } + + &.prevalues-templates { + margin: 0 20px 20px 0; + float: left; + } + + &:hover { + border-color: @turquoise; + cursor: pointer; + } + + .preview-row { + display: inline-block; + width: 100%; + vertical-align: bottom; + } + } + + .preview-rows.layout { + padding: 2px; + + .preview-row { + height: 100%; + } + + .preview-col { + height: 180px; + } + + .preview-cell { + background-color: @gray-10; + } + + .preview-overlay { + display: none; + } + } + + .preview-rows.columns { + min-height: 16px; + line-height: 11px; + padding: 1px; + + &.prevalues-rows { + min-height: 30px; + } + } + + .preview-rows { + .preview-col { + display: block; + float: left; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 33.3%; + + /* temp value */ + height: 10px; + margin: 0; + border: 1px solid @white; + + .preview-cell { + display: block; + width: 100%; + height: 100%; + background-color: @gray-8; + margin: 0 1px 1px 0; + } + } + + &.prevalues-templates { + .preview-col { + height: 80px; + } + } + } + + .preview-overlay { + display: block; + width: 100%; + position: absolute; + height: 100%; + top: 0; + box-sizing: border-box; + left: 0; + border: 3px solid @white; + } +} + +// Has Config +// ------------------------- + +.umb-grid .umb-grid-has-config { + display: inline; + font-size: 13px; + color: @gray-5; +} + +.umb-grid .umb-cell { + .umb-grid-has-config { + position: absolute; + top: 10px; + left: 10px; + } +} + + +// Overlay +// ------------------------- +.umb-grid .cell-tools-menu { + position: absolute; + width: 360px; + height: 380px; + overflow: auto; + border: 1px solid @gray-8; + margin-top: -270px; + margin-left: -150px; + background: @white; + padding: 7px; + top: 0; + left: 50%; + z-index: 6660; + box-shadow: 3px 3px 12px 0 rgba(50, 50, 50, 0.45); +} + +.umb-grid .cell-tools-menu h5 { + border-bottom: 1px solid @gray-8; + color: @gray-5; + padding: 10px; + margin-top: 0; +} + +.umb-grid .elements { + display: block; + padding: 0; + margin: 0; +} + +.umb-grid .elements li { + display: inline-block; + width: 90px; + height: 80px; + margin: 5px; + padding: 5px; + overflow: hidden; + font-size: 12px; + + &:hover, &:hover * { + background: @turquoise; + color: @white; + } +} + +.umb-grid .elements a { + color: @gray-1; + text-decoration: none; +} + +.umb-grid .elements i { + font-size: 30px; + line-height: 50px; + color: @gray-6; + display: block; +} + + + +// Configuration specific styles +// ------------------------- +.umb-grid-configuration .umb-templates { + text-align: left; +} + +.umb-grid-configuration ul { + display: block; +} + +.umb-grid-configuration ul li { + display: block; + width: auto; + text-align: left; +} + +.umb-grid-configuration .umb-templates .umb-templates-template .tb { + max-height: 50px; + border-width: 2px !important; + padding: 0; + border-spacing: 2px; + overflow: hidden; +} + +.umb-grid-configuration .umb-templates .umb-templates-template span { + background: @gray-8; + display: inline-block; +} + +.umb-grid-configuration .umb-templates .umb-templates-template .tb:hover { + border-width: 2px !important; +} + +.umb-grid-configuration .umb-templates-column { + display: block; + float: left; + margin-left: -1px; + border: 1px @white solid !important; + background: @gray-8; +} + +.umb-grid-configuration .umb-templates-column.last { + margin-right: -1px; +} + +.umb-grid-configuration .umb-templates-column.add { + text-align: center; + font-size: 20px; + line-height: 70px; + color: @gray-8; + text-decoration: none; + background: @white; +} + +.umb-grid-configuration .mainTdpt { + height: initial; + border: none; +} + +.umb-grid-configuration .umb-templates-rows .umb-templates-row { + margin: 0 50px 20px 0; + width: 60px; +} + +.umb-grid-configuration .umb-templates-rows .umb-templates-row .tb { + border-width: 2px !important; + padding: 0; + border-spacing: 2px; +} + +.umb-grid-configuration .umb-templates-rows .mainTdpt { + height: 10px !important; +} + +.umb-grid-configuration a.umb-templates-column { + height: 70px !important; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less index bd59df712f..9001f1d473 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less @@ -1,49 +1,49 @@ -.umb-locked-field { - font-size: 13px; - color: @gray-7; - position: relative; - display: block; -} - -.umb-locked-field__wrapper { - display: flex; - align-items: center; - margin-bottom: 5px; -} - -.umb-locked-field__toggle { - margin-right: 3px; -} - -.umb-locked-field__toggle:hover, -.umb-locked-field__toggle:focus { - text-decoration: none; -} - -.umb-locked-field__lock-icon { - color: @gray-7; - transition: color 0.25s; -} - -.umb-locked-field__lock-icon.-unlocked { - color: @gray-3; -} - -input.umb-locked-field__input { - background: rgba(255, 255, 255, 0); // if using transparent it will hide the text in safari - border-color: transparent !important; - font-size: 13px; - margin-bottom: 0; - color: @gray-6; - transition: color 0.25s; - padding: 0; - height: auto; -} - -input.umb-locked-field__input:focus { - box-shadow: none !important; -} - -input.umb-locked-field__input.-unlocked { - color: @gray-3; -} +.umb-locked-field { + font-size: 13px; + color: @gray-7; + position: relative; + display: block; +} + +.umb-locked-field__wrapper { + display: flex; + align-items: center; + margin-bottom: 5px; +} + +.umb-locked-field__toggle { + margin-right: 3px; +} + +.umb-locked-field__toggle:hover, +.umb-locked-field__toggle:focus { + text-decoration: none; +} + +.umb-locked-field__lock-icon { + color: @gray-7; + transition: color 0.25s; +} + +.umb-locked-field__lock-icon.-unlocked { + color: @gray-3; +} + +input.umb-locked-field__input { + background: rgba(255, 255, 255, 0); // if using transparent it will hide the text in safari + border-color: transparent !important; + font-size: 13px; + margin-bottom: 0; + color: @gray-6; + transition: color 0.25s; + padding: 0; + height: auto; +} + +input.umb-locked-field__input:focus { + box-shadow: none !important; +} + +input.umb-locked-field__input.-unlocked { + color: @gray-3; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less index 9319eec96e..d3c556bc50 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less @@ -1,75 +1,75 @@ -.umb-tabs-nav { - margin-left: 0; - margin-bottom: 0; - list-style: none; - border-bottom: 1px solid @gray-9; - display: inline-block; - margin-bottom: 20px; -} - -.umb-tab { - display: inline-flex; - position: relative; - top: 1px; -} - -.umb-tab > a { - cursor: pointer; - border-bottom: 2px solid transparent; - color: @gray-3; - padding: 5px 20px 10px 20px; - transition: color 150ms ease-in-out; -} - -.umb-tab > a:hover, -.umb-tab > a:focus { - color: @black; - text-decoration: none; -} - -.umb-tab--active > a, -.umb-tab--active > a:hover, -.umb-tab--active > a:focus { - color: @black; - border-bottom-color: @turquoise; -} - -.show-validation .umb-tab--error > a, -.show-validation .umb-tab--error > a:hover, -.show-validation .umb-tab--error > a:focus { - color: @white !important; - background-color: @red !important; - border-color: @errorBorder; -} - -.show-validation .umb-tab--error a:before { - content: "\e25d"; - font-family: 'icomoon'; - margin-right: 5px; - vertical-align: top; -} - -// tabs tray - -.umb-tabs-tray { - right: 0; - left: auto; -} - -.umb-tabs-tray > a { - cursor: pointer; -} - -.umb-tabs-tray-item--active { - border-left: 2px solid @turquoise; -} - -.umb-tab--expand > a > i { - height: 5px; - width: 5px; - border-radius: 50%; - background: @black; - display: inline-block; - margin: 0 5px 0 0; - opacity: .6; +.umb-tabs-nav { + margin-left: 0; + margin-bottom: 0; + list-style: none; + border-bottom: 1px solid @gray-9; + display: inline-block; + margin-bottom: 20px; +} + +.umb-tab { + display: inline-flex; + position: relative; + top: 1px; +} + +.umb-tab > a { + cursor: pointer; + border-bottom: 2px solid transparent; + color: @gray-3; + padding: 5px 20px 10px 20px; + transition: color 150ms ease-in-out; +} + +.umb-tab > a:hover, +.umb-tab > a:focus { + color: @black; + text-decoration: none; +} + +.umb-tab--active > a, +.umb-tab--active > a:hover, +.umb-tab--active > a:focus { + color: @black; + border-bottom-color: @turquoise; +} + +.show-validation .umb-tab--error > a, +.show-validation .umb-tab--error > a:hover, +.show-validation .umb-tab--error > a:focus { + color: @white !important; + background-color: @red !important; + border-color: @errorBorder; +} + +.show-validation .umb-tab--error a:before { + content: "\e25d"; + font-family: 'icomoon'; + margin-right: 5px; + vertical-align: top; +} + +// tabs tray + +.umb-tabs-tray { + right: 0; + left: auto; +} + +.umb-tabs-tray > a { + cursor: pointer; +} + +.umb-tabs-tray-item--active { + border-left: 2px solid @turquoise; +} + +.umb-tab--expand > a > i { + height: 5px; + width: 5px; + border-radius: 50%; + background: @black; + display: inline-block; + margin: 0 5px 0 0; + opacity: .6; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/dragdrop.less b/src/Umbraco.Web.UI.Client/src/less/dragdrop.less index 6bd5b55a0d..328ab27e5b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/dragdrop.less +++ b/src/Umbraco.Web.UI.Client/src/less/dragdrop.less @@ -1,37 +1,37 @@ -body.dragging, body.dragging * { - cursor: move !important; -} - -li.dragged { - position: absolute; - opacity: 0.5; - z-index: 2000; -} - -.umb-sort li{ - display: block; - margin: 5px; - padding: 5px; - border: 1px solid @gray-7; - background: @gray-10; -} - -.umb-sort .placeholder { - position: relative; - margin: 0; - padding: 0; - border: none; -} - -.umb-sort .placeholder:before { - position: absolute; - content: ""; - width: 0; - height: 0; - margin-top: -5px; - left: -5px; - top: -4px; - border: 5px solid transparent; - border-left-color: @red; - border-right: none; +body.dragging, body.dragging * { + cursor: move !important; +} + +li.dragged { + position: absolute; + opacity: 0.5; + z-index: 2000; +} + +.umb-sort li{ + display: block; + margin: 5px; + padding: 5px; + border: 1px solid @gray-7; + background: @gray-10; +} + +.umb-sort .placeholder { + position: relative; + margin: 0; + padding: 0; + border: none; +} + +.umb-sort .placeholder:before { + position: absolute; + content: ""; + width: 0; + height: 0; + margin-top: -5px; + left: -5px; + top: -4px; + border: 5px solid transparent; + border-left-color: @red; + border-right: none; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/fonts.less b/src/Umbraco.Web.UI.Client/src/less/fonts.less index 0500b401e6..613794d1bf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/fonts.less +++ b/src/Umbraco.Web.UI.Client/src/less/fonts.less @@ -1,145 +1,145 @@ -/* - Lato - - Light: font-weight: 300; - Regular: font-weight: normal; - Bold: font-weight: bold; - Black: font-weight: 900; - -*/ -/* Webfont: LatoLatin-Black */ -@font-face { - font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-Black.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-Black.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-Black.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Black.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Black.ttf') format('truetype'); - font-style: normal; - font-weight: 900; - text-rendering: optimizeLegibility; -} - -/* Webfont: LatoLatin-BlackItalic */ -@font-face { - font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-BlackItalic.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-BlackItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-BlackItalic.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-BlackItalic.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-BlackItalic.ttf') format('truetype'); - font-style: italic; - font-weight: 900; - text-rendering: optimizeLegibility; -} - -/* Webfont: LatoLatin-Bold */ -@font-face { - font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-Bold.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-Bold.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Bold.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Bold.ttf') format('truetype'); - font-style: normal; - font-weight: bold; - text-rendering: optimizeLegibility; -} - -/* Webfont: LatoLatin-BoldItalic */ -@font-face { - font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-BoldItalic.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-BoldItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-BoldItalic.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-BoldItalic.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-BoldItalic.ttf') format('truetype'); - font-style: italic; - font-weight: bold; - text-rendering: optimizeLegibility; -} - -/* Webfont: LatoLatin-Italic */ -@font-face { - font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-Italic.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-Italic.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Italic.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Italic.ttf') format('truetype'); - font-style: italic; - font-weight: normal; - text-rendering: optimizeLegibility; -} - -/* Webfont: LatoLatin-Regular */ -@font-face { - font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-Regular.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-Regular.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Regular.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Regular.ttf') format('truetype'); - font-style: normal; - font-weight: normal; - text-rendering: optimizeLegibility; -} - -/* Webfont: LatoLatin-Light */ -@font-face { - font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-Light.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-Light.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-Light.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Light.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-Light.ttf') format('truetype'); - font-style: normal; - font-weight: 300; - text-rendering: optimizeLegibility; -} - -/* Webfont: LatoLatin-LightItalic */ -@font-face { - font-family: 'Lato'; - src: url('../fonts/lato/LatoLatin-LightItalic.eot'); /* IE9 Compat Modes */ - src: url('../fonts/lato/LatoLatin-LightItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ - //url('../fonts/lato/LatoLatin-LightItalic.woff2') format('woff2'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-LightItalic.woff') format('woff'), /* Modern Browsers */ - url('../fonts/lato/LatoLatin-LightItalic.ttf') format('truetype'); - font-style: italic; - font-weight: 300; - text-rendering: optimizeLegibility; -} - - -/* - Open Sans - - Normal: font-weight: 400; - Semi-bold: font-weight: 600; - -*/ - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans/OpenSans-Regular-webfont.eot'); - src: local('Open Sans'), local('OpenSans'), - url('../fonts/opensans/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans/OpenSans-Regular-webfont.ttf') format('truetype'), - url('../fonts/opensans/OpenSans-Regular-webfont.svg#open_sansregular') format('svg'); - font-weight: 400; - font-style: normal; -} - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans/OpenSans-Semibold-webfont.eot'); - src: local('Open Sans Semibold'), local('OpenSans-Semibold'), - url('../fonts/opensans/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans/OpenSans-Semibold-webfont.ttf') format('truetype'), - url('../fonts/opensans/OpenSans-Semibold-webfont.svg#open_sanssemibold') format('svg'); - font-weight: 600; - font-style: normal; -} - - +/* + Lato + + Light: font-weight: 300; + Regular: font-weight: normal; + Bold: font-weight: bold; + Black: font-weight: 900; + +*/ +/* Webfont: LatoLatin-Black */ +@font-face { + font-family: 'Lato'; + src: url('../fonts/lato/LatoLatin-Black.eot'); /* IE9 Compat Modes */ + src: url('../fonts/lato/LatoLatin-Black.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + //url('../fonts/lato/LatoLatin-Black.woff2') format('woff2'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-Black.woff') format('woff'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-Black.ttf') format('truetype'); + font-style: normal; + font-weight: 900; + text-rendering: optimizeLegibility; +} + +/* Webfont: LatoLatin-BlackItalic */ +@font-face { + font-family: 'Lato'; + src: url('../fonts/lato/LatoLatin-BlackItalic.eot'); /* IE9 Compat Modes */ + src: url('../fonts/lato/LatoLatin-BlackItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + //url('../fonts/lato/LatoLatin-BlackItalic.woff2') format('woff2'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-BlackItalic.woff') format('woff'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-BlackItalic.ttf') format('truetype'); + font-style: italic; + font-weight: 900; + text-rendering: optimizeLegibility; +} + +/* Webfont: LatoLatin-Bold */ +@font-face { + font-family: 'Lato'; + src: url('../fonts/lato/LatoLatin-Bold.eot'); /* IE9 Compat Modes */ + src: url('../fonts/lato/LatoLatin-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + //url('../fonts/lato/LatoLatin-Bold.woff2') format('woff2'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-Bold.woff') format('woff'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-Bold.ttf') format('truetype'); + font-style: normal; + font-weight: bold; + text-rendering: optimizeLegibility; +} + +/* Webfont: LatoLatin-BoldItalic */ +@font-face { + font-family: 'Lato'; + src: url('../fonts/lato/LatoLatin-BoldItalic.eot'); /* IE9 Compat Modes */ + src: url('../fonts/lato/LatoLatin-BoldItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + //url('../fonts/lato/LatoLatin-BoldItalic.woff2') format('woff2'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-BoldItalic.woff') format('woff'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-BoldItalic.ttf') format('truetype'); + font-style: italic; + font-weight: bold; + text-rendering: optimizeLegibility; +} + +/* Webfont: LatoLatin-Italic */ +@font-face { + font-family: 'Lato'; + src: url('../fonts/lato/LatoLatin-Italic.eot'); /* IE9 Compat Modes */ + src: url('../fonts/lato/LatoLatin-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + //url('../fonts/lato/LatoLatin-Italic.woff2') format('woff2'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-Italic.woff') format('woff'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-Italic.ttf') format('truetype'); + font-style: italic; + font-weight: normal; + text-rendering: optimizeLegibility; +} + +/* Webfont: LatoLatin-Regular */ +@font-face { + font-family: 'Lato'; + src: url('../fonts/lato/LatoLatin-Regular.eot'); /* IE9 Compat Modes */ + src: url('../fonts/lato/LatoLatin-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + //url('../fonts/lato/LatoLatin-Regular.woff2') format('woff2'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-Regular.woff') format('woff'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-Regular.ttf') format('truetype'); + font-style: normal; + font-weight: normal; + text-rendering: optimizeLegibility; +} + +/* Webfont: LatoLatin-Light */ +@font-face { + font-family: 'Lato'; + src: url('../fonts/lato/LatoLatin-Light.eot'); /* IE9 Compat Modes */ + src: url('../fonts/lato/LatoLatin-Light.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + //url('../fonts/lato/LatoLatin-Light.woff2') format('woff2'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-Light.woff') format('woff'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-Light.ttf') format('truetype'); + font-style: normal; + font-weight: 300; + text-rendering: optimizeLegibility; +} + +/* Webfont: LatoLatin-LightItalic */ +@font-face { + font-family: 'Lato'; + src: url('../fonts/lato/LatoLatin-LightItalic.eot'); /* IE9 Compat Modes */ + src: url('../fonts/lato/LatoLatin-LightItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + //url('../fonts/lato/LatoLatin-LightItalic.woff2') format('woff2'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-LightItalic.woff') format('woff'), /* Modern Browsers */ + url('../fonts/lato/LatoLatin-LightItalic.ttf') format('truetype'); + font-style: italic; + font-weight: 300; + text-rendering: optimizeLegibility; +} + + +/* + Open Sans + + Normal: font-weight: 400; + Semi-bold: font-weight: 600; + +*/ + +@font-face { + font-family: 'Open Sans'; + src: url('../fonts/opensans/OpenSans-Regular-webfont.eot'); + src: local('Open Sans'), local('OpenSans'), + url('../fonts/opensans/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/opensans/OpenSans-Regular-webfont.ttf') format('truetype'), + url('../fonts/opensans/OpenSans-Regular-webfont.svg#open_sansregular') format('svg'); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: 'Open Sans'; + src: url('../fonts/opensans/OpenSans-Semibold-webfont.eot'); + src: local('Open Sans Semibold'), local('OpenSans-Semibold'), + url('../fonts/opensans/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/opensans/OpenSans-Semibold-webfont.ttf') format('truetype'), + url('../fonts/opensans/OpenSans-Semibold-webfont.svg#open_sanssemibold') format('svg'); + font-weight: 600; + font-style: normal; +} + + diff --git a/src/Umbraco.Web.UI.Client/src/less/footer.less b/src/Umbraco.Web.UI.Client/src/less/footer.less index 50b2011090..93fdb6ab5e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/footer.less +++ b/src/Umbraco.Web.UI.Client/src/less/footer.less @@ -1,2 +1,2 @@ -// Footer -// ------------------------- +// Footer +// ------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 1123e95b75..a219cb500a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -1,832 +1,832 @@ -// -// Forms -// -------------------------------------------------- - -// UMBRACO STYLES -// -------------- - - - -small.umb-detail, -label small, .guiDialogTiny { - color: @gray-5 !important; - text-decoration: none; - display: block; - font-weight: normal; - font-size: 11px -} - -label.control-label, .control-label { - padding: 0 10px 0 0 !important; - font-weight: bold; - color: @black; - font-size: 14px; -} - - -.umb-status-label{ - color: @gray-3 !important; - } - - -.controls-row label{padding: 0 10px 0 10px; vertical-align: middle;} - - - -//utill styll to hide child untill hover -/*.hover-show{visibility: hidden;} -*:hover > .hover-show{visibility: visible;} -*/ - -//breadcrumb modifications - -.breadcrumb{height: 30px; display: block; margin-top: 10px;} -.breadcrumb li{height: 30px; vertical-align: middle;} -.breadcrumb li a{vertical-align: middle; height: 30px;} -.breadcrumb input{font-size: 11px !Important;} - - - -/* SEACH FORM */ -.form-search { - position: relative; - padding: 0; -} -.form-search a{ - text-decoration:none; - cursor:pointer; -} -.form-search a:hover{ - color: @gray-3; -} -.form-search h4 { - color: @gray-3; -} -.form-search small { - color: @gray-8; -} -.form-search .icon, .form-search .icon-search { - position: absolute; - z-index: 1; - top: 6px; - left: 6px; - color: @gray-8; -} - -.form-search .icon-search { - pointer-events: none; -} - -.form-search input { - width: 90%; - font-size: @fontSizeLarge; - font-weight: 400; - border: 1px solid @gray-8; - padding: 4px 0px 4px 16px; - padding-left: 25px !Important; - line-height: 22px; - background: @white -} - -.form-search .icon-search + .search-input { - padding-left: 25px !important; -} - -.form-search .search-input { - font-weight: bold; - border-color: @gray-8; - - &:hover, - &:focus, - &:focus:hover { - border-color: @gray-7; - } - - &:-moz-placeholder { - font-weight: normal; - } - &:-ms-input-placeholder { - font-weight: normal; - } - &::-webkit-input-placeholder { - font-weight: normal; - } -} - - -// GENERAL STYLES -// -------------- - -// Make all forms have space below them -form { - margin: 0 0 @baseLineHeight; -} - -form.-no-margin-bottom { - margin-bottom: 0; -} - -fieldset { - padding: 0; - margin: 0; - border: 0; -} - -// Groups of fields with labels on top (legends) -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: @baseLineHeight; - font-size: @baseFontSize * 1.5; - line-height: @baseLineHeight * 2; - color: @grayDark; - border: 0; - border-bottom: 1px solid @gray-8; - - // Small - small { - font-size: @baseLineHeight * .75; - color: @gray-8; - } -} - - - -// Set font for forms -label, -input, -button, -select, -textarea { - #font > .shorthand(@baseFontSize,normal,@baseLineHeight); // Set size, weight, line-height here -} -input, -button, -select, -textarea { - font-family: @baseFontFamily; // And only set font-family here for those that need it (note the missing label element) -} - -// Identify controls by their labels -label { - display: inline-block; - margin-bottom: 5px; -} - -// Form controls -// ------------------------- - -// Shared size and type resets -select, -textarea, -input[type="text"], -input[type="password"], -input[type="datetime"], -input[type="datetime-local"], -input[type="date"], -input[type="month"], -input[type="time"], -input[type="week"], -input[type="number"], -input[type="email"], -input[type="url"], -input[type="search"], -input[type="tel"], -input[type="color"], -.uneditable-input { - display: inline-block; - height: @inputHeight; - padding: 4px 6px; - margin-bottom: @baseLineHeight / 2; - font-size: @baseFontSize; - line-height: @baseLineHeight; - color: @gray-3; - .border-radius(@inputBorderRadius); - vertical-align: middle; - box-sizing: border-box; -} - -input.-full-width-input { - width: 100%; - box-sizing: border-box; - padding: 4px 6px; -} - -// Reset appearance properties for textual inputs and textarea -// Declare width for legacy (can't be on input[type=*] selectors or it's too specific) -input, -textarea, -.uneditable-input { - width: 206px; // plus 12px padding and 2px border -} -// Reset height since textareas have rows -textarea { - height: auto; -} -// Everything else -textarea, -input[type="text"], -input[type="password"], -input[type="datetime"], -input[type="datetime-local"], -input[type="date"], -input[type="month"], -input[type="time"], -input[type="week"], -input[type="number"], -input[type="email"], -input[type="url"], -input[type="search"], -input[type="tel"], -input[type="color"], -.uneditable-input { - background-color: @inputBackground; - border: 1px solid @inputBorder; - .transition(~"border linear .2s, box-shadow linear .2s"); - - // Focus state - &:focus { - border-color: none; - outline: 0; - outline: none \9; /* IE6-9 */ - } -} - -// Position radios and checkboxes better -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - *margin-top: 0; /* IE7 */ - margin-top: 1px \9; /* IE8-9 */ - line-height: normal; -} - -// Reset width of input images, buttons, radios, checkboxes -input[type="file"], -input[type="image"], -input[type="submit"], -input[type="reset"], -input[type="button"], -input[type="radio"], -input[type="checkbox"] { - width: auto; // Override of generic input selector -} - -// Set the height of select and file controls to match text inputs -select, -input[type="file"] { - height: @inputHeight; /* In IE7, the height of the select element cannot be changed by height, only font-size */ - *margin-top: 4px; /* For IE7, add top margin to align select with labels */ - line-height: @inputHeight; -} - -// Make select elements obey height by applying a border -select { - width: 220px; // default input width + 10px of padding that doesn't get applied - border: 1px solid @inputBorder; - background-color: @inputBackground; // Chrome on Linux and Mobile Safari need background-color -} - -// Make multiple select elements height not fixed -select[multiple], -select[size] { - height: auto; -} - -// Focus for select, file, radio, and checkbox -select:focus, -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - .tab-focus(); -} - - -// Uneditable inputs -// ------------------------- - -// Make uneditable inputs look inactive -.uneditable-input, -.uneditable-textarea { - color: @gray-8; - background-color: darken(@inputBackground, 1%); - border-color: @inputBorder; - .box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); - cursor: not-allowed; -} - -// For text that needs to appear as an input but should not be an input -.uneditable-input { - overflow: hidden; // prevent text from wrapping, but still cut it off like an input does - white-space: nowrap; -} - -// Make uneditable textareas behave like a textarea -.uneditable-textarea { - width: auto; - height: auto; -} - - -// Placeholder -// ------------------------- - -// Placeholder text gets special styles because when browsers invalidate entire lines if it doesn't understand a selector -input, -textarea { - .placeholder(@gray-6); -} - - -// CHECKBOXES & RADIOS -// ------------------- - -// Indent the labels to position radios/checkboxes as hanging -.radio, -.checkbox { - min-height: @baseLineHeight; // clear the floating input if there is no label text - padding-left: 20px; -} - -.radio.no-indent, -.checkbox.no-indent { - padding-left: 0; -} - -.radio input[type="radio"], -.checkbox input[type="checkbox"] { - float: left; - margin-left: 0px; - margin-right:5px; -} - -// Move the options list down to align with labels -.controls > .radio:first-child, -.controls > .checkbox:first-child { - padding-top: 5px; // has to be padding because margin collaspes -} - -// Radios and checkboxes on same line -// TODO v3: Convert .inline to .control-inline -.radio.inline, -.checkbox.inline { - display: inline-block; - padding-top: 5px; - margin-bottom: 0; - vertical-align: middle; -} -.radio.inline + .radio.inline, -.checkbox.inline + .checkbox.inline { - margin-left: 10px; // space out consecutive inline controls -} - - - -// INPUT SIZES -// ----------- - -// General classes for quick sizes -.input-mini { width: 60px; } -.input-small { width: 90px; } -.input-medium { width: 150px; } -.input-large { width: 210px; } -.input-xlarge { width: 270px; } -.input-xxlarge { width: 530px; } - -input.input--no-border { border: none; } - -// Grid style input sizes -input[class*="span"], -select[class*="span"], -textarea[class*="span"], -.uneditable-input[class*="span"], -// Redeclare since the fluid row class is more specific -.row-fluid input[class*="span"], -.row-fluid select[class*="span"], -.row-fluid textarea[class*="span"], -.row-fluid .uneditable-input[class*="span"] { - float: none; - margin-left: 0; -} -// Ensure input-prepend/append never wraps -.input-append input[class*="span"], -.input-append .uneditable-input[class*="span"], -.input-prepend input[class*="span"], -.input-prepend .uneditable-input[class*="span"], -.row-fluid input[class*="span"], -.row-fluid select[class*="span"], -.row-fluid textarea[class*="span"], -.row-fluid .uneditable-input[class*="span"], -.row-fluid .input-prepend [class*="span"], -.row-fluid .input-append [class*="span"] { - display: inline-block; -} - -.bigInput, .input-large-type{ - font-size: 20px !important; -} - - -// GRID SIZING FOR INPUTS -// ---------------------- - -// Grid sizes -#grid > .input(@gridColumnWidth, @gridGutterWidth); - -// Control row for multiple inputs per line -.controls-row { - .clearfix(); // Clear the float from controls -} - -// Float to collapse white-space for proper grid alignment -.controls-row [class*="span"], -// Redeclare the fluid grid collapse since we undo the float for inputs -.row-fluid .controls-row [class*="span"] { - float: left; -} -// Explicity set top padding on all checkboxes/radios, not just first-child -.controls-row .checkbox[class*="span"], -.controls-row .radio[class*="span"] { - padding-top: 5px; -} - - - - -// DISABLED STATE -// -------------- - -// Disabled and read-only inputs -input[disabled], -select[disabled], -textarea[disabled], -input[readonly], -select[readonly], -textarea[readonly] { - cursor: not-allowed; - background-color: @inputDisabledBackground; -} -// Explicitly reset the colors here -input[type="radio"][disabled], -input[type="checkbox"][disabled], -input[type="radio"][readonly], -input[type="checkbox"][readonly] { - background-color: transparent; -} - - -//SD: NOTE: Had to change these to use our 'form' prefixed colors since we cannot -// share colors with the notifications/alerts. Also had to change them so that -// we do not show any errors unless the containing element has the show-validation -// class assigned. - -// FORM FIELD FEEDBACK STATES -// -------------------------- - -// Error -.show-validation.ng-invalid .control-group.error, -.show-validation.ng-invalid .umb-editor-header__name-wrapper { - .formFieldState(@formErrorText, @formErrorText, @formErrorBackground); -} - -//val-highlight directive styling -.highlight-error { - color: @formErrorText !important; - border-color: @red-l1 !important; -} - -//disable the glowing border for the umb-content-name -.show-validation .umb-headline-editor-wrapper input:focus:invalid, -.show-validation .umb-headline-editor-wrapper textarea:focus:invalid, -.show-validation .umb-headline-editor-wrapper select:focus:invalid { - border:none; - color:inherit; - border-color:inherit; - @shadow:inherit; - .box-shadow(@shadow); -} - -.ng-invalid > .umb-headline-editor-wrapper h1{ - border-bottom: 1px dashed @red; color: @red; cursor: pointer; -}; - -// FORM ACTIONS -// ------------ - -.form-actions { - padding: (@baseLineHeight - 1) 20px @baseLineHeight; - margin-top: @baseLineHeight; - margin-bottom: @baseLineHeight; - background-color: @formActionsBackground; - border-top: 1px solid @gray-8; - .clearfix(); // Adding clearfix to allow for .pull-right button containers -} - - - -// HELP TEXT -// --------- - -.help-block, -.help-inline { - color: lighten(@textColor, 15%); // lighten the text some for contrast -} - -.help-block { - display: block; // account for any element using help-block - margin-bottom: @baseLineHeight / 2; -} - -.help-inline { - display: inline-block; - .ie7-inline-block(); - vertical-align: middle; - padding-left: 5px; -} - - - -// INPUT GROUPS -// ------------ - -// Allow us to put symbols and text within the input field for a cleaner look -.input-append, -.input-prepend { - display: inline-block; - margin-bottom: @baseLineHeight / 2; - vertical-align: middle; - font-size: 0; // white space collapse hack - white-space: nowrap; // Prevent span and input from separating - - // Reset the white space collapse hack - input, - select, - .uneditable-input, - .dropdown-menu, - .popover { - font-size: @baseFontSize; - } - - input, - select, - .uneditable-input { - position: relative; // placed here by default so that on :focus we can place the input above the .add-on for full border and box-shadow goodness - margin-bottom: 0; // prevent bottom margin from screwing up alignment in stacked forms - *margin-left: 0; - vertical-align: top; - // Make input on top when focused so blue border and shadow always show - &:focus { - z-index: 2; - } - } - .add-on { - display: inline-block; - width: auto; - height: 22px; - min-width: 18px; - padding: 4px 6px; - font-size: @baseFontSize; - font-weight: normal; - line-height: @baseLineHeight; - text-align: center; - text-shadow: 0 1px 0 @white; - background-color: @gray-10; - border: 1px solid @gray-8; - } - .add-on, - .btn, - .btn-group > .dropdown-toggle { - vertical-align: top; - .border-radius(0); - } - .active { - background-color: lighten(@green, 30); - border-color: @green; - } -} - -.input-prepend { - .add-on, - .btn { - margin-right: -1px; - } -} - -.input-append { - .add-on, - .btn, - .btn-group { - margin-left: -1px; - } -} - -// Remove all border-radius for inputs with both prepend and append -.input-prepend.input-append { - input, - select, - .uneditable-input { - .border-radius(0); - + .btn-group .btn { - .border-radius(0 @inputBorderRadius @inputBorderRadius 0); - } - } - .add-on:first-child, - .btn:first-child { - margin-right: -1px; - .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); - } - .add-on:last-child, - .btn:last-child { - margin-left: -1px; - .border-radius(0 @inputBorderRadius @inputBorderRadius 0); - } - .btn-group:first-child { - margin-left: 0; - } -} - - - - -// SEARCH FORM -// ----------- - -input.search-query { - padding-right: 4px \9; - padding-left: 14px; - padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */ - margin: 0; // Remove the default margin on all inputs -} - -/* Allow for input prepend/append in search forms */ -.form-search { - .input-prepend { - .btn { - .border-radius(0 @borderRadiusSmall @borderRadiusSmall 0); - } - } - .input-append { - .btn { - .border-radius(0 @borderRadiusSmall @borderRadiusSmall 0); - } - } -} - -// HORIZONTAL & VERTICAL FORMS -// --------------------------- - -// Common properties -// ----------------- - -.form-search, -.form-inline, -.form-horizontal { - input, - textarea, - select, - .help-inline, - .uneditable-input, - .input-prepend, - .input-append { - display: inline-block; - .ie7-inline-block(); - margin-bottom: 0; - vertical-align: top; - } - // Re-hide hidden elements due to specifity - .hide { - display: none; - } -} -.form-search label, -.form-inline label, -.form-search .btn-group, -.form-inline .btn-group { - display: inline-block; -} -// Remove margin for input-prepend/-append -.form-search .input-append, -.form-inline .input-append, -.form-search .input-prepend, -.form-inline .input-prepend { - margin-bottom: 0; -} -// Inline checkbox/radio labels (remove padding on left) -.form-search .radio, -.form-search .checkbox, -.form-inline .radio, -.form-inline .checkbox { - padding-left: 0; - margin-bottom: 0; - vertical-align: middle; -} -// Remove float and margin, set to inline-block -.form-search .radio input[type="radio"], -.form-search .checkbox input[type="checkbox"], -.form-inline .radio input[type="radio"], -.form-inline .checkbox input[type="checkbox"] { - float: left; - margin-right: 3px; - margin-left: 0; -} - - -// Margin to space out fieldsets -.control-group { - margin-bottom: @baseLineHeight / 2; -} - -//modifier for control group -.control-group.-no-margin { - margin-bottom:0; -} - -// Legend collapses margin, so next element is responsible for spacing -legend + .control-group { - margin-top: @baseLineHeight; - -webkit-margin-top-collapse: separate; -} - -// Horizontal-specific styles -// -------------------------- - -.form-horizontal { - // Increase spacing between groups - .control-group { - margin-bottom: @baseLineHeight; - .clearfix(); - } - // Float the labels left - .control-label { - float: left; - width: @horizontalComponentOffset - 20; - padding-top: 5px; - text-align: right; - } - // Move over all input controls and content - .controls { - // Super jank IE7 fix to ensure the inputs in .input-append and input-prepend - // don't inherit the margin of the parent, in this case .controls - *display: inline-block; - *padding-left: 20px; - margin-left: @horizontalComponentOffset; - *margin-left: 0; - &:first-child { - *padding-left: @horizontalComponentOffset; - } - } - - - // Remove bottom margin on block level help text since that's accounted for on .control-group - .help-block { - margin-bottom: 0; - } - // And apply it only to .help-block instances that follow a form control - input, - select, - textarea, - .uneditable-input, - .input-prepend, - .input-append { - + .help-block { - margin-top: @baseLineHeight / 2; - } - } - // Move over buttons in .form-actions to align with .controls - .form-actions { - padding-left: @horizontalComponentOffset; - } -} - -// adjustments for properties tab -.form-horizontal .block-form .control-label { - display: block; - float: none; - width: 100%; -} - -//make sure buttons are always on top -.umb-panel-buttons .umb-btn-toolbar .btn { - position: relative; - z-index: 1000; -} - - - -@media (max-width: 767px) { - - // Labels on own row - .form-horizontal .control-label { - width: 100%; - } - .form-horizontal .controls { - margin-left: 0; - } - -} - -/* User/group selector */ -.group-selector .group-selector-list { float: left; } -.group-selector .group-selector-list div { height: 24px; } -.group-selector .group-selector-buttons { float: left; margin: 24px 16px; } +// +// Forms +// -------------------------------------------------- + +// UMBRACO STYLES +// -------------- + + + +small.umb-detail, +label small, .guiDialogTiny { + color: @gray-5 !important; + text-decoration: none; + display: block; + font-weight: normal; + font-size: 11px +} + +label.control-label, .control-label { + padding: 0 10px 0 0 !important; + font-weight: bold; + color: @black; + font-size: 14px; +} + + +.umb-status-label{ + color: @gray-3 !important; + } + + +.controls-row label{padding: 0 10px 0 10px; vertical-align: middle;} + + + +//utill styll to hide child untill hover +/*.hover-show{visibility: hidden;} +*:hover > .hover-show{visibility: visible;} +*/ + +//breadcrumb modifications + +.breadcrumb{height: 30px; display: block; margin-top: 10px;} +.breadcrumb li{height: 30px; vertical-align: middle;} +.breadcrumb li a{vertical-align: middle; height: 30px;} +.breadcrumb input{font-size: 11px !Important;} + + + +/* SEACH FORM */ +.form-search { + position: relative; + padding: 0; +} +.form-search a{ + text-decoration:none; + cursor:pointer; +} +.form-search a:hover{ + color: @gray-3; +} +.form-search h4 { + color: @gray-3; +} +.form-search small { + color: @gray-8; +} +.form-search .icon, .form-search .icon-search { + position: absolute; + z-index: 1; + top: 6px; + left: 6px; + color: @gray-8; +} + +.form-search .icon-search { + pointer-events: none; +} + +.form-search input { + width: 90%; + font-size: @fontSizeLarge; + font-weight: 400; + border: 1px solid @gray-8; + padding: 4px 0px 4px 16px; + padding-left: 25px !Important; + line-height: 22px; + background: @white +} + +.form-search .icon-search + .search-input { + padding-left: 25px !important; +} + +.form-search .search-input { + font-weight: bold; + border-color: @gray-8; + + &:hover, + &:focus, + &:focus:hover { + border-color: @gray-7; + } + + &:-moz-placeholder { + font-weight: normal; + } + &:-ms-input-placeholder { + font-weight: normal; + } + &::-webkit-input-placeholder { + font-weight: normal; + } +} + + +// GENERAL STYLES +// -------------- + +// Make all forms have space below them +form { + margin: 0 0 @baseLineHeight; +} + +form.-no-margin-bottom { + margin-bottom: 0; +} + +fieldset { + padding: 0; + margin: 0; + border: 0; +} + +// Groups of fields with labels on top (legends) +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: @baseLineHeight; + font-size: @baseFontSize * 1.5; + line-height: @baseLineHeight * 2; + color: @grayDark; + border: 0; + border-bottom: 1px solid @gray-8; + + // Small + small { + font-size: @baseLineHeight * .75; + color: @gray-8; + } +} + + + +// Set font for forms +label, +input, +button, +select, +textarea { + #font > .shorthand(@baseFontSize,normal,@baseLineHeight); // Set size, weight, line-height here +} +input, +button, +select, +textarea { + font-family: @baseFontFamily; // And only set font-family here for those that need it (note the missing label element) +} + +// Identify controls by their labels +label { + display: inline-block; + margin-bottom: 5px; +} + +// Form controls +// ------------------------- + +// Shared size and type resets +select, +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + display: inline-block; + height: @inputHeight; + padding: 4px 6px; + margin-bottom: @baseLineHeight / 2; + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @gray-3; + .border-radius(@inputBorderRadius); + vertical-align: middle; + box-sizing: border-box; +} + +input.-full-width-input { + width: 100%; + box-sizing: border-box; + padding: 4px 6px; +} + +// Reset appearance properties for textual inputs and textarea +// Declare width for legacy (can't be on input[type=*] selectors or it's too specific) +input, +textarea, +.uneditable-input { + width: 206px; // plus 12px padding and 2px border +} +// Reset height since textareas have rows +textarea { + height: auto; +} +// Everything else +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + background-color: @inputBackground; + border: 1px solid @inputBorder; + .transition(~"border linear .2s, box-shadow linear .2s"); + + // Focus state + &:focus { + border-color: none; + outline: 0; + outline: none \9; /* IE6-9 */ + } +} + +// Position radios and checkboxes better +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + *margin-top: 0; /* IE7 */ + margin-top: 1px \9; /* IE8-9 */ + line-height: normal; +} + +// Reset width of input images, buttons, radios, checkboxes +input[type="file"], +input[type="image"], +input[type="submit"], +input[type="reset"], +input[type="button"], +input[type="radio"], +input[type="checkbox"] { + width: auto; // Override of generic input selector +} + +// Set the height of select and file controls to match text inputs +select, +input[type="file"] { + height: @inputHeight; /* In IE7, the height of the select element cannot be changed by height, only font-size */ + *margin-top: 4px; /* For IE7, add top margin to align select with labels */ + line-height: @inputHeight; +} + +// Make select elements obey height by applying a border +select { + width: 220px; // default input width + 10px of padding that doesn't get applied + border: 1px solid @inputBorder; + background-color: @inputBackground; // Chrome on Linux and Mobile Safari need background-color +} + +// Make multiple select elements height not fixed +select[multiple], +select[size] { + height: auto; +} + +// Focus for select, file, radio, and checkbox +select:focus, +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + .tab-focus(); +} + + +// Uneditable inputs +// ------------------------- + +// Make uneditable inputs look inactive +.uneditable-input, +.uneditable-textarea { + color: @gray-8; + background-color: darken(@inputBackground, 1%); + border-color: @inputBorder; + .box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); + cursor: not-allowed; +} + +// For text that needs to appear as an input but should not be an input +.uneditable-input { + overflow: hidden; // prevent text from wrapping, but still cut it off like an input does + white-space: nowrap; +} + +// Make uneditable textareas behave like a textarea +.uneditable-textarea { + width: auto; + height: auto; +} + + +// Placeholder +// ------------------------- + +// Placeholder text gets special styles because when browsers invalidate entire lines if it doesn't understand a selector +input, +textarea { + .placeholder(@gray-6); +} + + +// CHECKBOXES & RADIOS +// ------------------- + +// Indent the labels to position radios/checkboxes as hanging +.radio, +.checkbox { + min-height: @baseLineHeight; // clear the floating input if there is no label text + padding-left: 20px; +} + +.radio.no-indent, +.checkbox.no-indent { + padding-left: 0; +} + +.radio input[type="radio"], +.checkbox input[type="checkbox"] { + float: left; + margin-left: 0px; + margin-right:5px; +} + +// Move the options list down to align with labels +.controls > .radio:first-child, +.controls > .checkbox:first-child { + padding-top: 5px; // has to be padding because margin collaspes +} + +// Radios and checkboxes on same line +// TODO v3: Convert .inline to .control-inline +.radio.inline, +.checkbox.inline { + display: inline-block; + padding-top: 5px; + margin-bottom: 0; + vertical-align: middle; +} +.radio.inline + .radio.inline, +.checkbox.inline + .checkbox.inline { + margin-left: 10px; // space out consecutive inline controls +} + + + +// INPUT SIZES +// ----------- + +// General classes for quick sizes +.input-mini { width: 60px; } +.input-small { width: 90px; } +.input-medium { width: 150px; } +.input-large { width: 210px; } +.input-xlarge { width: 270px; } +.input-xxlarge { width: 530px; } + +input.input--no-border { border: none; } + +// Grid style input sizes +input[class*="span"], +select[class*="span"], +textarea[class*="span"], +.uneditable-input[class*="span"], +// Redeclare since the fluid row class is more specific +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"] { + float: none; + margin-left: 0; +} +// Ensure input-prepend/append never wraps +.input-append input[class*="span"], +.input-append .uneditable-input[class*="span"], +.input-prepend input[class*="span"], +.input-prepend .uneditable-input[class*="span"], +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"], +.row-fluid .input-prepend [class*="span"], +.row-fluid .input-append [class*="span"] { + display: inline-block; +} + +.bigInput, .input-large-type{ + font-size: 20px !important; +} + + +// GRID SIZING FOR INPUTS +// ---------------------- + +// Grid sizes +#grid > .input(@gridColumnWidth, @gridGutterWidth); + +// Control row for multiple inputs per line +.controls-row { + .clearfix(); // Clear the float from controls +} + +// Float to collapse white-space for proper grid alignment +.controls-row [class*="span"], +// Redeclare the fluid grid collapse since we undo the float for inputs +.row-fluid .controls-row [class*="span"] { + float: left; +} +// Explicity set top padding on all checkboxes/radios, not just first-child +.controls-row .checkbox[class*="span"], +.controls-row .radio[class*="span"] { + padding-top: 5px; +} + + + + +// DISABLED STATE +// -------------- + +// Disabled and read-only inputs +input[disabled], +select[disabled], +textarea[disabled], +input[readonly], +select[readonly], +textarea[readonly] { + cursor: not-allowed; + background-color: @inputDisabledBackground; +} +// Explicitly reset the colors here +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"][readonly], +input[type="checkbox"][readonly] { + background-color: transparent; +} + + +//SD: NOTE: Had to change these to use our 'form' prefixed colors since we cannot +// share colors with the notifications/alerts. Also had to change them so that +// we do not show any errors unless the containing element has the show-validation +// class assigned. + +// FORM FIELD FEEDBACK STATES +// -------------------------- + +// Error +.show-validation.ng-invalid .control-group.error, +.show-validation.ng-invalid .umb-editor-header__name-wrapper { + .formFieldState(@formErrorText, @formErrorText, @formErrorBackground); +} + +//val-highlight directive styling +.highlight-error { + color: @formErrorText !important; + border-color: @red-l1 !important; +} + +//disable the glowing border for the umb-content-name +.show-validation .umb-headline-editor-wrapper input:focus:invalid, +.show-validation .umb-headline-editor-wrapper textarea:focus:invalid, +.show-validation .umb-headline-editor-wrapper select:focus:invalid { + border:none; + color:inherit; + border-color:inherit; + @shadow:inherit; + .box-shadow(@shadow); +} + +.ng-invalid > .umb-headline-editor-wrapper h1{ + border-bottom: 1px dashed @red; color: @red; cursor: pointer; +}; + +// FORM ACTIONS +// ------------ + +.form-actions { + padding: (@baseLineHeight - 1) 20px @baseLineHeight; + margin-top: @baseLineHeight; + margin-bottom: @baseLineHeight; + background-color: @formActionsBackground; + border-top: 1px solid @gray-8; + .clearfix(); // Adding clearfix to allow for .pull-right button containers +} + + + +// HELP TEXT +// --------- + +.help-block, +.help-inline { + color: lighten(@textColor, 15%); // lighten the text some for contrast +} + +.help-block { + display: block; // account for any element using help-block + margin-bottom: @baseLineHeight / 2; +} + +.help-inline { + display: inline-block; + .ie7-inline-block(); + vertical-align: middle; + padding-left: 5px; +} + + + +// INPUT GROUPS +// ------------ + +// Allow us to put symbols and text within the input field for a cleaner look +.input-append, +.input-prepend { + display: inline-block; + margin-bottom: @baseLineHeight / 2; + vertical-align: middle; + font-size: 0; // white space collapse hack + white-space: nowrap; // Prevent span and input from separating + + // Reset the white space collapse hack + input, + select, + .uneditable-input, + .dropdown-menu, + .popover { + font-size: @baseFontSize; + } + + input, + select, + .uneditable-input { + position: relative; // placed here by default so that on :focus we can place the input above the .add-on for full border and box-shadow goodness + margin-bottom: 0; // prevent bottom margin from screwing up alignment in stacked forms + *margin-left: 0; + vertical-align: top; + // Make input on top when focused so blue border and shadow always show + &:focus { + z-index: 2; + } + } + .add-on { + display: inline-block; + width: auto; + height: 22px; + min-width: 18px; + padding: 4px 6px; + font-size: @baseFontSize; + font-weight: normal; + line-height: @baseLineHeight; + text-align: center; + text-shadow: 0 1px 0 @white; + background-color: @gray-10; + border: 1px solid @gray-8; + } + .add-on, + .btn, + .btn-group > .dropdown-toggle { + vertical-align: top; + .border-radius(0); + } + .active { + background-color: lighten(@green, 30); + border-color: @green; + } +} + +.input-prepend { + .add-on, + .btn { + margin-right: -1px; + } +} + +.input-append { + .add-on, + .btn, + .btn-group { + margin-left: -1px; + } +} + +// Remove all border-radius for inputs with both prepend and append +.input-prepend.input-append { + input, + select, + .uneditable-input { + .border-radius(0); + + .btn-group .btn { + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } + } + .add-on:first-child, + .btn:first-child { + margin-right: -1px; + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + } + .add-on:last-child, + .btn:last-child { + margin-left: -1px; + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } + .btn-group:first-child { + margin-left: 0; + } +} + + + + +// SEARCH FORM +// ----------- + +input.search-query { + padding-right: 4px \9; + padding-left: 14px; + padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */ + margin: 0; // Remove the default margin on all inputs +} + +/* Allow for input prepend/append in search forms */ +.form-search { + .input-prepend { + .btn { + .border-radius(0 @borderRadiusSmall @borderRadiusSmall 0); + } + } + .input-append { + .btn { + .border-radius(0 @borderRadiusSmall @borderRadiusSmall 0); + } + } +} + +// HORIZONTAL & VERTICAL FORMS +// --------------------------- + +// Common properties +// ----------------- + +.form-search, +.form-inline, +.form-horizontal { + input, + textarea, + select, + .help-inline, + .uneditable-input, + .input-prepend, + .input-append { + display: inline-block; + .ie7-inline-block(); + margin-bottom: 0; + vertical-align: top; + } + // Re-hide hidden elements due to specifity + .hide { + display: none; + } +} +.form-search label, +.form-inline label, +.form-search .btn-group, +.form-inline .btn-group { + display: inline-block; +} +// Remove margin for input-prepend/-append +.form-search .input-append, +.form-inline .input-append, +.form-search .input-prepend, +.form-inline .input-prepend { + margin-bottom: 0; +} +// Inline checkbox/radio labels (remove padding on left) +.form-search .radio, +.form-search .checkbox, +.form-inline .radio, +.form-inline .checkbox { + padding-left: 0; + margin-bottom: 0; + vertical-align: middle; +} +// Remove float and margin, set to inline-block +.form-search .radio input[type="radio"], +.form-search .checkbox input[type="checkbox"], +.form-inline .radio input[type="radio"], +.form-inline .checkbox input[type="checkbox"] { + float: left; + margin-right: 3px; + margin-left: 0; +} + + +// Margin to space out fieldsets +.control-group { + margin-bottom: @baseLineHeight / 2; +} + +//modifier for control group +.control-group.-no-margin { + margin-bottom:0; +} + +// Legend collapses margin, so next element is responsible for spacing +legend + .control-group { + margin-top: @baseLineHeight; + -webkit-margin-top-collapse: separate; +} + +// Horizontal-specific styles +// -------------------------- + +.form-horizontal { + // Increase spacing between groups + .control-group { + margin-bottom: @baseLineHeight; + .clearfix(); + } + // Float the labels left + .control-label { + float: left; + width: @horizontalComponentOffset - 20; + padding-top: 5px; + text-align: right; + } + // Move over all input controls and content + .controls { + // Super jank IE7 fix to ensure the inputs in .input-append and input-prepend + // don't inherit the margin of the parent, in this case .controls + *display: inline-block; + *padding-left: 20px; + margin-left: @horizontalComponentOffset; + *margin-left: 0; + &:first-child { + *padding-left: @horizontalComponentOffset; + } + } + + + // Remove bottom margin on block level help text since that's accounted for on .control-group + .help-block { + margin-bottom: 0; + } + // And apply it only to .help-block instances that follow a form control + input, + select, + textarea, + .uneditable-input, + .input-prepend, + .input-append { + + .help-block { + margin-top: @baseLineHeight / 2; + } + } + // Move over buttons in .form-actions to align with .controls + .form-actions { + padding-left: @horizontalComponentOffset; + } +} + +// adjustments for properties tab +.form-horizontal .block-form .control-label { + display: block; + float: none; + width: 100%; +} + +//make sure buttons are always on top +.umb-panel-buttons .umb-btn-toolbar .btn { + position: relative; + z-index: 1000; +} + + + +@media (max-width: 767px) { + + // Labels on own row + .form-horizontal .control-label { + width: 100%; + } + .form-horizontal .controls { + margin-left: 0; + } + +} + +/* User/group selector */ +.group-selector .group-selector-list { float: left; } +.group-selector .group-selector-list div { height: 24px; } +.group-selector .group-selector-buttons { float: left; margin: 24px 16px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/gridview.less b/src/Umbraco.Web.UI.Client/src/less/gridview.less index a0f99b3cc2..6240c8c365 100644 --- a/src/Umbraco.Web.UI.Client/src/less/gridview.less +++ b/src/Umbraco.Web.UI.Client/src/less/gridview.less @@ -1,897 +1,897 @@ -// Gridview -// ------------------------- - - .mceContentBody{ - overflow-y:hidden!important; - } - -.usky-grid IFRAME {overflow:hidden;} - - - // Sortabel - // ------------------------- - - .usky-grid .ui-sortable-helper { - position:absolute !important; - border: dashed 1px @black !important; - background: @gray-7; - opacity: 0.4; - height: 80px !important; - width: 160px !important; - overflow: hidden; - padding: 5px; - border-radius:5px; - -webkit-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); - -moz-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); - box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); - } - - .usky-grid .ui-sortable-helper *{ - border: none !important; - background: none !important; - color: @gray-5 !important; - padding: 0 !important; - margin: 0 !important; - } - - .usky-grid .ui-sortable-helper .cell-tools{ - display: none !important; - } - - .usky-grid .ui-sortable-placeholder{ - border: 2px dashed @gray-8; - padding: 20px; - font-family: icomoon; - text-align: center; - font-size: 85px; - line-height: 65px; - color: @gray-3; - vertical-align: middle; - background-color:@gray-10; - } - - .usky-grid .ui-sortable-placeholder:hover{ - border-color: @gray-3; - } - - .usky-grid .ui-sortable-placeholder:before{ - content: "\e1bd"; - } - - - - // utils - // ------------------------- - .usky-grid-width { - margin: 20px auto; - width:100%; - } - .usky-grid .right { - float:right; - } - - - // general layout components - // ------------------------- - .usky-grid .tb { - width: 100%; - } - - .usky-grid .td { - width: 100%; - display: inline-block; - vertical-align:top; - border-right:1px dashed rgba(182, 182, 182, 0.0); - box-sizing: border-box; - } - - .usky-grid .tb:hover .td{ - border-right:1px dashed @gray-9; - } - .usky-grid .td.last { - border-right:1px dashed rgba(182, 182, 182, 0.0) !important; - } - .usky-grid .tr { - } - - .usky-grid .middle { - text-align: center; - } - - .usky-grid .mainTb { - border-collapse: separate; - } - .usky-grid .mainTd { - position:relative; - } - - - - // COLUMN - // ------------------------- - .usky-grid .usky-column { - - } - .usky-grid .usky-column.last { - - } - - - // ROW - // ------------------------- -.usky-grid .usky-row{ - position: relative; - border: 1px dashed rgba(0,0,0,0); -} - -.umb-grid .tb:hover .usky-row{ - border-bottom:1px dashed rgba(182, 182, 182, 0) !important; -} - -/* -.usky-grid .selectedRow { - border: 1px solid rgba(182, 182, 182, 0.3); -}*/ - - - - -// CELL -// ------------------------- -.usky-grid .usky-cell{ - position: relative; - border: 1px dashed rgba(0,0,0,0); - min-height:127px; -} - -.usky-grid .cell-tools { - transition: all .20s ease-in-out; - -moz-transition: all .20s ease-in-out; - -webkit-transition: all .20s ease-in-out; - position: absolute; - bottom: 0; - top: 0; - right: 0; - width: 50px; - opacity: 0.3; - z-index: 50; -} -//special rule to ensure forms doesnt overrride (forms will align in a later release) -.umb-grid .cell-tools{width: 50px !important;} - -.usky-grid .cell-tools.with-prompt { - width:200px; -} - -.usky-grid .cell-tools:hover{ - opacity: 1; -} - -.usky-grid .cell-tools-add { - position: absolute; - text-align: center; - bottom: 0px; - left: 0; - right: 0; - margin: 0 45px 1px 0; - - &.emptyArea{ - margin: 0 0 1px 0; - } -} - -.usky-grid .usky-control:hover .cell-tools-add{ - opacity: 1; -} - -.usky-grid .cell-tools-remove { - display:inline-block; - position: absolute; - top: 0px; - right: 5px; - text-align: right; - z-index: 500; -} - -.usky-grid .cell-tools-remove .iconBox:hover, .usky-grid .cell-tools-remove .iconBox:hover *{ - background: @red !important; - border-color: @red !important; -} - -.usky-grid .cell-tools-move { - display:inline-block; - position: absolute; - top: 33px; - right: 5px; - z-index: 500; - cursor: move -} - -.usky-grid .cell-tools-edit{ - position: absolute; - top: 66px; - right: 5px; -} - - -// CONTROL -// ------------------------- -.usky-grid .usky-control { - /*transition: all .20s ease-in-out; - -moz-transition: all .20s ease-in-out; - -webkit-transition: all .20s ease-in-out;*/ - position:relative; - display:block; - - /* - border: 1px dashed rgba(255, 0, 0, .0); - border-bottom-width: 1px; - */ - - -webkit-background-clip: padding-box; /* for Safari */ - background-clip: padding-box; /* for IE9+, Firefox 4+, Opera, Chrome */ - - margin: 10px 0 0 0; -} - -.usky-grid .usky-control:hover{ - /*border: 1px solid rgba(182, 182, 182, 0.3);*/ - /*border-bottom: 1px solid rgba(182, 182, 182, 0.3);*/ -} - -.usky-grid .usky-control-placeholder:hover{ - /*border: 1px solid rgba(182, 182, 182, 0);*/ - /*border-bottom:1px solid rgba(182, 182, 182, 0.0) !important;*/ -} - -.usky-grid .warnhighlight, .usky-grid .td.last.warnhighlight{ - border: 1px dashed @red !important; -} - -.usky-grid .infohighlight, .usky-grid .td.last.infohighlight{ - border: 1px dashed @turquoise !important; -} - -.usky-grid .warnhighlight > ins.item-label{border-color: @red; color: @red;} -.usky-grid .infohighlight > ins.item-label{border-color: @turquoise; color: @turquoise;} - - -.usky-grid ins.item-label { - position: absolute; - top: -22px; - left: -1px; - text-decoration: none; - padding: 0px 7px; - display:none; - font-size:0.8em; - background-color: @white; - color: @gray-8; - border: 1px dashed @gray-8; - border-bottom: 1px solid @white !important; - height: 20px; - overflow: hidden; -} - -.usky-grid .usky-row-inner > ins.item-label{ - top: -20px; - left: 0px; -} - -.usky-grid .usky-control-inner.selectedControl , .usky-grid .usky-row-inner.selectedRow{ - border: 1px dashed @gray-8; - - > ins.item-label { - display: block; - z-index:100000; - } -} - - - -// CONTROL PLACEHOLDER -// ------------------------- -.usky-grid .usky-control-placeholder { - min-height: 20px; - position: relative; - text-align: center; - text-align: -moz-center; - cursor: text; -} - -.usky-grid .usky-control-placeholder .placeholder{ - font-size: 14px; opacity: 0.7; text-align: left; - padding: 5px; - border:1px solid @gray-8; - height: 20px; -} - -.usky-grid .usky-control-placeholder:hover .placeholder{ - border:1px solid @gray-7; -} - - - - - - -// EDITOR PLACEHOLDER -// ------------------------- -.usky-grid .usky-editor-placeholder{ - min-height: 65px; - padding: 20px; - padding-bottom: 30px; - position: relative; - background-color: @white; - border: 4px dashed @gray-10; - text-align: center; - text-align: -moz-center; -} -.usky-grid .usky-editor-placeholder i{ - color: @gray-10; - font-size: 85px; - line-height: 85px; - display: block; - margin-bottom: 10px; -} - - - -// Form elements -// ------------------------- - .usky-grid textarea.textstring{ - display: block; - overflow: hidden; - border: none; - background: @white; - outline: none; - resize: none; - color: @gray-3; - } - -.usky-grid .usky-cell-rte textarea{ - display: none !important -} - -.usky-grid .usky-cell-media .caption{ - display: block; - overflow: hidden; - border: none; - background: @white; - outline: none; - width: 98%; - resize: none; - font-style: italic; -} - - .usky-grid .cellPanelRte { - min-height:60px; - } - - .usky-grid .usky-cell-embed iframe { - width: 100%; -} - -// ICONS -// ------------------------- - .usky-grid .iconBox { - padding: 4px 6px 4px 6px; - display: inline-block; - cursor: pointer; - border-radius: 200px; - background: @white; - border:1px solid @gray-7; - margin: 2px; - } - - .usky-grid .iconBox span.prompt { - display:block; - white-space: nowrap; - text-align:center; - - } - - .usky-grid .iconBox span.prompt > a { - text-decoration:underline; - } - - .usky-grid .iconBox:hover, .usky-grid .iconBox:hover *{ - background: @turquoise !important; - color: @white !important; - border-color: @turquoise !important; - text-decoration:none; - } - - .usky-grid .iconBox a:hover { - text-decoration:none; - color: @white !important; - } - - .usky-grid .iconBox.selected { - -webkit-appearance: none; - 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); - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - background: transparent; - } - - .usky-grid .iconBox i { - font-size:16px !important; - color: #5F5F5F; - display:block; - } - - - .usky-grid ul { - display:inline-block; - list-style:none; - padding:0; - margin: 10px 0 0 0; - text-align:center; - } - - .usky-grid .help-text { - background: @gray-10; - color: @gray-3; - font-size: 14px; - padding: 10px 20px 10px 20px; - border-radius: 15px; - display: inline-block; - clear: both; - } - - .usky-grid ul li { - display: inline-block; - width: 120px; - margin: 8px 8px 0px 8px; - } - - - - - // TINYMCE EDITOR - // ------------------------- - .usky-grid .mce-panel { - border: none !important; - clear:both; - } - - .usky-grid .mce-btn button { - padding: 8px 6px; - line-height: inherit; - } - - .usky-grid .mce-toolbar { - border: 1px solid @gray-8; - background-color: @gray-10; - z-index: 100; - display: inline-block; - float: left; - padding: -1px; - position: absolute; - margin: -1px -1px 0 -1px; - -webkit-box-shadow: 2px 2px 10px 0px rgba(50, 50, 50, 0.14); - -moz-box-shadow: 2px 2px 10px 0px rgba(50, 50, 50, 0.14); - box-shadow: 2px 2px 10px 0px rgba(50, 50, 50, 0.14); - z-index: 9999999; - } - - .mce-flow-layout-item { - margin: 0; - } - - .usky-grid .mce-panel { - background: transparent !important; - } - .usky-grid .mce-floatpanel { - background-color: @gray-10 !important; - } - -.usky-cell-rte{ - border: 1px solid @gray-10; -} - - // MEDIA EDITOR - // ------------------------- - .usky-grid .fullSizeImage { - width:100% - } - - - - - /**************************************************************************************************/ - /* Width */ - /**************************************************************************************************/ - - .usky-grid .boxWidth { - text-align:right; - margin-bottom: 10px; - } - - .usky-grid .boxWidth input { - text-align: center; - width: 40px; - } - - .usky-grid .boxWidth label { - font-size: 10px; - padding: 0; - margin: 5px 5px 0 0; - color: @gray-5; - } - - - - /**************************************************************************************************/ - /* Margin control */ - /**************************************************************************************************/ - - .usky-grid .usky-cell{ - padding-top: 5px; - padding-bottom: 15px; - } - - .usky-grid .usky-control{ - margin: 10px 0 0 0; - padding: 5px; - border: 1px dashed transparent; - } - - .usky-grid .usky-templates-columns{ - margin-top: 30px; - } - - .usky-grid .usky-row-inner{ - margin-right: 45px; - border: 1px dashed transparent; - } - - .usky-grid .usky-control-inner { - padding: 5px; - margin-right: 45px; - margin-bottom: 15px; - - border: 1px dashed transparent; - min-height: 60px; - position:relative; - } - - - - /**************************************************************************************************/ - /* template */ - /**************************************************************************************************/ - - .usky-grid .uSky-templates { - text-align:center; - overflow:hidden; - width:100%; - } - - .usky-grid .uSky-templates-template { - display:inline-block; - width:100px; - padding-right: 30px; - margin: 20px; - } - - .usky-grid .uSky-templates-template a.tb:hover { - border:5px solid @turquoise; - } - - .usky-grid .uSky-templates-template .tb { - width:100%; - height:150px; - padding:10px; - background-color: @gray-10; - border: 5px solid @gray-8; - cursor:pointer; - position: relative; - } - - .usky-grid .uSky-templates-template .tr { - height: 100%; - position: relative; - } - - .usky-grid .uSky-templates-template .tb .uSky-templates-column { - height:100%; - border: 1px dashed @gray-8; - border-right: none; - } - - .usky-grid .uSky-templates-template .tb .uSky-templates-column.last { - border-right: 1px dashed @gray-8 !important; - } - - .usky-grid a.uSky-templates-column:hover, .usky-grid a.uSky-templates-column.selected{ - background-color: @turquoise; - } - - - /**************************************************************************************************/ - /* template column */ - /**************************************************************************************************/ - -/* New template preview */ -.usky-grid { - - .templates-preview { - display: inline-block; - width: 100%; - text-align: center; - - small { - position: absolute; - width: 100%; - left: 0; - bottom: -25px; - padding-top: 15px; - } - - .help-text { - margin: 35px 35px 0 0; - } - } - - .preview-rows { - - display: inline-block; - position: relative; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 125px; - margin: 35px 40px 15px 0; - border: 2px solid @gray-8; /* @grayLight */ - transition: border 200ms linear; - - &.prevalues-rows { - margin: 0px 20px 20px 0px; - width: 80px; - float:left; - } - - &.prevalues-templates { - margin: 0px 20px 20px 0px; - float:left; - - } - - &:hover { - border-color: @turquoise; - cursor: pointer; - } - - .preview-row { - display: inline-block; - width: 100%; - vertical-align: bottom; - } - } - - .preview-rows.layout { - padding: 2px; - - .preview-row { - height: 100%; - } - - .preview-col { - height: 180px; - border: dashed 1px @gray-8; - } - - .preview-cell { - background-color: @gray-10; - } - .preview-overlay { - display: none; - } - } - - .preview-rows.columns { - min-height: 18px; - line-height: 11px; - padding: 1px; - - &.prevalues-rows { - min-height: 30px; - } - } - - .preview-rows { - - .preview-col { - - display: block; - float: left; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 33.3%; /* temp value */ - height: 10px; - margin: 0; - border: 1px solid @white; /* @white */ - - .preview-cell { - display: block; - width: 100%; - height: 100%; - background-color: @gray-8; /* @grayLight */ - margin: 0 1px 1px 0; - } - } - - &.prevalues-templates { - .preview-col { - height: 80px; - } - } - - } - - .preview-overlay { - display: block; - width: 100%; - position: absolute; - height: 100%; - top: 0; - box-sizing: border-box; - left: 0; - border: 3px solid @white; - } -} - - /**************************************************************************************************/ - /* overlay */ - /**************************************************************************************************/ - .usky-grid .cell-tools-menu{ - position: absolute; - width: 360px; - height: 380px; - overflow: auto; - border: 1px solid @gray-8; - margin-top: -270px; - margin-left: -150px; - background: @white; - padding: 7px; - top: 0; - left: 50%; - z-index: 6660; - -webkit-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); - -moz-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); - box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); - } - - - .usky-grid .cell-tools-menu h5{ - border-bottom: 1px solid @gray-8; - color: @gray-6; - padding: 10px; - margin-top: 0; - } - - .usky-grid .elements{ - display: block; - padding: 0; - margin: 0; - } - - .usky-grid .elements li{ - display: inline-block; - width: 90px; - height: 80px; - margin: 5px; - padding: 5px; - overflow: hidden; - font-size: 12px; - } - - .usky-grid .elements li:hover, .usky-grid .elements li:hover *{ - background: @turquoise; - color: @white; - } - - .usky-grid .elements a{ - color: @gray-2; - text-decoration: none; - } - - .usky-grid .elements i{ - font-size: 30px; - line-height: 50px; - color: @gray-5; - display: block - } - - - /**************************************************************************************************/ - /* Configuration specific styles */ - /**************************************************************************************************/ -.usky-grid-configuration .uSky-templates{ - text-align: left; -} - -.usky-grid-configuration ul{ - display: block; -} - -.usky-grid-configuration ul li{ - display: block; - width: auto; - text-align: left; -} - -.usky-grid-configuration .uSky-templates .uSky-templates-template .tb{ - max-height: 50px; - border-width: 2px !important; - padding: 0px; - border-spacing:2px; - overflow: hidden; -} - -.usky-grid-configuration .uSky-templates .uSky-templates-template span{ - background: @gray-8; - display: inline-block; -} - -.usky-grid-configuration .uSky-templates .uSky-templates-template .tb:hover{ - border-width: 2px !important; -} - -.usky-grid-configuration .uSky-templates-column{ - display: block; - float: left; - margin-left: -1px; - border: 1px @white solid !important; - background: @gray-8; -} - -.usky-grid-configuration .uSky-templates-column.last{ - margin-right: -1px; -} - -.usky-grid-configuration .uSky-templates-column.add{ - text-align: center; - font-size: 20px; - line-height: 70px; - color: @gray-8; - text-decoration: none; - background: @white; -} - -.usky-grid-configuration .mainTdpt{ - height: initial; - border: none; -} - -.usky-grid-configuration .uSky-templates-rows .uSky-templates-row{ - margin: 0px 50px 20px 0px; - width: 60px; -} - -.usky-grid-configuration .uSky-templates-rows .uSky-templates-row .tb{ - border-width: 2px !important; - padding: 0px; - border-spacing:2px; -} - -.usky-grid-configuration .uSky-templates-rows .mainTdpt{ - height: 10px !important; -} - -.usky-grid-configuration a.uSky-templates-column{height: 70px !important;} +// Gridview +// ------------------------- + + .mceContentBody{ + overflow-y:hidden!important; + } + +.usky-grid IFRAME {overflow:hidden;} + + + // Sortabel + // ------------------------- + + .usky-grid .ui-sortable-helper { + position:absolute !important; + border: dashed 1px @black !important; + background: @gray-7; + opacity: 0.4; + height: 80px !important; + width: 160px !important; + overflow: hidden; + padding: 5px; + border-radius:5px; + -webkit-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); + -moz-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); + box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); + } + + .usky-grid .ui-sortable-helper *{ + border: none !important; + background: none !important; + color: @gray-5 !important; + padding: 0 !important; + margin: 0 !important; + } + + .usky-grid .ui-sortable-helper .cell-tools{ + display: none !important; + } + + .usky-grid .ui-sortable-placeholder{ + border: 2px dashed @gray-8; + padding: 20px; + font-family: icomoon; + text-align: center; + font-size: 85px; + line-height: 65px; + color: @gray-3; + vertical-align: middle; + background-color:@gray-10; + } + + .usky-grid .ui-sortable-placeholder:hover{ + border-color: @gray-3; + } + + .usky-grid .ui-sortable-placeholder:before{ + content: "\e1bd"; + } + + + + // utils + // ------------------------- + .usky-grid-width { + margin: 20px auto; + width:100%; + } + .usky-grid .right { + float:right; + } + + + // general layout components + // ------------------------- + .usky-grid .tb { + width: 100%; + } + + .usky-grid .td { + width: 100%; + display: inline-block; + vertical-align:top; + border-right:1px dashed rgba(182, 182, 182, 0.0); + box-sizing: border-box; + } + + .usky-grid .tb:hover .td{ + border-right:1px dashed @gray-9; + } + .usky-grid .td.last { + border-right:1px dashed rgba(182, 182, 182, 0.0) !important; + } + .usky-grid .tr { + } + + .usky-grid .middle { + text-align: center; + } + + .usky-grid .mainTb { + border-collapse: separate; + } + .usky-grid .mainTd { + position:relative; + } + + + + // COLUMN + // ------------------------- + .usky-grid .usky-column { + + } + .usky-grid .usky-column.last { + + } + + + // ROW + // ------------------------- +.usky-grid .usky-row{ + position: relative; + border: 1px dashed rgba(0,0,0,0); +} + +.umb-grid .tb:hover .usky-row{ + border-bottom:1px dashed rgba(182, 182, 182, 0) !important; +} + +/* +.usky-grid .selectedRow { + border: 1px solid rgba(182, 182, 182, 0.3); +}*/ + + + + +// CELL +// ------------------------- +.usky-grid .usky-cell{ + position: relative; + border: 1px dashed rgba(0,0,0,0); + min-height:127px; +} + +.usky-grid .cell-tools { + transition: all .20s ease-in-out; + -moz-transition: all .20s ease-in-out; + -webkit-transition: all .20s ease-in-out; + position: absolute; + bottom: 0; + top: 0; + right: 0; + width: 50px; + opacity: 0.3; + z-index: 50; +} +//special rule to ensure forms doesnt overrride (forms will align in a later release) +.umb-grid .cell-tools{width: 50px !important;} + +.usky-grid .cell-tools.with-prompt { + width:200px; +} + +.usky-grid .cell-tools:hover{ + opacity: 1; +} + +.usky-grid .cell-tools-add { + position: absolute; + text-align: center; + bottom: 0px; + left: 0; + right: 0; + margin: 0 45px 1px 0; + + &.emptyArea{ + margin: 0 0 1px 0; + } +} + +.usky-grid .usky-control:hover .cell-tools-add{ + opacity: 1; +} + +.usky-grid .cell-tools-remove { + display:inline-block; + position: absolute; + top: 0px; + right: 5px; + text-align: right; + z-index: 500; +} + +.usky-grid .cell-tools-remove .iconBox:hover, .usky-grid .cell-tools-remove .iconBox:hover *{ + background: @red !important; + border-color: @red !important; +} + +.usky-grid .cell-tools-move { + display:inline-block; + position: absolute; + top: 33px; + right: 5px; + z-index: 500; + cursor: move +} + +.usky-grid .cell-tools-edit{ + position: absolute; + top: 66px; + right: 5px; +} + + +// CONTROL +// ------------------------- +.usky-grid .usky-control { + /*transition: all .20s ease-in-out; + -moz-transition: all .20s ease-in-out; + -webkit-transition: all .20s ease-in-out;*/ + position:relative; + display:block; + + /* + border: 1px dashed rgba(255, 0, 0, .0); + border-bottom-width: 1px; + */ + + -webkit-background-clip: padding-box; /* for Safari */ + background-clip: padding-box; /* for IE9+, Firefox 4+, Opera, Chrome */ + + margin: 10px 0 0 0; +} + +.usky-grid .usky-control:hover{ + /*border: 1px solid rgba(182, 182, 182, 0.3);*/ + /*border-bottom: 1px solid rgba(182, 182, 182, 0.3);*/ +} + +.usky-grid .usky-control-placeholder:hover{ + /*border: 1px solid rgba(182, 182, 182, 0);*/ + /*border-bottom:1px solid rgba(182, 182, 182, 0.0) !important;*/ +} + +.usky-grid .warnhighlight, .usky-grid .td.last.warnhighlight{ + border: 1px dashed @red !important; +} + +.usky-grid .infohighlight, .usky-grid .td.last.infohighlight{ + border: 1px dashed @turquoise !important; +} + +.usky-grid .warnhighlight > ins.item-label{border-color: @red; color: @red;} +.usky-grid .infohighlight > ins.item-label{border-color: @turquoise; color: @turquoise;} + + +.usky-grid ins.item-label { + position: absolute; + top: -22px; + left: -1px; + text-decoration: none; + padding: 0px 7px; + display:none; + font-size:0.8em; + background-color: @white; + color: @gray-8; + border: 1px dashed @gray-8; + border-bottom: 1px solid @white !important; + height: 20px; + overflow: hidden; +} + +.usky-grid .usky-row-inner > ins.item-label{ + top: -20px; + left: 0px; +} + +.usky-grid .usky-control-inner.selectedControl , .usky-grid .usky-row-inner.selectedRow{ + border: 1px dashed @gray-8; + + > ins.item-label { + display: block; + z-index:100000; + } +} + + + +// CONTROL PLACEHOLDER +// ------------------------- +.usky-grid .usky-control-placeholder { + min-height: 20px; + position: relative; + text-align: center; + text-align: -moz-center; + cursor: text; +} + +.usky-grid .usky-control-placeholder .placeholder{ + font-size: 14px; opacity: 0.7; text-align: left; + padding: 5px; + border:1px solid @gray-8; + height: 20px; +} + +.usky-grid .usky-control-placeholder:hover .placeholder{ + border:1px solid @gray-7; +} + + + + + + +// EDITOR PLACEHOLDER +// ------------------------- +.usky-grid .usky-editor-placeholder{ + min-height: 65px; + padding: 20px; + padding-bottom: 30px; + position: relative; + background-color: @white; + border: 4px dashed @gray-10; + text-align: center; + text-align: -moz-center; +} +.usky-grid .usky-editor-placeholder i{ + color: @gray-10; + font-size: 85px; + line-height: 85px; + display: block; + margin-bottom: 10px; +} + + + +// Form elements +// ------------------------- + .usky-grid textarea.textstring{ + display: block; + overflow: hidden; + border: none; + background: @white; + outline: none; + resize: none; + color: @gray-3; + } + +.usky-grid .usky-cell-rte textarea{ + display: none !important +} + +.usky-grid .usky-cell-media .caption{ + display: block; + overflow: hidden; + border: none; + background: @white; + outline: none; + width: 98%; + resize: none; + font-style: italic; +} + + .usky-grid .cellPanelRte { + min-height:60px; + } + + .usky-grid .usky-cell-embed iframe { + width: 100%; +} + +// ICONS +// ------------------------- + .usky-grid .iconBox { + padding: 4px 6px 4px 6px; + display: inline-block; + cursor: pointer; + border-radius: 200px; + background: @white; + border:1px solid @gray-7; + margin: 2px; + } + + .usky-grid .iconBox span.prompt { + display:block; + white-space: nowrap; + text-align:center; + + } + + .usky-grid .iconBox span.prompt > a { + text-decoration:underline; + } + + .usky-grid .iconBox:hover, .usky-grid .iconBox:hover *{ + background: @turquoise !important; + color: @white !important; + border-color: @turquoise !important; + text-decoration:none; + } + + .usky-grid .iconBox a:hover { + text-decoration:none; + color: @white !important; + } + + .usky-grid .iconBox.selected { + -webkit-appearance: none; + 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); + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + background: transparent; + } + + .usky-grid .iconBox i { + font-size:16px !important; + color: #5F5F5F; + display:block; + } + + + .usky-grid ul { + display:inline-block; + list-style:none; + padding:0; + margin: 10px 0 0 0; + text-align:center; + } + + .usky-grid .help-text { + background: @gray-10; + color: @gray-3; + font-size: 14px; + padding: 10px 20px 10px 20px; + border-radius: 15px; + display: inline-block; + clear: both; + } + + .usky-grid ul li { + display: inline-block; + width: 120px; + margin: 8px 8px 0px 8px; + } + + + + + // TINYMCE EDITOR + // ------------------------- + .usky-grid .mce-panel { + border: none !important; + clear:both; + } + + .usky-grid .mce-btn button { + padding: 8px 6px; + line-height: inherit; + } + + .usky-grid .mce-toolbar { + border: 1px solid @gray-8; + background-color: @gray-10; + z-index: 100; + display: inline-block; + float: left; + padding: -1px; + position: absolute; + margin: -1px -1px 0 -1px; + -webkit-box-shadow: 2px 2px 10px 0px rgba(50, 50, 50, 0.14); + -moz-box-shadow: 2px 2px 10px 0px rgba(50, 50, 50, 0.14); + box-shadow: 2px 2px 10px 0px rgba(50, 50, 50, 0.14); + z-index: 9999999; + } + + .mce-flow-layout-item { + margin: 0; + } + + .usky-grid .mce-panel { + background: transparent !important; + } + .usky-grid .mce-floatpanel { + background-color: @gray-10 !important; + } + +.usky-cell-rte{ + border: 1px solid @gray-10; +} + + // MEDIA EDITOR + // ------------------------- + .usky-grid .fullSizeImage { + width:100% + } + + + + + /**************************************************************************************************/ + /* Width */ + /**************************************************************************************************/ + + .usky-grid .boxWidth { + text-align:right; + margin-bottom: 10px; + } + + .usky-grid .boxWidth input { + text-align: center; + width: 40px; + } + + .usky-grid .boxWidth label { + font-size: 10px; + padding: 0; + margin: 5px 5px 0 0; + color: @gray-5; + } + + + + /**************************************************************************************************/ + /* Margin control */ + /**************************************************************************************************/ + + .usky-grid .usky-cell{ + padding-top: 5px; + padding-bottom: 15px; + } + + .usky-grid .usky-control{ + margin: 10px 0 0 0; + padding: 5px; + border: 1px dashed transparent; + } + + .usky-grid .usky-templates-columns{ + margin-top: 30px; + } + + .usky-grid .usky-row-inner{ + margin-right: 45px; + border: 1px dashed transparent; + } + + .usky-grid .usky-control-inner { + padding: 5px; + margin-right: 45px; + margin-bottom: 15px; + + border: 1px dashed transparent; + min-height: 60px; + position:relative; + } + + + + /**************************************************************************************************/ + /* template */ + /**************************************************************************************************/ + + .usky-grid .uSky-templates { + text-align:center; + overflow:hidden; + width:100%; + } + + .usky-grid .uSky-templates-template { + display:inline-block; + width:100px; + padding-right: 30px; + margin: 20px; + } + + .usky-grid .uSky-templates-template a.tb:hover { + border:5px solid @turquoise; + } + + .usky-grid .uSky-templates-template .tb { + width:100%; + height:150px; + padding:10px; + background-color: @gray-10; + border: 5px solid @gray-8; + cursor:pointer; + position: relative; + } + + .usky-grid .uSky-templates-template .tr { + height: 100%; + position: relative; + } + + .usky-grid .uSky-templates-template .tb .uSky-templates-column { + height:100%; + border: 1px dashed @gray-8; + border-right: none; + } + + .usky-grid .uSky-templates-template .tb .uSky-templates-column.last { + border-right: 1px dashed @gray-8 !important; + } + + .usky-grid a.uSky-templates-column:hover, .usky-grid a.uSky-templates-column.selected{ + background-color: @turquoise; + } + + + /**************************************************************************************************/ + /* template column */ + /**************************************************************************************************/ + +/* New template preview */ +.usky-grid { + + .templates-preview { + display: inline-block; + width: 100%; + text-align: center; + + small { + position: absolute; + width: 100%; + left: 0; + bottom: -25px; + padding-top: 15px; + } + + .help-text { + margin: 35px 35px 0 0; + } + } + + .preview-rows { + + display: inline-block; + position: relative; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 125px; + margin: 35px 40px 15px 0; + border: 2px solid @gray-8; /* @grayLight */ + transition: border 200ms linear; + + &.prevalues-rows { + margin: 0px 20px 20px 0px; + width: 80px; + float:left; + } + + &.prevalues-templates { + margin: 0px 20px 20px 0px; + float:left; + + } + + &:hover { + border-color: @turquoise; + cursor: pointer; + } + + .preview-row { + display: inline-block; + width: 100%; + vertical-align: bottom; + } + } + + .preview-rows.layout { + padding: 2px; + + .preview-row { + height: 100%; + } + + .preview-col { + height: 180px; + border: dashed 1px @gray-8; + } + + .preview-cell { + background-color: @gray-10; + } + .preview-overlay { + display: none; + } + } + + .preview-rows.columns { + min-height: 18px; + line-height: 11px; + padding: 1px; + + &.prevalues-rows { + min-height: 30px; + } + } + + .preview-rows { + + .preview-col { + + display: block; + float: left; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 33.3%; /* temp value */ + height: 10px; + margin: 0; + border: 1px solid @white; /* @white */ + + .preview-cell { + display: block; + width: 100%; + height: 100%; + background-color: @gray-8; /* @grayLight */ + margin: 0 1px 1px 0; + } + } + + &.prevalues-templates { + .preview-col { + height: 80px; + } + } + + } + + .preview-overlay { + display: block; + width: 100%; + position: absolute; + height: 100%; + top: 0; + box-sizing: border-box; + left: 0; + border: 3px solid @white; + } +} + + /**************************************************************************************************/ + /* overlay */ + /**************************************************************************************************/ + .usky-grid .cell-tools-menu{ + position: absolute; + width: 360px; + height: 380px; + overflow: auto; + border: 1px solid @gray-8; + margin-top: -270px; + margin-left: -150px; + background: @white; + padding: 7px; + top: 0; + left: 50%; + z-index: 6660; + -webkit-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); + -moz-box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); + box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); + } + + + .usky-grid .cell-tools-menu h5{ + border-bottom: 1px solid @gray-8; + color: @gray-6; + padding: 10px; + margin-top: 0; + } + + .usky-grid .elements{ + display: block; + padding: 0; + margin: 0; + } + + .usky-grid .elements li{ + display: inline-block; + width: 90px; + height: 80px; + margin: 5px; + padding: 5px; + overflow: hidden; + font-size: 12px; + } + + .usky-grid .elements li:hover, .usky-grid .elements li:hover *{ + background: @turquoise; + color: @white; + } + + .usky-grid .elements a{ + color: @gray-2; + text-decoration: none; + } + + .usky-grid .elements i{ + font-size: 30px; + line-height: 50px; + color: @gray-5; + display: block + } + + + /**************************************************************************************************/ + /* Configuration specific styles */ + /**************************************************************************************************/ +.usky-grid-configuration .uSky-templates{ + text-align: left; +} + +.usky-grid-configuration ul{ + display: block; +} + +.usky-grid-configuration ul li{ + display: block; + width: auto; + text-align: left; +} + +.usky-grid-configuration .uSky-templates .uSky-templates-template .tb{ + max-height: 50px; + border-width: 2px !important; + padding: 0px; + border-spacing:2px; + overflow: hidden; +} + +.usky-grid-configuration .uSky-templates .uSky-templates-template span{ + background: @gray-8; + display: inline-block; +} + +.usky-grid-configuration .uSky-templates .uSky-templates-template .tb:hover{ + border-width: 2px !important; +} + +.usky-grid-configuration .uSky-templates-column{ + display: block; + float: left; + margin-left: -1px; + border: 1px @white solid !important; + background: @gray-8; +} + +.usky-grid-configuration .uSky-templates-column.last{ + margin-right: -1px; +} + +.usky-grid-configuration .uSky-templates-column.add{ + text-align: center; + font-size: 20px; + line-height: 70px; + color: @gray-8; + text-decoration: none; + background: @white; +} + +.usky-grid-configuration .mainTdpt{ + height: initial; + border: none; +} + +.usky-grid-configuration .uSky-templates-rows .uSky-templates-row{ + margin: 0px 50px 20px 0px; + width: 60px; +} + +.usky-grid-configuration .uSky-templates-rows .uSky-templates-row .tb{ + border-width: 2px !important; + padding: 0px; + border-spacing:2px; +} + +.usky-grid-configuration .uSky-templates-rows .mainTdpt{ + height: 10px !important; +} + +.usky-grid-configuration a.uSky-templates-column{height: 70px !important;} diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index 29035213a2..be48fd9ab6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -1,220 +1,220 @@ -// Hacks -// ------------------------- - -/* CONTAINS ALL HACKS AND OTHER QUICK-FIXES */ - -/*wft ms?*/ -*{ -ms-touch-action: none;} - -.ace_editor { height: 200px; } - -.nounderline {text-decoration: none !important} -.nounderline:hover {text-decoration: underline !important} -.nounderline * {text-decoration: none !important; border: none} - -.ui-sortable-placeholder { - margin-left: 0 !important; -} -.controls-row img { - max-width: none; -} - -.thumbnail { - border-radius: 0px; -} - -.thumbnail img { - max-width: 100% !important; - width: 100%; -} - -#mapCanvas img { - max-width: none !important; -} - -.btn-group .dropdown-backdrop { - display: none; -} - -/* loading animation for iframes and content pages */ -iframe, .content-column-body { - background: center center url(../img/loader.gif) no-repeat; - border: none; -} - -/* JQUERY FILEUPLOAD TEMP STYLES */ -.fileinput-button { - position: relative; - overflow: hidden; - margin-bottom:5px; -} -.fileinput-button input { - position: absolute; - top: 0; - right: 0; - margin: 0; - opacity: 0; - filter: alpha(opacity=0); - transform: translate(-300px, 0) scale(4); - font-size: 23px; - direction: ltr; - cursor: pointer; -} - - -/*tree legacy icon*/ -.legacy-custom-file { - width: 16px; - height: 16px; - min-width: 20px; /* this ensure the icon takes up same space as font-icon (20px) */ - display: inline-block; - background-position: center center; - background-repeat: no-repeat; -} - -/* - missing icon names in helveticons that are in font-awesome - used by the datepicker, - basically making them equivalent to their helviton icon -*/ -.icon-chevron-up:before { - content: "\e128"; -} -.icon-chevron-down:before { - content: "\e0c9"; -} - - -/* Styling for validation in Public Access */ - -.pa-umb-overlay { - -webkit-font-smoothing: antialiased; - font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.pa-umb-overlay + .pa-umb-overlay { - padding-top: 30px; - border-top: 1px solid @gray-8; -} - -.pa-select-type { - display: flex; - flex-wrap: nowrap; - flex-direction: row; - justify-content: center; - align-items: flex-start; - - margin-top: 15px; -} - -.pa-select-type label { - padding: 0 20px; -} - -.pa-access-header { - font-weight: bold; - margin: 0 0 3px 0; - padding-bottom: 0; -} - -.pa-access-description { - color: @gray-7; - margin: 0; -} - -.pa-validation-message { - padding: 6px 12px !important; - margin: 5px 0 0 0 !important; - display: inline-block; -} - -.pa-select-pages label { - margin: 0; - font-size: 15px; -} - -.pa-select-pages label + .controls-row { - padding-top: 0; -} - -.pa-select-pages .umb-detail { - font-size: 13px; - margin: 2px 0 5px; -} - -.pa-choose-page a { - color: @turquoise-d1; - font-size: 15px; -} - -.pa-choose-page a:hover, .pa-choose-page a:active, .pa-choose-page a:focus { - color: @turquoise-d1; - text-decoration: none; -} - -.pa-choose-page a:before { - content:"+"; - margin-right: 3px; - font-weight: bold; -} - -.pa-choose-page .treePickerTitle { - font-weight: bold; - font-size: 13px; - font-style: italic; - background: @gray-10; - padding: 3px 5px; - color: @gray-5; - - border-bottom: none; -} - - -.pa-form + .pa-form { - margin-top: 10px; -} - - -// The below adds a default selector to all pre elements to ensure that styles are applied -// without having to add the class ".code" to the element. Styles have been created by -// combining the various declarations from the Bootstrap code.less file and fixing some mistakes -// in Bootstrap 2. -// This fixes issues with the markdown editor preview and should not cause issues with any other editor. - -// Inline code -// 1: Revert border radius to match look and feel of 7.4+ -code{ - .border-radius(@baseBorderRadius); // 1 -} - -// Blocks of code -// 1: Wrapping code is unreadable on small devices. -pre { - display: block; - padding: (@baseLineHeight - 1) / 2; - margin: 0 0 @baseLineHeight / 2; - font-family: @sansFontFamily; - //font-size: @baseFontSize - 1; // 14px to 13px - color: @gray-2; - line-height: @baseLineHeight; - white-space: pre-line; // 1 - overflow-x: auto; // 1 - background-color: @gray-10; - border: 1px solid @gray-8; - .border-radius(@baseBorderRadius); - - - // Make prettyprint styles more spaced out for readability - &.prettyprint { - margin-bottom: @baseLineHeight; - } - - // Account for some code outputs that place code tags in pre tags - code { - padding: 0; - white-space: pre; // 1 - word-wrap: normal; // 1 - background-color: transparent; - border: 0; - } +// Hacks +// ------------------------- + +/* CONTAINS ALL HACKS AND OTHER QUICK-FIXES */ + +/*wft ms?*/ +*{ -ms-touch-action: none;} + +.ace_editor { height: 200px; } + +.nounderline {text-decoration: none !important} +.nounderline:hover {text-decoration: underline !important} +.nounderline * {text-decoration: none !important; border: none} + +.ui-sortable-placeholder { + margin-left: 0 !important; +} +.controls-row img { + max-width: none; +} + +.thumbnail { + border-radius: 0px; +} + +.thumbnail img { + max-width: 100% !important; + width: 100%; +} + +#mapCanvas img { + max-width: none !important; +} + +.btn-group .dropdown-backdrop { + display: none; +} + +/* loading animation for iframes and content pages */ +iframe, .content-column-body { + background: center center url(../img/loader.gif) no-repeat; + border: none; +} + +/* JQUERY FILEUPLOAD TEMP STYLES */ +.fileinput-button { + position: relative; + overflow: hidden; + margin-bottom:5px; +} +.fileinput-button input { + position: absolute; + top: 0; + right: 0; + margin: 0; + opacity: 0; + filter: alpha(opacity=0); + transform: translate(-300px, 0) scale(4); + font-size: 23px; + direction: ltr; + cursor: pointer; +} + + +/*tree legacy icon*/ +.legacy-custom-file { + width: 16px; + height: 16px; + min-width: 20px; /* this ensure the icon takes up same space as font-icon (20px) */ + display: inline-block; + background-position: center center; + background-repeat: no-repeat; +} + +/* + missing icon names in helveticons that are in font-awesome - used by the datepicker, + basically making them equivalent to their helviton icon +*/ +.icon-chevron-up:before { + content: "\e128"; +} +.icon-chevron-down:before { + content: "\e0c9"; +} + + +/* Styling for validation in Public Access */ + +.pa-umb-overlay { + -webkit-font-smoothing: antialiased; + font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.pa-umb-overlay + .pa-umb-overlay { + padding-top: 30px; + border-top: 1px solid @gray-8; +} + +.pa-select-type { + display: flex; + flex-wrap: nowrap; + flex-direction: row; + justify-content: center; + align-items: flex-start; + + margin-top: 15px; +} + +.pa-select-type label { + padding: 0 20px; +} + +.pa-access-header { + font-weight: bold; + margin: 0 0 3px 0; + padding-bottom: 0; +} + +.pa-access-description { + color: @gray-7; + margin: 0; +} + +.pa-validation-message { + padding: 6px 12px !important; + margin: 5px 0 0 0 !important; + display: inline-block; +} + +.pa-select-pages label { + margin: 0; + font-size: 15px; +} + +.pa-select-pages label + .controls-row { + padding-top: 0; +} + +.pa-select-pages .umb-detail { + font-size: 13px; + margin: 2px 0 5px; +} + +.pa-choose-page a { + color: @turquoise-d1; + font-size: 15px; +} + +.pa-choose-page a:hover, .pa-choose-page a:active, .pa-choose-page a:focus { + color: @turquoise-d1; + text-decoration: none; +} + +.pa-choose-page a:before { + content:"+"; + margin-right: 3px; + font-weight: bold; +} + +.pa-choose-page .treePickerTitle { + font-weight: bold; + font-size: 13px; + font-style: italic; + background: @gray-10; + padding: 3px 5px; + color: @gray-5; + + border-bottom: none; +} + + +.pa-form + .pa-form { + margin-top: 10px; +} + + +// The below adds a default selector to all pre elements to ensure that styles are applied +// without having to add the class ".code" to the element. Styles have been created by +// combining the various declarations from the Bootstrap code.less file and fixing some mistakes +// in Bootstrap 2. +// This fixes issues with the markdown editor preview and should not cause issues with any other editor. + +// Inline code +// 1: Revert border radius to match look and feel of 7.4+ +code{ + .border-radius(@baseBorderRadius); // 1 +} + +// Blocks of code +// 1: Wrapping code is unreadable on small devices. +pre { + display: block; + padding: (@baseLineHeight - 1) / 2; + margin: 0 0 @baseLineHeight / 2; + font-family: @sansFontFamily; + //font-size: @baseFontSize - 1; // 14px to 13px + color: @gray-2; + line-height: @baseLineHeight; + white-space: pre-line; // 1 + overflow-x: auto; // 1 + background-color: @gray-10; + border: 1px solid @gray-8; + .border-radius(@baseBorderRadius); + + + // Make prettyprint styles more spaced out for readability + &.prettyprint { + margin-bottom: @baseLineHeight; + } + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + white-space: pre; // 1 + word-wrap: normal; // 1 + background-color: transparent; + border: 0; + } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/helveticons.less b/src/Umbraco.Web.UI.Client/src/less/helveticons.less index 9a317e09fb..60ccd3c357 100644 --- a/src/Umbraco.Web.UI.Client/src/less/helveticons.less +++ b/src/Umbraco.Web.UI.Client/src/less/helveticons.less @@ -1,1872 +1,1872 @@ -@font-face { - font-family: 'icomoon'; - src:url('../fonts/helveticons/helveticons.eot'); - src:url('../fonts/helveticons/helveticons.eot?#iefix') format('embedded-opentype'), - url('../fonts/helveticons/helveticons.ttf') format('truetype'), - url('../fonts/helveticons/helveticons.svg#icomoon') format('svg'); - font-weight: normal; - font-style: normal; -} - -[class^="icon-"], -[class*=" icon-"] { - font-family: icomoon; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - *margin-right: .3em; -} -[class^="icon-"]:before, -[class*=" icon-"]:before { - text-decoration: inherit; - display: inline-block; - speak: none; -} -/* -[class^="icon-"]:before, [class*=" icon-"]:before { - font-family: 'icomoon'; - speak: none; - font-weight: normal; - font-style: normal; - display: inline-block; - text-decoration: inherit; - font-size: 14px; - -webkit-font-smoothing: antialiased; -}*/ - -i.large{ - font-size: 32px; -} - -i.medium{ - font-size: 24px; -} -i.small{ - font-size: 14px; -} - -.icon-zoom-out:before { - content: "\e000"; -} -.icon-truck:before { - content: "\e001"; -} -.icon-zoom-in:before { - content: "\e002"; -} -.icon-zip:before { - content: "\e003"; -} -.icon-axis-rotation:before { - content: "\e004"; -} -.icon-yen-bag:before { - content: "\e005"; -} -.icon-axis-rotation-2:before { - content: "\e006"; -} -.icon-axis-rotation-3:before { - content: "\e007"; -} -.icon-wrench:before { - content: "\e008"; -} -.icon-wine-glass:before { - content: "\e009"; -} -.icon-wrong:before { - content: "\e00a"; -} -.icon-windows:before { - content: "\e00b"; -} -.icon-window-sizes:before { - content: "\e00c"; -} -.icon-window-popin:before { - content: "\e00d"; -} -.icon-wifi:before { - content: "\e00e"; -} -.icon-width:before { - content: "\e00f"; -} -.icon-weight:before { - content: "\e010"; -} -.icon-war:before { - content: "\e011"; -} -.icon-wand:before { - content: "\e012"; -} -.icon-wallet:before { - content: "\e013"; -} -.icon-wall-plug:before { - content: "\e014"; -} -.icon-voice:before { - content: "\e016"; -} -.icon-video:before { - content: "\e017"; -} -.icon-vcard:before { - content: "\e018"; -} -.icon-utilities:before { - content: "\e019"; -} -.icon-users:before { - content: "\e01a"; -} -.icon-users-alt:before { - content: "\e01b"; -} -.icon-user:before { - content: "\e01c"; -} -.icon-user-glasses:before { - content: "\e01d"; -} -.icon-user-females:before { - content: "\e01e"; -} -.icon-user-females-alt:before { - content: "\e01f"; -} -.icon-user-female:before { - content: "\e020"; -} -.icon-usb:before { - content: "\e021"; -} -.icon-usb-connector:before { - content: "\e022"; -} -.icon-unlocked:before { - content: "\e023"; -} -.icon-universal:before { - content: "\e024"; -} -.icon-undo:before { - content: "\e025"; -} -.icon-umbrella:before { - content: "\e026"; -} -.icon-umb-deploy:before { - content: "\e027"; -} -.icon-umb-contour:before, .traycontour:before, { - content: "\e028"; -} -.icon-umb-settings:before, .traysettings:before, { - content: "\e029"; -} -.icon-umb-users:before, .trayuser:before, .trayusers:before{ - content: "\e02a"; -} -.icon-umb-media:before, .traymedia:before, { - content: "\e02b"; -} -.icon-umb-content:before, .traycontent:before{ - content: "\e02c"; -} -.icon-umb-developer:before, .traydeveloper:before, { - content: "\e02d"; -} -.icon-umb-members:before, .traymember:before { - content: "\e015"; -} -.icon-umb-translation:before, .traytranslation:before { - content: "\e1fd"; -} - - -.icon-tv:before { - content: "\e02e"; -} -.icon-tv-old:before { - content: "\e02f"; -} -.icon-trophy:before { - content: "\e030"; -} -.icon-tree:before { - content: "\e031"; -} -.icon-trash:before { - content: "\e032"; -} -.icon-trash-alt:before { - content: "\e033"; -} -.icon-trash-alt-2:before { - content: "\e034"; -} -.icon-train:before { - content: "\e035"; -} -.icon-trafic:before { - content: "\e036"; -} -.icon-traffic-alt:before { - content: "\e037"; -} -.icon-top:before { - content: "\e038"; -} -.icon-tools:before { - content: "\e039"; -} -.icon-timer:before { - content: "\e03a"; -} -.icon-time:before { - content: "\e03b"; -} -.icon-t-shirt:before { - content: "\e03c"; -} -.icon-tab-key:before { - content: "\e03d"; -} -.icon-tab:before { - content: "\e03e"; -} -.icon-tactics:before { - content: "\e03f"; -} -.icon-tag:before { - content: "\e040"; -} -.icon-tags:before { - content: "\e041"; -} -.icon-takeaway-cup:before { - content: "\e042"; -} -.icon-target:before { - content: "\e043"; -} -.icon-temperatrure-alt:before { - content: "\e044"; -} -.icon-temperature:before { - content: "\e045"; -} -.icon-terminal:before { - content: "\e046"; -} -.icon-theater:before { - content: "\e047"; -} -.icon-theif:before { - content: "\e048"; -} -.icon-thought-bubble:before { - content: "\e049"; -} -.icon-thumb-down:before { - content: "\e04a"; -} -.icon-thumb-up:before { - content: "\e04b"; -} -.icon-thumbnail-list:before { - content: "\e04c"; -} -.icon-thumbnails-small:before { - content: "\e04d"; -} -.icon-thumbnails:before { - content: "\e04e"; -} -.icon-ticket:before { - content: "\e04f"; -} -.icon-sync:before { - content: "\e050"; -} -.icon-sweatshirt:before { - content: "\e051"; -} -.icon-sunny:before { - content: "\e052"; -} -.icon-stream:before { - content: "\e053"; -} -.icon-store:before { - content: "\e054"; -} -.icon-stop:before { - content: "\e055"; -} -.icon-stop-hand:before { - content: "\e056"; -} -.icon-stop-alt:before { - content: "\e057"; -} -.icon-stamp:before { - content: "\e058"; -} -.icon-stacked-disks:before { - content: "\e059"; -} -.icon-ssd:before { - content: "\e05a"; -} -.icon-squiggly-line:before { - content: "\e05b"; -} -.icon-sprout:before { - content: "\e05c"; -} -.icon-split:before { - content: "\e05d"; -} -.icon-split-alt:before { - content: "\e05e"; -} -.icon-speed-gauge:before { - content: "\e05f"; -} -.icon-speaker:before { - content: "\e060"; -} -.icon-sound:before { - content: "\e061"; -} -.icon-spades:before { - content: "\e062"; -} -.icon-sound-waves:before { - content: "\e063"; -} -.icon-shipping-box:before { - content: "\e064"; -} -.icon-shipping:before { - content: "\e065"; -} -.icon-shoe:before { - content: "\e066"; -} -.icon-shopping-basket-alt-2:before { - content: "\e067"; -} -.icon-shopping-basket:before { - content: "\e068"; -} -.icon-shopping-basket-alt:before { - content: "\e069"; -} -.icon-shorts:before { - content: "\e06a"; -} -.icon-shuffle:before { - content: "\e06b"; -} -.icon-sience:before { - content: "\e06c"; -} -.icon-simcard:before { - content: "\e06d"; -} -.icon-single-note:before { - content: "\e06e"; -} -.icon-sitemap:before { - content: "\e06f"; -} -.icon-sleep:before { - content: "\e070"; -} -.icon-slideshow:before { - content: "\e071"; -} -.icon-smiley-inverted:before { - content: "\e072"; -} -.icon-smiley:before { - content: "\e073"; -} -.icon-snow:before { - content: "\e074"; -} -.icon-sound-low:before { - content: "\e075"; -} -.icon-sound-medium:before { - content: "\e076"; -} -.icon-sound-off:before { - content: "\e077"; -} -.icon-shift:before { - content: "\e078"; -} -.icon-shield:before { - content: "\e079"; -} -.icon-sharing-iphone:before { - content: "\e07a"; -} -.icon-share:before { - content: "\e07b"; -} -.icon-share-alt:before { - content: "\e07c"; -} -.icon-share-alt-2:before { - content: "\e07d"; -} -.icon-settings:before { - content: "\e07e"; -} -.icon-settings-alt:before { - content: "\e07f"; -} -.icon-settings-alt-2:before { - content: "\e080"; -} -.icon-server:before { - content: "\e081"; -} -.icon-server-alt:before { - content: "\e082"; -} -.icon-sensor:before { - content: "\e083"; -} -.icon-security-camera:before { - content: "\e084"; -} -.icon-search:before { - content: "\e085"; -} -.icon-scull:before { - content: "\e086"; -} -.icon-script:before { - content: "\e087"; -} -.icon-script-alt:before { - content: "\e088"; -} -.icon-screensharing:before { - content: "\e089"; -} -.icon-school:before { - content: "\e08a"; -} -.icon-scan:before { - content: "\e08b"; -} -.icon-refresh:before { - content: "\e08c"; -} -.icon-remote:before { - content: "\e08d"; -} -.icon-remove:before { - content: "\e08e"; -} -.icon-repeat-one:before { - content: "\e08f"; -} -.icon-repeat:before { - content: "\e090"; -} -.icon-resize:before { - content: "\e091"; -} -.icon-reply-arrow:before { - content: "\e092"; -} -.icon-return-to-top:before { - content: "\e093"; -} -.icon-right-double-arrow:before { - content: "\e094"; -} -.icon-road:before { - content: "\e095"; -} -.icon-roadsign:before { - content: "\e096"; -} -.icon-rocket:before { - content: "\e097"; -} -.icon-rss:before { - content: "\e098"; -} -.icon-ruler-alt:before { - content: "\e099"; -} -.icon-ruler:before { - content: "\e09a"; -} -.icon-sandbox-toys:before { - content: "\e09b"; -} -.icon-satellite-dish:before { - content: "\e09c"; -} -.icon-save:before { - content: "\e09d"; -} -.icon-safedial:before { - content: "\e09e"; -} -.icon-safe:before { - content: "\e09f"; -} -.icon-redo:before { - content: "\e0a0"; -} -.icon-printer-alt:before { - content: "\e0a1"; -} -.icon-planet:before { - content: "\e0a2"; -} -.icon-paste-in:before { - content: "\e0a3"; -} -.icon-os-x:before { - content: "\e0a4"; -} -.icon-navigation-left:before { - content: "\e0a5"; -} -.icon-message:before { - content: "\e0a6"; -} -.icon-lock:before { - content: "\e0a7"; -} -.icon-layers-alt:before { - content: "\e0a8"; -} -.icon-record:before { - content: "\e0a9"; -} -.icon-print:before { - content: "\e0aa"; -} -.icon-plane:before { - content: "\e0ab"; -} -.icon-partly-cloudy:before { - content: "\e0ac"; -} -.icon-ordered-list:before { - content: "\e0ad"; -} -.icon-navigation-last:before { - content: "\e0ae"; -} -.icon-message-unopened:before { - content: "\e0af"; -} -.icon-location-nearby:before { - content: "\e0b0"; -} -.icon-laptop:before { - content: "\e0b1"; -} -.icon-reception:before { - content: "\e0b2"; -} -.icon-price-yen:before { - content: "\e0b3"; -} -.icon-piracy:before { - content: "\e0b4"; -} -.icon-parental-control:before { - content: "\e0b5"; -} -.icon-operator:before { - content: "\e0b6"; -} -.icon-navigation-horizontal:before { - content: "\e0b7"; -} -.icon-message-open:before { - content: "\e0b8"; -} -.icon-lab:before { - content: "\e0b9"; -} -.icon-location-near-me:before { - content: "\e0ba"; -} -.icon-receipt-yen:before { - content: "\e0bb"; -} -.icon-price-pound:before { - content: "\e0bc"; -} -.icon-pin-location:before { - content: "\e0bd"; -} -.icon-parachute-drop:before { - content: "\e0be"; -} -.icon-old-phone:before { - content: "\e0bf"; -} -.icon-merge:before { - content: "\e0c0"; -} -.icon-navigation-first:before { - content: "\e0c1"; -} -.icon-locate:before { - content: "\e0c2"; -} -.icon-keyhole:before { - content: "\e0c3"; -} -.icon-receipt-pound:before { - content: "\e0c4"; -} -.icon-price-euro:before { - content: "\e0c5"; -} -.icon-piggy-bank:before { - content: "\e0c6"; -} -.icon-paper-plane:before { - content: "\e0c7"; -} -.icon-old-key:before { - content: "\e0c8"; -} -.icon-navigation-down:before { - content: "\e0c9"; -} -.icon-megaphone:before { - content: "\e0ca"; -} -.icon-loading:before { - content: "\e0cb"; -} -.icon-keychain:before { - content: "\e0cc"; -} -.icon-receipt-euro:before { - content: "\e0cd"; -} -.icon-price-dollar:before { - content: "\e0ce"; -} -.icon-pie-chart:before { - content: "\e0cf"; -} -.icon-paper-plane-alt:before { - content: "\e0d0"; -} -.icon-notepad:before { - content: "\e0d1"; -} -.icon-navigation-bottom:before { - content: "\e0d2"; -} -.icon-meeting:before { - content: "\e0d3"; -} -.icon-keyboard:before { - content: "\e0d4"; -} -.icon-load:before { - content: "\e0d5"; -} -.icon-receipt-dollar:before { - content: "\e0d6"; -} -.icon-previous:before { - content: "\e0d7"; -} -.icon-pictures:before { - content: "\e0d8"; -} -.icon-notepad-alt:before { - content: "\e0d9"; -} -.icon-paper-bag:before { - content: "\e0da"; -} -.icon-name-badge:before { - content: "\e0db"; -} -.icon-medicine:before { - content: "\e0dc"; -} -.icon-list:before { - content: "\e0dd"; -} -.icon-key:before { - content: "\e0de"; -} -.icon-receipt-alt:before { - content: "\e0df"; -} -.icon-previous-media:before { - content: "\e0e0"; -} -.icon-pictures-alt:before { - content: "\e0e1"; -} -.icon-pants:before { - content: "\e0e2"; -} -.icon-nodes:before { - content: "\e0e3"; -} -.icon-music:before { - content: "\e0e4"; -} -.icon-readonly:before { - content: "\e0e5"; -} -.icon-presentation:before { - content: "\e0e6"; -} -.icon-pictures-alt-2:before { - content: "\e0e7"; -} -.icon-pannel-close:before { - content: "\e0e8"; -} -.icon-next:before { - content: "\e0e9"; -} -.icon-multiple-windows:before { - content: "\e0ea"; -} -.icon-medical-emergency:before { - content: "\e0eb"; -} -.icon-medal:before { - content: "\e0ec"; -} -.icon-link:before { - content: "\e0ed"; -} -.icon-linux-tux:before { - content: "\e0ee"; -} -.icon-junk:before { - content: "\e0ef"; -} -.icon-item-arrangement:before { - content: "\e0f0"; -} -.icon-iphone:before { - content: "\e0f1"; -} -.icon-lightning:before { - content: "\e0f2"; -} -.icon-map:before { - content: "\e0f3"; -} -.icon-multiple-credit-cards:before { - content: "\e0f4"; -} -.icon-next-media:before { - content: "\e0f5"; -} -.icon-panel-show:before { - content: "\e0f6"; -} -.icon-picture:before { - content: "\e0f7"; -} -.icon-power:before { - content: "\e0f8"; -} -.icon-re-post:before { - content: "\e0f9"; -} -.icon-rate:before { - content: "\e0fa"; -} -.icon-rain:before { - content: "\e0fb"; -} -.icon-radio:before { - content: "\e0fc"; -} -.icon-radio-receiver:before { - content: "\e0fd"; -} -.icon-radio-alt:before { - content: "\e0fe"; -} -.icon-quote:before { - content: "\e0ff"; -} -.icon-qr-code:before { - content: "\e100"; -} -.icon-pushpin:before { - content: "\e101"; -} -.icon-pulse:before { - content: "\e102"; -} -.icon-projector:before { - content: "\e103"; -} -.icon-play:before { - content: "\e104"; -} -.icon-playing-cards:before { - content: "\e105"; -} -.icon-playlist:before { - content: "\e106"; -} -.icon-plugin:before { - content: "\e107"; -} -.icon-podcast:before { - content: "\e108"; -} -.icon-poker-chip:before { - content: "\e109"; -} -.icon-poll:before { - content: "\e10a"; -} -.icon-post-it:before { - content: "\e10b"; -} -.icon-pound-bag:before { - content: "\e10c"; -} -.icon-power-outlet:before { - content: "\e10d"; -} -.icon-photo-album:before { - content: "\e10e"; -} -.icon-phone:before { - content: "\e10f"; -} -.icon-phone-ring:before { - content: "\e110"; -} -.icon-people:before { - content: "\e111"; -} -.icon-people-female:before { - content: "\e112"; -} -.icon-people-alt:before { - content: "\e113"; -} -.icon-people-alt-2:before { - content: "\e114"; -} -.icon-pc:before { - content: "\e115"; -} -.icon-pause:before { - content: "\e116"; -} -.icon-path:before { - content: "\e117"; -} -.icon-out:before { - content: "\e118"; -} -.icon-outbox:before { - content: "\e119"; -} -.icon-outdent:before { - content: "\e11a"; -} -.icon-page-add:before { - content: "\e11b"; -} -.icon-page-down:before { - content: "\e11c"; -} -.icon-page-remove:before { - content: "\e11d"; -} -.icon-page-restricted:before { - content: "\e11e"; -} -.icon-page-up:before { - content: "\e11f"; -} -.icon-paint-roller:before { - content: "\e120"; -} -.icon-palette:before { - content: "\e121"; -} -.icon-newspaper:before { - content: "\e122"; -} -.icon-newspaper-alt:before { - content: "\e123"; -} -.icon-network-alt:before { - content: "\e124"; -} -.icon-navigational-arrow:before { - content: "\e125"; -} -.icon-navigation:before { - content: "\e126"; -} -.icon-navigation-vertical:before { - content: "\e127"; -} -.icon-navigation-up:before { - content: "\e128"; -} -.icon-navigation-top:before { - content: "\e129"; -} -.icon-navigation-road:before { - content: "\e12a"; -} -.icon-navigation-right:before { - content: "\e12b"; -} -.icon-microscope:before { - content: "\e12c"; -} -.icon-mindmap:before { - content: "\e12d"; -} -.icon-molecular-network:before { - content: "\e12e"; -} -.icon-molecular:before { - content: "\e12f"; -} -.icon-mountain:before { - content: "\e130"; -} -.icon-mouse-cursor:before { - content: "\e131"; -} -.icon-mouse:before { - content: "\e132"; -} -.icon-movie-alt:before { - content: "\e133"; -} -.icon-map-marker:before { - content: "\e134"; -} -.icon-movie:before { - content: "\e135"; -} -.icon-map-location:before { - content: "\e136"; -} -.icon-map-alt:before { - content: "\e137"; -} -.icon-male-symbol:before { - content: "\e138"; -} -.icon-male-and-female:before { - content: "\e139"; -} -.icon-mailbox:before { - content: "\e13a"; -} -.icon-magnet:before { - content: "\e13b"; -} -.icon-loupe:before { - content: "\e13c"; -} -.icon-mobile:before { - content: "\e13d"; -} -.icon-logout:before { - content: "\e13e"; -} -.icon-log-out:before { - content: "\e13f"; -} -.icon-layers:before { - content: "\e140"; -} -.icon-left-double-arrow:before { - content: "\e141"; -} -.icon-layout:before { - content: "\e142"; -} -.icon-legal:before { - content: "\e143"; -} -.icon-lense:before { - content: "\e144"; -} -.icon-library:before { - content: "\e145"; -} -.icon-light-down:before { - content: "\e146"; -} -.icon-light-up:before { - content: "\e147"; -} -.icon-lightbulb-active:before { - content: "\e148"; -} -.icon-lightbulb:before { - content: "\e149"; -} -.icon-ipad:before { - content: "\e14a"; -} -.icon-invoice:before { - content: "\e14b"; -} -.icon-info:before { - content: "\e14c"; -} -.icon-infinity:before { - content: "\e14d"; -} -.icon-indent:before { - content: "\e14e"; -} -.icon-inbox:before { - content: "\e14f"; -} -.icon-inbox-full:before { - content: "\e150"; -} -.icon-inactive-line:before { - content: "\e151"; -} -.icon-imac:before { - content: "\e152"; -} -.icon-hourglass:before { - content: "\e153"; -} -.icon-home:before { - content: "\e154"; -} -.icon-grid:before { - content: "\e155"; -} -.icon-food:before { - content: "\e156"; -} -.icon-favorite:before { - content: "\e157"; -} -.icon-door-open-alt:before { - content: "\e158"; -} -.icon-diagnostics:before { - content: "\e159"; -} -.icon-contrast:before { - content: "\e15a"; -} -.icon-coins-dollar-alt:before { - content: "\e15b"; -} -.icon-circle-dotted-active:before { - content: "\e15c"; -} -.icon-cinema:before { - content: "\e15d"; -} -.icon-chip:before { - content: "\e15e"; -} -.icon-chip-alt:before { - content: "\e15f"; -} -.icon-chess:before { - content: "\e160"; -} -.icon-checkbox:before { - content: "\e161"; -} -.icon-checkbox-empty:before { - content: "\e162"; -} -.icon-checkbox-dotted:before { - content: "\e163"; -} -.icon-checkbox-dotted-active:before { - content: "\e164"; -} -.icon-check:before { - content: "\e165"; -} -.icon-chat:before { - content: "\e166"; -} -.icon-chat-active:before { - content: "\e167"; -} -.icon-chart:before { - content: "\e168"; -} -.icon-chart-curve:before { - content: "\e169"; -} -.icon-certificate:before { - content: "\e16a"; -} -.icon-categories:before { - content: "\e16b"; -} -.icon-cash-register:before { - content: "\e16c"; -} -.icon-car:before { - content: "\e16d"; -} -.icon-caps-lock:before { - content: "\e16e"; -} -.icon-candy:before { - content: "\e16f"; -} -.icon-circle-dotted:before { - content: "\e170"; -} -.icon-circuits:before { - content: "\e171"; -} -.icon-circus:before { - content: "\e172"; -} -.icon-client:before { - content: "\e173"; -} -.icon-clothes-hanger:before { - content: "\e174"; -} -.icon-cloud-drive:before { - content: "\e175"; -} -.icon-cloud-upload:before { - content: "\e176"; -} -.icon-cloud:before { - content: "\e177"; -} -.icon-cloudy:before { - content: "\e178"; -} -.icon-clubs:before { - content: "\e179"; -} -.icon-cocktail:before { - content: "\e17a"; -} -.icon-code:before { - content: "\e17b"; -} -.icon-coffee:before { - content: "\e17c"; -} -.icon-coin-dollar:before { - content: "\e17d"; -} -.icon-coin-pound:before { - content: "\e17e"; -} -.icon-coin-yen:before { - content: "\e17f"; -} -.icon-coin:before { - content: "\e180"; -} -.icon-coins-alt:before { - content: "\e181"; -} -.icon-console:before { - content: "\e182"; -} -.icon-connection:before { - content: "\e183"; -} -.icon-compress:before { - content: "\e184"; -} -.icon-company:before { - content: "\e185"; -} -.icon-command:before { - content: "\e186"; -} -.icon-coin-euro:before { - content: "\e187"; -} -.icon-combination-lock:before { - content: "\e188"; -} -.icon-combination-lock-open:before { - content: "\e189"; -} -.icon-comb:before { - content: "\e18a"; -} -.icon-columns:before { - content: "\e18b"; -} -.icon-colorpicker:before { - content: "\e18c"; -} -.icon-color-bucket:before { - content: "\e18d"; -} -.icon-coins:before { - content: "\e18e"; -} -.icon-coins-yen:before { - content: "\e18f"; -} -.icon-coins-yen-alt:before { - content: "\e190"; -} -.icon-coins-pound:before { - content: "\e191"; -} -.icon-coins-pound-alt:before { - content: "\e192"; -} -.icon-coins-euro:before { - content: "\e193"; -} -.icon-coins-euro-alt:before { - content: "\e194"; -} -.icon-coins-dollar:before { - content: "\e195"; -} -.icon-conversation-alt:before { - content: "\e196"; -} -.icon-conversation:before { - content: "\e197"; -} -.icon-coverflow:before { - content: "\e198"; -} -.icon-credit-card-alt:before { - content: "\e199"; -} -.icon-credit-card:before { - content: "\e19a"; -} -.icon-crop:before { - content: "\e19b"; -} -.icon-crosshair:before { - content: "\e19c"; -} -.icon-crown-alt:before { - content: "\e19d"; -} -.icon-crown:before { - content: "\e19e"; -} -.icon-cupcake:before { - content: "\e19f"; -} -.icon-curve:before { - content: "\e1a0"; -} -.icon-cut:before { - content: "\e1a1"; -} -.icon-dashboard:before { - content: "\e1a2"; -} -.icon-defrag:before { - content: "\e1a3"; -} -.icon-delete:before { - content: "\e1a4"; -} -.icon-delete-key:before { - content: "\e1a5"; -} -.icon-departure:before { - content: "\e1a6"; -} -.icon-desk:before { - content: "\e1a7"; -} -.icon-desktop:before { - content: "\e1a8"; -} -.icon-donate:before { - content: "\e1a9"; -} -.icon-dollar-bag:before { - content: "\e1aa"; -} -.icon-documents:before { - content: "\e1ab"; -} -.icon-document:before { - content: "\e1ac"; -} -.icon-document-dashed-line:before { - content: "\e1ad"; -} -.icon-dock-connector:before { - content: "\e1ae"; -} -.icon-dna:before { - content: "\e1af"; -} -.icon-display:before { - content: "\e1b0"; -} -.icon-disk-image:before { - content: "\e1b1"; -} -.icon-disc:before { - content: "\e1b2"; -} -.icon-directions:before { - content: "\e1b3"; -} -.icon-directions-alt:before { - content: "\e1b4"; -} -.icon-diploma:before { - content: "\e1b5"; -} -.icon-diploma-alt:before { - content: "\e1b6"; -} -.icon-dice:before { - content: "\e1b7"; -} -.icon-diamonds:before { - content: "\e1b8"; -} -.icon-diamond:before { - content: "\e1b9"; -} -.icon-diagonal-arrow:before { - content: "\e1ba"; -} -.icon-diagonal-arrow-alt:before { - content: "\e1bb"; -} -.icon-door-open:before { - content: "\e1bc"; -} -.icon-download-alt:before { - content: "\e1bd"; -} -.icon-download:before { - content: "\e1be"; -} -.icon-drop:before { - content: "\e1bf"; -} -.icon-eco:before { - content: "\e1c0"; -} -.icon-economy:before { - content: "\e1c1"; -} -.icon-edit:before { - content: "\e1c2"; -} -.icon-eject:before { - content: "\e1c3"; -} -.icon-employee:before { - content: "\e1c4"; -} -.icon-energy-saving-bulb:before { - content: "\e1c5"; -} -.icon-enter:before { - content: "\e1c6"; -} -.icon-equalizer:before { - content: "\e1c7"; -} -.icon-escape:before { - content: "\e1c8"; -} -.icon-ethernet:before { - content: "\e1c9"; -} -.icon-euro-bag:before { - content: "\e1ca"; -} -.icon-exit-fullscreen:before { - content: "\e1cb"; -} -.icon-eye:before { - content: "\e1cc"; -} -.icon-facebook-like:before { - content: "\e1cd"; -} -.icon-factory:before { - content: "\e1ce"; -} -.icon-font:before { - content: "\e1cf"; -} -.icon-folders:before { - content: "\e1d0"; -} -.icon-folder:before, .icon-folder-close:before { - content: "\e1d1"; -} -.icon-folder-outline:before { - content: "\e1d2"; -} -.icon-folder-open:before { - content: "\e1d3"; -} -.icon-flowerpot:before { - content: "\e1d4"; -} -.icon-flashlight:before { - content: "\e1d5"; -} -.icon-flash:before { - content: "\e1d6"; -} -.icon-flag:before { - content: "\e1d7"; -} -.icon-flag-alt:before { - content: "\e1d8"; -} -.icon-firewire:before { - content: "\e1d9"; -} -.icon-firewall:before { - content: "\e1da"; -} -.icon-fire:before { - content: "\e1db"; -} -.icon-fingerprint:before { - content: "\e1dc"; -} -.icon-filter:before { - content: "\e1dd"; -} -.icon-filter-arrows:before { - content: "\e1de"; -} -.icon-files:before { - content: "\e1df"; -} -.icon-file-cabinet:before { - content: "\e1e0"; -} -.icon-female-symbol:before { - content: "\e1e1"; -} -.icon-footprints:before { - content: "\e1e2"; -} -.icon-hammer:before { - content: "\e1e3"; -} -.icon-hand-active-alt:before { - content: "\e1e4"; -} -.icon-forking:before { - content: "\e1e5"; -} -.icon-hand-active:before { - content: "\e1e6"; -} -.icon-hand-pointer-alt:before { - content: "\e1e7"; -} -.icon-hand-pointer:before { - content: "\e1e8"; -} -.icon-handprint:before { - content: "\e1e9"; -} -.icon-handshake:before { - content: "\e1ea"; -} -.icon-handtool:before { - content: "\e1eb"; -} -.icon-hard-drive:before { - content: "\e1ec"; -} -.icon-help:before { - content: "\e1ed"; -} -.icon-graduate:before { - content: "\e1ee"; -} -.icon-gps:before { - content: "\e1ef"; -} -.icon-help-alt:before { - content: "\e1f0"; -} -.icon-height:before { - content: "\e1f1"; -} -.icon-globe:before { - content: "\e1f2"; -} -.icon-hearts:before { - content: "\e1f3"; -} -.icon-globe-inverted-europe-africa:before { - content: "\e1f4"; -} -.icon-headset:before { - content: "\e1f5"; -} -.icon-globe-inverted-asia:before { - content: "\e1f6"; -} -.icon-headphones:before { - content: "\e1f7"; -} -.icon-globe-inverted-america:before { - content: "\e1f8"; -} -.icon-hd:before { - content: "\e1f9"; -} -.icon-globe-europe-africa:before, -.icon-globe-europe---africa:before { - content: "\e1fa"; -} -.icon-hat:before { - content: "\e1fb"; -} -.icon-globe-asia:before { - content: "\e1fc"; -} -.icon-globe-alt:before { - content: "\e1fd"; -} -.icon-hard-drive-alt:before { - content: "\e1fe"; -} -.icon-glasses:before { - content: "\e1ff"; -} -.icon-gift:before { - content: "\e200"; -} -.icon-handtool-alt:before { - content: "\e201"; -} -.icon-geometry:before { - content: "\e202"; -} -.icon-game:before { - content: "\e203"; -} -.icon-fullscreen:before { - content: "\e204"; -} -.icon-fullscreen-alt:before { - content: "\e205"; -} -.icon-frame:before { - content: "\e206"; -} -.icon-frame-alt:before { - content: "\e207"; -} -.icon-camera-roll:before { - content: "\e208"; -} -.icon-bookmark:before { - content: "\e209"; -} -.icon-bill:before { - content: "\e20a"; -} -.icon-baby-stroller:before { - content: "\e20b"; -} -.icon-alarm-clock:before { - content: "\e20c"; -} -.icon-adressbook:before { - content: "\e20d"; -} -.icon-add:before { - content: "\e20e"; -} -.icon-activity:before { - content: "\e20f"; -} -.icon-untitled:before { - content: "\e210"; -} -.icon-glasses:before { - content: "\e211"; -} -.icon-camcorder:before { - content: "\e212"; -} -.icon-calendar:before { - content: "\e213"; -} -.icon-calendar-alt:before { - content: "\e214"; -} -.icon-calculator:before { - content: "\e215"; -} -.icon-bus:before { - content: "\e216"; -} -.icon-burn:before { - content: "\e217"; -} -.icon-bulleted-list:before { - content: "\e218"; -} -.icon-bug:before { - content: "\e219"; -} -.icon-brush:before { - content: "\e21a"; -} -.icon-brush-alt:before { - content: "\e21b"; -} -.icon-brush-alt-2:before { - content: "\e21c"; -} -.icon-browser-window:before { - content: "\e21d"; -} -.icon-briefcase:before { - content: "\e21e"; -} -.icon-brick:before { - content: "\e21f"; -} -.icon-brackets:before { - content: "\e220"; -} -.icon-box:before { - content: "\e221"; -} -.icon-box-open:before { - content: "\e222"; -} -.icon-box-alt:before { - content: "\e223"; -} -.icon-books:before { - content: "\e224"; -} -.icon-billboard:before { - content: "\e225"; -} -.icon-bills-dollar:before { - content: "\e226"; -} -.icon-bills-euro:before { - content: "\e227"; -} -.icon-bills-pound:before { - content: "\e228"; -} -.icon-bills-yen:before { - content: "\e229"; -} -.icon-bills:before { - content: "\e22a"; -} -.icon-binarycode:before { - content: "\e22b"; -} -.icon-binoculars:before { - content: "\e22c"; -} -.icon-bird:before { - content: "\e22d"; -} -.icon-birthday-cake:before { - content: "\e22e"; -} -.icon-blueprint:before { - content: "\e22f"; -} -.icon-block:before { - content: "\e230"; -} -.icon-bluetooth:before { - content: "\e231"; -} -.icon-boat-shipping:before { - content: "\e232"; -} -.icon-bomb:before { - content: "\e233"; -} -.icon-book-alt-2:before { - content: "\e234"; -} -.icon-bones:before { - content: "\e235"; -} -.icon-book-alt:before { - content: "\e236"; -} -.icon-book:before { - content: "\e237"; -} -.icon-bill-yen:before { - content: "\e238"; -} -.icon-award:before { - content: "\e239"; -} -.icon-bill-pound:before { - content: "\e23a"; -} -.icon-autofill:before { - content: "\e23b"; -} -.icon-bill-euro:before { - content: "\e23c"; -} -.icon-auction-hammer:before { - content: "\e23d"; -} -.icon-bill-dollar:before { - content: "\e23e"; -} -.icon-attachment:before { - content: "\e23f"; -} -.icon-bell:before { - content: "\e240"; -} -.icon-article:before { - content: "\e241"; -} -.icon-bell-off:before { - content: "\e242"; -} -.icon-art-easel:before { - content: "\e243"; -} -.icon-beer-glass:before { - content: "\e244"; -} -.icon-arrow-up:before { - content: "\e245"; -} -.icon-battery-low:before { - content: "\e246"; -} -.icon-arrow-right:before { - content: "\e247"; -} -.icon-battery-full:before { - content: "\e248"; -} -.icon-arrow-left:before { - content: "\e249"; -} -.icon-bars:before { - content: "\e24a"; -} -.icon-arrow-down:before { - content: "\e24b"; -} -.icon-barcode:before { - content: "\e24c"; -} -.icon-arrivals:before { - content: "\e24d"; -} -.icon-bar-chart:before { - content: "\e24e"; -} -.icon-application-window:before { - content: "\e24f"; -} -.icon-band-aid:before { - content: "\e250"; -} -.icon-application-window-alt:before { - content: "\e251"; -} -.icon-ball:before { - content: "\e252"; -} -.icon-application-error:before { - content: "\e253"; -} -.icon-badge-restricted:before { - content: "\e254"; -} -.icon-app:before { - content: "\e255"; -} -.icon-badge-remove:before { - content: "\e256"; -} -.icon-anchor:before { - content: "\e257"; -} -.icon-badge-count:before { - content: "\e258"; -} -.icon-alt:before { - content: "\e259"; -} -.icon-badge-add:before { - content: "\e25a"; -} -.icon-alert:before { - content: "\e25b"; -} -.icon-backspace:before { - content: "\e25c"; -} -.icon-alert-alt:before { - content: "\e25d"; -} +@font-face { + font-family: 'icomoon'; + src:url('../fonts/helveticons/helveticons.eot'); + src:url('../fonts/helveticons/helveticons.eot?#iefix') format('embedded-opentype'), + url('../fonts/helveticons/helveticons.ttf') format('truetype'), + url('../fonts/helveticons/helveticons.svg#icomoon') format('svg'); + font-weight: normal; + font-style: normal; +} + +[class^="icon-"], +[class*=" icon-"] { + font-family: icomoon; + font-weight: normal; + font-style: normal; + text-decoration: inherit; + -webkit-font-smoothing: antialiased; + *margin-right: .3em; +} +[class^="icon-"]:before, +[class*=" icon-"]:before { + text-decoration: inherit; + display: inline-block; + speak: none; +} +/* +[class^="icon-"]:before, [class*=" icon-"]:before { + font-family: 'icomoon'; + speak: none; + font-weight: normal; + font-style: normal; + display: inline-block; + text-decoration: inherit; + font-size: 14px; + -webkit-font-smoothing: antialiased; +}*/ + +i.large{ + font-size: 32px; +} + +i.medium{ + font-size: 24px; +} +i.small{ + font-size: 14px; +} + +.icon-zoom-out:before { + content: "\e000"; +} +.icon-truck:before { + content: "\e001"; +} +.icon-zoom-in:before { + content: "\e002"; +} +.icon-zip:before { + content: "\e003"; +} +.icon-axis-rotation:before { + content: "\e004"; +} +.icon-yen-bag:before { + content: "\e005"; +} +.icon-axis-rotation-2:before { + content: "\e006"; +} +.icon-axis-rotation-3:before { + content: "\e007"; +} +.icon-wrench:before { + content: "\e008"; +} +.icon-wine-glass:before { + content: "\e009"; +} +.icon-wrong:before { + content: "\e00a"; +} +.icon-windows:before { + content: "\e00b"; +} +.icon-window-sizes:before { + content: "\e00c"; +} +.icon-window-popin:before { + content: "\e00d"; +} +.icon-wifi:before { + content: "\e00e"; +} +.icon-width:before { + content: "\e00f"; +} +.icon-weight:before { + content: "\e010"; +} +.icon-war:before { + content: "\e011"; +} +.icon-wand:before { + content: "\e012"; +} +.icon-wallet:before { + content: "\e013"; +} +.icon-wall-plug:before { + content: "\e014"; +} +.icon-voice:before { + content: "\e016"; +} +.icon-video:before { + content: "\e017"; +} +.icon-vcard:before { + content: "\e018"; +} +.icon-utilities:before { + content: "\e019"; +} +.icon-users:before { + content: "\e01a"; +} +.icon-users-alt:before { + content: "\e01b"; +} +.icon-user:before { + content: "\e01c"; +} +.icon-user-glasses:before { + content: "\e01d"; +} +.icon-user-females:before { + content: "\e01e"; +} +.icon-user-females-alt:before { + content: "\e01f"; +} +.icon-user-female:before { + content: "\e020"; +} +.icon-usb:before { + content: "\e021"; +} +.icon-usb-connector:before { + content: "\e022"; +} +.icon-unlocked:before { + content: "\e023"; +} +.icon-universal:before { + content: "\e024"; +} +.icon-undo:before { + content: "\e025"; +} +.icon-umbrella:before { + content: "\e026"; +} +.icon-umb-deploy:before { + content: "\e027"; +} +.icon-umb-contour:before, .traycontour:before, { + content: "\e028"; +} +.icon-umb-settings:before, .traysettings:before, { + content: "\e029"; +} +.icon-umb-users:before, .trayuser:before, .trayusers:before{ + content: "\e02a"; +} +.icon-umb-media:before, .traymedia:before, { + content: "\e02b"; +} +.icon-umb-content:before, .traycontent:before{ + content: "\e02c"; +} +.icon-umb-developer:before, .traydeveloper:before, { + content: "\e02d"; +} +.icon-umb-members:before, .traymember:before { + content: "\e015"; +} +.icon-umb-translation:before, .traytranslation:before { + content: "\e1fd"; +} + + +.icon-tv:before { + content: "\e02e"; +} +.icon-tv-old:before { + content: "\e02f"; +} +.icon-trophy:before { + content: "\e030"; +} +.icon-tree:before { + content: "\e031"; +} +.icon-trash:before { + content: "\e032"; +} +.icon-trash-alt:before { + content: "\e033"; +} +.icon-trash-alt-2:before { + content: "\e034"; +} +.icon-train:before { + content: "\e035"; +} +.icon-trafic:before { + content: "\e036"; +} +.icon-traffic-alt:before { + content: "\e037"; +} +.icon-top:before { + content: "\e038"; +} +.icon-tools:before { + content: "\e039"; +} +.icon-timer:before { + content: "\e03a"; +} +.icon-time:before { + content: "\e03b"; +} +.icon-t-shirt:before { + content: "\e03c"; +} +.icon-tab-key:before { + content: "\e03d"; +} +.icon-tab:before { + content: "\e03e"; +} +.icon-tactics:before { + content: "\e03f"; +} +.icon-tag:before { + content: "\e040"; +} +.icon-tags:before { + content: "\e041"; +} +.icon-takeaway-cup:before { + content: "\e042"; +} +.icon-target:before { + content: "\e043"; +} +.icon-temperatrure-alt:before { + content: "\e044"; +} +.icon-temperature:before { + content: "\e045"; +} +.icon-terminal:before { + content: "\e046"; +} +.icon-theater:before { + content: "\e047"; +} +.icon-theif:before { + content: "\e048"; +} +.icon-thought-bubble:before { + content: "\e049"; +} +.icon-thumb-down:before { + content: "\e04a"; +} +.icon-thumb-up:before { + content: "\e04b"; +} +.icon-thumbnail-list:before { + content: "\e04c"; +} +.icon-thumbnails-small:before { + content: "\e04d"; +} +.icon-thumbnails:before { + content: "\e04e"; +} +.icon-ticket:before { + content: "\e04f"; +} +.icon-sync:before { + content: "\e050"; +} +.icon-sweatshirt:before { + content: "\e051"; +} +.icon-sunny:before { + content: "\e052"; +} +.icon-stream:before { + content: "\e053"; +} +.icon-store:before { + content: "\e054"; +} +.icon-stop:before { + content: "\e055"; +} +.icon-stop-hand:before { + content: "\e056"; +} +.icon-stop-alt:before { + content: "\e057"; +} +.icon-stamp:before { + content: "\e058"; +} +.icon-stacked-disks:before { + content: "\e059"; +} +.icon-ssd:before { + content: "\e05a"; +} +.icon-squiggly-line:before { + content: "\e05b"; +} +.icon-sprout:before { + content: "\e05c"; +} +.icon-split:before { + content: "\e05d"; +} +.icon-split-alt:before { + content: "\e05e"; +} +.icon-speed-gauge:before { + content: "\e05f"; +} +.icon-speaker:before { + content: "\e060"; +} +.icon-sound:before { + content: "\e061"; +} +.icon-spades:before { + content: "\e062"; +} +.icon-sound-waves:before { + content: "\e063"; +} +.icon-shipping-box:before { + content: "\e064"; +} +.icon-shipping:before { + content: "\e065"; +} +.icon-shoe:before { + content: "\e066"; +} +.icon-shopping-basket-alt-2:before { + content: "\e067"; +} +.icon-shopping-basket:before { + content: "\e068"; +} +.icon-shopping-basket-alt:before { + content: "\e069"; +} +.icon-shorts:before { + content: "\e06a"; +} +.icon-shuffle:before { + content: "\e06b"; +} +.icon-sience:before { + content: "\e06c"; +} +.icon-simcard:before { + content: "\e06d"; +} +.icon-single-note:before { + content: "\e06e"; +} +.icon-sitemap:before { + content: "\e06f"; +} +.icon-sleep:before { + content: "\e070"; +} +.icon-slideshow:before { + content: "\e071"; +} +.icon-smiley-inverted:before { + content: "\e072"; +} +.icon-smiley:before { + content: "\e073"; +} +.icon-snow:before { + content: "\e074"; +} +.icon-sound-low:before { + content: "\e075"; +} +.icon-sound-medium:before { + content: "\e076"; +} +.icon-sound-off:before { + content: "\e077"; +} +.icon-shift:before { + content: "\e078"; +} +.icon-shield:before { + content: "\e079"; +} +.icon-sharing-iphone:before { + content: "\e07a"; +} +.icon-share:before { + content: "\e07b"; +} +.icon-share-alt:before { + content: "\e07c"; +} +.icon-share-alt-2:before { + content: "\e07d"; +} +.icon-settings:before { + content: "\e07e"; +} +.icon-settings-alt:before { + content: "\e07f"; +} +.icon-settings-alt-2:before { + content: "\e080"; +} +.icon-server:before { + content: "\e081"; +} +.icon-server-alt:before { + content: "\e082"; +} +.icon-sensor:before { + content: "\e083"; +} +.icon-security-camera:before { + content: "\e084"; +} +.icon-search:before { + content: "\e085"; +} +.icon-scull:before { + content: "\e086"; +} +.icon-script:before { + content: "\e087"; +} +.icon-script-alt:before { + content: "\e088"; +} +.icon-screensharing:before { + content: "\e089"; +} +.icon-school:before { + content: "\e08a"; +} +.icon-scan:before { + content: "\e08b"; +} +.icon-refresh:before { + content: "\e08c"; +} +.icon-remote:before { + content: "\e08d"; +} +.icon-remove:before { + content: "\e08e"; +} +.icon-repeat-one:before { + content: "\e08f"; +} +.icon-repeat:before { + content: "\e090"; +} +.icon-resize:before { + content: "\e091"; +} +.icon-reply-arrow:before { + content: "\e092"; +} +.icon-return-to-top:before { + content: "\e093"; +} +.icon-right-double-arrow:before { + content: "\e094"; +} +.icon-road:before { + content: "\e095"; +} +.icon-roadsign:before { + content: "\e096"; +} +.icon-rocket:before { + content: "\e097"; +} +.icon-rss:before { + content: "\e098"; +} +.icon-ruler-alt:before { + content: "\e099"; +} +.icon-ruler:before { + content: "\e09a"; +} +.icon-sandbox-toys:before { + content: "\e09b"; +} +.icon-satellite-dish:before { + content: "\e09c"; +} +.icon-save:before { + content: "\e09d"; +} +.icon-safedial:before { + content: "\e09e"; +} +.icon-safe:before { + content: "\e09f"; +} +.icon-redo:before { + content: "\e0a0"; +} +.icon-printer-alt:before { + content: "\e0a1"; +} +.icon-planet:before { + content: "\e0a2"; +} +.icon-paste-in:before { + content: "\e0a3"; +} +.icon-os-x:before { + content: "\e0a4"; +} +.icon-navigation-left:before { + content: "\e0a5"; +} +.icon-message:before { + content: "\e0a6"; +} +.icon-lock:before { + content: "\e0a7"; +} +.icon-layers-alt:before { + content: "\e0a8"; +} +.icon-record:before { + content: "\e0a9"; +} +.icon-print:before { + content: "\e0aa"; +} +.icon-plane:before { + content: "\e0ab"; +} +.icon-partly-cloudy:before { + content: "\e0ac"; +} +.icon-ordered-list:before { + content: "\e0ad"; +} +.icon-navigation-last:before { + content: "\e0ae"; +} +.icon-message-unopened:before { + content: "\e0af"; +} +.icon-location-nearby:before { + content: "\e0b0"; +} +.icon-laptop:before { + content: "\e0b1"; +} +.icon-reception:before { + content: "\e0b2"; +} +.icon-price-yen:before { + content: "\e0b3"; +} +.icon-piracy:before { + content: "\e0b4"; +} +.icon-parental-control:before { + content: "\e0b5"; +} +.icon-operator:before { + content: "\e0b6"; +} +.icon-navigation-horizontal:before { + content: "\e0b7"; +} +.icon-message-open:before { + content: "\e0b8"; +} +.icon-lab:before { + content: "\e0b9"; +} +.icon-location-near-me:before { + content: "\e0ba"; +} +.icon-receipt-yen:before { + content: "\e0bb"; +} +.icon-price-pound:before { + content: "\e0bc"; +} +.icon-pin-location:before { + content: "\e0bd"; +} +.icon-parachute-drop:before { + content: "\e0be"; +} +.icon-old-phone:before { + content: "\e0bf"; +} +.icon-merge:before { + content: "\e0c0"; +} +.icon-navigation-first:before { + content: "\e0c1"; +} +.icon-locate:before { + content: "\e0c2"; +} +.icon-keyhole:before { + content: "\e0c3"; +} +.icon-receipt-pound:before { + content: "\e0c4"; +} +.icon-price-euro:before { + content: "\e0c5"; +} +.icon-piggy-bank:before { + content: "\e0c6"; +} +.icon-paper-plane:before { + content: "\e0c7"; +} +.icon-old-key:before { + content: "\e0c8"; +} +.icon-navigation-down:before { + content: "\e0c9"; +} +.icon-megaphone:before { + content: "\e0ca"; +} +.icon-loading:before { + content: "\e0cb"; +} +.icon-keychain:before { + content: "\e0cc"; +} +.icon-receipt-euro:before { + content: "\e0cd"; +} +.icon-price-dollar:before { + content: "\e0ce"; +} +.icon-pie-chart:before { + content: "\e0cf"; +} +.icon-paper-plane-alt:before { + content: "\e0d0"; +} +.icon-notepad:before { + content: "\e0d1"; +} +.icon-navigation-bottom:before { + content: "\e0d2"; +} +.icon-meeting:before { + content: "\e0d3"; +} +.icon-keyboard:before { + content: "\e0d4"; +} +.icon-load:before { + content: "\e0d5"; +} +.icon-receipt-dollar:before { + content: "\e0d6"; +} +.icon-previous:before { + content: "\e0d7"; +} +.icon-pictures:before { + content: "\e0d8"; +} +.icon-notepad-alt:before { + content: "\e0d9"; +} +.icon-paper-bag:before { + content: "\e0da"; +} +.icon-name-badge:before { + content: "\e0db"; +} +.icon-medicine:before { + content: "\e0dc"; +} +.icon-list:before { + content: "\e0dd"; +} +.icon-key:before { + content: "\e0de"; +} +.icon-receipt-alt:before { + content: "\e0df"; +} +.icon-previous-media:before { + content: "\e0e0"; +} +.icon-pictures-alt:before { + content: "\e0e1"; +} +.icon-pants:before { + content: "\e0e2"; +} +.icon-nodes:before { + content: "\e0e3"; +} +.icon-music:before { + content: "\e0e4"; +} +.icon-readonly:before { + content: "\e0e5"; +} +.icon-presentation:before { + content: "\e0e6"; +} +.icon-pictures-alt-2:before { + content: "\e0e7"; +} +.icon-pannel-close:before { + content: "\e0e8"; +} +.icon-next:before { + content: "\e0e9"; +} +.icon-multiple-windows:before { + content: "\e0ea"; +} +.icon-medical-emergency:before { + content: "\e0eb"; +} +.icon-medal:before { + content: "\e0ec"; +} +.icon-link:before { + content: "\e0ed"; +} +.icon-linux-tux:before { + content: "\e0ee"; +} +.icon-junk:before { + content: "\e0ef"; +} +.icon-item-arrangement:before { + content: "\e0f0"; +} +.icon-iphone:before { + content: "\e0f1"; +} +.icon-lightning:before { + content: "\e0f2"; +} +.icon-map:before { + content: "\e0f3"; +} +.icon-multiple-credit-cards:before { + content: "\e0f4"; +} +.icon-next-media:before { + content: "\e0f5"; +} +.icon-panel-show:before { + content: "\e0f6"; +} +.icon-picture:before { + content: "\e0f7"; +} +.icon-power:before { + content: "\e0f8"; +} +.icon-re-post:before { + content: "\e0f9"; +} +.icon-rate:before { + content: "\e0fa"; +} +.icon-rain:before { + content: "\e0fb"; +} +.icon-radio:before { + content: "\e0fc"; +} +.icon-radio-receiver:before { + content: "\e0fd"; +} +.icon-radio-alt:before { + content: "\e0fe"; +} +.icon-quote:before { + content: "\e0ff"; +} +.icon-qr-code:before { + content: "\e100"; +} +.icon-pushpin:before { + content: "\e101"; +} +.icon-pulse:before { + content: "\e102"; +} +.icon-projector:before { + content: "\e103"; +} +.icon-play:before { + content: "\e104"; +} +.icon-playing-cards:before { + content: "\e105"; +} +.icon-playlist:before { + content: "\e106"; +} +.icon-plugin:before { + content: "\e107"; +} +.icon-podcast:before { + content: "\e108"; +} +.icon-poker-chip:before { + content: "\e109"; +} +.icon-poll:before { + content: "\e10a"; +} +.icon-post-it:before { + content: "\e10b"; +} +.icon-pound-bag:before { + content: "\e10c"; +} +.icon-power-outlet:before { + content: "\e10d"; +} +.icon-photo-album:before { + content: "\e10e"; +} +.icon-phone:before { + content: "\e10f"; +} +.icon-phone-ring:before { + content: "\e110"; +} +.icon-people:before { + content: "\e111"; +} +.icon-people-female:before { + content: "\e112"; +} +.icon-people-alt:before { + content: "\e113"; +} +.icon-people-alt-2:before { + content: "\e114"; +} +.icon-pc:before { + content: "\e115"; +} +.icon-pause:before { + content: "\e116"; +} +.icon-path:before { + content: "\e117"; +} +.icon-out:before { + content: "\e118"; +} +.icon-outbox:before { + content: "\e119"; +} +.icon-outdent:before { + content: "\e11a"; +} +.icon-page-add:before { + content: "\e11b"; +} +.icon-page-down:before { + content: "\e11c"; +} +.icon-page-remove:before { + content: "\e11d"; +} +.icon-page-restricted:before { + content: "\e11e"; +} +.icon-page-up:before { + content: "\e11f"; +} +.icon-paint-roller:before { + content: "\e120"; +} +.icon-palette:before { + content: "\e121"; +} +.icon-newspaper:before { + content: "\e122"; +} +.icon-newspaper-alt:before { + content: "\e123"; +} +.icon-network-alt:before { + content: "\e124"; +} +.icon-navigational-arrow:before { + content: "\e125"; +} +.icon-navigation:before { + content: "\e126"; +} +.icon-navigation-vertical:before { + content: "\e127"; +} +.icon-navigation-up:before { + content: "\e128"; +} +.icon-navigation-top:before { + content: "\e129"; +} +.icon-navigation-road:before { + content: "\e12a"; +} +.icon-navigation-right:before { + content: "\e12b"; +} +.icon-microscope:before { + content: "\e12c"; +} +.icon-mindmap:before { + content: "\e12d"; +} +.icon-molecular-network:before { + content: "\e12e"; +} +.icon-molecular:before { + content: "\e12f"; +} +.icon-mountain:before { + content: "\e130"; +} +.icon-mouse-cursor:before { + content: "\e131"; +} +.icon-mouse:before { + content: "\e132"; +} +.icon-movie-alt:before { + content: "\e133"; +} +.icon-map-marker:before { + content: "\e134"; +} +.icon-movie:before { + content: "\e135"; +} +.icon-map-location:before { + content: "\e136"; +} +.icon-map-alt:before { + content: "\e137"; +} +.icon-male-symbol:before { + content: "\e138"; +} +.icon-male-and-female:before { + content: "\e139"; +} +.icon-mailbox:before { + content: "\e13a"; +} +.icon-magnet:before { + content: "\e13b"; +} +.icon-loupe:before { + content: "\e13c"; +} +.icon-mobile:before { + content: "\e13d"; +} +.icon-logout:before { + content: "\e13e"; +} +.icon-log-out:before { + content: "\e13f"; +} +.icon-layers:before { + content: "\e140"; +} +.icon-left-double-arrow:before { + content: "\e141"; +} +.icon-layout:before { + content: "\e142"; +} +.icon-legal:before { + content: "\e143"; +} +.icon-lense:before { + content: "\e144"; +} +.icon-library:before { + content: "\e145"; +} +.icon-light-down:before { + content: "\e146"; +} +.icon-light-up:before { + content: "\e147"; +} +.icon-lightbulb-active:before { + content: "\e148"; +} +.icon-lightbulb:before { + content: "\e149"; +} +.icon-ipad:before { + content: "\e14a"; +} +.icon-invoice:before { + content: "\e14b"; +} +.icon-info:before { + content: "\e14c"; +} +.icon-infinity:before { + content: "\e14d"; +} +.icon-indent:before { + content: "\e14e"; +} +.icon-inbox:before { + content: "\e14f"; +} +.icon-inbox-full:before { + content: "\e150"; +} +.icon-inactive-line:before { + content: "\e151"; +} +.icon-imac:before { + content: "\e152"; +} +.icon-hourglass:before { + content: "\e153"; +} +.icon-home:before { + content: "\e154"; +} +.icon-grid:before { + content: "\e155"; +} +.icon-food:before { + content: "\e156"; +} +.icon-favorite:before { + content: "\e157"; +} +.icon-door-open-alt:before { + content: "\e158"; +} +.icon-diagnostics:before { + content: "\e159"; +} +.icon-contrast:before { + content: "\e15a"; +} +.icon-coins-dollar-alt:before { + content: "\e15b"; +} +.icon-circle-dotted-active:before { + content: "\e15c"; +} +.icon-cinema:before { + content: "\e15d"; +} +.icon-chip:before { + content: "\e15e"; +} +.icon-chip-alt:before { + content: "\e15f"; +} +.icon-chess:before { + content: "\e160"; +} +.icon-checkbox:before { + content: "\e161"; +} +.icon-checkbox-empty:before { + content: "\e162"; +} +.icon-checkbox-dotted:before { + content: "\e163"; +} +.icon-checkbox-dotted-active:before { + content: "\e164"; +} +.icon-check:before { + content: "\e165"; +} +.icon-chat:before { + content: "\e166"; +} +.icon-chat-active:before { + content: "\e167"; +} +.icon-chart:before { + content: "\e168"; +} +.icon-chart-curve:before { + content: "\e169"; +} +.icon-certificate:before { + content: "\e16a"; +} +.icon-categories:before { + content: "\e16b"; +} +.icon-cash-register:before { + content: "\e16c"; +} +.icon-car:before { + content: "\e16d"; +} +.icon-caps-lock:before { + content: "\e16e"; +} +.icon-candy:before { + content: "\e16f"; +} +.icon-circle-dotted:before { + content: "\e170"; +} +.icon-circuits:before { + content: "\e171"; +} +.icon-circus:before { + content: "\e172"; +} +.icon-client:before { + content: "\e173"; +} +.icon-clothes-hanger:before { + content: "\e174"; +} +.icon-cloud-drive:before { + content: "\e175"; +} +.icon-cloud-upload:before { + content: "\e176"; +} +.icon-cloud:before { + content: "\e177"; +} +.icon-cloudy:before { + content: "\e178"; +} +.icon-clubs:before { + content: "\e179"; +} +.icon-cocktail:before { + content: "\e17a"; +} +.icon-code:before { + content: "\e17b"; +} +.icon-coffee:before { + content: "\e17c"; +} +.icon-coin-dollar:before { + content: "\e17d"; +} +.icon-coin-pound:before { + content: "\e17e"; +} +.icon-coin-yen:before { + content: "\e17f"; +} +.icon-coin:before { + content: "\e180"; +} +.icon-coins-alt:before { + content: "\e181"; +} +.icon-console:before { + content: "\e182"; +} +.icon-connection:before { + content: "\e183"; +} +.icon-compress:before { + content: "\e184"; +} +.icon-company:before { + content: "\e185"; +} +.icon-command:before { + content: "\e186"; +} +.icon-coin-euro:before { + content: "\e187"; +} +.icon-combination-lock:before { + content: "\e188"; +} +.icon-combination-lock-open:before { + content: "\e189"; +} +.icon-comb:before { + content: "\e18a"; +} +.icon-columns:before { + content: "\e18b"; +} +.icon-colorpicker:before { + content: "\e18c"; +} +.icon-color-bucket:before { + content: "\e18d"; +} +.icon-coins:before { + content: "\e18e"; +} +.icon-coins-yen:before { + content: "\e18f"; +} +.icon-coins-yen-alt:before { + content: "\e190"; +} +.icon-coins-pound:before { + content: "\e191"; +} +.icon-coins-pound-alt:before { + content: "\e192"; +} +.icon-coins-euro:before { + content: "\e193"; +} +.icon-coins-euro-alt:before { + content: "\e194"; +} +.icon-coins-dollar:before { + content: "\e195"; +} +.icon-conversation-alt:before { + content: "\e196"; +} +.icon-conversation:before { + content: "\e197"; +} +.icon-coverflow:before { + content: "\e198"; +} +.icon-credit-card-alt:before { + content: "\e199"; +} +.icon-credit-card:before { + content: "\e19a"; +} +.icon-crop:before { + content: "\e19b"; +} +.icon-crosshair:before { + content: "\e19c"; +} +.icon-crown-alt:before { + content: "\e19d"; +} +.icon-crown:before { + content: "\e19e"; +} +.icon-cupcake:before { + content: "\e19f"; +} +.icon-curve:before { + content: "\e1a0"; +} +.icon-cut:before { + content: "\e1a1"; +} +.icon-dashboard:before { + content: "\e1a2"; +} +.icon-defrag:before { + content: "\e1a3"; +} +.icon-delete:before { + content: "\e1a4"; +} +.icon-delete-key:before { + content: "\e1a5"; +} +.icon-departure:before { + content: "\e1a6"; +} +.icon-desk:before { + content: "\e1a7"; +} +.icon-desktop:before { + content: "\e1a8"; +} +.icon-donate:before { + content: "\e1a9"; +} +.icon-dollar-bag:before { + content: "\e1aa"; +} +.icon-documents:before { + content: "\e1ab"; +} +.icon-document:before { + content: "\e1ac"; +} +.icon-document-dashed-line:before { + content: "\e1ad"; +} +.icon-dock-connector:before { + content: "\e1ae"; +} +.icon-dna:before { + content: "\e1af"; +} +.icon-display:before { + content: "\e1b0"; +} +.icon-disk-image:before { + content: "\e1b1"; +} +.icon-disc:before { + content: "\e1b2"; +} +.icon-directions:before { + content: "\e1b3"; +} +.icon-directions-alt:before { + content: "\e1b4"; +} +.icon-diploma:before { + content: "\e1b5"; +} +.icon-diploma-alt:before { + content: "\e1b6"; +} +.icon-dice:before { + content: "\e1b7"; +} +.icon-diamonds:before { + content: "\e1b8"; +} +.icon-diamond:before { + content: "\e1b9"; +} +.icon-diagonal-arrow:before { + content: "\e1ba"; +} +.icon-diagonal-arrow-alt:before { + content: "\e1bb"; +} +.icon-door-open:before { + content: "\e1bc"; +} +.icon-download-alt:before { + content: "\e1bd"; +} +.icon-download:before { + content: "\e1be"; +} +.icon-drop:before { + content: "\e1bf"; +} +.icon-eco:before { + content: "\e1c0"; +} +.icon-economy:before { + content: "\e1c1"; +} +.icon-edit:before { + content: "\e1c2"; +} +.icon-eject:before { + content: "\e1c3"; +} +.icon-employee:before { + content: "\e1c4"; +} +.icon-energy-saving-bulb:before { + content: "\e1c5"; +} +.icon-enter:before { + content: "\e1c6"; +} +.icon-equalizer:before { + content: "\e1c7"; +} +.icon-escape:before { + content: "\e1c8"; +} +.icon-ethernet:before { + content: "\e1c9"; +} +.icon-euro-bag:before { + content: "\e1ca"; +} +.icon-exit-fullscreen:before { + content: "\e1cb"; +} +.icon-eye:before { + content: "\e1cc"; +} +.icon-facebook-like:before { + content: "\e1cd"; +} +.icon-factory:before { + content: "\e1ce"; +} +.icon-font:before { + content: "\e1cf"; +} +.icon-folders:before { + content: "\e1d0"; +} +.icon-folder:before, .icon-folder-close:before { + content: "\e1d1"; +} +.icon-folder-outline:before { + content: "\e1d2"; +} +.icon-folder-open:before { + content: "\e1d3"; +} +.icon-flowerpot:before { + content: "\e1d4"; +} +.icon-flashlight:before { + content: "\e1d5"; +} +.icon-flash:before { + content: "\e1d6"; +} +.icon-flag:before { + content: "\e1d7"; +} +.icon-flag-alt:before { + content: "\e1d8"; +} +.icon-firewire:before { + content: "\e1d9"; +} +.icon-firewall:before { + content: "\e1da"; +} +.icon-fire:before { + content: "\e1db"; +} +.icon-fingerprint:before { + content: "\e1dc"; +} +.icon-filter:before { + content: "\e1dd"; +} +.icon-filter-arrows:before { + content: "\e1de"; +} +.icon-files:before { + content: "\e1df"; +} +.icon-file-cabinet:before { + content: "\e1e0"; +} +.icon-female-symbol:before { + content: "\e1e1"; +} +.icon-footprints:before { + content: "\e1e2"; +} +.icon-hammer:before { + content: "\e1e3"; +} +.icon-hand-active-alt:before { + content: "\e1e4"; +} +.icon-forking:before { + content: "\e1e5"; +} +.icon-hand-active:before { + content: "\e1e6"; +} +.icon-hand-pointer-alt:before { + content: "\e1e7"; +} +.icon-hand-pointer:before { + content: "\e1e8"; +} +.icon-handprint:before { + content: "\e1e9"; +} +.icon-handshake:before { + content: "\e1ea"; +} +.icon-handtool:before { + content: "\e1eb"; +} +.icon-hard-drive:before { + content: "\e1ec"; +} +.icon-help:before { + content: "\e1ed"; +} +.icon-graduate:before { + content: "\e1ee"; +} +.icon-gps:before { + content: "\e1ef"; +} +.icon-help-alt:before { + content: "\e1f0"; +} +.icon-height:before { + content: "\e1f1"; +} +.icon-globe:before { + content: "\e1f2"; +} +.icon-hearts:before { + content: "\e1f3"; +} +.icon-globe-inverted-europe-africa:before { + content: "\e1f4"; +} +.icon-headset:before { + content: "\e1f5"; +} +.icon-globe-inverted-asia:before { + content: "\e1f6"; +} +.icon-headphones:before { + content: "\e1f7"; +} +.icon-globe-inverted-america:before { + content: "\e1f8"; +} +.icon-hd:before { + content: "\e1f9"; +} +.icon-globe-europe-africa:before, +.icon-globe-europe---africa:before { + content: "\e1fa"; +} +.icon-hat:before { + content: "\e1fb"; +} +.icon-globe-asia:before { + content: "\e1fc"; +} +.icon-globe-alt:before { + content: "\e1fd"; +} +.icon-hard-drive-alt:before { + content: "\e1fe"; +} +.icon-glasses:before { + content: "\e1ff"; +} +.icon-gift:before { + content: "\e200"; +} +.icon-handtool-alt:before { + content: "\e201"; +} +.icon-geometry:before { + content: "\e202"; +} +.icon-game:before { + content: "\e203"; +} +.icon-fullscreen:before { + content: "\e204"; +} +.icon-fullscreen-alt:before { + content: "\e205"; +} +.icon-frame:before { + content: "\e206"; +} +.icon-frame-alt:before { + content: "\e207"; +} +.icon-camera-roll:before { + content: "\e208"; +} +.icon-bookmark:before { + content: "\e209"; +} +.icon-bill:before { + content: "\e20a"; +} +.icon-baby-stroller:before { + content: "\e20b"; +} +.icon-alarm-clock:before { + content: "\e20c"; +} +.icon-adressbook:before { + content: "\e20d"; +} +.icon-add:before { + content: "\e20e"; +} +.icon-activity:before { + content: "\e20f"; +} +.icon-untitled:before { + content: "\e210"; +} +.icon-glasses:before { + content: "\e211"; +} +.icon-camcorder:before { + content: "\e212"; +} +.icon-calendar:before { + content: "\e213"; +} +.icon-calendar-alt:before { + content: "\e214"; +} +.icon-calculator:before { + content: "\e215"; +} +.icon-bus:before { + content: "\e216"; +} +.icon-burn:before { + content: "\e217"; +} +.icon-bulleted-list:before { + content: "\e218"; +} +.icon-bug:before { + content: "\e219"; +} +.icon-brush:before { + content: "\e21a"; +} +.icon-brush-alt:before { + content: "\e21b"; +} +.icon-brush-alt-2:before { + content: "\e21c"; +} +.icon-browser-window:before { + content: "\e21d"; +} +.icon-briefcase:before { + content: "\e21e"; +} +.icon-brick:before { + content: "\e21f"; +} +.icon-brackets:before { + content: "\e220"; +} +.icon-box:before { + content: "\e221"; +} +.icon-box-open:before { + content: "\e222"; +} +.icon-box-alt:before { + content: "\e223"; +} +.icon-books:before { + content: "\e224"; +} +.icon-billboard:before { + content: "\e225"; +} +.icon-bills-dollar:before { + content: "\e226"; +} +.icon-bills-euro:before { + content: "\e227"; +} +.icon-bills-pound:before { + content: "\e228"; +} +.icon-bills-yen:before { + content: "\e229"; +} +.icon-bills:before { + content: "\e22a"; +} +.icon-binarycode:before { + content: "\e22b"; +} +.icon-binoculars:before { + content: "\e22c"; +} +.icon-bird:before { + content: "\e22d"; +} +.icon-birthday-cake:before { + content: "\e22e"; +} +.icon-blueprint:before { + content: "\e22f"; +} +.icon-block:before { + content: "\e230"; +} +.icon-bluetooth:before { + content: "\e231"; +} +.icon-boat-shipping:before { + content: "\e232"; +} +.icon-bomb:before { + content: "\e233"; +} +.icon-book-alt-2:before { + content: "\e234"; +} +.icon-bones:before { + content: "\e235"; +} +.icon-book-alt:before { + content: "\e236"; +} +.icon-book:before { + content: "\e237"; +} +.icon-bill-yen:before { + content: "\e238"; +} +.icon-award:before { + content: "\e239"; +} +.icon-bill-pound:before { + content: "\e23a"; +} +.icon-autofill:before { + content: "\e23b"; +} +.icon-bill-euro:before { + content: "\e23c"; +} +.icon-auction-hammer:before { + content: "\e23d"; +} +.icon-bill-dollar:before { + content: "\e23e"; +} +.icon-attachment:before { + content: "\e23f"; +} +.icon-bell:before { + content: "\e240"; +} +.icon-article:before { + content: "\e241"; +} +.icon-bell-off:before { + content: "\e242"; +} +.icon-art-easel:before { + content: "\e243"; +} +.icon-beer-glass:before { + content: "\e244"; +} +.icon-arrow-up:before { + content: "\e245"; +} +.icon-battery-low:before { + content: "\e246"; +} +.icon-arrow-right:before { + content: "\e247"; +} +.icon-battery-full:before { + content: "\e248"; +} +.icon-arrow-left:before { + content: "\e249"; +} +.icon-bars:before { + content: "\e24a"; +} +.icon-arrow-down:before { + content: "\e24b"; +} +.icon-barcode:before { + content: "\e24c"; +} +.icon-arrivals:before { + content: "\e24d"; +} +.icon-bar-chart:before { + content: "\e24e"; +} +.icon-application-window:before { + content: "\e24f"; +} +.icon-band-aid:before { + content: "\e250"; +} +.icon-application-window-alt:before { + content: "\e251"; +} +.icon-ball:before { + content: "\e252"; +} +.icon-application-error:before { + content: "\e253"; +} +.icon-badge-restricted:before { + content: "\e254"; +} +.icon-app:before { + content: "\e255"; +} +.icon-badge-remove:before { + content: "\e256"; +} +.icon-anchor:before { + content: "\e257"; +} +.icon-badge-count:before { + content: "\e258"; +} +.icon-alt:before { + content: "\e259"; +} +.icon-badge-add:before { + content: "\e25a"; +} +.icon-alert:before { + content: "\e25b"; +} +.icon-backspace:before { + content: "\e25c"; +} +.icon-alert-alt:before { + content: "\e25d"; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index a2201df9c2..e60a2a0c87 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -1,264 +1,264 @@ -// Listview -// ------------------------- - -.umb-listview{width: auto !important;} - -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus, -.dropdown-submenu:hover > a, -.dropdown-submenu:focus > a { - color: @black; - background: @gray-10; -} - -.umb-listview table { - border: 1px solid @gray-8; -} - -.umb-listview table caption { - background: @white; - text-align: left; - vertical-align: middle; -} - -.umb-sub-header { - padding: 0 0 20px 0; -} - -.umb-sub-header .header-content-right { - float: right; -} - -/* listview search */ -.form-search { - .inner-addon { - position: relative; - - [class^="icon-"], [class*=" icon-"] { - position: absolute; - padding: 5px 8px; - pointer-events: none; - top: 0; - } - - input[type="text"] { - width: 190px; - } - } - - /* align icon */ - .left-addon [class^="icon-"], .left-addon [class*=" icon-"] { left: 0; right: inherit; } - .right-addon [class^="icon-"], .right-addon [class*=" icon-"] { right: 0; left: inherit; } - - /* add padding */ - .left-addon input[type="text"] { padding-left: 30px !important; padding-right: 6px; } - .right-addon input[type="text"] { padding-right: 30px; padding-left: 6px !important; } -} - -.umb-listview table form { - position: relative; - margin: 0; -} - -.umb-listview table input[type="text"] { - background: none; - -webkit-transition: all .5s; - -moz-transition: all .5s; - -o-transition: all .5s; - transition: all .5s; - width: 60px; - padding: 4px 0 4px 20px; - border: 1px solid @gray-8; -} - -.umb-listview table input::-webkit-input-placeholder, -.umb-listview table input:-moz-placeholder, -.umb-listview table input::-moz-placeholder, -.umb-listview table input:-ms-input-placeholder { - color: @gray-3; -} - -.umb-listview table input[type="text"]:focus { - width: 200px; - border: 1px solid @gray-8; - background: @white; - color: @black -} - -.umb-listview table thead a { - cursor: default; -} -.umb-listview table thead a:hover { - text-decoration: none; -} - -.umb-listview table thead a.sortable { - cursor: pointer; -} -.umb-listview table thead a.sortable span:hover { - text-decoration: underline; -} - -.umb-listview .icon-star { - color: @gray-8; -} - -.umb-listview .selected i.icon, .umb-listview tbody tr:hover i.icon{display: none} -.umb-listview .selected input[type="checkbox"], -.umb-listview tr:hover input[type="checkbox"]{display: inline-block !important;} -.umb-listview .inactive{color: @gray-8;} - -.umb-listview .selected td{font-weight: bold;} - -.umb-listview table thead { - font-size: 12px; - font-weight: bold; - text-transform: uppercase; - background-color: @white; -} - -.umb-listview table tfoot { - background: @gray-10; -} - -.umb-listview table tfoot td:last-child { - border-left: none -} - -.umb-listview table tfoot th { - padding: 0 20px; -} - -.umb-listview .label { - color: @black; - text-shadow:none; - background: @gray-10; - border: 1px solid @gray-8; - font-size: 12px; - font-weight: normal; -} - -.umb-listview .table-striped tbody > tr:nth-child(even) > td, .umb-listview .table-striped tbody > tr:nth-child(even) > th { - background-color: @gray-10; -} - -.table-striped tbody > tr:nth-child(odd) > td, .table-striped tbody > tr:nth-child(odd) > th { - background: none -} - -/* TEMP */ - -.umb-listview .table-striped tbody td { - position: relative -} - - -.umb-listview .table-striped thead input[type="checkbox"] { - margin-left: 7px; -} - -.umb-listview .table-striped tbody input[type="checkbox"] { - display: none; - margin-left: 7px; - z-index: 5; -} - -.umb-listview .table-striped tbody i { - display: block; - top: 10px; - left:6px; - padding: 0 0 0 4px; - z-index: 6; - background: @white; - width: 20px; - height: 20px; -} - -.umb-listview .table-striped tbody > tr:nth-child(even) > td i, .umb-listview .table-striped tbody > tr:nth-child(even) > th i{ - background-color: @gray-10; -} - -/* don't hide all icons, e.g. for a sortable handle */ -.umb-listview .table-striped tbody i:not(.handle):hover { - display: none !important -} - -/* ---------- LAYOUTS ---------- */ - -.list-view-layouts { - -} - -.list-view-layout { - display: flex; - align-items: center; - padding: 10px 15px; - background: @gray-10; - margin-bottom: 1px; -} - -.list-view-layout__sort-handle { - font-size: 14px; - color: @gray-8; - margin-right: 15px; -} - -.list-view-layout__name { - flex: 5; - font-weight: bold; - margin-right: 15px; - display: flex; - align-content: center; - flex-wrap: wrap; - line-height: 1.2em; -} - -.list-view-layout__name-text { - margin-right: 3px; -} - -.list-view-layout__system { - font-size: 10px; - font-weight: normal; -} - -.list-view-layout__path { - flex: 10; - margin-right: 15px; -} - -.list-view-layout__icon { - font-size: 18px; - margin-right: 10px; - vertical-align: middle; - border: 1px solid @gray-8; - background: @white; - padding: 6px 8px; - display: block; -} - -.list-view-layout__icon:hover, -.list-view-layout__icon:focus, -.list-view-layout__icon:active { - text-decoration: none; -} - -.list-view-layout__remove { - position: relative; - cursor: pointer; -} - -.list-view-add-layout { - margin-top: 10px; - color: @turquoise-d1; - border: 1px dashed @gray-8; - display: flex; - align-items: center; - justify-content: center; - padding: 5px 0; - box-sizing: border-box; -} - -.list-view-add-layout:hover { - text-decoration: none; -} +// Listview +// ------------------------- + +.umb-listview{width: auto !important;} + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus, +.dropdown-submenu:hover > a, +.dropdown-submenu:focus > a { + color: @black; + background: @gray-10; +} + +.umb-listview table { + border: 1px solid @gray-8; +} + +.umb-listview table caption { + background: @white; + text-align: left; + vertical-align: middle; +} + +.umb-sub-header { + padding: 0 0 20px 0; +} + +.umb-sub-header .header-content-right { + float: right; +} + +/* listview search */ +.form-search { + .inner-addon { + position: relative; + + [class^="icon-"], [class*=" icon-"] { + position: absolute; + padding: 5px 8px; + pointer-events: none; + top: 0; + } + + input[type="text"] { + width: 190px; + } + } + + /* align icon */ + .left-addon [class^="icon-"], .left-addon [class*=" icon-"] { left: 0; right: inherit; } + .right-addon [class^="icon-"], .right-addon [class*=" icon-"] { right: 0; left: inherit; } + + /* add padding */ + .left-addon input[type="text"] { padding-left: 30px !important; padding-right: 6px; } + .right-addon input[type="text"] { padding-right: 30px; padding-left: 6px !important; } +} + +.umb-listview table form { + position: relative; + margin: 0; +} + +.umb-listview table input[type="text"] { + background: none; + -webkit-transition: all .5s; + -moz-transition: all .5s; + -o-transition: all .5s; + transition: all .5s; + width: 60px; + padding: 4px 0 4px 20px; + border: 1px solid @gray-8; +} + +.umb-listview table input::-webkit-input-placeholder, +.umb-listview table input:-moz-placeholder, +.umb-listview table input::-moz-placeholder, +.umb-listview table input:-ms-input-placeholder { + color: @gray-3; +} + +.umb-listview table input[type="text"]:focus { + width: 200px; + border: 1px solid @gray-8; + background: @white; + color: @black +} + +.umb-listview table thead a { + cursor: default; +} +.umb-listview table thead a:hover { + text-decoration: none; +} + +.umb-listview table thead a.sortable { + cursor: pointer; +} +.umb-listview table thead a.sortable span:hover { + text-decoration: underline; +} + +.umb-listview .icon-star { + color: @gray-8; +} + +.umb-listview .selected i.icon, .umb-listview tbody tr:hover i.icon{display: none} +.umb-listview .selected input[type="checkbox"], +.umb-listview tr:hover input[type="checkbox"]{display: inline-block !important;} +.umb-listview .inactive{color: @gray-8;} + +.umb-listview .selected td{font-weight: bold;} + +.umb-listview table thead { + font-size: 12px; + font-weight: bold; + text-transform: uppercase; + background-color: @white; +} + +.umb-listview table tfoot { + background: @gray-10; +} + +.umb-listview table tfoot td:last-child { + border-left: none +} + +.umb-listview table tfoot th { + padding: 0 20px; +} + +.umb-listview .label { + color: @black; + text-shadow:none; + background: @gray-10; + border: 1px solid @gray-8; + font-size: 12px; + font-weight: normal; +} + +.umb-listview .table-striped tbody > tr:nth-child(even) > td, .umb-listview .table-striped tbody > tr:nth-child(even) > th { + background-color: @gray-10; +} + +.table-striped tbody > tr:nth-child(odd) > td, .table-striped tbody > tr:nth-child(odd) > th { + background: none +} + +/* TEMP */ + +.umb-listview .table-striped tbody td { + position: relative +} + + +.umb-listview .table-striped thead input[type="checkbox"] { + margin-left: 7px; +} + +.umb-listview .table-striped tbody input[type="checkbox"] { + display: none; + margin-left: 7px; + z-index: 5; +} + +.umb-listview .table-striped tbody i { + display: block; + top: 10px; + left:6px; + padding: 0 0 0 4px; + z-index: 6; + background: @white; + width: 20px; + height: 20px; +} + +.umb-listview .table-striped tbody > tr:nth-child(even) > td i, .umb-listview .table-striped tbody > tr:nth-child(even) > th i{ + background-color: @gray-10; +} + +/* don't hide all icons, e.g. for a sortable handle */ +.umb-listview .table-striped tbody i:not(.handle):hover { + display: none !important +} + +/* ---------- LAYOUTS ---------- */ + +.list-view-layouts { + +} + +.list-view-layout { + display: flex; + align-items: center; + padding: 10px 15px; + background: @gray-10; + margin-bottom: 1px; +} + +.list-view-layout__sort-handle { + font-size: 14px; + color: @gray-8; + margin-right: 15px; +} + +.list-view-layout__name { + flex: 5; + font-weight: bold; + margin-right: 15px; + display: flex; + align-content: center; + flex-wrap: wrap; + line-height: 1.2em; +} + +.list-view-layout__name-text { + margin-right: 3px; +} + +.list-view-layout__system { + font-size: 10px; + font-weight: normal; +} + +.list-view-layout__path { + flex: 10; + margin-right: 15px; +} + +.list-view-layout__icon { + font-size: 18px; + margin-right: 10px; + vertical-align: middle; + border: 1px solid @gray-8; + background: @white; + padding: 6px 8px; + display: block; +} + +.list-view-layout__icon:hover, +.list-view-layout__icon:focus, +.list-view-layout__icon:active { + text-decoration: none; +} + +.list-view-layout__remove { + position: relative; + cursor: pointer; +} + +.list-view-add-layout { + margin-top: 10px; + color: @turquoise-d1; + border: 1px dashed @gray-8; + display: flex; + align-items: center; + justify-content: center; + padding: 5px 0; + box-sizing: border-box; +} + +.list-view-add-layout:hover { + text-decoration: none; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 00afa49934..5d6eca7cda 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -1,644 +1,644 @@ -// Main -// ------------------------- - - -// Utillity classes -// @Per not sure where to put this part -// ------------------------- - -.fill { - height: 100%; - min-height: 100%; -} -.shadow { - box-shadow: 3px 0px 7px rgba(0,0,0,0.16); -} - -.umb-scrollable, .umb-auto-overflow { - overflow: auto; -} - -.umb-abstract { - display: block; - margin-top: 0px; - margin-bottom: 15px; - font-size: 14px; - color: @gray-7; -} - -h5{ - color: @gray-1; - font-weight: bold; - font-size: 15px; - margin-top: 15px; -} - -h5.-border-bottom { - border-bottom: 1px solid @gray-9; - padding-bottom: 5px; -} - -h5.-black { - color: @black; -} - -/* MISC FORM ELEMENTS */ -.umb-form-actions { - background: none; - border: none -} -.bootstrap-datetimepicker-widget { - td { - &.active, span.active { - background: @turquoise !important; - } - &.today:not(.active):before { - border-bottom-color: @purple-l1 !important; - } - a[data-action] { - padding: 0 !important; - } - .timepicker-hour, - .timepicker-minute, - .timepicker-second { - margin: 8px 0; - } - } -} - -.umb-datetime-picker div.info { - vertical-align: middle -} - -.umb-userwidget img { - float: left; - margin-right: 15px; -} -.umb-userwidget small { - display: block -} -.popover-title { - display: none -} - - -/*BUTTONS */ -.thumbnails > li.umb-plus-btn { - margin: 0 10px 10px 0 -} - -.umb-plus-btn a { - border: 2px dashed @gray-8; - width: 136px; - height: 136px; - line-height: 136px; - text-align: center; - font-size: 50px; - display: block; - color: @gray-8; - text-decoration: none; - -webkit-transition: all 0.3s ease-in-out; - -moz-transition: all 0.3s ease-in-out; - -ms-transition: all 0.3s ease-in-out; - -o-transition: all 0.3s ease-in-out; - transition: all 0.3s ease-in-out; -} - -.umb-plus-btn a:hover { - border: 2px dashed @black; - color: @black -} - -.umb-plus-btn i { - vertical-align: middle; - margin: auto -} - -/* FORM GRID */ -.umb-pane { - margin: 30px 20px; -} -.umb-control-group { - border-bottom: 1px solid @gray-10; - padding-bottom: 20px; - margin-bottom: 15px !important; -} - -.umb-control-group.-no-border { - border: none; -} - -.umb-property:last-of-type .umb-control-group { - border: none; - margin-bottom: 0 !important; - padding-bottom: 0; -} - -/* BLOCK MODE */ -.block-form .umb-control-group { - border-bottom: none; - padding-bottom: 0; -} - -.block-form .umb-control-group label .help-block, -.block-form .umb-control-group label small { - font-size: 13px; - padding-top: 2px; - margin-bottom: 5px; -} - -/*COMPACT MODE */ -.compact .umb-pane{margin: 0px 0px 15px 0px;} -.compact .umb-control-group { - border-bottom: 1px solid @gray-10; - padding-bottom: 10px; - margin-bottom: 5px !important; -} -.compact label.control-label { - padding-top: 0px !important; - margin-bottom: 0px; -} - -.compact .controls-row{padding-top: 0px } - -.umb-pane > .umb-control-group:last-child { - border: none; - padding-bottom: 0 !important; -} - -.umb-control-group .umb-el-wrap { - padding: 0 -} - -/* LABELS*/ -.umb-control-group label.control-label, .umb-control-group .control-label { - text-align: left; -} -.umb-control-group label.control-label > div > label { - padding-left: 0; -} -.umb-control-group label .help-block, -.umb-control-group label small { - font-size: 12px; - color: @gray-6; - line-height: 1.5em; - padding-top: 5px; -} -.umb-nolabel .controls { - margin-left: 0; -} - -/* CONTROL VALIDATION */ -.umb-control-required { - color: @controlRequiredColor; -} - -.controls-row { - padding-bottom: 5px; - margin-left: 240px; -} - -.umb-user-panel .controls-row { - margin-left: 0; -} - -.controls-row label { - display: inline-block; -} - -.controls-row > div > label { - padding-left: 0; -} - -.block-form .controls-row { - margin-left: 0; - padding-top: 0; -} - -.hidelabel > div > .controls-row, .hidelabel > .controls-row, .hidelabel > div > .controls { - padding: 0; - border: none; - margin: 0 !important; -} - -.controls-row > .vertical-align-items { - display: flex; - align-items: center; -} - -.controls-row > .vertical-align-items > input.umb-property-editor-tiny { - margin-left: 5px; - margin-right: 5px; -} - -.controls-row > .vertical-align-items > input.umb-property-editor-tiny:first-child { - margin-left: 0; -} - -.thumbnails .selected { - border-color: @black; - background: @black -} - -.umb-version { - color: @gray-7; - position: absolute; - bottom: 5px; - right: 20px; -} - -/* DASHBOARD */ -.dashboardHideLink { - display: none; -} -.dashboardWrapper { - position: relative -} -.dashboardWrapper h2 { - padding: 0px 0px 0px 45px -} -.dashboardWrapper h3 { - font-size: 14px; - font-weight: bold -} -.dashboardIcon { - position: absolute; - top: 2px; - left: 2px -} - -.umb-dashboard-control - iframe{ position: absolute; display: block; width: 99%; height: 99%; overflow: auto !important;} - - -/* TABLE */ -.umb-table { - table-layout: fixed; - word-wrap: break-word; -} -.umb-no-border { - border: none !important; -} - -table thead a { - color: @gray-2; -} - -/* UI interactions */ - -.umb-table tbody.ui-sortable tr -{ - cursor:pointer; -} - -.umb-table tbody.ui-sortable tr.ui-sortable-helper { - background-color: @sortableHelperBg; - border: none; -} -.umb-table tbody.ui-sortable tr.ui-sortable-helper td - { - border:none; -} - -.umb-table tbody.ui-sortable tr.ui-sortable-placeholder { - background-color: @sortablePlaceholderBg; - border:none; -} - -.umb-table tbody.ui-sortable tr.ui-sortable-placeholder td -{ - height:5px; - padding:0px; - line-height:0px; -} - -/* MEDIA PICKER */ - -.thumbnails > li.umb-thumbnail { - margin: 0 10px 10px 0; - position: relative; -} - -.thumbnails > li.umb-thumbnail .umb-icons { - background: @gray-1; - position: absolute; - top: 0; - left: 0; - width: 100%; - z-index: 1000; - padding: 17px 0 -} - -.thumbnails > li.umb-thumbnail .icon-crop { - position: absolute; - left: 10px; - top: 10px; - color: @white; - font-size: 14px -} - -.thumbnails > li.umb-thumbnail .icon-remove { - position: absolute; - right: 10px; - top: 10px; - color: @white; - font-size: 14px -} - -/* IMAGE CROPPER */ - -.umb-image-crop { - margin: 0 30px 25px 0; - padding: 0 0 30px 0; - width: 400px; - float: left -} - -.umb-image-mask { - width: 399px; - height: 300px; - position: relative; - margin: 0 30px 0 0 -} - -.umb-image-mask:after { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 5999; - opacity: .6; - filter: alpha(opacity=6); - -webkit-box-shadow: inset 0 0 0 40px white,inset 0 0 0 41px rgba(0,0,0,0.2),inset 0 0 20px 41px rgba(0,0,0,0.2); - box-shadow: inset 0 0 0 40px white,inset 0 0 0 41px rgba(0,0,0,0.2),inset 0 0 20px 41px rgba(0,0,0,0.2); -} - -.umb-image-mask .icon-screenshot { - color: @white; - font-size: 30px; - position: absolute; - top: 125px; - left: 110px; - z-index: 1000; -} - -.umb-image-mask .icon-circle { - color: @turquoise-d1; - position: absolute; - top: 130px; - left: 115px; - z-index: 1; - font-size: 20px -} - -.umb-crop-preview { - float: left; - width: 400px -} - - -.umb-image-controls { - width: 400px; /* Need to be set dynamic, accounding to the width of the current image */ -} - -.umb-image-controls .icon-minus { - float: left; - padding: 14px 10px 0 10px; - color: @inputBorder; -} - -.umb-image-controls .icon-plus { - float: right; - text-align: left; - padding: 14px 10px 0 10px; - color: @inputBorder; -} - -.umb-image-crop .range { - display: block; - -webkit-appearance: none; - background: @inputBorder; - height: 1px; - margin: 20px 0 0 0; - width: 82%; - float: left; -} - -.umb-image-crop .range::-webkit-slider-thumb { - -webkit-appearance: none; - width:14px; - height:14px; - border:1px solid @inputBorder; - border-radius:2px; - content:"1"; - background: @gray-10; - margin-top: -1px; -} - -/* SEARCH */ - -.umb-search-group li > div { - padding-left: 20px; -} - -.umb-search-group li > div a > i { - height: 100%; -} - -/* DICTIONARY */ -#dictionaryItems tr { - border-top:solid 1px @gray-8; -} - -#dictionaryItems thead tr { - border-top:none; - font-weight:bold; -} - -#dictionaryItems th { - text-align:left; - font-weight:normal; -} - -#dictionaryItems td { - text-align:center; - } - -#dictionaryItems thead td:first-of-type { - text-align: left; -} - -#dictioanryItems i { - font-size:18px; -} - -#dictionaryItems .icon-alert { - color:@red; -} - -#dictionaryItems .icon-check { - color:@green; -} - -// Loading Animation -// ------------------------ - -.umb-loader{ -background-color: @turquoise-d1; -margin-top:0; -margin-left:-100%; --moz-animation-name:bounce_loadingProgressG; --moz-animation-duration:1s; --moz-animation-iteration-count:infinite; --moz-animation-timing-function:linear; --webkit-animation-name:bounce_loadingProgressG; --webkit-animation-duration:1s; --webkit-animation-iteration-count:infinite; --webkit-animation-timing-function:linear; --ms-animation-name:bounce_loadingProgressG; --ms-animation-duration:1s; --ms-animation-iteration-count:infinite; --ms-animation-timing-function:linear; --o-animation-name:bounce_loadingProgressG; --o-animation-duration:1s; --o-animation-iteration-count:infinite; --o-animationtiming-function:linear; -animation-name:bounce_loadingProgressG; -animation-duration:1s; -animation-iteration-count:infinite; -animation-timing-function:linear; -width:100%; -height:1px; -} - - - @-moz-keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @-webkit-keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @-ms-keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @-o-keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - - 100%{ - margin-left:100%; - } - - } - - @keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - 100%{ - margin-left:100%; - } -} - -.umb-loader-wrapper { - - position: absolute; - right: 0; - left: 0; - margin: 10px 0; - overflow: hidden; - -} - -.umb-loader-wrapper.-bottom { - bottom: 0; -} - -// Helpers - -.strong { - font-weight: bold; -} - -.inline { - display: inline; -} - - -// Input label styles -// @Simon: not sure where to put this part yet -// --- TODO Needs to be divided into the right .less directories - - -// Titles for input fields -.input-label--title { - font-weight: bold; - color: @black; - - margin-bottom: 3px; -} - - -// Used for input checkmark fields -.input-label--small { - display: inline; - - font-size: 12px; - font-weight: bold; - color: @gray-3; - - &:hover { - color: @black; - } -} - -input[type=checkbox]:checked + .input-label--small { - color: @turquoise-d1; -} - - -// Use this for headers in the panels -.panel-dialog--header { - border-bottom: 1px solid @gray-3; - margin: 10px 0; - padding-bottom: 10px; - font-size: @fontSizeLarge; - font-weight: bold; - line-height: 20px; -} - - -// Datepicker styles -.bootstrap-datetimepicker-widget, -.bootstrap-datetimepicker-widget td, -.bootstrap-datetimepicker-widget th, -.bootstrap-datetimepicker-widget td span { - border-radius: 0 !important; -} +// Main +// ------------------------- + + +// Utillity classes +// @Per not sure where to put this part +// ------------------------- + +.fill { + height: 100%; + min-height: 100%; +} +.shadow { + box-shadow: 3px 0px 7px rgba(0,0,0,0.16); +} + +.umb-scrollable, .umb-auto-overflow { + overflow: auto; +} + +.umb-abstract { + display: block; + margin-top: 0px; + margin-bottom: 15px; + font-size: 14px; + color: @gray-7; +} + +h5{ + color: @gray-1; + font-weight: bold; + font-size: 15px; + margin-top: 15px; +} + +h5.-border-bottom { + border-bottom: 1px solid @gray-9; + padding-bottom: 5px; +} + +h5.-black { + color: @black; +} + +/* MISC FORM ELEMENTS */ +.umb-form-actions { + background: none; + border: none +} +.bootstrap-datetimepicker-widget { + td { + &.active, span.active { + background: @turquoise !important; + } + &.today:not(.active):before { + border-bottom-color: @purple-l1 !important; + } + a[data-action] { + padding: 0 !important; + } + .timepicker-hour, + .timepicker-minute, + .timepicker-second { + margin: 8px 0; + } + } +} + +.umb-datetime-picker div.info { + vertical-align: middle +} + +.umb-userwidget img { + float: left; + margin-right: 15px; +} +.umb-userwidget small { + display: block +} +.popover-title { + display: none +} + + +/*BUTTONS */ +.thumbnails > li.umb-plus-btn { + margin: 0 10px 10px 0 +} + +.umb-plus-btn a { + border: 2px dashed @gray-8; + width: 136px; + height: 136px; + line-height: 136px; + text-align: center; + font-size: 50px; + display: block; + color: @gray-8; + text-decoration: none; + -webkit-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; + transition: all 0.3s ease-in-out; +} + +.umb-plus-btn a:hover { + border: 2px dashed @black; + color: @black +} + +.umb-plus-btn i { + vertical-align: middle; + margin: auto +} + +/* FORM GRID */ +.umb-pane { + margin: 30px 20px; +} +.umb-control-group { + border-bottom: 1px solid @gray-10; + padding-bottom: 20px; + margin-bottom: 15px !important; +} + +.umb-control-group.-no-border { + border: none; +} + +.umb-property:last-of-type .umb-control-group { + border: none; + margin-bottom: 0 !important; + padding-bottom: 0; +} + +/* BLOCK MODE */ +.block-form .umb-control-group { + border-bottom: none; + padding-bottom: 0; +} + +.block-form .umb-control-group label .help-block, +.block-form .umb-control-group label small { + font-size: 13px; + padding-top: 2px; + margin-bottom: 5px; +} + +/*COMPACT MODE */ +.compact .umb-pane{margin: 0px 0px 15px 0px;} +.compact .umb-control-group { + border-bottom: 1px solid @gray-10; + padding-bottom: 10px; + margin-bottom: 5px !important; +} +.compact label.control-label { + padding-top: 0px !important; + margin-bottom: 0px; +} + +.compact .controls-row{padding-top: 0px } + +.umb-pane > .umb-control-group:last-child { + border: none; + padding-bottom: 0 !important; +} + +.umb-control-group .umb-el-wrap { + padding: 0 +} + +/* LABELS*/ +.umb-control-group label.control-label, .umb-control-group .control-label { + text-align: left; +} +.umb-control-group label.control-label > div > label { + padding-left: 0; +} +.umb-control-group label .help-block, +.umb-control-group label small { + font-size: 12px; + color: @gray-6; + line-height: 1.5em; + padding-top: 5px; +} +.umb-nolabel .controls { + margin-left: 0; +} + +/* CONTROL VALIDATION */ +.umb-control-required { + color: @controlRequiredColor; +} + +.controls-row { + padding-bottom: 5px; + margin-left: 240px; +} + +.umb-user-panel .controls-row { + margin-left: 0; +} + +.controls-row label { + display: inline-block; +} + +.controls-row > div > label { + padding-left: 0; +} + +.block-form .controls-row { + margin-left: 0; + padding-top: 0; +} + +.hidelabel > div > .controls-row, .hidelabel > .controls-row, .hidelabel > div > .controls { + padding: 0; + border: none; + margin: 0 !important; +} + +.controls-row > .vertical-align-items { + display: flex; + align-items: center; +} + +.controls-row > .vertical-align-items > input.umb-property-editor-tiny { + margin-left: 5px; + margin-right: 5px; +} + +.controls-row > .vertical-align-items > input.umb-property-editor-tiny:first-child { + margin-left: 0; +} + +.thumbnails .selected { + border-color: @black; + background: @black +} + +.umb-version { + color: @gray-7; + position: absolute; + bottom: 5px; + right: 20px; +} + +/* DASHBOARD */ +.dashboardHideLink { + display: none; +} +.dashboardWrapper { + position: relative +} +.dashboardWrapper h2 { + padding: 0px 0px 0px 45px +} +.dashboardWrapper h3 { + font-size: 14px; + font-weight: bold +} +.dashboardIcon { + position: absolute; + top: 2px; + left: 2px +} + +.umb-dashboard-control + iframe{ position: absolute; display: block; width: 99%; height: 99%; overflow: auto !important;} + + +/* TABLE */ +.umb-table { + table-layout: fixed; + word-wrap: break-word; +} +.umb-no-border { + border: none !important; +} + +table thead a { + color: @gray-2; +} + +/* UI interactions */ + +.umb-table tbody.ui-sortable tr +{ + cursor:pointer; +} + +.umb-table tbody.ui-sortable tr.ui-sortable-helper { + background-color: @sortableHelperBg; + border: none; +} +.umb-table tbody.ui-sortable tr.ui-sortable-helper td + { + border:none; +} + +.umb-table tbody.ui-sortable tr.ui-sortable-placeholder { + background-color: @sortablePlaceholderBg; + border:none; +} + +.umb-table tbody.ui-sortable tr.ui-sortable-placeholder td +{ + height:5px; + padding:0px; + line-height:0px; +} + +/* MEDIA PICKER */ + +.thumbnails > li.umb-thumbnail { + margin: 0 10px 10px 0; + position: relative; +} + +.thumbnails > li.umb-thumbnail .umb-icons { + background: @gray-1; + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 1000; + padding: 17px 0 +} + +.thumbnails > li.umb-thumbnail .icon-crop { + position: absolute; + left: 10px; + top: 10px; + color: @white; + font-size: 14px +} + +.thumbnails > li.umb-thumbnail .icon-remove { + position: absolute; + right: 10px; + top: 10px; + color: @white; + font-size: 14px +} + +/* IMAGE CROPPER */ + +.umb-image-crop { + margin: 0 30px 25px 0; + padding: 0 0 30px 0; + width: 400px; + float: left +} + +.umb-image-mask { + width: 399px; + height: 300px; + position: relative; + margin: 0 30px 0 0 +} + +.umb-image-mask:after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 5999; + opacity: .6; + filter: alpha(opacity=6); + -webkit-box-shadow: inset 0 0 0 40px white,inset 0 0 0 41px rgba(0,0,0,0.2),inset 0 0 20px 41px rgba(0,0,0,0.2); + box-shadow: inset 0 0 0 40px white,inset 0 0 0 41px rgba(0,0,0,0.2),inset 0 0 20px 41px rgba(0,0,0,0.2); +} + +.umb-image-mask .icon-screenshot { + color: @white; + font-size: 30px; + position: absolute; + top: 125px; + left: 110px; + z-index: 1000; +} + +.umb-image-mask .icon-circle { + color: @turquoise-d1; + position: absolute; + top: 130px; + left: 115px; + z-index: 1; + font-size: 20px +} + +.umb-crop-preview { + float: left; + width: 400px +} + + +.umb-image-controls { + width: 400px; /* Need to be set dynamic, accounding to the width of the current image */ +} + +.umb-image-controls .icon-minus { + float: left; + padding: 14px 10px 0 10px; + color: @inputBorder; +} + +.umb-image-controls .icon-plus { + float: right; + text-align: left; + padding: 14px 10px 0 10px; + color: @inputBorder; +} + +.umb-image-crop .range { + display: block; + -webkit-appearance: none; + background: @inputBorder; + height: 1px; + margin: 20px 0 0 0; + width: 82%; + float: left; +} + +.umb-image-crop .range::-webkit-slider-thumb { + -webkit-appearance: none; + width:14px; + height:14px; + border:1px solid @inputBorder; + border-radius:2px; + content:"1"; + background: @gray-10; + margin-top: -1px; +} + +/* SEARCH */ + +.umb-search-group li > div { + padding-left: 20px; +} + +.umb-search-group li > div a > i { + height: 100%; +} + +/* DICTIONARY */ +#dictionaryItems tr { + border-top:solid 1px @gray-8; +} + +#dictionaryItems thead tr { + border-top:none; + font-weight:bold; +} + +#dictionaryItems th { + text-align:left; + font-weight:normal; +} + +#dictionaryItems td { + text-align:center; + } + +#dictionaryItems thead td:first-of-type { + text-align: left; +} + +#dictioanryItems i { + font-size:18px; +} + +#dictionaryItems .icon-alert { + color:@red; +} + +#dictionaryItems .icon-check { + color:@green; +} + +// Loading Animation +// ------------------------ + +.umb-loader{ +background-color: @turquoise-d1; +margin-top:0; +margin-left:-100%; +-moz-animation-name:bounce_loadingProgressG; +-moz-animation-duration:1s; +-moz-animation-iteration-count:infinite; +-moz-animation-timing-function:linear; +-webkit-animation-name:bounce_loadingProgressG; +-webkit-animation-duration:1s; +-webkit-animation-iteration-count:infinite; +-webkit-animation-timing-function:linear; +-ms-animation-name:bounce_loadingProgressG; +-ms-animation-duration:1s; +-ms-animation-iteration-count:infinite; +-ms-animation-timing-function:linear; +-o-animation-name:bounce_loadingProgressG; +-o-animation-duration:1s; +-o-animation-iteration-count:infinite; +-o-animationtiming-function:linear; +animation-name:bounce_loadingProgressG; +animation-duration:1s; +animation-iteration-count:infinite; +animation-timing-function:linear; +width:100%; +height:1px; +} + + + @-moz-keyframes bounce_loadingProgressG{ + 0%{ + margin-left:-100%; + } + + 100%{ + margin-left:100%; + } + + } + + @-webkit-keyframes bounce_loadingProgressG{ + 0%{ + margin-left:-100%; + } + + 100%{ + margin-left:100%; + } + + } + + @-ms-keyframes bounce_loadingProgressG{ + 0%{ + margin-left:-100%; + } + + 100%{ + margin-left:100%; + } + + } + + @-o-keyframes bounce_loadingProgressG{ + 0%{ + margin-left:-100%; + } + + 100%{ + margin-left:100%; + } + + } + + @keyframes bounce_loadingProgressG{ + 0%{ + margin-left:-100%; + } + 100%{ + margin-left:100%; + } +} + +.umb-loader-wrapper { + + position: absolute; + right: 0; + left: 0; + margin: 10px 0; + overflow: hidden; + +} + +.umb-loader-wrapper.-bottom { + bottom: 0; +} + +// Helpers + +.strong { + font-weight: bold; +} + +.inline { + display: inline; +} + + +// Input label styles +// @Simon: not sure where to put this part yet +// --- TODO Needs to be divided into the right .less directories + + +// Titles for input fields +.input-label--title { + font-weight: bold; + color: @black; + + margin-bottom: 3px; +} + + +// Used for input checkmark fields +.input-label--small { + display: inline; + + font-size: 12px; + font-weight: bold; + color: @gray-3; + + &:hover { + color: @black; + } +} + +input[type=checkbox]:checked + .input-label--small { + color: @turquoise-d1; +} + + +// Use this for headers in the panels +.panel-dialog--header { + border-bottom: 1px solid @gray-3; + margin: 10px 0; + padding-bottom: 10px; + font-size: @fontSizeLarge; + font-weight: bold; + line-height: 20px; +} + + +// Datepicker styles +.bootstrap-datetimepicker-widget, +.bootstrap-datetimepicker-widget td, +.bootstrap-datetimepicker-widget th, +.bootstrap-datetimepicker-widget td span { + border-radius: 0 !important; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index d86becd06c..5b4211f177 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -1,711 +1,711 @@ -// -// Mixins -// -------------------------------------------------- - - -// UTILITY MIXINS -// -------------------------------------------------- - -// Clearfix -// -------- -// For clearing floats like a boss h5bp.com/q -.clearfix { - *zoom: 1; - &:before, - &:after { - display: table; - content: ""; - // Fixes Opera/contenteditable bug: - // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 - line-height: 0; - } - &:after { - clear: both; - } -} - -// Webkit-style focus -// ------------------ -.tab-focus() { - // Default - outline: thin dotted @gray-3; - // Webkit - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -// Center-align a block level element -// ---------------------------------- -.center-block() { - display: block; - margin-left: auto; - margin-right: auto; -} - -// IE7 inline-block -// ---------------- -.ie7-inline-block() { - *display: inline; /* IE7 inline-block hack */ - *zoom: 1; -} - -// IE7 likes to collapse whitespace on either side of the inline-block elements. -// Ems because we're attempting to match the width of a space character. Left -// version is for form buttons, which typically come after other elements, and -// right version is for icons, which come before. Applying both is ok, but it will -// mean that space between those elements will be .6em (~2 space characters) in IE7, -// instead of the 1 space in other browsers. -.ie7-restore-left-whitespace() { - *margin-left: .3em; - - &:first-child { - *margin-left: 0; - } -} - -.ie7-restore-right-whitespace() { - *margin-right: .3em; -} - -// Sizing shortcuts -// ------------------------- -.size(@height, @width) { - width: @width; - height: @height; -} -.square(@size) { - .size(@size, @size); -} - -// Placeholder text -// ------------------------- -.placeholder(@color: @placeholderText) { - &:-moz-placeholder { - color: @color; - } - &:-ms-input-placeholder { - color: @color; - } - &::-webkit-input-placeholder { - color: @color; - } -} - -// Text overflow -// ------------------------- -// Requires inline-block or block for proper styling -.text-overflow() { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -// CSS image replacement -// ------------------------- -// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 -.hide-text { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} - - -// FONTS -// -------------------------------------------------- - -#font { - #family { - .serif() { - font-family: @serifFontFamily; - } - .sans-serif() { - font-family: @sansFontFamily; - } - .monospace() { - font-family: @monoFontFamily; - } - } - .shorthand(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { - font-size: @size; - font-weight: @weight; - line-height: @lineHeight; - } - .serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { - #font > #family > .serif; - #font > .shorthand(@size, @weight, @lineHeight); - } - .sans-serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { - #font > #family > .sans-serif; - #font > .shorthand(@size, @weight, @lineHeight); - } - .monospace(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { - #font > #family > .monospace; - #font > .shorthand(@size, @weight, @lineHeight); - } -} - - -// FORMS -// -------------------------------------------------- - -// Block level inputs -.input-block-level { - display: block; - width: 100%; - min-height: @inputHeight; // Make inputs at least the height of their button counterpart (base line-height + padding + border) - .box-sizing(border-box); // Makes inputs behave like true block-level elements -} - - - -// Mixin for form field states -//SD: I've had to modify this slightly to work nicely with angular validation , note the -// additional targetting of the ng-invalid class. -.formFieldState(@textColor: @gray-4, @borderColor: @gray-7, @backgroundColor: @gray-10) { - // Set the text color - .control-label, - .help-block, - .help-inline { - color: @textColor; - } - // Style inputs accordingly - .checkbox.ng-invalid, - .radio.ng-invalid, - input.ng-invalid, - select.ng-invalid, - textarea.ng-invalid { - color: @textColor; - } - input.ng-invalid, - select.ng-invalid, - textarea.ng-invalid { - border-color: @borderColor; - } - // Give a small background color for input-prepend/-append - .input-prepend .add-on, - .input-append .add-on { - color: @textColor; - background-color: @backgroundColor; - border-color: @textColor; - } - //SD: We could do this but need to get the colors right - /*input.ng-invalid { - &:-moz-placeholder { - color: lighten(@textColor, 50%) !important; - } - &:-ms-input-placeholder { - color: lighten(@textColor, 50%) !important; - } - &::-webkit-input-placeholder { - color: lighten(@textColor, 50%) !important; - } - }*/ -} - -// CSS3 PROPERTIES -// -------------------------------------------------- - -// Border Radius -.border-radius(@radius) { - -webkit-border-radius: @radius; - -moz-border-radius: @radius; - border-radius: @radius; -} - -// Single Corner Border Radius -.border-top-left-radius(@radius) { - -webkit-border-top-left-radius: @radius; - -moz-border-radius-topleft: @radius; - border-top-left-radius: @radius; -} -.border-top-right-radius(@radius) { - -webkit-border-top-right-radius: @radius; - -moz-border-radius-topright: @radius; - border-top-right-radius: @radius; -} -.border-bottom-right-radius(@radius) { - -webkit-border-bottom-right-radius: @radius; - -moz-border-radius-bottomright: @radius; - border-bottom-right-radius: @radius; -} -.border-bottom-left-radius(@radius) { - -webkit-border-bottom-left-radius: @radius; - -moz-border-radius-bottomleft: @radius; - border-bottom-left-radius: @radius; -} - -// Single Side Border Radius -.border-top-radius(@radius) { - .border-top-right-radius(@radius); - .border-top-left-radius(@radius); -} -.border-right-radius(@radius) { - .border-top-right-radius(@radius); - .border-bottom-right-radius(@radius); -} -.border-bottom-radius(@radius) { - .border-bottom-right-radius(@radius); - .border-bottom-left-radius(@radius); -} -.border-left-radius(@radius) { - .border-top-left-radius(@radius); - .border-bottom-left-radius(@radius); -} - -// Drop shadows -.box-shadow(@shadow) { - -webkit-box-shadow: @shadow; - -moz-box-shadow: @shadow; - box-shadow: @shadow; -} - -// Transitions -.transition(@transition) { - -webkit-transition: @transition; - -moz-transition: @transition; - -o-transition: @transition; - transition: @transition; -} -.transition-delay(@transition-delay) { - -webkit-transition-delay: @transition-delay; - -moz-transition-delay: @transition-delay; - -o-transition-delay: @transition-delay; - transition-delay: @transition-delay; -} -.transition-duration(@transition-duration) { - -webkit-transition-duration: @transition-duration; - -moz-transition-duration: @transition-duration; - -o-transition-duration: @transition-duration; - transition-duration: @transition-duration; -} - -// Transformations -.rotate(@degrees) { - -webkit-transform: rotate(@degrees); - -moz-transform: rotate(@degrees); - -ms-transform: rotate(@degrees); - -o-transform: rotate(@degrees); - transform: rotate(@degrees); -} -.scale(@ratio) { - -webkit-transform: scale(@ratio); - -moz-transform: scale(@ratio); - -ms-transform: scale(@ratio); - -o-transform: scale(@ratio); - transform: scale(@ratio); -} -.translate(@x, @y) { - -webkit-transform: translate(@x, @y); - -moz-transform: translate(@x, @y); - -ms-transform: translate(@x, @y); - -o-transform: translate(@x, @y); - transform: translate(@x, @y); -} -.skew(@x, @y) { - -webkit-transform: skew(@x, @y); - -moz-transform: skew(@x, @y); - -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twitter/bootstrap/issues/4885 - -o-transform: skew(@x, @y); - transform: skew(@x, @y); - -webkit-backface-visibility: hidden; // See https://github.com/twitter/bootstrap/issues/5319 -} -.translate3d(@x, @y, @z) { - -webkit-transform: translate3d(@x, @y, @z); - -moz-transform: translate3d(@x, @y, @z); - -o-transform: translate3d(@x, @y, @z); - transform: translate3d(@x, @y, @z); -} - -// Backface visibility -// Prevent browsers from flickering when using CSS 3D transforms. -// Default value is `visible`, but can be changed to `hidden -// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples -.backface-visibility(@visibility){ - -webkit-backface-visibility: @visibility; - -moz-backface-visibility: @visibility; - backface-visibility: @visibility; -} - -// Background clipping -// Heads up: FF 3.6 and under need "padding" instead of "padding-box" -.background-clip(@clip) { - -webkit-background-clip: @clip; - -moz-background-clip: @clip; - background-clip: @clip; -} - -// Background sizing -.background-size(@size) { - -webkit-background-size: @size; - -moz-background-size: @size; - -o-background-size: @size; - background-size: @size; -} - - -// Box sizing -.box-sizing(@boxmodel) { - -webkit-box-sizing: @boxmodel; - -moz-box-sizing: @boxmodel; - box-sizing: @boxmodel; -} - -// User select -// For selecting text on the page -.user-select(@select) { - -webkit-user-select: @select; - -moz-user-select: @select; - -ms-user-select: @select; - -o-user-select: @select; - user-select: @select; -} - -// Resize anything -.resizable(@direction) { - resize: @direction; // Options: horizontal, vertical, both - overflow: auto; // Safari fix -} - -// CSS3 Content Columns -.content-columns(@columnCount, @columnGap: @gridGutterWidth) { - -webkit-column-count: @columnCount; - -moz-column-count: @columnCount; - column-count: @columnCount; - -webkit-column-gap: @columnGap; - -moz-column-gap: @columnGap; - column-gap: @columnGap; -} - -// Optional hyphenation -.hyphens(@mode: auto) { - word-wrap: break-word; - -webkit-hyphens: @mode; - -moz-hyphens: @mode; - -ms-hyphens: @mode; - -o-hyphens: @mode; - hyphens: @mode; -} - -// Opacity -.opacity(@opacity) { - opacity: @opacity / 100; - filter: ~"alpha(opacity=@{opacity})"; -} - - - -// BACKGROUNDS -// -------------------------------------------------- - -// Add an alphatransparency value to any background or border color (via Elyse Holladay) -#translucent { - .background(@color: @white, @alpha: 1) { - background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); - } - .border(@color: @white, @alpha: 1) { - border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); - .background-clip(padding-box); - } -} - -// Gradient Bar Colors for buttons and alerts -.gradientBar(@primaryColor, @secondaryColor, @textColor: #fff) { - color: @textColor; - #gradient > .vertical(@primaryColor, @secondaryColor); - border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); - border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); -} - -// Gradients -#gradient { - .horizontal(@startColor: #555, @endColor: #333) { - background-color: @endColor; - background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ - background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ - background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ - background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 - background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 - background-repeat: repeat-x; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down - } - .vertical(@startColor: #555, @endColor: #333) { - background-color: mix(@startColor, @endColor, 60%); - background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ - background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ - background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 - background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 - background-repeat: repeat-x; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down - } - .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { - background-color: @endColor; - background-repeat: repeat-x; - background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ - background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ - background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 - background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10 - } - .horizontal-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { - background-color: mix(@midColor, @endColor, 80%); - background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); - background-image: -webkit-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); - background-image: -moz-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); - background-image: -o-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); - background-image: linear-gradient(to right, @startColor, @midColor @colorStop, @endColor); - background-repeat: no-repeat; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback - } - - .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { - background-color: mix(@midColor, @endColor, 80%); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); - background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); - background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor); - background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); - background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); - background-repeat: no-repeat; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback - } - .radial(@innerColor: #555, @outerColor: #333) { - background-color: @outerColor; - background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor)); - background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor); - background-image: -moz-radial-gradient(circle, @innerColor, @outerColor); - background-image: -o-radial-gradient(circle, @innerColor, @outerColor); - background-repeat: no-repeat; - } - .striped(@color: #555, @angle: 45deg) { - background-color: @color; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); - } -} -// Reset filters for IE -.reset-filter() { - filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); -} - - - -// COMPONENT MIXINS -// -------------------------------------------------- - -// Horizontal dividers -// ------------------------- -// Dividers (basically an hr) within dropdowns and nav lists -.nav-divider(@top: @gray-8, @bottom: @white) { - // IE7 needs a set width since we gave a height. Restricting just - // to IE7 to keep the 1px left/right space in other browsers. - // It is unclear where IE is getting the extra space that we need - // to negative-margin away, but so it goes. - *width: 100%; - height: 1px; - margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px - *margin: -5px 0 5px; - overflow: hidden; - background-color: @top; - border-bottom: 1px solid @bottom; -} - -// Button backgrounds -// ------------------ -.buttonBackground(@startColor, @endColor, @textColor: #fff) { - // gradientBar will set the background to a pleasing blend of these, to support IE<=9 - .gradientBar(@startColor, @endColor, @textColor); - *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - .reset-filter(); - - // button states - &:hover, &:focus, &:active { - background-color: darken(@startColor, 2%); - } - - // in these cases the gradient won't cover the background, so we override - &:hover, &:focus, &:active, &.active, &.disabled, &[disabled] { - color: @textColor; - *background-color: darken(@endColor, 5%); - } - - // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves - &:active, - &.active { - background-color: darken(@endColor, 10%) e("\9"); - } -} - -// Navbar vertical align -// ------------------------- -// Vertically center elements in the navbar. -// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin. -.navbarVerticalAlign(@elementHeight) { - margin-top: (@navbarHeight - @elementHeight) / 2; -} - - - -// Grid System -// ----------- - -// Centered container element -.container-fixed() { - margin-right: auto; - margin-left: auto; - .clearfix(); -} - -// Table columns -.tableColumns(@columnSpan: 1) { - float: none; // undo default grid column styles - width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells - margin-left: 0; // undo default grid column styles -} - -// Make a Grid -// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior -.makeRow() { - margin-left: @gridGutterWidth * -1; - .clearfix(); -} -.makeColumn(@columns: 1, @offset: 0) { - float: left; - margin-left: (@gridColumnWidth * @offset) + (@gridGutterWidth * (@offset - 1)) + (@gridGutterWidth * 2); - width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); -} - -// The Grid -#grid { - - .core (@gridColumnWidth, @gridGutterWidth) { - - .spanX (@index) when (@index > 0) { - .span@{index} { .span(@index); } - .spanX(@index - 1); - } - .spanX (0) {} - - .offsetX (@index) when (@index > 0) { - .offset@{index} { .offset(@index); } - .offsetX(@index - 1); - } - .offsetX (0) {} - - .offset (@columns) { - margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1)); - } - - .span (@columns) { - width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); - } - - .row { - margin-left: @gridGutterWidth * -1; - .clearfix(); - } - - [class*="span"] { - float: left; - min-height: 1px; // prevent collapsing columns - margin-left: @gridGutterWidth; - } - - // Set the container width, and override it for fixed navbars in media queries - .container, - .navbar-static-top .container, - .navbar-fixed-top .container, - .navbar-fixed-bottom .container { .span(@gridColumns); } - - // generate .spanX and .offsetX - .spanX (@gridColumns); - .offsetX (@gridColumns); - - } - - .fluid (@fluidGridColumnWidth, @fluidGridGutterWidth) { - - .spanX (@index) when (@index > 0) { - .span@{index} { .span(@index); } - .spanX(@index - 1); - } - .spanX (0) {} - - .offsetX (@index) when (@index > 0) { - .offset@{index} { .offset(@index); } - .offset@{index}:first-child { .offsetFirstChild(@index); } - .offsetX(@index - 1); - } - .offsetX (0) {} - - .offset (@columns) { - margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth*2); - *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + (@fluidGridGutterWidth*2) - (.5 / @gridRowWidth * 100 * 1%); - } - - .offsetFirstChild (@columns) { - margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth); - *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); - } - - .span (@columns) { - width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)); - *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%); - } - - .row-fluid { - width: 100%; - .clearfix(); - [class*="span"] { - .input-block-level(); - float: left; - margin-left: @fluidGridGutterWidth; - *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); - } - [class*="span"]:first-child { - margin-left: 0; - } - - // Space grid-sized controls properly if multiple per line - .controls-row [class*="span"] + [class*="span"] { - margin-left: @fluidGridGutterWidth; - } - - // generate .spanX and .offsetX - .spanX (@gridColumns); - .offsetX (@gridColumns); - } - - } - - .input(@gridColumnWidth, @gridGutterWidth) { - - .spanX (@index) when (@index > 0) { - input.span@{index}, textarea.span@{index}, .uneditable-input.span@{index} { .span(@index); } - .spanX(@index - 1); - } - .spanX (0) {} - - .span(@columns) { - width: ((@gridColumnWidth) * @columns) + (@gridGutterWidth * (@columns - 1)) - 14; - } - - input, - textarea, - .uneditable-input { - margin-left: 0; // override margin-left from core grid system - } - - // Space grid-sized controls properly if multiple per line - .controls-row [class*="span"] + [class*="span"] { - margin-left: @gridGutterWidth; - } - - // generate .spanX - .spanX (@gridColumns); - - } -} +// +// Mixins +// -------------------------------------------------- + + +// UTILITY MIXINS +// -------------------------------------------------- + +// Clearfix +// -------- +// For clearing floats like a boss h5bp.com/q +.clearfix { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + // Fixes Opera/contenteditable bug: + // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 + line-height: 0; + } + &:after { + clear: both; + } +} + +// Webkit-style focus +// ------------------ +.tab-focus() { + // Default + outline: thin dotted @gray-3; + // Webkit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Center-align a block level element +// ---------------------------------- +.center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// IE7 inline-block +// ---------------- +.ie7-inline-block() { + *display: inline; /* IE7 inline-block hack */ + *zoom: 1; +} + +// IE7 likes to collapse whitespace on either side of the inline-block elements. +// Ems because we're attempting to match the width of a space character. Left +// version is for form buttons, which typically come after other elements, and +// right version is for icons, which come before. Applying both is ok, but it will +// mean that space between those elements will be .6em (~2 space characters) in IE7, +// instead of the 1 space in other browsers. +.ie7-restore-left-whitespace() { + *margin-left: .3em; + + &:first-child { + *margin-left: 0; + } +} + +.ie7-restore-right-whitespace() { + *margin-right: .3em; +} + +// Sizing shortcuts +// ------------------------- +.size(@height, @width) { + width: @width; + height: @height; +} +.square(@size) { + .size(@size, @size); +} + +// Placeholder text +// ------------------------- +.placeholder(@color: @placeholderText) { + &:-moz-placeholder { + color: @color; + } + &:-ms-input-placeholder { + color: @color; + } + &::-webkit-input-placeholder { + color: @color; + } +} + +// Text overflow +// ------------------------- +// Requires inline-block or block for proper styling +.text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// CSS image replacement +// ------------------------- +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + + +// FONTS +// -------------------------------------------------- + +#font { + #family { + .serif() { + font-family: @serifFontFamily; + } + .sans-serif() { + font-family: @sansFontFamily; + } + .monospace() { + font-family: @monoFontFamily; + } + } + .shorthand(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + font-size: @size; + font-weight: @weight; + line-height: @lineHeight; + } + .serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .sans-serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .sans-serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .monospace(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .monospace; + #font > .shorthand(@size, @weight, @lineHeight); + } +} + + +// FORMS +// -------------------------------------------------- + +// Block level inputs +.input-block-level { + display: block; + width: 100%; + min-height: @inputHeight; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + .box-sizing(border-box); // Makes inputs behave like true block-level elements +} + + + +// Mixin for form field states +//SD: I've had to modify this slightly to work nicely with angular validation , note the +// additional targetting of the ng-invalid class. +.formFieldState(@textColor: @gray-4, @borderColor: @gray-7, @backgroundColor: @gray-10) { + // Set the text color + .control-label, + .help-block, + .help-inline { + color: @textColor; + } + // Style inputs accordingly + .checkbox.ng-invalid, + .radio.ng-invalid, + input.ng-invalid, + select.ng-invalid, + textarea.ng-invalid { + color: @textColor; + } + input.ng-invalid, + select.ng-invalid, + textarea.ng-invalid { + border-color: @borderColor; + } + // Give a small background color for input-prepend/-append + .input-prepend .add-on, + .input-append .add-on { + color: @textColor; + background-color: @backgroundColor; + border-color: @textColor; + } + //SD: We could do this but need to get the colors right + /*input.ng-invalid { + &:-moz-placeholder { + color: lighten(@textColor, 50%) !important; + } + &:-ms-input-placeholder { + color: lighten(@textColor, 50%) !important; + } + &::-webkit-input-placeholder { + color: lighten(@textColor, 50%) !important; + } + }*/ +} + +// CSS3 PROPERTIES +// -------------------------------------------------- + +// Border Radius +.border-radius(@radius) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} + +// Single Corner Border Radius +.border-top-left-radius(@radius) { + -webkit-border-top-left-radius: @radius; + -moz-border-radius-topleft: @radius; + border-top-left-radius: @radius; +} +.border-top-right-radius(@radius) { + -webkit-border-top-right-radius: @radius; + -moz-border-radius-topright: @radius; + border-top-right-radius: @radius; +} +.border-bottom-right-radius(@radius) { + -webkit-border-bottom-right-radius: @radius; + -moz-border-radius-bottomright: @radius; + border-bottom-right-radius: @radius; +} +.border-bottom-left-radius(@radius) { + -webkit-border-bottom-left-radius: @radius; + -moz-border-radius-bottomleft: @radius; + border-bottom-left-radius: @radius; +} + +// Single Side Border Radius +.border-top-radius(@radius) { + .border-top-right-radius(@radius); + .border-top-left-radius(@radius); +} +.border-right-radius(@radius) { + .border-top-right-radius(@radius); + .border-bottom-right-radius(@radius); +} +.border-bottom-radius(@radius) { + .border-bottom-right-radius(@radius); + .border-bottom-left-radius(@radius); +} +.border-left-radius(@radius) { + .border-top-left-radius(@radius); + .border-bottom-left-radius(@radius); +} + +// Drop shadows +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; + -moz-box-shadow: @shadow; + box-shadow: @shadow; +} + +// Transitions +.transition(@transition) { + -webkit-transition: @transition; + -moz-transition: @transition; + -o-transition: @transition; + transition: @transition; +} +.transition-delay(@transition-delay) { + -webkit-transition-delay: @transition-delay; + -moz-transition-delay: @transition-delay; + -o-transition-delay: @transition-delay; + transition-delay: @transition-delay; +} +.transition-duration(@transition-duration) { + -webkit-transition-duration: @transition-duration; + -moz-transition-duration: @transition-duration; + -o-transition-duration: @transition-duration; + transition-duration: @transition-duration; +} + +// Transformations +.rotate(@degrees) { + -webkit-transform: rotate(@degrees); + -moz-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); + -o-transform: rotate(@degrees); + transform: rotate(@degrees); +} +.scale(@ratio) { + -webkit-transform: scale(@ratio); + -moz-transform: scale(@ratio); + -ms-transform: scale(@ratio); + -o-transform: scale(@ratio); + transform: scale(@ratio); +} +.translate(@x, @y) { + -webkit-transform: translate(@x, @y); + -moz-transform: translate(@x, @y); + -ms-transform: translate(@x, @y); + -o-transform: translate(@x, @y); + transform: translate(@x, @y); +} +.skew(@x, @y) { + -webkit-transform: skew(@x, @y); + -moz-transform: skew(@x, @y); + -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twitter/bootstrap/issues/4885 + -o-transform: skew(@x, @y); + transform: skew(@x, @y); + -webkit-backface-visibility: hidden; // See https://github.com/twitter/bootstrap/issues/5319 +} +.translate3d(@x, @y, @z) { + -webkit-transform: translate3d(@x, @y, @z); + -moz-transform: translate3d(@x, @y, @z); + -o-transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden +// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples +.backface-visibility(@visibility){ + -webkit-backface-visibility: @visibility; + -moz-backface-visibility: @visibility; + backface-visibility: @visibility; +} + +// Background clipping +// Heads up: FF 3.6 and under need "padding" instead of "padding-box" +.background-clip(@clip) { + -webkit-background-clip: @clip; + -moz-background-clip: @clip; + background-clip: @clip; +} + +// Background sizing +.background-size(@size) { + -webkit-background-size: @size; + -moz-background-size: @size; + -o-background-size: @size; + background-size: @size; +} + + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + box-sizing: @boxmodel; +} + +// User select +// For selecting text on the page +.user-select(@select) { + -webkit-user-select: @select; + -moz-user-select: @select; + -ms-user-select: @select; + -o-user-select: @select; + user-select: @select; +} + +// Resize anything +.resizable(@direction) { + resize: @direction; // Options: horizontal, vertical, both + overflow: auto; // Safari fix +} + +// CSS3 Content Columns +.content-columns(@columnCount, @columnGap: @gridGutterWidth) { + -webkit-column-count: @columnCount; + -moz-column-count: @columnCount; + column-count: @columnCount; + -webkit-column-gap: @columnGap; + -moz-column-gap: @columnGap; + column-gap: @columnGap; +} + +// Optional hyphenation +.hyphens(@mode: auto) { + word-wrap: break-word; + -webkit-hyphens: @mode; + -moz-hyphens: @mode; + -ms-hyphens: @mode; + -o-hyphens: @mode; + hyphens: @mode; +} + +// Opacity +.opacity(@opacity) { + opacity: @opacity / 100; + filter: ~"alpha(opacity=@{opacity})"; +} + + + +// BACKGROUNDS +// -------------------------------------------------- + +// Add an alphatransparency value to any background or border color (via Elyse Holladay) +#translucent { + .background(@color: @white, @alpha: 1) { + background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + } + .border(@color: @white, @alpha: 1) { + border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + .background-clip(padding-box); + } +} + +// Gradient Bar Colors for buttons and alerts +.gradientBar(@primaryColor, @secondaryColor, @textColor: #fff) { + color: @textColor; + #gradient > .vertical(@primaryColor, @secondaryColor); + border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); +} + +// Gradients +#gradient { + .horizontal(@startColor: #555, @endColor: #333) { + background-color: @endColor; + background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down + } + .vertical(@startColor: #555, @endColor: #333) { + background-color: mix(@startColor, @endColor, 60%); + background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down + } + .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { + background-color: @endColor; + background-repeat: repeat-x; + background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10 + } + .horizontal-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + background-color: mix(@midColor, @endColor, 80%); + background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); + background-image: -webkit-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: -moz-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: -o-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: linear-gradient(to right, @startColor, @midColor @colorStop, @endColor); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback + } + + .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + background-color: mix(@midColor, @endColor, 80%); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); + background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor); + background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback + } + .radial(@innerColor: #555, @outerColor: #333) { + background-color: @outerColor; + background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor)); + background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor); + background-image: -moz-radial-gradient(circle, @innerColor, @outerColor); + background-image: -o-radial-gradient(circle, @innerColor, @outerColor); + background-repeat: no-repeat; + } + .striped(@color: #555, @angle: 45deg) { + background-color: @color; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + } +} +// Reset filters for IE +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} + + + +// COMPONENT MIXINS +// -------------------------------------------------- + +// Horizontal dividers +// ------------------------- +// Dividers (basically an hr) within dropdowns and nav lists +.nav-divider(@top: @gray-8, @bottom: @white) { + // IE7 needs a set width since we gave a height. Restricting just + // to IE7 to keep the 1px left/right space in other browsers. + // It is unclear where IE is getting the extra space that we need + // to negative-margin away, but so it goes. + *width: 100%; + height: 1px; + margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px + *margin: -5px 0 5px; + overflow: hidden; + background-color: @top; + border-bottom: 1px solid @bottom; +} + +// Button backgrounds +// ------------------ +.buttonBackground(@startColor, @endColor, @textColor: #fff) { + // gradientBar will set the background to a pleasing blend of these, to support IE<=9 + .gradientBar(@startColor, @endColor, @textColor); + *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + .reset-filter(); + + // button states + &:hover, &:focus, &:active { + background-color: darken(@startColor, 2%); + } + + // in these cases the gradient won't cover the background, so we override + &:hover, &:focus, &:active, &.active, &.disabled, &[disabled] { + color: @textColor; + *background-color: darken(@endColor, 5%); + } + + // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves + &:active, + &.active { + background-color: darken(@endColor, 10%) e("\9"); + } +} + +// Navbar vertical align +// ------------------------- +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin. +.navbarVerticalAlign(@elementHeight) { + margin-top: (@navbarHeight - @elementHeight) / 2; +} + + + +// Grid System +// ----------- + +// Centered container element +.container-fixed() { + margin-right: auto; + margin-left: auto; + .clearfix(); +} + +// Table columns +.tableColumns(@columnSpan: 1) { + float: none; // undo default grid column styles + width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells + margin-left: 0; // undo default grid column styles +} + +// Make a Grid +// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior +.makeRow() { + margin-left: @gridGutterWidth * -1; + .clearfix(); +} +.makeColumn(@columns: 1, @offset: 0) { + float: left; + margin-left: (@gridColumnWidth * @offset) + (@gridGutterWidth * (@offset - 1)) + (@gridGutterWidth * 2); + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); +} + +// The Grid +#grid { + + .core (@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + .span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + .offset@{index} { .offset(@index); } + .offsetX(@index - 1); + } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1)); + } + + .span (@columns) { + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); + } + + .row { + margin-left: @gridGutterWidth * -1; + .clearfix(); + } + + [class*="span"] { + float: left; + min-height: 1px; // prevent collapsing columns + margin-left: @gridGutterWidth; + } + + // Set the container width, and override it for fixed navbars in media queries + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { .span(@gridColumns); } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + + } + + .fluid (@fluidGridColumnWidth, @fluidGridGutterWidth) { + + .spanX (@index) when (@index > 0) { + .span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + .offset@{index} { .offset(@index); } + .offset@{index}:first-child { .offsetFirstChild(@index); } + .offsetX(@index - 1); + } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth*2); + *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + (@fluidGridGutterWidth*2) - (.5 / @gridRowWidth * 100 * 1%); + } + + .offsetFirstChild (@columns) { + margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth); + *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + + .span (@columns) { + width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)); + *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%); + } + + .row-fluid { + width: 100%; + .clearfix(); + [class*="span"] { + .input-block-level(); + float: left; + margin-left: @fluidGridGutterWidth; + *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + [class*="span"]:first-child { + margin-left: 0; + } + + // Space grid-sized controls properly if multiple per line + .controls-row [class*="span"] + [class*="span"] { + margin-left: @fluidGridGutterWidth; + } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + } + + } + + .input(@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + input.span@{index}, textarea.span@{index}, .uneditable-input.span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .span(@columns) { + width: ((@gridColumnWidth) * @columns) + (@gridGutterWidth * (@columns - 1)) - 14; + } + + input, + textarea, + .uneditable-input { + margin-left: 0; // override margin-left from core grid system + } + + // Space grid-sized controls properly if multiple per line + .controls-row [class*="span"] + [class*="span"] { + margin-left: @gridGutterWidth; + } + + // generate .spanX + .spanX (@gridColumns); + + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 369cb5c50c..5a85c19124 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -1,196 +1,196 @@ -/* Modals -// -------------------------*/ - -/* Modalcolumn is used for menu panels */ -.umb-modalcolumn { - background: @white; -} - -.umb-modalcolumn-header { - border-bottom: 1px solid @gray-9; - height: @editorHeaderHeight; - box-sizing: border-box; - padding: 0 20px; - display: flex; - align-items: center; - white-space: nowrap -} - -.umb-modalcolumn-header h1{ - margin: 0; - white-space: nowrap; - font-size: @fontSizeLarge; - font-weight: 400; -} - -.umb-modalcolumn-body { - padding: 0px; - background: @white; - top: @editorHeaderHeight; - position: absolute; - left: 0px; - right: 0px; - bottom: 0px; - overflow: auto; -} - -.no-padding .umb-modalcolumn-body { - padding: 0px -} - -.umb-modalcolumn .umb-modalcolumn-header .btn { - position: absolute; - top: 13px; - right: 15px -} - -.umb-modalcolumn iframe.auto-expand, .umb-modal iframe.auto-expand { - border: none; - padding: 0px; - margin: 0px; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; - position: absolute;; -} - -/* re-align loader */ -.umb-modal .umb-loader-wrapper, .umb-modalcolumn .umb-loader-wrapper, .umb-dialog .umb-loader-wrapper{ - position:relative; - margin: 20px -20px; -} - -.umb-modal-left .umb-panel-header .umb-headline, .umb-modal-left .umb-panel-header h1 { - width: auto; - padding-left: 0; -} - -/* umb.dialog is used for the dialogs on the conent tree*/ -.umb-dialog { - outline: none; - top: 0px; - left: 0px; - right: 0px; - bottom: 0px; - position: absolute; - padding: 0px; -} - -.umb-dialog .umb-btn-toolbar .umb-control-group{ - border: none; - padding: none; -} - -.umb-dialog-body{ - position: absolute; - overflow:auto; - top: 0px; - left: 0px; - right: 0px; - bottom: 52px; -} -.umb-dialog-body .umb-pane{margin-top: 15px;} - -.umb-dialog-footer{ - position: absolute; - overflow:auto; - text-align: right; - height: 32px; - left: 0px; - right: 0px; - bottom: 0px; - padding: 20px; - margin: 0; -} - -/*we will always make sure to wrap iframe dialogs in proper padding*/ -.umbracoDialog{ - width: auto !Important; - height: auto !Important; - padding: 20px; -} -.umbracoDialog .umb-pane{margin-left: 0px; margin-right: 0px; margin-top: 0px;} -.umbracoDialog .umb-dialog-body .umb-pane{margin-left: 20px; margin-right: 20px; margin-top: 20px;} -.umbracoDialog form{height: 100%;} - -/*ensures dialogs doesnt have side-by-side labels*/ -.umbracoDialog .controls-row, -.umb-modal .controls-row{margin-left: 0px !important;} - -/* modal and umb-modal are used for right.hand dialogs */ -.modal { - border-radius: 0 !important; - - &.fade.in{border: none !important;} -} -.umb-modal.fade { - outline: none; - top: 0 !important; - left: -100% !important; - width: 0px !important; - -webkit-transition: left 0.3s linear, left 0.3s ease-out; - -moz-transition: opacity 0.3s linear, top 0.3s ease-out; - -o-transition: opacity 0.3s linear, top 0.3s ease-out; - transition: opacity 0.3s linear, top 0.3s ease-out; - height: 100% !important; -} - -.umb-modal.fade.in { - top: 0 !important; - left: 100% !important; - margin-left: -440px; - width: 440px !important; - height: 100% !important; - display: block; -} - -.umb-modal-left.fade { - top: 0 !important; - left: -100% !important; - width: 0px !important; - -webkit-transition: left 0.3s linear, left 0.3s ease-out; - -moz-transition: opacity 0.3s linear, top 0.3s ease-out; - -o-transition: opacity 0.3s linear, top 0.3s ease-out; - transition: opacity 0.3s linear, top 0.3s ease-out; - height: 100% !important; -} -.umb-modal-left.fade.in { - top: 0 !important; - left: 0 !important; - margin-left: 80px; - width: 440px !important; - height: 100% !important; - display: block; -} - -/*Modal default panel styles*/ -.umb-modal .umb-panel-header { - padding: 20px; - background: @white; - border: none; - height: auto; -} -.umb-modal .umb-panel-body{ - padding: 0px 20px 0px 20px; -} - -.umb-modal.fade.in.wide { - margin-left: -640px; - width: 640px !important; -} -.umb-modal i { - font-size: 20px; -} -.umb-modal .breadcrumb { - background: none; - padding: 0 -} - -.umb-modal .breadcrumb input { - height: 12px -} - -.umb-modal.ysod { - z-index: 10000; -} +/* Modals +// -------------------------*/ + +/* Modalcolumn is used for menu panels */ +.umb-modalcolumn { + background: @white; +} + +.umb-modalcolumn-header { + border-bottom: 1px solid @gray-9; + height: @editorHeaderHeight; + box-sizing: border-box; + padding: 0 20px; + display: flex; + align-items: center; + white-space: nowrap +} + +.umb-modalcolumn-header h1{ + margin: 0; + white-space: nowrap; + font-size: @fontSizeLarge; + font-weight: 400; +} + +.umb-modalcolumn-body { + padding: 0px; + background: @white; + top: @editorHeaderHeight; + position: absolute; + left: 0px; + right: 0px; + bottom: 0px; + overflow: auto; +} + +.no-padding .umb-modalcolumn-body { + padding: 0px +} + +.umb-modalcolumn .umb-modalcolumn-header .btn { + position: absolute; + top: 13px; + right: 15px +} + +.umb-modalcolumn iframe.auto-expand, .umb-modal iframe.auto-expand { + border: none; + padding: 0px; + margin: 0px; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + position: absolute;; +} + +/* re-align loader */ +.umb-modal .umb-loader-wrapper, .umb-modalcolumn .umb-loader-wrapper, .umb-dialog .umb-loader-wrapper{ + position:relative; + margin: 20px -20px; +} + +.umb-modal-left .umb-panel-header .umb-headline, .umb-modal-left .umb-panel-header h1 { + width: auto; + padding-left: 0; +} + +/* umb.dialog is used for the dialogs on the conent tree*/ +.umb-dialog { + outline: none; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + position: absolute; + padding: 0px; +} + +.umb-dialog .umb-btn-toolbar .umb-control-group{ + border: none; + padding: none; +} + +.umb-dialog-body{ + position: absolute; + overflow:auto; + top: 0px; + left: 0px; + right: 0px; + bottom: 52px; +} +.umb-dialog-body .umb-pane{margin-top: 15px;} + +.umb-dialog-footer{ + position: absolute; + overflow:auto; + text-align: right; + height: 32px; + left: 0px; + right: 0px; + bottom: 0px; + padding: 20px; + margin: 0; +} + +/*we will always make sure to wrap iframe dialogs in proper padding*/ +.umbracoDialog{ + width: auto !Important; + height: auto !Important; + padding: 20px; +} +.umbracoDialog .umb-pane{margin-left: 0px; margin-right: 0px; margin-top: 0px;} +.umbracoDialog .umb-dialog-body .umb-pane{margin-left: 20px; margin-right: 20px; margin-top: 20px;} +.umbracoDialog form{height: 100%;} + +/*ensures dialogs doesnt have side-by-side labels*/ +.umbracoDialog .controls-row, +.umb-modal .controls-row{margin-left: 0px !important;} + +/* modal and umb-modal are used for right.hand dialogs */ +.modal { + border-radius: 0 !important; + + &.fade.in{border: none !important;} +} +.umb-modal.fade { + outline: none; + top: 0 !important; + left: -100% !important; + width: 0px !important; + -webkit-transition: left 0.3s linear, left 0.3s ease-out; + -moz-transition: opacity 0.3s linear, top 0.3s ease-out; + -o-transition: opacity 0.3s linear, top 0.3s ease-out; + transition: opacity 0.3s linear, top 0.3s ease-out; + height: 100% !important; +} + +.umb-modal.fade.in { + top: 0 !important; + left: 100% !important; + margin-left: -440px; + width: 440px !important; + height: 100% !important; + display: block; +} + +.umb-modal-left.fade { + top: 0 !important; + left: -100% !important; + width: 0px !important; + -webkit-transition: left 0.3s linear, left 0.3s ease-out; + -moz-transition: opacity 0.3s linear, top 0.3s ease-out; + -o-transition: opacity 0.3s linear, top 0.3s ease-out; + transition: opacity 0.3s linear, top 0.3s ease-out; + height: 100% !important; +} +.umb-modal-left.fade.in { + top: 0 !important; + left: 0 !important; + margin-left: 80px; + width: 440px !important; + height: 100% !important; + display: block; +} + +/*Modal default panel styles*/ +.umb-modal .umb-panel-header { + padding: 20px; + background: @white; + border: none; + height: auto; +} +.umb-modal .umb-panel-body{ + padding: 0px 20px 0px 20px; +} + +.umb-modal.fade.in.wide { + margin-left: -640px; + width: 640px !important; +} +.umb-modal i { + font-size: 20px; +} +.umb-modal .breadcrumb { + background: none; + padding: 0 +} + +.umb-modal .breadcrumb input { + height: 12px +} + +.umb-modal.ysod { + z-index: 10000; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less index dad5107f69..08c1942554 100644 --- a/src/Umbraco.Web.UI.Client/src/less/navs.less +++ b/src/Umbraco.Web.UI.Client/src/less/navs.less @@ -1,429 +1,429 @@ -// -// Navs -// -------------------------------------------------- - -.list-icons li{ - padding-left: 35px; - max-width: 300px -} - -.list-icons li > i.icon{ - margin-left: -25px; - padding-right: 7px; -} - -.icon.handle{color: @gray-8;} - - -// BASE CLASS -// ---------- - -.nav { - margin-left: 0; - margin-bottom: @baseLineHeight; - list-style: none; -} - -// Make links block level -.nav > li > a { - display: block; -} -.nav > li > a:hover, -.nav > li > a:focus { - text-decoration: none; - background-color: @gray-10; -} - -// Prevent IE8 from misplacing imgs -// See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989 -.nav > li > a > img { - max-width: none; -} - -// Redeclare pull classes because of specifity -.nav > .pull-right { - float: right; -} - -// Nav headers (for dropdowns and lists) -.nav-header { - display: block; - padding: 3px 15px; - font-size: 12px; - font-weight: bold; - line-height: @baseLineHeight; - color: @gray-8; - text-transform: uppercase; -} -// Space them out when they follow another list item (link) -.nav li + .nav-header { - margin-top: 9px; -} - - - -// NAV LIST -// -------- - -.nav-list { - padding-left: 15px; - padding-right: 15px; - margin-bottom: 0; -} -.nav-list > li > a, -.nav-list .nav-header { - margin-left: -15px; - margin-right: -15px; -} -.nav-list > li > a { - padding: 3px 15px; -} -.nav-list > .active > a, -.nav-list > .active > a:hover, -.nav-list > .active > a:focus { - color: @white; - background-color: @linkColor; -} -.nav-list [class^="icon-"], -.nav-list [class*=" icon-"] { - margin-right: 2px; -} -// Dividers (basically an hr) within the dropdown -.nav-list .divider { - .nav-divider(); -} - - - -// TABS AND PILLS -// ------------- - -// Common styles -.nav-tabs, -.nav-pills { - .clearfix(); -} -.nav-tabs > li, -.nav-pills > li { - float: left; -} -.nav-tabs > li > a, -.nav-pills > li > a { - margin-right: 15px; -} - -// TABS -// ---- - -// Give the tabs something to sit on -.nav-tabs { - // border-bottom: 1px solid @gray-9; -} -// Make the list-items overlay the bottom border -.nav-tabs > li { - // margin-bottom: -1px; -} -// Actual tabs (as links) -.nav-tabs > li > a { - color: @gray-3; - border-bottom: 2px solid transparent; - padding-bottom: 15px; - - &:hover { - color: @black; - } - &:hover, - &:focus { - // border-color: transparent transparent @purple-l3; - } -} -// Active state, and it's :hover/:focus to override normal :hover/:focus -.nav-tabs > .active > a, -.nav-tabs > .active > a:hover, -.nav-tabs > .active > a:focus { - color: @black; - border-bottom-color: @turquoise; - cursor: default; -} - -// PILLS -// ----- - -// Links rendered as pills -.nav-pills > li > a { - padding-top: 8px; - padding-bottom: 8px; - margin-top: 2px; - margin-bottom: 2px; - .border-radius(5px); -} - -// Active state -.nav-pills > .active > a, -.nav-pills > .active > a:hover, -.nav-pills > .active > a:focus { - color: @white; - background-color: @linkColor; -} - - - -// STACKED NAV -// ----------- - -// Stacked tabs and pills -.nav-stacked > li { - float: none; -} -.nav-stacked > li > a { - margin-right: 0; // no need for the gap between nav items -} - -// Tabs -.nav-tabs.nav-stacked { - border-bottom: 0; -} -.nav-tabs.nav-stacked > li > a { - border: 1px solid @gray-8; - .border-radius(0); -} -.nav-tabs.nav-stacked > li:first-child > a { - .border-top-radius(4px); -} -.nav-tabs.nav-stacked > li:last-child > a { - .border-bottom-radius(4px); -} -.nav-tabs.nav-stacked > li > a:hover, -.nav-tabs.nav-stacked > li > a:focus { - border-color: @gray-8; - z-index: 2; -} - -// Pills -.nav-pills.nav-stacked > li > a { - margin-bottom: 3px; -} -.nav-pills.nav-stacked > li:last-child > a { - margin-bottom: 1px; // decrease margin to match sizing of stacked tabs -} - - - -// DROPDOWNS -// --------- -.dropdown-menu { - border-radius: @dropdownBorderRadius; - box-shadow: 0 5px 20px rgba(0,0,0,.3); - padding-top: 0; - padding-bottom: 0; -} - -// fix dropdown with checkbox + long text in label -.dropdown-menu > li > .flex > label { - flex: 1 1 0; -} - -.dropdown-menu > li > a { - padding: 8px 20px; -} - -.nav-tabs .dropdown-menu { - .border-radius(0 0 3px 3px); // remove the top rounded corners here since there is a hard edge above the menu -} -.nav-pills .dropdown-menu { - .border-radius(6px); // make rounded corners match the pills -} - -// Default dropdown links -// ------------------------- -// Make carets use linkColor to start -.nav .dropdown-toggle .caret { - border-top-color: @linkColor; - border-bottom-color: @linkColor; - margin-top: 6px; -} -.nav .dropdown-toggle:hover .caret, -.nav .dropdown-toggle:focus .caret { - border-top-color: @linkColorHover; - border-bottom-color: @linkColorHover; -} -/* move down carets for tabs */ -.nav-tabs .dropdown-toggle .caret { - margin-top: 8px; -} - -// Active dropdown links -// ------------------------- -.nav .active .dropdown-toggle .caret { - border-top-color: @white; - border-bottom-color: @white; -} -.nav-tabs .active .dropdown-toggle .caret { - border-top-color: @gray-3; - border-bottom-color: @gray-3; -} - -// Active:hover/:focus dropdown links -// ------------------------- -.nav > .dropdown.active > a:hover, -.nav > .dropdown.active > a:focus { - cursor: pointer; -} - -// Open dropdowns -// ------------------------- -.nav-tabs .open .dropdown-toggle, -.nav-pills .open .dropdown-toggle, -.nav > li.dropdown.open.active > a:hover, -.nav > li.dropdown.open.active > a:focus { - /*color: @white;*/ - background-color: @gray-8; - border-color: @gray-8; -} -.nav li.dropdown.open .caret, -.nav li.dropdown.open.active .caret, -.nav li.dropdown.open a:hover .caret, -.nav li.dropdown.open a:focus .caret { - border-top-color: @white; - border-bottom-color: @white; - .opacity(100); -} - -// Dropdowns in stacked tabs -.tabs-stacked .open > a:hover, -.tabs-stacked .open > a:focus { - border-color: @gray-8; -} - - - -// TABBABLE -// -------- - - -// COMMON STYLES -// ------------- - -// Clear any floats -.tabbable { - .clearfix(); -} -.tab-content { - overflow: auto; // prevent content from running below tabs -} - -// Remove border on bottom, left, right -.tabs-below > .nav-tabs, -.tabs-right > .nav-tabs, -.tabs-left > .nav-tabs { - border-bottom: 0; -} - -// Show/hide tabbable areas -.tab-content > .tab-pane, -.pill-content > .pill-pane { - display: none; -} -.tab-content > .active, -.pill-content > .active { - display: block; -} - - -// BOTTOM -// ------ - -.tabs-below > .nav-tabs { - border-top: 1px solid @gray-8; -} -.tabs-below > .nav-tabs > li { - margin-top: -1px; - margin-bottom: 0; -} -.tabs-below > .nav-tabs > li > a { - .border-radius(0 0 4px 4px); - &:hover, - &:focus { - border-bottom-color: transparent; - border-top-color: @gray-8; - } -} -.tabs-below > .nav-tabs > .active > a, -.tabs-below > .nav-tabs > .active > a:hover, -.tabs-below > .nav-tabs > .active > a:focus { - border-color: transparent @gray-8 @gray-8 @gray-8; -} - -// LEFT & RIGHT -// ------------ - -// Common styles -.tabs-left > .nav-tabs > li, -.tabs-right > .nav-tabs > li { - float: none; -} -.tabs-left > .nav-tabs > li > a, -.tabs-right > .nav-tabs > li > a { - min-width: 74px; - margin-right: 0; - margin-bottom: 3px; -} - -// Tabs on the left -.tabs-left > .nav-tabs { - float: left; - margin-right: 19px; - border-right: 1px solid @gray-8; -} -.tabs-left > .nav-tabs > li > a { - margin-right: -1px; - .border-radius(4px 0 0 4px); -} -.tabs-left > .nav-tabs > li > a:hover, -.tabs-left > .nav-tabs > li > a:focus { - border-color: @gray-10 @gray-8 @gray-10 @gray-10; -} -.tabs-left > .nav-tabs .active > a, -.tabs-left > .nav-tabs .active > a:hover, -.tabs-left > .nav-tabs .active > a:focus { - border-color: @gray-8 transparent @gray-8 @gray-8; - *border-right-color: @white; -} - -// Tabs on the right -.tabs-right > .nav-tabs { - float: right; - margin-left: 19px; - border-left: 1px solid @gray-8; -} -.tabs-right > .nav-tabs > li > a { - margin-left: -1px; - .border-radius(0 4px 4px 0); -} -.tabs-right > .nav-tabs > li > a:hover, -.tabs-right > .nav-tabs > li > a:focus { - border-color: @gray-10 @gray-10 @gray-10 @gray-8; -} -.tabs-right > .nav-tabs .active > a, -.tabs-right > .nav-tabs .active > a:hover, -.tabs-right > .nav-tabs .active > a:focus { - border-color: @gray-8 @gray-8 @gray-8 transparent; - *border-left-color: @white; -} - - - -// DISABLED STATES -// --------------- - -// Gray out text -.nav > .disabled > a { - color: @gray-8; -} -// Nuke hover/focus effects -.nav > .disabled > a:hover, -.nav > .disabled > a:focus { - text-decoration: none; - background-color: transparent; - cursor: default; -} +// +// Navs +// -------------------------------------------------- + +.list-icons li{ + padding-left: 35px; + max-width: 300px +} + +.list-icons li > i.icon{ + margin-left: -25px; + padding-right: 7px; +} + +.icon.handle{color: @gray-8;} + + +// BASE CLASS +// ---------- + +.nav { + margin-left: 0; + margin-bottom: @baseLineHeight; + list-style: none; +} + +// Make links block level +.nav > li > a { + display: block; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: @gray-10; +} + +// Prevent IE8 from misplacing imgs +// See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989 +.nav > li > a > img { + max-width: none; +} + +// Redeclare pull classes because of specifity +.nav > .pull-right { + float: right; +} + +// Nav headers (for dropdowns and lists) +.nav-header { + display: block; + padding: 3px 15px; + font-size: 12px; + font-weight: bold; + line-height: @baseLineHeight; + color: @gray-8; + text-transform: uppercase; +} +// Space them out when they follow another list item (link) +.nav li + .nav-header { + margin-top: 9px; +} + + + +// NAV LIST +// -------- + +.nav-list { + padding-left: 15px; + padding-right: 15px; + margin-bottom: 0; +} +.nav-list > li > a, +.nav-list .nav-header { + margin-left: -15px; + margin-right: -15px; +} +.nav-list > li > a { + padding: 3px 15px; +} +.nav-list > .active > a, +.nav-list > .active > a:hover, +.nav-list > .active > a:focus { + color: @white; + background-color: @linkColor; +} +.nav-list [class^="icon-"], +.nav-list [class*=" icon-"] { + margin-right: 2px; +} +// Dividers (basically an hr) within the dropdown +.nav-list .divider { + .nav-divider(); +} + + + +// TABS AND PILLS +// ------------- + +// Common styles +.nav-tabs, +.nav-pills { + .clearfix(); +} +.nav-tabs > li, +.nav-pills > li { + float: left; +} +.nav-tabs > li > a, +.nav-pills > li > a { + margin-right: 15px; +} + +// TABS +// ---- + +// Give the tabs something to sit on +.nav-tabs { + // border-bottom: 1px solid @gray-9; +} +// Make the list-items overlay the bottom border +.nav-tabs > li { + // margin-bottom: -1px; +} +// Actual tabs (as links) +.nav-tabs > li > a { + color: @gray-3; + border-bottom: 2px solid transparent; + padding-bottom: 15px; + + &:hover { + color: @black; + } + &:hover, + &:focus { + // border-color: transparent transparent @purple-l3; + } +} +// Active state, and it's :hover/:focus to override normal :hover/:focus +.nav-tabs > .active > a, +.nav-tabs > .active > a:hover, +.nav-tabs > .active > a:focus { + color: @black; + border-bottom-color: @turquoise; + cursor: default; +} + +// PILLS +// ----- + +// Links rendered as pills +.nav-pills > li > a { + padding-top: 8px; + padding-bottom: 8px; + margin-top: 2px; + margin-bottom: 2px; + .border-radius(5px); +} + +// Active state +.nav-pills > .active > a, +.nav-pills > .active > a:hover, +.nav-pills > .active > a:focus { + color: @white; + background-color: @linkColor; +} + + + +// STACKED NAV +// ----------- + +// Stacked tabs and pills +.nav-stacked > li { + float: none; +} +.nav-stacked > li > a { + margin-right: 0; // no need for the gap between nav items +} + +// Tabs +.nav-tabs.nav-stacked { + border-bottom: 0; +} +.nav-tabs.nav-stacked > li > a { + border: 1px solid @gray-8; + .border-radius(0); +} +.nav-tabs.nav-stacked > li:first-child > a { + .border-top-radius(4px); +} +.nav-tabs.nav-stacked > li:last-child > a { + .border-bottom-radius(4px); +} +.nav-tabs.nav-stacked > li > a:hover, +.nav-tabs.nav-stacked > li > a:focus { + border-color: @gray-8; + z-index: 2; +} + +// Pills +.nav-pills.nav-stacked > li > a { + margin-bottom: 3px; +} +.nav-pills.nav-stacked > li:last-child > a { + margin-bottom: 1px; // decrease margin to match sizing of stacked tabs +} + + + +// DROPDOWNS +// --------- +.dropdown-menu { + border-radius: @dropdownBorderRadius; + box-shadow: 0 5px 20px rgba(0,0,0,.3); + padding-top: 0; + padding-bottom: 0; +} + +// fix dropdown with checkbox + long text in label +.dropdown-menu > li > .flex > label { + flex: 1 1 0; +} + +.dropdown-menu > li > a { + padding: 8px 20px; +} + +.nav-tabs .dropdown-menu { + .border-radius(0 0 3px 3px); // remove the top rounded corners here since there is a hard edge above the menu +} +.nav-pills .dropdown-menu { + .border-radius(6px); // make rounded corners match the pills +} + +// Default dropdown links +// ------------------------- +// Make carets use linkColor to start +.nav .dropdown-toggle .caret { + border-top-color: @linkColor; + border-bottom-color: @linkColor; + margin-top: 6px; +} +.nav .dropdown-toggle:hover .caret, +.nav .dropdown-toggle:focus .caret { + border-top-color: @linkColorHover; + border-bottom-color: @linkColorHover; +} +/* move down carets for tabs */ +.nav-tabs .dropdown-toggle .caret { + margin-top: 8px; +} + +// Active dropdown links +// ------------------------- +.nav .active .dropdown-toggle .caret { + border-top-color: @white; + border-bottom-color: @white; +} +.nav-tabs .active .dropdown-toggle .caret { + border-top-color: @gray-3; + border-bottom-color: @gray-3; +} + +// Active:hover/:focus dropdown links +// ------------------------- +.nav > .dropdown.active > a:hover, +.nav > .dropdown.active > a:focus { + cursor: pointer; +} + +// Open dropdowns +// ------------------------- +.nav-tabs .open .dropdown-toggle, +.nav-pills .open .dropdown-toggle, +.nav > li.dropdown.open.active > a:hover, +.nav > li.dropdown.open.active > a:focus { + /*color: @white;*/ + background-color: @gray-8; + border-color: @gray-8; +} +.nav li.dropdown.open .caret, +.nav li.dropdown.open.active .caret, +.nav li.dropdown.open a:hover .caret, +.nav li.dropdown.open a:focus .caret { + border-top-color: @white; + border-bottom-color: @white; + .opacity(100); +} + +// Dropdowns in stacked tabs +.tabs-stacked .open > a:hover, +.tabs-stacked .open > a:focus { + border-color: @gray-8; +} + + + +// TABBABLE +// -------- + + +// COMMON STYLES +// ------------- + +// Clear any floats +.tabbable { + .clearfix(); +} +.tab-content { + overflow: auto; // prevent content from running below tabs +} + +// Remove border on bottom, left, right +.tabs-below > .nav-tabs, +.tabs-right > .nav-tabs, +.tabs-left > .nav-tabs { + border-bottom: 0; +} + +// Show/hide tabbable areas +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} +.tab-content > .active, +.pill-content > .active { + display: block; +} + + +// BOTTOM +// ------ + +.tabs-below > .nav-tabs { + border-top: 1px solid @gray-8; +} +.tabs-below > .nav-tabs > li { + margin-top: -1px; + margin-bottom: 0; +} +.tabs-below > .nav-tabs > li > a { + .border-radius(0 0 4px 4px); + &:hover, + &:focus { + border-bottom-color: transparent; + border-top-color: @gray-8; + } +} +.tabs-below > .nav-tabs > .active > a, +.tabs-below > .nav-tabs > .active > a:hover, +.tabs-below > .nav-tabs > .active > a:focus { + border-color: transparent @gray-8 @gray-8 @gray-8; +} + +// LEFT & RIGHT +// ------------ + +// Common styles +.tabs-left > .nav-tabs > li, +.tabs-right > .nav-tabs > li { + float: none; +} +.tabs-left > .nav-tabs > li > a, +.tabs-right > .nav-tabs > li > a { + min-width: 74px; + margin-right: 0; + margin-bottom: 3px; +} + +// Tabs on the left +.tabs-left > .nav-tabs { + float: left; + margin-right: 19px; + border-right: 1px solid @gray-8; +} +.tabs-left > .nav-tabs > li > a { + margin-right: -1px; + .border-radius(4px 0 0 4px); +} +.tabs-left > .nav-tabs > li > a:hover, +.tabs-left > .nav-tabs > li > a:focus { + border-color: @gray-10 @gray-8 @gray-10 @gray-10; +} +.tabs-left > .nav-tabs .active > a, +.tabs-left > .nav-tabs .active > a:hover, +.tabs-left > .nav-tabs .active > a:focus { + border-color: @gray-8 transparent @gray-8 @gray-8; + *border-right-color: @white; +} + +// Tabs on the right +.tabs-right > .nav-tabs { + float: right; + margin-left: 19px; + border-left: 1px solid @gray-8; +} +.tabs-right > .nav-tabs > li > a { + margin-left: -1px; + .border-radius(0 4px 4px 0); +} +.tabs-right > .nav-tabs > li > a:hover, +.tabs-right > .nav-tabs > li > a:focus { + border-color: @gray-10 @gray-10 @gray-10 @gray-8; +} +.tabs-right > .nav-tabs .active > a, +.tabs-right > .nav-tabs .active > a:hover, +.tabs-right > .nav-tabs .active > a:focus { + border-color: @gray-8 @gray-8 @gray-8 transparent; + *border-left-color: @white; +} + + + +// DISABLED STATES +// --------------- + +// Gray out text +.nav > .disabled > a { + color: @gray-8; +} +// Nuke hover/focus effects +.nav > .disabled > a:hover, +.nav > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + cursor: default; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index 1596605762..68a703201a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -1,462 +1,462 @@ -// Panel -// ------------------------- -.umb-panel { - background: @white; - position: absolute; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; -} - -.umb-panel-nobody { - padding-top: 100px; - overflow: auto; -} - -.umb-panel-header { - background: @gray-10; - border-bottom: 1px solid @gray-8; - position: absolute; - height: 99px; - top: 0px; - right: 0px; - left: 0px; -} - -.umb-panel-body { - top: 101px; - left: 0px; - right: 0px; - bottom: 0px; - position: absolute; - clear: both; - overflow: auto; -} - -.umb-panel-body.no-header { - top: 20px; -} - -.umb-panel-body.with-footer { - bottom: 90px; -} - -.umb-mediapicker-upload { - display: -ms-flexbox; - display: -webkit-box; - display: -webkit-flex; - display: flex; - - .form-search { - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - } - - .upload-button { - margin-left: 16px; - } -} - -.umb-panel.editor-breadcrumb .umb-panel-body, .umb-panel.editor-breadcrumb .umb-bottom-bar { - bottom: 31px !important; -} - -.umb-panel-header .umb-headline, .umb-panel-header h1 { - font-size: 16px; - border: none; - background: none; - margin: 15px 0 0 20px; - padding: 3px 5px; - line-height: 1.4; - height: auto; - width: 100%; - border: 1px solid @gray-10; -} - -.umb-panel-header .umb-headline:focus, .umb-panel-header .umb-headline:active { - border: 1px solid @gray-8; - background-color: @white; -} - -.umb-headline-editor-wrapper { - position: relative; -} - -.umb-headline-editor-wrapper .help-inline { - right: 0px; - top: 25px; - position: absolute; - font-size: 10px; - color: @red; -} - -.umb-panel-header p { - margin: 0px 20px; -} - -.umb-btn-toolbar .dimmed, .umb-dimmed { - opacity: 0.6; -} - -.umb-headline-editor-wrapper input { - background: none; - border: none; - margin: -6px 0 0 0; - padding: 0 0 2px 0; - border-radius: 0; - line-height: normal; - border: 1px solid transparent; - color: @black; - letter-spacing: -0.01em; -} - -.umb-headline-editor-wrapper input.ng-invalid { - color: @red; -} - -.umb-headline-editor-wrapper input.ng-invalid::-moz-placeholder, -.umb-headline-editor-wrapper input.ng-invalid:-ms-input-placeholder, -.umb-headline-editor-wrapper input.ng-invalid::-webkit-input-placeholder { - color: @red; - line-height: 22px; -} - -/* -.umb-panel-header i { - font-size: 13px; - vertical-align: middle; -} -*/ - -.umb-panel-header-meta { - height: 50px; -} - -.umb-panel-header .umb-btn-toolbar { - float: right; - padding: 5px 20px 0 0; -} - -.umb-panel-footer { - margin: 0; - padding: 20px; - z-index: 999; - position: absolute; - bottom: 0px; - left: 0px; - right: 0px; -} - - -/* Publish */ -.umb-btn-toolbar .dropdown-menu { - right: 0; - left: auto; - border-radius: @tabsBorderRadius; - box-shadow: none; - padding: 0; - z-index: 6020; -} - -.umb-btn-toolbar .dropdown-menu small { - background: @turquoise-l3; - display: block; - padding: 10px 20px; -} - -.umb-btn-toolbar .dropdown-menu .btn  { - margin: 20px 29px; - width: 80px; -} - -/* tab buttons */ -.umb-bottom-bar { - background: @white; - -webkit-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); - -moz-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); - box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); - border-top: 1px solid @gray-10; - padding: 10px 0 10px 0; - position: fixed; - bottom: 0; - left: 100px; - right: 40px; - z-index: 6010; - - @media (min-width: 1101px) { - left: 460px; - } - - @media (max-width: 767px) { - left: 80px; - } - - @media (max-width: 500px) { - left: 60px; - } -} - -.umb-tab-buttons { - padding-left: 0; - - > .btn-group:not([style*="display:none"]):not([style*="display: none"]) { - margin-left: 0; - } - - @media (min-width: 768px) { - padding-left: 180px; - } -} - -.umb-tab-pane { - padding-bottom: 90px; -} - -.tab-content { - overflow: visible; -} - -.umb-panel-footer-nav { - position: absolute; - bottom: 0px; - height: 30px; - left: 0px; - right: 0px; - background: @gray-10; - border-top: @gray-8 1px solid; - display: block; - margin: 0; - overflow: hidden; -} - -.umb-panel-footer-nav li a { - border-radius: 0; - display: block; - float: left; - height: 30px; - background: @gray-10; - text-align: center; - padding: 8px 0px 8px 30px; - position: relative; - margin: 0 1px 0 0; - text-decoration: none; - color: @gray-3; - font-size: 12px; -} - -.umb-panel-footer-nav li a:after { - content: ""; - border-top: 16px solid transparent; - border-bottom: 16px solid transparent; - border-left: 16px solid @gray-10; - position: absolute; - right: -16px; - top: 0; - z-index: 1; -} - -.umb-panel-footer-nav li a:before { - content: ""; - border-top: 16px solid transparent; - border-bottom: 16px solid transparent; - border-left: 16px solid @gray-8; - position: absolute; - left: 0; - top: 0; -} - -.umb-panel-footer-nav li:first-child a { - padding-left: 20px; -} - -.umb-panel-footer-nav li:first-child a:before { - display: none; -} - -.umb-panel-footer-nav li:last-child a:after { - display: none; -} - -// Utility classes - -//SD: Had to add these because if we want to use the bootstrap text colors -// in a panel/editor they'll all show up as white on white - so we need to use the -// form styles -.umb-dialog .muted, -.umb-panel .muted { - color: @gray-5; -} - -.umb-dialog a.muted:hover, -.umb-dialog a.muted:focus, -.umb-panel a.muted:hover, -.umb-panel a.muted:focus { - color: darken(@gray-5, 10%); -} - -.umb-dialog .text-warning, -.umb-panel .text-warning { - color: @formWarningText; -} - -.umb-dialog a.text-warning:hover, -.umb-dialog a.text-warning:focus, -.umb-panel a.text-warning:hover, -.umb-panel a.text-warning:focus { - color: darken(@formWarningText, 10%); -} - -.umb-dialog .text-error, -.umb-panel .text-error { - color: @formErrorText; -} - -.umb-dialog a.text-error:hover, -.umb-dialog a.text-error:focus, -.umb-panel a.text-error:hover, -.umb-panel a.text-error:focus { - color: darken(@formErrorText, 10%); -} - -.umb-dialog .text-info, -.umb-panel .text-info { - color: @formInfoText; -} - -.umb-dialog a.text-info:hover, -.umb-dialog a.text-info:focus, -.umb-panel a.text-info:hover, -.umb-panel a.text-info:focus { - color: darken(@formInfoText, 10%); -} - -.umb-dialog .text-success, -.umb-panel .text-success { - color: @formSuccessText; -} - -.umb-dialog a.text-success:hover, -.umb-dialog a.text-success:focus, -.umb-panel a.text-success:hover, -.umb-panel a.text-success:focus { - color: darken(@formSuccessText, 10%); -} - -.external-logins form { - margin:0; -} -.external-logins button { - margin:5px; -} - -/* --------- UMB PANEL HEADER ---------- */ - -.umb-panel-header-content-wrapper { - display: flex; - flex-direction: column; -} - -.umb-panel-header-content { - display: flex; - align-items: center; - flex: 1; -} - -.umb-panel-header-left-side { - display: flex; - flex: 1; - flex-direction: row; -} - -.umb-panel-header-icon { - cursor: pointer; - margin-right: 5px; - height: 50px; - display: flex; - justify-content: center; - align-items: center; - background: @white; - border: 1px solid @gray-8; - animation: fadeIn 0.5s; - border-radius: 3px; - width: 50px; -} - -.umb-panel-header-title-wrapper { - position: relative; - width: 80%; -} - -.umb-panel-header-alias { - position: absolute; - top: 5px; - right: 10px; -} - -.umb-panel-header-alias .umb-locked-field { - display: flex; - align-items: center; -} - -.umb-panel-header-alias .umb-locked-field, -.umb-panel-header-alias .umb-locked-field .umb-locked-field__wrapper { - margin-bottom: 0; -} - -.umb-panel-header-alias .umb-validation-label:after { - visibility: hidden; -} - -.umb-panel-header-alias .umb-locked-field:after { - display: none; -} - -.umb-panel-header-icon.-placeholder { - border: 1px dashed @gray-8; -} - -.umb-panel-header-icon .icon { - font-size: 30px; - color: @gray-7; -} - -.umb-panel-header-icon-text { - color: @green; - font-weight: bold; - font-size: 10px; -} - -input.umb-panel-header-name-input.name-is-empty { - border: 1px dashed @gray-8; - background: @white; -} - -.umb-panel-header-name { - font-size: 16px; - font-weight: bold; -} - - -input.umb-panel-header-description { - background: transparent; - border-color: transparent; - margin-bottom: 0; - font-size: 13px; - box-sizing: border-box; - height: 22px; - line-height: 22px; - width: 100%; - &:hover { - background: @white; - border-color: @gray-8; - } -} - -.umb-panel-header-locked-description { - font-size: 12px; - margin-top: 2px; - height: 22px; - line-height: 22px; -} +// Panel +// ------------------------- +.umb-panel { + background: @white; + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; +} + +.umb-panel-nobody { + padding-top: 100px; + overflow: auto; +} + +.umb-panel-header { + background: @gray-10; + border-bottom: 1px solid @gray-8; + position: absolute; + height: 99px; + top: 0px; + right: 0px; + left: 0px; +} + +.umb-panel-body { + top: 101px; + left: 0px; + right: 0px; + bottom: 0px; + position: absolute; + clear: both; + overflow: auto; +} + +.umb-panel-body.no-header { + top: 20px; +} + +.umb-panel-body.with-footer { + bottom: 90px; +} + +.umb-mediapicker-upload { + display: -ms-flexbox; + display: -webkit-box; + display: -webkit-flex; + display: flex; + + .form-search { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + } + + .upload-button { + margin-left: 16px; + } +} + +.umb-panel.editor-breadcrumb .umb-panel-body, .umb-panel.editor-breadcrumb .umb-bottom-bar { + bottom: 31px !important; +} + +.umb-panel-header .umb-headline, .umb-panel-header h1 { + font-size: 16px; + border: none; + background: none; + margin: 15px 0 0 20px; + padding: 3px 5px; + line-height: 1.4; + height: auto; + width: 100%; + border: 1px solid @gray-10; +} + +.umb-panel-header .umb-headline:focus, .umb-panel-header .umb-headline:active { + border: 1px solid @gray-8; + background-color: @white; +} + +.umb-headline-editor-wrapper { + position: relative; +} + +.umb-headline-editor-wrapper .help-inline { + right: 0px; + top: 25px; + position: absolute; + font-size: 10px; + color: @red; +} + +.umb-panel-header p { + margin: 0px 20px; +} + +.umb-btn-toolbar .dimmed, .umb-dimmed { + opacity: 0.6; +} + +.umb-headline-editor-wrapper input { + background: none; + border: none; + margin: -6px 0 0 0; + padding: 0 0 2px 0; + border-radius: 0; + line-height: normal; + border: 1px solid transparent; + color: @black; + letter-spacing: -0.01em; +} + +.umb-headline-editor-wrapper input.ng-invalid { + color: @red; +} + +.umb-headline-editor-wrapper input.ng-invalid::-moz-placeholder, +.umb-headline-editor-wrapper input.ng-invalid:-ms-input-placeholder, +.umb-headline-editor-wrapper input.ng-invalid::-webkit-input-placeholder { + color: @red; + line-height: 22px; +} + +/* +.umb-panel-header i { + font-size: 13px; + vertical-align: middle; +} +*/ + +.umb-panel-header-meta { + height: 50px; +} + +.umb-panel-header .umb-btn-toolbar { + float: right; + padding: 5px 20px 0 0; +} + +.umb-panel-footer { + margin: 0; + padding: 20px; + z-index: 999; + position: absolute; + bottom: 0px; + left: 0px; + right: 0px; +} + + +/* Publish */ +.umb-btn-toolbar .dropdown-menu { + right: 0; + left: auto; + border-radius: @tabsBorderRadius; + box-shadow: none; + padding: 0; + z-index: 6020; +} + +.umb-btn-toolbar .dropdown-menu small { + background: @turquoise-l3; + display: block; + padding: 10px 20px; +} + +.umb-btn-toolbar .dropdown-menu .btn  { + margin: 20px 29px; + width: 80px; +} + +/* tab buttons */ +.umb-bottom-bar { + background: @white; + -webkit-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); + -moz-box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); + box-shadow: 0px -18px 20px rgba(255, 255, 255, 1); + border-top: 1px solid @gray-10; + padding: 10px 0 10px 0; + position: fixed; + bottom: 0; + left: 100px; + right: 40px; + z-index: 6010; + + @media (min-width: 1101px) { + left: 460px; + } + + @media (max-width: 767px) { + left: 80px; + } + + @media (max-width: 500px) { + left: 60px; + } +} + +.umb-tab-buttons { + padding-left: 0; + + > .btn-group:not([style*="display:none"]):not([style*="display: none"]) { + margin-left: 0; + } + + @media (min-width: 768px) { + padding-left: 180px; + } +} + +.umb-tab-pane { + padding-bottom: 90px; +} + +.tab-content { + overflow: visible; +} + +.umb-panel-footer-nav { + position: absolute; + bottom: 0px; + height: 30px; + left: 0px; + right: 0px; + background: @gray-10; + border-top: @gray-8 1px solid; + display: block; + margin: 0; + overflow: hidden; +} + +.umb-panel-footer-nav li a { + border-radius: 0; + display: block; + float: left; + height: 30px; + background: @gray-10; + text-align: center; + padding: 8px 0px 8px 30px; + position: relative; + margin: 0 1px 0 0; + text-decoration: none; + color: @gray-3; + font-size: 12px; +} + +.umb-panel-footer-nav li a:after { + content: ""; + border-top: 16px solid transparent; + border-bottom: 16px solid transparent; + border-left: 16px solid @gray-10; + position: absolute; + right: -16px; + top: 0; + z-index: 1; +} + +.umb-panel-footer-nav li a:before { + content: ""; + border-top: 16px solid transparent; + border-bottom: 16px solid transparent; + border-left: 16px solid @gray-8; + position: absolute; + left: 0; + top: 0; +} + +.umb-panel-footer-nav li:first-child a { + padding-left: 20px; +} + +.umb-panel-footer-nav li:first-child a:before { + display: none; +} + +.umb-panel-footer-nav li:last-child a:after { + display: none; +} + +// Utility classes + +//SD: Had to add these because if we want to use the bootstrap text colors +// in a panel/editor they'll all show up as white on white - so we need to use the +// form styles +.umb-dialog .muted, +.umb-panel .muted { + color: @gray-5; +} + +.umb-dialog a.muted:hover, +.umb-dialog a.muted:focus, +.umb-panel a.muted:hover, +.umb-panel a.muted:focus { + color: darken(@gray-5, 10%); +} + +.umb-dialog .text-warning, +.umb-panel .text-warning { + color: @formWarningText; +} + +.umb-dialog a.text-warning:hover, +.umb-dialog a.text-warning:focus, +.umb-panel a.text-warning:hover, +.umb-panel a.text-warning:focus { + color: darken(@formWarningText, 10%); +} + +.umb-dialog .text-error, +.umb-panel .text-error { + color: @formErrorText; +} + +.umb-dialog a.text-error:hover, +.umb-dialog a.text-error:focus, +.umb-panel a.text-error:hover, +.umb-panel a.text-error:focus { + color: darken(@formErrorText, 10%); +} + +.umb-dialog .text-info, +.umb-panel .text-info { + color: @formInfoText; +} + +.umb-dialog a.text-info:hover, +.umb-dialog a.text-info:focus, +.umb-panel a.text-info:hover, +.umb-panel a.text-info:focus { + color: darken(@formInfoText, 10%); +} + +.umb-dialog .text-success, +.umb-panel .text-success { + color: @formSuccessText; +} + +.umb-dialog a.text-success:hover, +.umb-dialog a.text-success:focus, +.umb-panel a.text-success:hover, +.umb-panel a.text-success:focus { + color: darken(@formSuccessText, 10%); +} + +.external-logins form { + margin:0; +} +.external-logins button { + margin:5px; +} + +/* --------- UMB PANEL HEADER ---------- */ + +.umb-panel-header-content-wrapper { + display: flex; + flex-direction: column; +} + +.umb-panel-header-content { + display: flex; + align-items: center; + flex: 1; +} + +.umb-panel-header-left-side { + display: flex; + flex: 1; + flex-direction: row; +} + +.umb-panel-header-icon { + cursor: pointer; + margin-right: 5px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + background: @white; + border: 1px solid @gray-8; + animation: fadeIn 0.5s; + border-radius: 3px; + width: 50px; +} + +.umb-panel-header-title-wrapper { + position: relative; + width: 80%; +} + +.umb-panel-header-alias { + position: absolute; + top: 5px; + right: 10px; +} + +.umb-panel-header-alias .umb-locked-field { + display: flex; + align-items: center; +} + +.umb-panel-header-alias .umb-locked-field, +.umb-panel-header-alias .umb-locked-field .umb-locked-field__wrapper { + margin-bottom: 0; +} + +.umb-panel-header-alias .umb-validation-label:after { + visibility: hidden; +} + +.umb-panel-header-alias .umb-locked-field:after { + display: none; +} + +.umb-panel-header-icon.-placeholder { + border: 1px dashed @gray-8; +} + +.umb-panel-header-icon .icon { + font-size: 30px; + color: @gray-7; +} + +.umb-panel-header-icon-text { + color: @green; + font-weight: bold; + font-size: 10px; +} + +input.umb-panel-header-name-input.name-is-empty { + border: 1px dashed @gray-8; + background: @white; +} + +.umb-panel-header-name { + font-size: 16px; + font-weight: bold; +} + + +input.umb-panel-header-description { + background: transparent; + border-color: transparent; + margin-bottom: 0; + font-size: 13px; + box-sizing: border-box; + height: 22px; + line-height: 22px; + width: 100%; + &:hover { + background: @white; + border-color: @gray-8; + } +} + +.umb-panel-header-locked-description { + font-size: 12px; + margin-top: 2px; + height: 22px; + line-height: 22px; +} 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 a778b72373..47fabb7573 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -1,895 +1,895 @@ -// -// Container styles -// -------------------------------------------------- -.umb-property-editor { - min-width:66.6%; -} - -.umb-property-editor-tiny { - width: 60px; -} - -.umb-property-editor-small { - width: 90px; -} - -.umb-modal .umb-property-editor { - width: 95%; -} - -.umb-dialog .umb-property-editor { - width: 95%; -} -.umb-dialog .umb-control-group .help-block { - width: 95%; -} - -.umb-codeeditor{ - width: 99%; -} - -// -// Content picker -// -------------------------------------------------- -.umb-contentpicker li a:hover .hover-hide, .umb-contentpicker li a .hover-show{ - display: none; -} -.umb-contentpicker li a:hover .hover-show{display: inline-block;} - -.umb-contentpicker-popover .search-holder { - padding: 10px; -} - -.umb-contentpicker__min-max-help { - font-size: 13px; - margin-top: 5px; - color: @gray-4; -} - -.show-validation .umb-contentpicker__min-max-help { - display: none; -} - -.umb-contentpicker small { - - &:not(:last-child) { - padding-right: 3px; - border-right: 1px solid @gray-5; - } - - a { - color: @gray-3; - } -} - -/* CODEMIRROR DATATYPE */ -div.umb-codeeditor { - border: 1px solid @gray-8; -} -div.umb-codeeditor .umb-el-wrap { - padding: 0px; -} -div.umb-codeeditor .umb-btn-toolbar { - padding: 0px; - margin: 0px; - border-bottom: @gray-8 1px solid; - background: @gray-10; -} - - -// -// RTE -// -------------------------------------------------- -.mce-tinymce{border: 1px solid @gray-8 !important; border-radius: 0px !important;} -.mce-panel{background: @gray-10 !important; border-color: @gray-8 !important;} -.mce-btn-group, .mce-btn{border: none !important; background: none !important;} -.mce-ico{font-size: 12px !important; color: @gray-1 !important;} -/* Special case to support helviticons for the tiny mce button controls */ -.mce-ico.mce-i-custom[class^="icon-"], -.mce-ico.mce-i-custom[class*=" icon-"] { - font-family: icomoon; - font-size:16px !important; -} - -/* pre-value editor */ -.rte-editor-preval .control-group .controls > div > label .mce-ico { line-height: 20px; } - - -// -// Color picker -// -------------------------------------------------- -ul.color-picker li { - padding: 2px; - margin: 3px; - border: 2px solid transparent; - width: 60px; -} -ul.color-picker li.active { - border: 2px dashed @gray-8; -} -ul.color-picker li a { - height: 50px; - display:block; - cursor:pointer; -} - -.control-group.color-picker-preval .thumbnail { - width:30px; - border:none; -} - -/* pre-value editor */ -/*.control-group.color-picker-preval:before { - content: ""; - display: inline-block; - vertical-align: middle; - height: 100%; -}*/ - -/*.control-group.color-picker-preval div.thumbnail { - display: inline-block; - vertical-align: middle; -}*/ -.control-group.color-picker-preval div.color-picker-prediv { - display: inline-block; - width: 60%; -} - -.control-group.color-picker-preval pre { - display: inline; - margin-right: 20px; - margin-left: 10px; - width: 50%; - white-space: nowrap; - overflow: hidden; - margin-bottom: 0; - vertical-align: middle; -} - -.control-group.color-picker-preval btn { - //vertical-align: middle; -} - -.control-group.color-picker-preval input[type="text"] { - min-width: 40%; - width: 40%; - display: inline-block; - margin-right: 20px; - margin-top: 1px; -} - -.control-group.color-picker-preval label { - border: solid @white 1px; - padding: 6px; -} - - -// -// Media picker -// -------------------------------------------------- -.umb-mediapicker .add-link { - display: flex; - justify-content:center; - align-items:center; - width: 120px; - text-align: center; - color: @gray-8; - border: 2px @gray-8 dashed; - text-decoration: none; - - transition: all 150ms ease-in-out; - - &:hover { - color: @turquoise-d1; - border-color: @turquoise; - } -} - -.umb-mediapicker .picked-image { - position: absolute; - bottom: 10px; - right: 10px; - opacity: 0.5; - - font-size: 24px; - color: @red; - background: @white; - - line-height: 36px; - text-align: center; - -moz-border-radius: 15px; - border-radius: 15px; - - height: 32px; - width: 32px; - overflow: hidden; - display: none; - text-decoration: none; -} - -.umb-mediapicker .add-link-square { - height: 120px; -} - - - -.umb-thumbnails { - position: relative; - display: flex; - -ms-flex-direction: row; - -webkit-flex-direction: row; - flex-direction: row; - -ms-flex-wrap: wrap; - -webkit-flex-wrap: wrap; - flex-wrap: wrap; - justify-content: flex-start; -} - -.umb-thumbnails > li.icon { - width: 14%; - text-align: center; -} - -.umb-thumbnails i{margin: auto;} -.umb-thumbnails a{ - outline: none; - border:none !important; - box-shadow:none !important; -} - - -.umb-sortable-thumbnails { - list-style-type: none; - margin: 0; - padding: 0; - display: flex; - flex-direction: row; - flex-wrap: wrap; -} - - -.umb-sortable-thumbnails li { - position: relative; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - flex-wrap: wrap; - padding: 2px; - margin: 5px; - background: @white; - border: 1px solid @gray-10; - max-width: 100%; -} - - -.umb-mediapicker .umb-sortable-thumbnails li { - flex-direction: column; - margin: 0 5px 5px 0; - padding: 5px; -} - -.umb-sortable-thumbnails li:hover a { - display: flex; - justify-content: center; - align-items: center; -} - -.umb-sortable-thumbnails li img { - max-width:100%; - max-height:100%; - margin:auto; - display:block; - background-image: url(../img/checkered-background.png); -} - -.umb-sortable-thumbnails li img.trashed { - opacity:0.3; -} - -.umb-sortable-thumbnails li img.noScale { - max-width: none !important; - max-height: none !important; -} - -.umb-sortable-thumbnails .umb-icon-holder { - text-align: center; -} - -.umb-sortable-thumbnails .umb-icon-holder .icon{ - font-size: 40px; - line-height: 50px; - color: @gray-3; - display: block; -} - -.umb-sortable-thumbnails .umb-sortable-thumbnails__wrapper { - width: 124px; - height: 124px; - overflow: hidden; - position: relative; -} - -.umb-sortable-thumbnails .umb-sortable-thumbnails__loading { - position: absolute; - background-color: rgba(255,255,255,0.8); - top: 0; - right: 0; - bottom: 0; - left: 0; -} - -.umb-sortable-thumbnails .umb-sortable-thumbnails__actions { - position: absolute; - bottom: 10px; - right: 10px; - text-decoration: none; - display: flex; - flex-direction: row; - opacity: 0; - visibility: hidden; -} - -.umb-sortable-thumbnails.ui-sortable:not(.ui-sortable-disabled) { - > li:not(.unsortable) { - cursor: move; - } -} - -.umb-sortable-thumbnails li:hover .umb-sortable-thumbnails__actions { - opacity: 1; - visibility: visible; -} - -.umb-sortable-thumbnails .umb-sortable-thumbnails__action { - font-size: 16px; - background: @white; - height: 25px; - width: 25px; - border-radius: 15px; - color: @gray-1; - display: flex; - justify-content: center; - align-items: center; - margin-left: 5px; - text-decoration: none; -} - -.umb-sortable-thumbnails .umb-sortable-thumbnails__action.-red { - color: @red; -} - -.umb-sortable-thumbnails .umb-sortable-thumbnails__action:hover { - text-decoration: none; -} - - -// -// Cropper -// ------------------------------------------------- - -.umb-cropper{ - position: relative; -} - -.umb-cropper img, .umb-cropper-gravity img{ - position: relative; - max-width: 100%; - height: auto; - top: 0; - left: 0; - } - - .umb-cropper img { - max-width: none; - } - - .umb-cropper .overlay, .umb-cropper-gravity .overlay { - top: 0; - left: 0; - cursor: move; - z-index: @zindexCropperOverlay; - position: absolute; -} - -.umb-cropper .viewport{ - overflow: hidden; - position: relative; - margin: auto; - max-width: 100%; - height: auto; - } - -.umb-cropper-gravity .viewport{ - overflow: hidden; - position: relative; - width: 100%; - height: 100%; -} - - -.umb-cropper .viewport:after { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: @zindexCropperOverlay - 1; - -moz-opacity: .75; - opacity: .75; - filter: alpha(opacity=7); - -webkit-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); - -moz-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); - box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); -} - -.umb-cropper-gravity .overlay{ - width: 14px; - height: 14px; - text-align: center; - border-radius: 20px; - background: @turquoise; - border: 3px solid @white; - opacity: 0.8; -} - -.umb-cropper-gravity .overlay i { - font-size: 26px; - line-height: 26px; - opacity: 0.8 !important; -} - -.umb-cropper .crop-container { - text-align: center; -} - -.umb-cropper .crop-slider { - padding: 10px; - border-top: 1px solid @gray-10; - margin-top: 10px; - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - @media (min-width: 769px) { - padding: 10px 50px 10px 50px; - } -} - -.umb-cropper .crop-slider i { - color: @gray-3; - flex: 0 0 25px; - padding: 0 5px; - box-sizing: border-box; -} - -.umb-cropper .crop-slider i:first-of-type { - text-align: right; -} - -.umb-cropper .crop-slider input { - flex: 0 1 auto; -} - .umb-cropper-gravity .viewport, .umb-cropper-gravity, .umb-cropper-imageholder { - display: inline-block; - max-width: 100%; - } - - .umb-cropper-imageholder { - float: left; - } - - .cropList { - display: inline-block; - position: relative; - vertical-align: top; - } - - .gravity-container .viewport { - max-width: 600px; - } - - .gravity-container .viewport:hover { - cursor: pointer; - } - - .imagecropper { - display: flex; - align-items: flex-start; - flex-direction: row; - - @media (max-width: 768px) { - flex-direction: column; - float: left; - max-width: 100%; - } - } - - .imagecropper .umb-cropper__container { - position: relative; - margin-bottom: 10px; - max-width: 100%; - border: 1px solid @gray-10; - - @media (min-width: 769px) { - width: 600px; - } - } - - .umb-close-cropper { - position: absolute; - top: 3px; - right: 3px; - cursor: pointer; - z-index: 1; - } - - .umb-close-cropper:hover { - opacity: .9; - background: @gray-10; - } - - .imagecropper .umb-sortable-thumbnails { - display: flex; - flex-direction: row; - flex-wrap: wrap; - } - - .imagecropper .umb-sortable-thumbnails li { - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 8px; - margin-top: 0; - } - - .imagecropper .umb-sortable-thumbnails li.current { - border-color: @gray-8; - background: @gray-10; - color: @black; - cursor: pointer; - } - - .imagecropper .umb-sortable-thumbnails li:hover, - .imagecropper .umb-sortable-thumbnails li.current:hover { - border-color: @gray-8; - background: @gray-10; - color: @black; - cursor: pointer; - opacity: .95; - } - - .imagecropper .umb-sortable-thumbnails li .crop-name, - .imagecropper .umb-sortable-thumbnails li .crop-size { - display: block; - text-align: left; - font-size: 13px; - line-height: 1; - } - - .imagecropper .umb-sortable-thumbnails li .crop-name { - font-weight: bold; - margin: 10px 0 5px; - } - - .imagecropper .umb-sortable-thumbnails li .crop-size { - font-size: 10px; - font-style: italic; - margin: 0 0 5px; - } - - .btn-crop-delete { - display: block; - text-align: left; - } - -// -// folder-browser -// -------------------------------------------------- -.umb-folderbrowser .add-link{ - display: inline-block; - height: 120px; - width: 120px; - text-align: center; - border: 1px @gray-10 dashed; - line-height: 120px -} - -.umb-upload-drop-zone{ - margin-bottom:5px; -} - -.umb-upload-drop-zone .info, .umb-upload-button-big{ - display: block; - padding: 20px; - opacity: 1; - border: 1px dashed @gray-8; - background: none; - text-align: center; - font-size: 14px; - color: @gray-8; -} - -.umb-upload-button-big:hover{color: @gray-8;} - -.umb-upload-drop-zone .info i.icon, .umb-upload-button-big i.icon{ - font-size: 55px; - line-height: 70px -} - -.umb-upload-button-big {display: block} -.umb-upload-button-big input { - left: 0; - bottom: 0; - height: 100%; - width: 100%; -} - - - -// -// Photo folder styling -// -------------------------------------------------- - -.umb-photo-folder .picrow{ - overflow-y: hidden; - position: relative; -} - - - -.umb-photo-folder .picrow div, .umb-photo-preview{ - margin: 0px; - padding: 0px; - border: none; - display: inline-block; - vertical-align: top; - position: relative; -} - - - -.umb-photo-folder .picrow div a:first-child { - width:100%; - height:100%; -} - -.umb-photo-folder .picrow div.umb-photo { - width:100%; - height:100%; - background-color: @gray-10; -} - -.umb-photo-folder a:hover{text-decoration: none} -.umb-photo-folder .umb-non-thumbnail{ - text-align: center; - vertical-align: middle; - font-size: 12px; - background: @gray-10; - color: @black; - text-decoration: none; -} - -.umb-photo-folder .selector-overlay{ - display: none; -} - -//this is a temp hack, to provide selectors in the dialog: -.umb-photo-folder .pic:hover .selector-overlay { - position: absolute; - bottom: 0px; - left: 0px; - right: 0px; - padding: 5px; - background: @black; - z-index: 100; - display: block; - text-align: center; - color: @white; - opacity: 0.4; - text-decoration:none; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.umb-photo-folder .umb-non-thumbnail i{ - color: @gray-8; - font-size: 50px; - line-height: 60px; - display: block; - margin: auto; - /*vertically aligns */ - position: relative; - top: 50%; - transform: translateY(-50%); -} - -.umb-photo-folder .umb-non-thumbnail span{ - position: absolute; - display: block; - margin: auto; - width: 100%; - top: 20px; -} - -.umb-photo-folder .selected{ - position: relative; -} - -.umb-photo-folder .selected:before{ - content: "\e165"; - font-family: Icomoon; - - position: absolute; - bottom: 10px; - right: 10px; - - font-size: 24px; - color: @black; - opacity: 0.5; - background: @white; - - line-height: 36px; - text-align: center; - -moz-border-radius: 15px; - border-radius: 15px; - - height: 32px; - width: 32px; - overflow: hidden; - display: block; - z-index: 100; -} - - -// -// File upload -// -------------------------------------------------- -.umb-fileupload .preview { - border-radius: 5px; - border: 1px solid @gray-6; - padding: 3px; - background: @gray-9; - float: left; - margin-right: 30px; - margin-bottom: 30px; -} - -.umb-fileupload ul { - list-style: none; - vertical-align: middle; - margin-bottom: 0px; -} - -.umb-fileupload label { - vertical-align: middle; - padding-left: 7px; - font-weight: normal; -} - -.umb-fileupload .preview-file { - color: @gray-4; - height: 45px; - width: 55px; - text-align: center; - text-transform: uppercase; - font-size: 10px; - padding-top: 27px; -} - -.umb-fileupload .file-icon { - text-align: center; - display: block; - position: relative; - padding: 5px 0; - - > .icon { - font-size: 70px; - line-height: 110%; - color: @gray-4; - text-align: center; - } - - > span { - color: @white; - background: @gray-4; - padding: 1px 3px; - font-size: 12px; - line-height: 130%; - position: absolute; - top: 45px; - left: 110px; - } -} - -.umb-fileupload input { - font-size: 12px; - line-height: 1; -} - -// -// Member group picker -// -------------------------------------------------- - -.umb-member-group-box { - width: 45%; -} -.umb-member-group-box:nth-child(1){ - float:left; -} -.umb-member-group-box:nth-child(2){ - float:right; -} - -// -// Related links -// -------------------------------------------------- -.umb-relatedlinks table > tr > td { word-wrap:break-word; word-break: break-all; border-bottom: 1px solid transparent; } -.umb-relatedlinks .handle { cursor:move; } -.umb-relatedlinks table > tbody > tr.unsortable .handle { cursor:default; } - -.umb-relatedlinks table td.col-sort { width: 20px; } -.umb-relatedlinks table td.col-caption { min-width: 200px; } -.umb-relatedlinks table td.col-link { min-width: 200px; } -.umb-relatedlinks table td.col-actions { min-width: 120px; } - -.umb-relatedlinks table td.col-caption .control-wrapper, -.umb-relatedlinks table td.col-link .control-wrapper { display: flex; } - -.umb-relatedlinks table td.col-caption .control-wrapper input[type="text"], -.umb-relatedlinks table td.col-link .control-wrapper input[type="text"] { width: auto; flex: 1; } - -/* sortable placeholder */ -.umb-relatedlinks .sortable-placeholder { - background-color: @tableBackgroundAccent; - display: table-row; -} -.umb-relatedlinks .sortable-placeholder > td { - display: table-cell; - padding: 8px; -} -.umb-relatedlinks .ui-sortable-helper { - display: table-row; - background-color: @white; - opacity: 0.7; -} -.umb-relatedlinks .ui-sortable-helper > td { - display: table-cell; - border-bottom: 1px solid @tableBorder; -} - - -// -// Tags -// -------------------------------------------------- -.umb-tags{border: @gray-10 solid 1px; padding: 10px; font-size: 13px; text-shadow: none;} -.umb-tags .tag{cursor: pointer; margin: 7px; padding: 7px; background: @turquoise} -.umb-tags .tag i{padding: 2px;} -.umb-tags input{border: none; background: @white} - -// -// Date/time picker -// -------------------------------------------------- -.bootstrap-datetimepicker-widget .btn{padding: 0;} -.bootstrap-datetimepicker-widget .picker-switch .btn{ background: none; border: none;} -.umb-datepicker .input-append .add-on{cursor: pointer;} -.umb-datepicker p {margin-top:10px;} -.umb-datepicker p a{color: @gray-3;} - -// -// Code mirror - even though this isn't a proprety editor right now, it could be so I'm putting the styles here -// -------------------------------------------------- - -.CodeMirror, .CodeMirror-scroll { - height: 100%; - min-height:200px; -} - -// -// Nested boolean (e.g. list view bulk action permissions) -// ---------------------=====----------------------------- -.umb-nested-boolean label {margin-bottom: 8px; float: left; width: 320px;} -.umb-nested-boolean label span {float: left; width: 80%;} -.umb-nested-boolean label input[type='checkbox'] {margin-right: 10px; float: left;} +// +// Container styles +// -------------------------------------------------- +.umb-property-editor { + min-width:66.6%; +} + +.umb-property-editor-tiny { + width: 60px; +} + +.umb-property-editor-small { + width: 90px; +} + +.umb-modal .umb-property-editor { + width: 95%; +} + +.umb-dialog .umb-property-editor { + width: 95%; +} +.umb-dialog .umb-control-group .help-block { + width: 95%; +} + +.umb-codeeditor{ + width: 99%; +} + +// +// Content picker +// -------------------------------------------------- +.umb-contentpicker li a:hover .hover-hide, .umb-contentpicker li a .hover-show{ + display: none; +} +.umb-contentpicker li a:hover .hover-show{display: inline-block;} + +.umb-contentpicker-popover .search-holder { + padding: 10px; +} + +.umb-contentpicker__min-max-help { + font-size: 13px; + margin-top: 5px; + color: @gray-4; +} + +.show-validation .umb-contentpicker__min-max-help { + display: none; +} + +.umb-contentpicker small { + + &:not(:last-child) { + padding-right: 3px; + border-right: 1px solid @gray-5; + } + + a { + color: @gray-3; + } +} + +/* CODEMIRROR DATATYPE */ +div.umb-codeeditor { + border: 1px solid @gray-8; +} +div.umb-codeeditor .umb-el-wrap { + padding: 0px; +} +div.umb-codeeditor .umb-btn-toolbar { + padding: 0px; + margin: 0px; + border-bottom: @gray-8 1px solid; + background: @gray-10; +} + + +// +// RTE +// -------------------------------------------------- +.mce-tinymce{border: 1px solid @gray-8 !important; border-radius: 0px !important;} +.mce-panel{background: @gray-10 !important; border-color: @gray-8 !important;} +.mce-btn-group, .mce-btn{border: none !important; background: none !important;} +.mce-ico{font-size: 12px !important; color: @gray-1 !important;} +/* Special case to support helviticons for the tiny mce button controls */ +.mce-ico.mce-i-custom[class^="icon-"], +.mce-ico.mce-i-custom[class*=" icon-"] { + font-family: icomoon; + font-size:16px !important; +} + +/* pre-value editor */ +.rte-editor-preval .control-group .controls > div > label .mce-ico { line-height: 20px; } + + +// +// Color picker +// -------------------------------------------------- +ul.color-picker li { + padding: 2px; + margin: 3px; + border: 2px solid transparent; + width: 60px; +} +ul.color-picker li.active { + border: 2px dashed @gray-8; +} +ul.color-picker li a { + height: 50px; + display:block; + cursor:pointer; +} + +.control-group.color-picker-preval .thumbnail { + width:30px; + border:none; +} + +/* pre-value editor */ +/*.control-group.color-picker-preval:before { + content: ""; + display: inline-block; + vertical-align: middle; + height: 100%; +}*/ + +/*.control-group.color-picker-preval div.thumbnail { + display: inline-block; + vertical-align: middle; +}*/ +.control-group.color-picker-preval div.color-picker-prediv { + display: inline-block; + width: 60%; +} + +.control-group.color-picker-preval pre { + display: inline; + margin-right: 20px; + margin-left: 10px; + width: 50%; + white-space: nowrap; + overflow: hidden; + margin-bottom: 0; + vertical-align: middle; +} + +.control-group.color-picker-preval btn { + //vertical-align: middle; +} + +.control-group.color-picker-preval input[type="text"] { + min-width: 40%; + width: 40%; + display: inline-block; + margin-right: 20px; + margin-top: 1px; +} + +.control-group.color-picker-preval label { + border: solid @white 1px; + padding: 6px; +} + + +// +// Media picker +// -------------------------------------------------- +.umb-mediapicker .add-link { + display: flex; + justify-content:center; + align-items:center; + width: 120px; + text-align: center; + color: @gray-8; + border: 2px @gray-8 dashed; + text-decoration: none; + + transition: all 150ms ease-in-out; + + &:hover { + color: @turquoise-d1; + border-color: @turquoise; + } +} + +.umb-mediapicker .picked-image { + position: absolute; + bottom: 10px; + right: 10px; + opacity: 0.5; + + font-size: 24px; + color: @red; + background: @white; + + line-height: 36px; + text-align: center; + -moz-border-radius: 15px; + border-radius: 15px; + + height: 32px; + width: 32px; + overflow: hidden; + display: none; + text-decoration: none; +} + +.umb-mediapicker .add-link-square { + height: 120px; +} + + + +.umb-thumbnails { + position: relative; + display: flex; + -ms-flex-direction: row; + -webkit-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: wrap; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + justify-content: flex-start; +} + +.umb-thumbnails > li.icon { + width: 14%; + text-align: center; +} + +.umb-thumbnails i{margin: auto;} +.umb-thumbnails a{ + outline: none; + border:none !important; + box-shadow:none !important; +} + + +.umb-sortable-thumbnails { + list-style-type: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + + +.umb-sortable-thumbnails li { + position: relative; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + flex-wrap: wrap; + padding: 2px; + margin: 5px; + background: @white; + border: 1px solid @gray-10; + max-width: 100%; +} + + +.umb-mediapicker .umb-sortable-thumbnails li { + flex-direction: column; + margin: 0 5px 5px 0; + padding: 5px; +} + +.umb-sortable-thumbnails li:hover a { + display: flex; + justify-content: center; + align-items: center; +} + +.umb-sortable-thumbnails li img { + max-width:100%; + max-height:100%; + margin:auto; + display:block; + background-image: url(../img/checkered-background.png); +} + +.umb-sortable-thumbnails li img.trashed { + opacity:0.3; +} + +.umb-sortable-thumbnails li img.noScale { + max-width: none !important; + max-height: none !important; +} + +.umb-sortable-thumbnails .umb-icon-holder { + text-align: center; +} + +.umb-sortable-thumbnails .umb-icon-holder .icon{ + font-size: 40px; + line-height: 50px; + color: @gray-3; + display: block; +} + +.umb-sortable-thumbnails .umb-sortable-thumbnails__wrapper { + width: 124px; + height: 124px; + overflow: hidden; + position: relative; +} + +.umb-sortable-thumbnails .umb-sortable-thumbnails__loading { + position: absolute; + background-color: rgba(255,255,255,0.8); + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.umb-sortable-thumbnails .umb-sortable-thumbnails__actions { + position: absolute; + bottom: 10px; + right: 10px; + text-decoration: none; + display: flex; + flex-direction: row; + opacity: 0; + visibility: hidden; +} + +.umb-sortable-thumbnails.ui-sortable:not(.ui-sortable-disabled) { + > li:not(.unsortable) { + cursor: move; + } +} + +.umb-sortable-thumbnails li:hover .umb-sortable-thumbnails__actions { + opacity: 1; + visibility: visible; +} + +.umb-sortable-thumbnails .umb-sortable-thumbnails__action { + font-size: 16px; + background: @white; + height: 25px; + width: 25px; + border-radius: 15px; + color: @gray-1; + display: flex; + justify-content: center; + align-items: center; + margin-left: 5px; + text-decoration: none; +} + +.umb-sortable-thumbnails .umb-sortable-thumbnails__action.-red { + color: @red; +} + +.umb-sortable-thumbnails .umb-sortable-thumbnails__action:hover { + text-decoration: none; +} + + +// +// Cropper +// ------------------------------------------------- + +.umb-cropper{ + position: relative; +} + +.umb-cropper img, .umb-cropper-gravity img{ + position: relative; + max-width: 100%; + height: auto; + top: 0; + left: 0; + } + + .umb-cropper img { + max-width: none; + } + + .umb-cropper .overlay, .umb-cropper-gravity .overlay { + top: 0; + left: 0; + cursor: move; + z-index: @zindexCropperOverlay; + position: absolute; +} + +.umb-cropper .viewport{ + overflow: hidden; + position: relative; + margin: auto; + max-width: 100%; + height: auto; + } + +.umb-cropper-gravity .viewport{ + overflow: hidden; + position: relative; + width: 100%; + height: 100%; +} + + +.umb-cropper .viewport:after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: @zindexCropperOverlay - 1; + -moz-opacity: .75; + opacity: .75; + filter: alpha(opacity=7); + -webkit-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); + -moz-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); + box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); +} + +.umb-cropper-gravity .overlay{ + width: 14px; + height: 14px; + text-align: center; + border-radius: 20px; + background: @turquoise; + border: 3px solid @white; + opacity: 0.8; +} + +.umb-cropper-gravity .overlay i { + font-size: 26px; + line-height: 26px; + opacity: 0.8 !important; +} + +.umb-cropper .crop-container { + text-align: center; +} + +.umb-cropper .crop-slider { + padding: 10px; + border-top: 1px solid @gray-10; + margin-top: 10px; + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + @media (min-width: 769px) { + padding: 10px 50px 10px 50px; + } +} + +.umb-cropper .crop-slider i { + color: @gray-3; + flex: 0 0 25px; + padding: 0 5px; + box-sizing: border-box; +} + +.umb-cropper .crop-slider i:first-of-type { + text-align: right; +} + +.umb-cropper .crop-slider input { + flex: 0 1 auto; +} + .umb-cropper-gravity .viewport, .umb-cropper-gravity, .umb-cropper-imageholder { + display: inline-block; + max-width: 100%; + } + + .umb-cropper-imageholder { + float: left; + } + + .cropList { + display: inline-block; + position: relative; + vertical-align: top; + } + + .gravity-container .viewport { + max-width: 600px; + } + + .gravity-container .viewport:hover { + cursor: pointer; + } + + .imagecropper { + display: flex; + align-items: flex-start; + flex-direction: row; + + @media (max-width: 768px) { + flex-direction: column; + float: left; + max-width: 100%; + } + } + + .imagecropper .umb-cropper__container { + position: relative; + margin-bottom: 10px; + max-width: 100%; + border: 1px solid @gray-10; + + @media (min-width: 769px) { + width: 600px; + } + } + + .umb-close-cropper { + position: absolute; + top: 3px; + right: 3px; + cursor: pointer; + z-index: 1; + } + + .umb-close-cropper:hover { + opacity: .9; + background: @gray-10; + } + + .imagecropper .umb-sortable-thumbnails { + display: flex; + flex-direction: row; + flex-wrap: wrap; + } + + .imagecropper .umb-sortable-thumbnails li { + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 8px; + margin-top: 0; + } + + .imagecropper .umb-sortable-thumbnails li.current { + border-color: @gray-8; + background: @gray-10; + color: @black; + cursor: pointer; + } + + .imagecropper .umb-sortable-thumbnails li:hover, + .imagecropper .umb-sortable-thumbnails li.current:hover { + border-color: @gray-8; + background: @gray-10; + color: @black; + cursor: pointer; + opacity: .95; + } + + .imagecropper .umb-sortable-thumbnails li .crop-name, + .imagecropper .umb-sortable-thumbnails li .crop-size { + display: block; + text-align: left; + font-size: 13px; + line-height: 1; + } + + .imagecropper .umb-sortable-thumbnails li .crop-name { + font-weight: bold; + margin: 10px 0 5px; + } + + .imagecropper .umb-sortable-thumbnails li .crop-size { + font-size: 10px; + font-style: italic; + margin: 0 0 5px; + } + + .btn-crop-delete { + display: block; + text-align: left; + } + +// +// folder-browser +// -------------------------------------------------- +.umb-folderbrowser .add-link{ + display: inline-block; + height: 120px; + width: 120px; + text-align: center; + border: 1px @gray-10 dashed; + line-height: 120px +} + +.umb-upload-drop-zone{ + margin-bottom:5px; +} + +.umb-upload-drop-zone .info, .umb-upload-button-big{ + display: block; + padding: 20px; + opacity: 1; + border: 1px dashed @gray-8; + background: none; + text-align: center; + font-size: 14px; + color: @gray-8; +} + +.umb-upload-button-big:hover{color: @gray-8;} + +.umb-upload-drop-zone .info i.icon, .umb-upload-button-big i.icon{ + font-size: 55px; + line-height: 70px +} + +.umb-upload-button-big {display: block} +.umb-upload-button-big input { + left: 0; + bottom: 0; + height: 100%; + width: 100%; +} + + + +// +// Photo folder styling +// -------------------------------------------------- + +.umb-photo-folder .picrow{ + overflow-y: hidden; + position: relative; +} + + + +.umb-photo-folder .picrow div, .umb-photo-preview{ + margin: 0px; + padding: 0px; + border: none; + display: inline-block; + vertical-align: top; + position: relative; +} + + + +.umb-photo-folder .picrow div a:first-child { + width:100%; + height:100%; +} + +.umb-photo-folder .picrow div.umb-photo { + width:100%; + height:100%; + background-color: @gray-10; +} + +.umb-photo-folder a:hover{text-decoration: none} +.umb-photo-folder .umb-non-thumbnail{ + text-align: center; + vertical-align: middle; + font-size: 12px; + background: @gray-10; + color: @black; + text-decoration: none; +} + +.umb-photo-folder .selector-overlay{ + display: none; +} + +//this is a temp hack, to provide selectors in the dialog: +.umb-photo-folder .pic:hover .selector-overlay { + position: absolute; + bottom: 0px; + left: 0px; + right: 0px; + padding: 5px; + background: @black; + z-index: 100; + display: block; + text-align: center; + color: @white; + opacity: 0.4; + text-decoration:none; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.umb-photo-folder .umb-non-thumbnail i{ + color: @gray-8; + font-size: 50px; + line-height: 60px; + display: block; + margin: auto; + /*vertically aligns */ + position: relative; + top: 50%; + transform: translateY(-50%); +} + +.umb-photo-folder .umb-non-thumbnail span{ + position: absolute; + display: block; + margin: auto; + width: 100%; + top: 20px; +} + +.umb-photo-folder .selected{ + position: relative; +} + +.umb-photo-folder .selected:before{ + content: "\e165"; + font-family: Icomoon; + + position: absolute; + bottom: 10px; + right: 10px; + + font-size: 24px; + color: @black; + opacity: 0.5; + background: @white; + + line-height: 36px; + text-align: center; + -moz-border-radius: 15px; + border-radius: 15px; + + height: 32px; + width: 32px; + overflow: hidden; + display: block; + z-index: 100; +} + + +// +// File upload +// -------------------------------------------------- +.umb-fileupload .preview { + border-radius: 5px; + border: 1px solid @gray-6; + padding: 3px; + background: @gray-9; + float: left; + margin-right: 30px; + margin-bottom: 30px; +} + +.umb-fileupload ul { + list-style: none; + vertical-align: middle; + margin-bottom: 0px; +} + +.umb-fileupload label { + vertical-align: middle; + padding-left: 7px; + font-weight: normal; +} + +.umb-fileupload .preview-file { + color: @gray-4; + height: 45px; + width: 55px; + text-align: center; + text-transform: uppercase; + font-size: 10px; + padding-top: 27px; +} + +.umb-fileupload .file-icon { + text-align: center; + display: block; + position: relative; + padding: 5px 0; + + > .icon { + font-size: 70px; + line-height: 110%; + color: @gray-4; + text-align: center; + } + + > span { + color: @white; + background: @gray-4; + padding: 1px 3px; + font-size: 12px; + line-height: 130%; + position: absolute; + top: 45px; + left: 110px; + } +} + +.umb-fileupload input { + font-size: 12px; + line-height: 1; +} + +// +// Member group picker +// -------------------------------------------------- + +.umb-member-group-box { + width: 45%; +} +.umb-member-group-box:nth-child(1){ + float:left; +} +.umb-member-group-box:nth-child(2){ + float:right; +} + +// +// Related links +// -------------------------------------------------- +.umb-relatedlinks table > tr > td { word-wrap:break-word; word-break: break-all; border-bottom: 1px solid transparent; } +.umb-relatedlinks .handle { cursor:move; } +.umb-relatedlinks table > tbody > tr.unsortable .handle { cursor:default; } + +.umb-relatedlinks table td.col-sort { width: 20px; } +.umb-relatedlinks table td.col-caption { min-width: 200px; } +.umb-relatedlinks table td.col-link { min-width: 200px; } +.umb-relatedlinks table td.col-actions { min-width: 120px; } + +.umb-relatedlinks table td.col-caption .control-wrapper, +.umb-relatedlinks table td.col-link .control-wrapper { display: flex; } + +.umb-relatedlinks table td.col-caption .control-wrapper input[type="text"], +.umb-relatedlinks table td.col-link .control-wrapper input[type="text"] { width: auto; flex: 1; } + +/* sortable placeholder */ +.umb-relatedlinks .sortable-placeholder { + background-color: @tableBackgroundAccent; + display: table-row; +} +.umb-relatedlinks .sortable-placeholder > td { + display: table-cell; + padding: 8px; +} +.umb-relatedlinks .ui-sortable-helper { + display: table-row; + background-color: @white; + opacity: 0.7; +} +.umb-relatedlinks .ui-sortable-helper > td { + display: table-cell; + border-bottom: 1px solid @tableBorder; +} + + +// +// Tags +// -------------------------------------------------- +.umb-tags{border: @gray-10 solid 1px; padding: 10px; font-size: 13px; text-shadow: none;} +.umb-tags .tag{cursor: pointer; margin: 7px; padding: 7px; background: @turquoise} +.umb-tags .tag i{padding: 2px;} +.umb-tags input{border: none; background: @white} + +// +// Date/time picker +// -------------------------------------------------- +.bootstrap-datetimepicker-widget .btn{padding: 0;} +.bootstrap-datetimepicker-widget .picker-switch .btn{ background: none; border: none;} +.umb-datepicker .input-append .add-on{cursor: pointer;} +.umb-datepicker p {margin-top:10px;} +.umb-datepicker p a{color: @gray-3;} + +// +// Code mirror - even though this isn't a proprety editor right now, it could be so I'm putting the styles here +// -------------------------------------------------- + +.CodeMirror, .CodeMirror-scroll { + height: 100%; + min-height:200px; +} + +// +// Nested boolean (e.g. list view bulk action permissions) +// ---------------------=====----------------------------- +.umb-nested-boolean label {margin-bottom: 8px; float: left; width: 320px;} +.umb-nested-boolean label span {float: left; width: 80%;} +.umb-nested-boolean label input[type='checkbox'] {margin-right: 10px; float: left;} diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index eebaa6c969..ac41f69998 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -1,115 +1,115 @@ -// Sections -// ------------------------- - -ul.sections { - margin: 0; - display: flex; - margin-left: -20px; -} - -ul.sections>li { - display: flex; - justify-content: center; - align-items: center; - padding: 0 20px; - position: relative; -} - -ul.sections>li>a { - color: @white; - height: @appHeaderHeight; - display: flex; - align-items: center; - justify-content: center; - position: relative; - text-decoration: none; - outline: none; - cursor: pointer; -} - -ul.sections>li>a .section__name { - opacity: 0.6; - transition: opacity .1s linear; -} - -ul.sections>li>a::after { - content: ""; - height: 4px; - width: 100%; - background-color: @turquoise; - position: absolute; - bottom: -4px; - border-radius: 3px 3px 0 0; - opacity: 0; - padding: 0 2px; - transition: all .2s linear; -} - -ul.sections>li.current>a::after { - opacity: 1; - bottom: 0; -} - -ul.sections>li.current>a .section__name, -ul.sections>li>a:hover .section__name, -ul.sections>li>a:focus .section__name { - opacity: 1; -} - - - -/* Sections tray */ - -ul.sections>li.expand i { - height: 5px; - width: 5px; - border-radius: 50%; - background: #fff; - display: inline-block; - margin: 0 5px 0 0; - opacity: 0.6; -} - -ul.sections-tray { - position: absolute; - top: @appHeaderHeight; - left: 0; - margin: 0; - list-style: none; - background: @purple; - z-index: 10000; - border-radius: 0 0 3px 3px; -} - -ul.sections-tray>li>a { - padding: 8px 24px; - color: @white; - text-decoration: none; - display: block; - position: relative; -} - -ul.sections-tray>li>a::after { - content: ""; - width: 4px; - height: 100%; - background-color: @turquoise; - position: absolute; - border-radius: 0 3px 3px 0; - opacity: 0; - transition: all .2s linear; - top: 0; - left: 0; -} - -ul.sections-tray>li.current>a::after { - opacity: 1; -} - -ul.sections-tray>li>a .section__name { - opacity: 0.6; -} - -ul.sections-tray>li>a:hover .section__name { - opacity: 1; -} +// Sections +// ------------------------- + +ul.sections { + margin: 0; + display: flex; + margin-left: -20px; +} + +ul.sections>li { + display: flex; + justify-content: center; + align-items: center; + padding: 0 20px; + position: relative; +} + +ul.sections>li>a { + color: @white; + height: @appHeaderHeight; + display: flex; + align-items: center; + justify-content: center; + position: relative; + text-decoration: none; + outline: none; + cursor: pointer; +} + +ul.sections>li>a .section__name { + opacity: 0.6; + transition: opacity .1s linear; +} + +ul.sections>li>a::after { + content: ""; + height: 4px; + width: 100%; + background-color: @turquoise; + position: absolute; + bottom: -4px; + border-radius: 3px 3px 0 0; + opacity: 0; + padding: 0 2px; + transition: all .2s linear; +} + +ul.sections>li.current>a::after { + opacity: 1; + bottom: 0; +} + +ul.sections>li.current>a .section__name, +ul.sections>li>a:hover .section__name, +ul.sections>li>a:focus .section__name { + opacity: 1; +} + + + +/* Sections tray */ + +ul.sections>li.expand i { + height: 5px; + width: 5px; + border-radius: 50%; + background: #fff; + display: inline-block; + margin: 0 5px 0 0; + opacity: 0.6; +} + +ul.sections-tray { + position: absolute; + top: @appHeaderHeight; + left: 0; + margin: 0; + list-style: none; + background: @purple; + z-index: 10000; + border-radius: 0 0 3px 3px; +} + +ul.sections-tray>li>a { + padding: 8px 24px; + color: @white; + text-decoration: none; + display: block; + position: relative; +} + +ul.sections-tray>li>a::after { + content: ""; + width: 4px; + height: 100%; + background-color: @turquoise; + position: absolute; + border-radius: 0 3px 3px 0; + opacity: 0; + transition: all .2s linear; + top: 0; + left: 0; +} + +ul.sections-tray>li.current>a::after { + opacity: 1; +} + +ul.sections-tray>li>a .section__name { + opacity: 0.6; +} + +ul.sections-tray>li>a:hover .section__name { + opacity: 1; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/tables.less b/src/Umbraco.Web.UI.Client/src/less/tables.less index 7317d93d4a..5f1acfaf5d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tables.less +++ b/src/Umbraco.Web.UI.Client/src/less/tables.less @@ -1,251 +1,251 @@ -// -// Tables -// -------------------------------------------------- - - -// BASE TABLES -// ----------------- - -table { - max-width: 100%; - background-color: @tableBackground; - border-collapse: collapse; - border-spacing: 0; -} - -// BASELINE STYLES -// --------------- - -.table { - width: 100%; - margin-bottom: @baseLineHeight; - box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); - background: @tableBackground; - border-radius: @baseBorderRadius; - font-size: 14px; - // Cells - th, - td { - padding: 10px 20px; - line-height: @baseLineHeight; - text-align: left; - border-top: 1px solid @tableBorder; - } - th { - font-weight: bold; - } - // Bottom align for column headings - thead th { - padding-top: 15px; - padding-bottom: 15px; - color: @gray-3; - vertical-align: bottom; - } - // Remove top border from thead by default - caption + thead tr:first-child th, - caption + thead tr:first-child td, - colgroup + thead tr:first-child th, - colgroup + thead tr:first-child td, - thead:first-child tr:first-child th, - thead:first-child tr:first-child td { - border-top: 0; - } - // Account for multiple tbody instances - tbody + tbody { - border-top: 2px solid @tableBorder; - } - - // Nesting - .table { - background-color: @bodyBackground; - } -} - - - -// CONDENSED TABLE W/ HALF PADDING -// ------------------------------- - -.table-condensed { - th, - td { - padding: 4px 5px; - } -} - - -// BORDERED VERSION -// ---------------- - -.table-bordered { - border: 1px solid @tableBorder; - border-collapse: separate; // Done so we can round those corners! - *border-collapse: collapse; // IE7 can't round corners anyway - border-left: 0; - box-shadow: none; - .border-radius(@baseBorderRadius); - th, - td { - border-left: 1px solid @tableBorder; - } - // Prevent a double border - caption + thead tr:first-child th, - caption + tbody tr:first-child th, - caption + tbody tr:first-child td, - colgroup + thead tr:first-child th, - colgroup + tbody tr:first-child th, - colgroup + tbody tr:first-child td, - thead:first-child tr:first-child th, - tbody:first-child tr:first-child th, - tbody:first-child tr:first-child td { - border-top: 0; - } - // For first th/td in the first row in the first thead or tbody - thead:first-child tr:first-child > th:first-child, - tbody:first-child tr:first-child > td:first-child, - tbody:first-child tr:first-child > th:first-child { - .border-top-left-radius(@baseBorderRadius); - } - // For last th/td in the first row in the first thead or tbody - thead:first-child tr:first-child > th:last-child, - tbody:first-child tr:first-child > td:last-child, - tbody:first-child tr:first-child > th:last-child { - .border-top-right-radius(@baseBorderRadius); - } - // For first th/td (can be either) in the last row in the last thead, tbody, and tfoot - thead:last-child tr:last-child > th:first-child, - tbody:last-child tr:last-child > td:first-child, - tbody:last-child tr:last-child > th:first-child, - tfoot:last-child tr:last-child > td:first-child, - tfoot:last-child tr:last-child > th:first-child { - .border-bottom-left-radius(@baseBorderRadius); - } - // For last th/td (can be either) in the last row in the last thead, tbody, and tfoot - thead:last-child tr:last-child > th:last-child, - tbody:last-child tr:last-child > td:last-child, - tbody:last-child tr:last-child > th:last-child, - tfoot:last-child tr:last-child > td:last-child, - tfoot:last-child tr:last-child > th:last-child { - .border-bottom-right-radius(@baseBorderRadius); - } - - // Clear border-radius for first and last td in the last row in the last tbody for table with tfoot - tfoot + tbody:last-child tr:last-child td:first-child { - .border-bottom-left-radius(0); - } - tfoot + tbody:last-child tr:last-child td:last-child { - .border-bottom-right-radius(0); - } - - // Special fixes to round the left border on the first td/th - caption + thead tr:first-child th:first-child, - caption + tbody tr:first-child td:first-child, - colgroup + thead tr:first-child th:first-child, - colgroup + tbody tr:first-child td:first-child { - .border-top-left-radius(@baseBorderRadius); - } - caption + thead tr:first-child th:last-child, - caption + tbody tr:first-child td:last-child, - colgroup + thead tr:first-child th:last-child, - colgroup + tbody tr:first-child td:last-child { - .border-top-right-radius(@baseBorderRadius); - } - -} - - - - -// ZEBRA-STRIPING -// -------------- - -// Default zebra-stripe styles (alternating gray and transparent backgrounds) -.table-striped { - tbody { - > tr:nth-child(odd) > td, - > tr:nth-child(odd) > th { - background-color: @tableBackgroundAccent; - } - } -} - - -// HOVER EFFECT -// ------------ -// Placed here since it has to come after the potential zebra striping -.table-hover { - tbody { - tr:hover > td, - tr:hover > th { - background-color: @tableBackgroundHover; - } - } -} - - -// TABLE CELL SIZING -// ----------------- - -// Reset default grid behavior -table td[class*="span"], -table th[class*="span"], -.row-fluid table td[class*="span"], -.row-fluid table th[class*="span"] { - display: table-cell; - float: none; // undo default grid column styles - margin-left: 0; // undo default grid column styles -} - -// Change the column widths to account for td/th padding -.table td, -.table th { - &.span1 { .tableColumns(1); } - &.span2 { .tableColumns(2); } - &.span3 { .tableColumns(3); } - &.span4 { .tableColumns(4); } - &.span5 { .tableColumns(5); } - &.span6 { .tableColumns(6); } - &.span7 { .tableColumns(7); } - &.span8 { .tableColumns(8); } - &.span9 { .tableColumns(9); } - &.span10 { .tableColumns(10); } - &.span11 { .tableColumns(11); } - &.span12 { .tableColumns(12); } -} - - - -// TABLE BACKGROUNDS -// ----------------- -// Exact selectors below required to override .table-striped - -.table tbody tr { - &.success > td { - background-color: @successBackground; - } - &.error > td { - background-color: @errorBackground; - } - &.warning > td { - background-color: @warningBackground; - } - &.info > td { - background-color: @infoBackground; - } -} - -// Hover states for .table-hover -.table-hover tbody tr { - &.success:hover > td { - background-color: darken(@successBackground, 5%); - } - &.error:hover > td { - background-color: darken(@errorBackground, 5%); - } - &.warning:hover > td { - background-color: darken(@warningBackground, 5%); - } - &.info:hover > td { - background-color: darken(@infoBackground, 5%); - } -} +// +// Tables +// -------------------------------------------------- + + +// BASE TABLES +// ----------------- + +table { + max-width: 100%; + background-color: @tableBackground; + border-collapse: collapse; + border-spacing: 0; +} + +// BASELINE STYLES +// --------------- + +.table { + width: 100%; + margin-bottom: @baseLineHeight; + box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); + background: @tableBackground; + border-radius: @baseBorderRadius; + font-size: 14px; + // Cells + th, + td { + padding: 10px 20px; + line-height: @baseLineHeight; + text-align: left; + border-top: 1px solid @tableBorder; + } + th { + font-weight: bold; + } + // Bottom align for column headings + thead th { + padding-top: 15px; + padding-bottom: 15px; + color: @gray-3; + vertical-align: bottom; + } + // Remove top border from thead by default + caption + thead tr:first-child th, + caption + thead tr:first-child td, + colgroup + thead tr:first-child th, + colgroup + thead tr:first-child td, + thead:first-child tr:first-child th, + thead:first-child tr:first-child td { + border-top: 0; + } + // Account for multiple tbody instances + tbody + tbody { + border-top: 2px solid @tableBorder; + } + + // Nesting + .table { + background-color: @bodyBackground; + } +} + + + +// CONDENSED TABLE W/ HALF PADDING +// ------------------------------- + +.table-condensed { + th, + td { + padding: 4px 5px; + } +} + + +// BORDERED VERSION +// ---------------- + +.table-bordered { + border: 1px solid @tableBorder; + border-collapse: separate; // Done so we can round those corners! + *border-collapse: collapse; // IE7 can't round corners anyway + border-left: 0; + box-shadow: none; + .border-radius(@baseBorderRadius); + th, + td { + border-left: 1px solid @tableBorder; + } + // Prevent a double border + caption + thead tr:first-child th, + caption + tbody tr:first-child th, + caption + tbody tr:first-child td, + colgroup + thead tr:first-child th, + colgroup + tbody tr:first-child th, + colgroup + tbody tr:first-child td, + thead:first-child tr:first-child th, + tbody:first-child tr:first-child th, + tbody:first-child tr:first-child td { + border-top: 0; + } + // For first th/td in the first row in the first thead or tbody + thead:first-child tr:first-child > th:first-child, + tbody:first-child tr:first-child > td:first-child, + tbody:first-child tr:first-child > th:first-child { + .border-top-left-radius(@baseBorderRadius); + } + // For last th/td in the first row in the first thead or tbody + thead:first-child tr:first-child > th:last-child, + tbody:first-child tr:first-child > td:last-child, + tbody:first-child tr:first-child > th:last-child { + .border-top-right-radius(@baseBorderRadius); + } + // For first th/td (can be either) in the last row in the last thead, tbody, and tfoot + thead:last-child tr:last-child > th:first-child, + tbody:last-child tr:last-child > td:first-child, + tbody:last-child tr:last-child > th:first-child, + tfoot:last-child tr:last-child > td:first-child, + tfoot:last-child tr:last-child > th:first-child { + .border-bottom-left-radius(@baseBorderRadius); + } + // For last th/td (can be either) in the last row in the last thead, tbody, and tfoot + thead:last-child tr:last-child > th:last-child, + tbody:last-child tr:last-child > td:last-child, + tbody:last-child tr:last-child > th:last-child, + tfoot:last-child tr:last-child > td:last-child, + tfoot:last-child tr:last-child > th:last-child { + .border-bottom-right-radius(@baseBorderRadius); + } + + // Clear border-radius for first and last td in the last row in the last tbody for table with tfoot + tfoot + tbody:last-child tr:last-child td:first-child { + .border-bottom-left-radius(0); + } + tfoot + tbody:last-child tr:last-child td:last-child { + .border-bottom-right-radius(0); + } + + // Special fixes to round the left border on the first td/th + caption + thead tr:first-child th:first-child, + caption + tbody tr:first-child td:first-child, + colgroup + thead tr:first-child th:first-child, + colgroup + tbody tr:first-child td:first-child { + .border-top-left-radius(@baseBorderRadius); + } + caption + thead tr:first-child th:last-child, + caption + tbody tr:first-child td:last-child, + colgroup + thead tr:first-child th:last-child, + colgroup + tbody tr:first-child td:last-child { + .border-top-right-radius(@baseBorderRadius); + } + +} + + + + +// ZEBRA-STRIPING +// -------------- + +// Default zebra-stripe styles (alternating gray and transparent backgrounds) +.table-striped { + tbody { + > tr:nth-child(odd) > td, + > tr:nth-child(odd) > th { + background-color: @tableBackgroundAccent; + } + } +} + + +// HOVER EFFECT +// ------------ +// Placed here since it has to come after the potential zebra striping +.table-hover { + tbody { + tr:hover > td, + tr:hover > th { + background-color: @tableBackgroundHover; + } + } +} + + +// TABLE CELL SIZING +// ----------------- + +// Reset default grid behavior +table td[class*="span"], +table th[class*="span"], +.row-fluid table td[class*="span"], +.row-fluid table th[class*="span"] { + display: table-cell; + float: none; // undo default grid column styles + margin-left: 0; // undo default grid column styles +} + +// Change the column widths to account for td/th padding +.table td, +.table th { + &.span1 { .tableColumns(1); } + &.span2 { .tableColumns(2); } + &.span3 { .tableColumns(3); } + &.span4 { .tableColumns(4); } + &.span5 { .tableColumns(5); } + &.span6 { .tableColumns(6); } + &.span7 { .tableColumns(7); } + &.span8 { .tableColumns(8); } + &.span9 { .tableColumns(9); } + &.span10 { .tableColumns(10); } + &.span11 { .tableColumns(11); } + &.span12 { .tableColumns(12); } +} + + + +// TABLE BACKGROUNDS +// ----------------- +// Exact selectors below required to override .table-striped + +.table tbody tr { + &.success > td { + background-color: @successBackground; + } + &.error > td { + background-color: @errorBackground; + } + &.warning > td { + background-color: @warningBackground; + } + &.info > td { + background-color: @infoBackground; + } +} + +// Hover states for .table-hover +.table-hover tbody tr { + &.success:hover > td { + background-color: darken(@successBackground, 5%); + } + &.error:hover > td { + background-color: darken(@errorBackground, 5%); + } + &.warning:hover > td { + background-color: darken(@warningBackground, 5%); + } + &.info:hover > td { + background-color: darken(@infoBackground, 5%); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index 6076c256c3..201ea238bf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -1,552 +1,552 @@ -// item-list -// ------------------------- - - -.umb-item-list { - margin: 0; - width: auto; - display: block -} -.umb-item-list li { - display: block; - width: auto; -} - - - - -// Tree -// ------------------------- - -.umb-tree { - margin: 0; - min-width: 100%; - width: auto; -} - -.umb-tree li { - display: block; - min-width: 100%; - width: auto; -} -.umb-tree li.current > div, -.umb-tree div.selected { - background: @turquoise-d1; -} -.umb-tree li.current > div a.umb-options i, -.umb-tree div.selected i { - background: @white; - border-color: @turquoise-d1; - transition: opacity 120ms ease; -} - -.umb-tree li.current > div a.umb-options:hover i, -.umb-tree div.selected i { - opacity: .7; -} - -.umb-tree li.current > div a, -.umb-tree li.current > div i.icon, -.umb-tree li.current > div ins { - color: @white !important; - background-color: @turquoise-d1; - border-color: @turquoise-d1; -} - -.umb-tree li.root > div:first-child { - padding: 0; -} - -.umb-tree li.root > div h5, .umb-tree li.root > div h6 { - margin: 0; - width: 100%; - display: flex; - align-items: center; -} - -.umb-tree li.root > div:first-child h5 > a, .umb-tree-header { - display: flex; - padding: 20px 0 20px 20px; - box-sizing: border-box; -} - -.umb-tree * { - white-space: nowrap -} -.umb-tree ul { - padding: 0; - margin: 0; - min-width: 100%; - width: 100%; - //display: table -} - -.umb-tree ul.collapsed { - display:none; -} - -.umb-tree a { - cursor:pointer; - text-decoration: none; - outline: none; -} - -.umb-tree a:hover { - text-decoration: none -} - -/*.umb-tree div.tree-node { - padding: 5px 0 5px 0; - position: relative; - overflow: hidden; - display: flex; - flex-wrap: nowrap; - align-items: center; -}*/ - -.umb-tree div { - padding: 5px 0 5px 0; - position: relative; - overflow: hidden; - display: flex; - flex-wrap: nowrap; - align-items: center; -} - -.umb-tree a.noSpr { - background-position: 0 -} - -.umb-tree div > a.umb-options { - visibility: hidden; - flex: 0 0 auto; - margin-left: auto; -} - -.umb-tree div:hover > a.umb-options { - visibility: visible; -} - -.umb-tree li.root > div a, -.umb-tree li.root h5, .umb-tree-header { - color: @gray-2; - font-weight: bold; - font-size: 15px; -} - -.umb-tree ins { - margin: -4px 0 0 -16px; - width: 16px; - height: 16px; - visibility: hidden; - text-decoration: none; - font-size: 12px; - transition: opacity 120ms ease; -} - -.umb-tree ins:hover { - opacity: .7; -} - -.umb-tree li:hover ins { - visibility: visible; - cursor: pointer -} - -.umb-tree li div { - padding: 0; -} - -.umb-tree li > div a:not(.umb-options) { - padding: 6px 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.umb-tree li > div:hover a:not(.umb-options) { - overflow: hidden; - margin-right: 6px; -} - -.umb-tree .icon { - vertical-align: middle; - margin: 0 13px 0 0; - color: @gray-1; - font-size: 20px; -} - -.umb-tree-icon { - cursor: pointer; -} - -.umb-tree i.noSpr { - display: inline-block; - margin-top: 1px; - width: 16px; - height: 16px; - line-height: 16px; -} - -.umb-tree div:hover { - background: @gray-10; -} - -.umb-tree small.search-subtitle{ - color: @gray-7; - display: block; - padding-left: 35px; -} - -.umb-tree .umb-tree-node-search { - cursor:pointer; - /*color:@turquoise;*/ -} - -.umb-tree div.umb-search-group { - position: inherit; - display: inherit; -} - -.umb-tree div.umb-search-group:hover { - background: inherit; -} -.umb-tree div.umb-search-group h6 { - /*color: @gray-5;*/ - padding: 10px 0 10px 20px; - font-weight: inherit; - background: @gray-10; - font-size: 14px; - font-weight: bold; -} - -.umb-tree .umb-search-group-item { - padding-left: 20px; -} - -.umb-tree .umb-search-group-item-link { - display: flex; - flex-wrap: wrap; - flex-direction: column; - font-weight: normal !important; -} - -.icon-check:before { - content: "\e165"; -} - -.umb-tree .umb-tree-node-checked i[class^="icon-"], -.umb-tree .umb-tree-node-checked i[class*=" icon-"] { - font-family: 'icomoon' !important; - color:@green !important; -} -.umb-tree .umb-tree-node-checked i:before { - /*check box*/ - content: "\e165" !important; - font-family: inherit; -} - -a.umb-options { - visibility: hidden; - display: flex; - justify-content: flex-end; - padding: 9px 5px; - text-align: center; - cursor: pointer; - margin-right: 10px; -} - -a.umb-options i { - height: 5px !important; - width: 5px !important; - border-radius: 20px; - background: @black; - display: inline-block; - margin: 0 2px 0 0; -} - -a.umb-options i:last-child { - margin: 0; -} - -a.umb-options:hover { - background: @btnBackgroundHighlight; - .border-radius(@baseBorderRadius); -} - -li.root > div > a.umb-options { - top: 18px; - display: flex; - padding: 10px 5px; -} - -.hide-options a.umb-options{display: none !important} -.hide-header h5{display: none !important} - - -.umb-icon-item { - padding: 2px; - padding-left: 55px; - display: block; - position: relative; -} - -.umb-icon-item:hover { - background: @gray-10; -} -.umb-icon-item i.icon { - position: absolute; - top: 8px; - left: 19px; -} -.umb-icon-item a:hover div { - text-decoration: underline; -} - -.umb-icon-item a { - color: @gray-3; - padding-top: 3px; - height: 15px; - font-size: 12px; - text-decoration: none; -} -.umb-icon-item small { - color: @gray-6; - font-size: 10px; - display: block -} -.umb-icon-item:hover a.umb-options { - visibility: visible -} -.umb-icon-item .umb-spr { - float: left -} - - - -// Tree item states -// ------------------------- -div.not-published > i.icon,div.not-published > a{ - opacity: 0.6; -} -div.protected:before{ - content:"\e256"; - font-family: 'icomoon'; - color: @red; - position: absolute; - font-size: 20px; - padding-left: 7px; - padding-top: 7px; - bottom: 0; -} - -div.has-unpublished-version:before{ - content:"\e25a"; - font-family: 'icomoon'; - color: @green; - position: absolute; - font-size: 20px; - padding-left: 7px; - padding-top: 7px; - bottom: 0; -} - -div.not-allowed > i.icon,div.not-allowed > a{ - cursor: not-allowed; -} - -// override small icon color -.umb-tree li.current > div:before { - color: @turquoise-l2; -} -div.is-container:before{ - content:"\e04e"; - font-family: 'icomoon'; - color: @turquoise; - position: absolute; - font-size: 8px; - padding-left: 13px; - padding-top: 8px; - pointer-events: none; - bottom: 0; -} - -div.locked:before{ - content:"\e0a7"; - font-family: 'icomoon'; - color: @red; - position: absolute; - font-size: 20px; - padding-left: 7px; - padding-top: 7px; - 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 { - margin: 0; - padding: 0px; - list-style: none; - user-select: none; -} - -.umb-actions li.sep { - display: block; - border-top: 1px solid @gray-9; -} - -.umb-actions li.sep:first-child { - border-top: none; -} - -.umb-actions a { - white-space: nowrap; - display: block; - font-size: 15px; - color: @black; - padding: 9px 25px 9px 20px; - text-decoration: none; - cursor: pointer; - display: flex; - align-items: center; -} - -.umb-actions a:hover, .umb-actions a:focus, -.umb-actions li.selected { - color: @black !important; - background: @gray-10 !important; -} - -.umb-actions .menu-label { - display: inline-block; - vertical-align: middle; - padding-left: 15px; -} - -.umb-actions i { - color: @gray-6; - font-size: 18px; - vertical-align: middle; - color: @gray-3; -} - -.umb-actions-child { - list-style: none; - display: block; - margin: 0px; -} - -.umb-actions-child li { - display: block; -} - -.umb-actions-child a { - display: block; - clear: both; - text-decoration: none; - padding-left: 10px; -} -.umb-actions-child li .menu-label { - font-size: 14px; - color: @black; - margin-left: 10px; -} - -.umb-actions-child li .menu-label small { - font-size: 12px; - display: block; - clear: right; - line-height: 14px; - color: @gray-6; - white-space: normal; - margin-top: 2px; -} -.umb-actions-child li a:hover .menuLabel small { - text-decoration: none !important -} -.umb-actions-child i { - font-size: 30px; - min-width: 30px; - text-align: center; - line-height: 24px; /* set line-height to ensure all icons use same line-height */ -} - -.umb-actions-child li.add { - margin-top: 20px; - border-top: 1px solid @gray-8; - padding-top: 20px; -} -.umb-actions-child li.add i { - opacity: 0.4; -} - - -// Tree icon colors -// ------------------------- - -.umb-tree i.icon.blue { - color: @blue; -} -.umb-tree i.icon.green { - color: @green; -} -.umb-tree i.icon.purple { - color: @purple; -} -.umb-tree i.icon.orange { - color: @orange; -} -.umb-tree i.icon.red { - color: @red; -} - - - - -// Loading Animation -// ------------------------ - -.umb-tree li div.l{ - width:100%; - height:1px; - overflow:hidden; - position: absolute; - left: 0; - bottom: 0; -} - -.umb-tree li div.l div { - .umb-loader; -} - -//loader defaults -.umb-tree .umb-loader{ - height: 10px; margin: 10px 10px 10px 10px; -} - - -/*body.touch .umb-tree .icon{font-size: 19px;}*/ -body.touch .umb-tree ins{font-size: 14px; visibility: visible; padding: 7px;} -body.touch .umb-tree li > div { - padding-top: 8px; - padding-bottom: 8px; - font-size: 110%; -} - -// change height of this if touch devices should have a different height of preloader. -body.touch .umb-tree li div.l div { - padding: 0; -} - -body.touch .umb-actions a { - padding: 7px 25px 7px 20px; - font-size: 110%; +// item-list +// ------------------------- + + +.umb-item-list { + margin: 0; + width: auto; + display: block +} +.umb-item-list li { + display: block; + width: auto; +} + + + + +// Tree +// ------------------------- + +.umb-tree { + margin: 0; + min-width: 100%; + width: auto; +} + +.umb-tree li { + display: block; + min-width: 100%; + width: auto; +} +.umb-tree li.current > div, +.umb-tree div.selected { + background: @turquoise-d1; +} +.umb-tree li.current > div a.umb-options i, +.umb-tree div.selected i { + background: @white; + border-color: @turquoise-d1; + transition: opacity 120ms ease; +} + +.umb-tree li.current > div a.umb-options:hover i, +.umb-tree div.selected i { + opacity: .7; +} + +.umb-tree li.current > div a, +.umb-tree li.current > div i.icon, +.umb-tree li.current > div ins { + color: @white !important; + background-color: @turquoise-d1; + border-color: @turquoise-d1; +} + +.umb-tree li.root > div:first-child { + padding: 0; +} + +.umb-tree li.root > div h5, .umb-tree li.root > div h6 { + margin: 0; + width: 100%; + display: flex; + align-items: center; +} + +.umb-tree li.root > div:first-child h5 > a, .umb-tree-header { + display: flex; + padding: 20px 0 20px 20px; + box-sizing: border-box; +} + +.umb-tree * { + white-space: nowrap +} +.umb-tree ul { + padding: 0; + margin: 0; + min-width: 100%; + width: 100%; + //display: table +} + +.umb-tree ul.collapsed { + display:none; +} + +.umb-tree a { + cursor:pointer; + text-decoration: none; + outline: none; +} + +.umb-tree a:hover { + text-decoration: none +} + +/*.umb-tree div.tree-node { + padding: 5px 0 5px 0; + position: relative; + overflow: hidden; + display: flex; + flex-wrap: nowrap; + align-items: center; +}*/ + +.umb-tree div { + padding: 5px 0 5px 0; + position: relative; + overflow: hidden; + display: flex; + flex-wrap: nowrap; + align-items: center; +} + +.umb-tree a.noSpr { + background-position: 0 +} + +.umb-tree div > a.umb-options { + visibility: hidden; + flex: 0 0 auto; + margin-left: auto; +} + +.umb-tree div:hover > a.umb-options { + visibility: visible; +} + +.umb-tree li.root > div a, +.umb-tree li.root h5, .umb-tree-header { + color: @gray-2; + font-weight: bold; + font-size: 15px; +} + +.umb-tree ins { + margin: -4px 0 0 -16px; + width: 16px; + height: 16px; + visibility: hidden; + text-decoration: none; + font-size: 12px; + transition: opacity 120ms ease; +} + +.umb-tree ins:hover { + opacity: .7; +} + +.umb-tree li:hover ins { + visibility: visible; + cursor: pointer +} + +.umb-tree li div { + padding: 0; +} + +.umb-tree li > div a:not(.umb-options) { + padding: 6px 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.umb-tree li > div:hover a:not(.umb-options) { + overflow: hidden; + margin-right: 6px; +} + +.umb-tree .icon { + vertical-align: middle; + margin: 0 13px 0 0; + color: @gray-1; + font-size: 20px; +} + +.umb-tree-icon { + cursor: pointer; +} + +.umb-tree i.noSpr { + display: inline-block; + margin-top: 1px; + width: 16px; + height: 16px; + line-height: 16px; +} + +.umb-tree div:hover { + background: @gray-10; +} + +.umb-tree small.search-subtitle{ + color: @gray-7; + display: block; + padding-left: 35px; +} + +.umb-tree .umb-tree-node-search { + cursor:pointer; + /*color:@turquoise;*/ +} + +.umb-tree div.umb-search-group { + position: inherit; + display: inherit; +} + +.umb-tree div.umb-search-group:hover { + background: inherit; +} +.umb-tree div.umb-search-group h6 { + /*color: @gray-5;*/ + padding: 10px 0 10px 20px; + font-weight: inherit; + background: @gray-10; + font-size: 14px; + font-weight: bold; +} + +.umb-tree .umb-search-group-item { + padding-left: 20px; +} + +.umb-tree .umb-search-group-item-link { + display: flex; + flex-wrap: wrap; + flex-direction: column; + font-weight: normal !important; +} + +.icon-check:before { + content: "\e165"; +} + +.umb-tree .umb-tree-node-checked i[class^="icon-"], +.umb-tree .umb-tree-node-checked i[class*=" icon-"] { + font-family: 'icomoon' !important; + color:@green !important; +} +.umb-tree .umb-tree-node-checked i:before { + /*check box*/ + content: "\e165" !important; + font-family: inherit; +} + +a.umb-options { + visibility: hidden; + display: flex; + justify-content: flex-end; + padding: 9px 5px; + text-align: center; + cursor: pointer; + margin-right: 10px; +} + +a.umb-options i { + height: 5px !important; + width: 5px !important; + border-radius: 20px; + background: @black; + display: inline-block; + margin: 0 2px 0 0; +} + +a.umb-options i:last-child { + margin: 0; +} + +a.umb-options:hover { + background: @btnBackgroundHighlight; + .border-radius(@baseBorderRadius); +} + +li.root > div > a.umb-options { + top: 18px; + display: flex; + padding: 10px 5px; +} + +.hide-options a.umb-options{display: none !important} +.hide-header h5{display: none !important} + + +.umb-icon-item { + padding: 2px; + padding-left: 55px; + display: block; + position: relative; +} + +.umb-icon-item:hover { + background: @gray-10; +} +.umb-icon-item i.icon { + position: absolute; + top: 8px; + left: 19px; +} +.umb-icon-item a:hover div { + text-decoration: underline; +} + +.umb-icon-item a { + color: @gray-3; + padding-top: 3px; + height: 15px; + font-size: 12px; + text-decoration: none; +} +.umb-icon-item small { + color: @gray-6; + font-size: 10px; + display: block +} +.umb-icon-item:hover a.umb-options { + visibility: visible +} +.umb-icon-item .umb-spr { + float: left +} + + + +// Tree item states +// ------------------------- +div.not-published > i.icon,div.not-published > a{ + opacity: 0.6; +} +div.protected:before{ + content:"\e256"; + font-family: 'icomoon'; + color: @red; + position: absolute; + font-size: 20px; + padding-left: 7px; + padding-top: 7px; + bottom: 0; +} + +div.has-unpublished-version:before{ + content:"\e25a"; + font-family: 'icomoon'; + color: @green; + position: absolute; + font-size: 20px; + padding-left: 7px; + padding-top: 7px; + bottom: 0; +} + +div.not-allowed > i.icon,div.not-allowed > a{ + cursor: not-allowed; +} + +// override small icon color +.umb-tree li.current > div:before { + color: @turquoise-l2; +} +div.is-container:before{ + content:"\e04e"; + font-family: 'icomoon'; + color: @turquoise; + position: absolute; + font-size: 8px; + padding-left: 13px; + padding-top: 8px; + pointer-events: none; + bottom: 0; +} + +div.locked:before{ + content:"\e0a7"; + font-family: 'icomoon'; + color: @red; + position: absolute; + font-size: 20px; + padding-left: 7px; + padding-top: 7px; + 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 { + margin: 0; + padding: 0px; + list-style: none; + user-select: none; +} + +.umb-actions li.sep { + display: block; + border-top: 1px solid @gray-9; +} + +.umb-actions li.sep:first-child { + border-top: none; +} + +.umb-actions a { + white-space: nowrap; + display: block; + font-size: 15px; + color: @black; + padding: 9px 25px 9px 20px; + text-decoration: none; + cursor: pointer; + display: flex; + align-items: center; +} + +.umb-actions a:hover, .umb-actions a:focus, +.umb-actions li.selected { + color: @black !important; + background: @gray-10 !important; +} + +.umb-actions .menu-label { + display: inline-block; + vertical-align: middle; + padding-left: 15px; +} + +.umb-actions i { + color: @gray-6; + font-size: 18px; + vertical-align: middle; + color: @gray-3; +} + +.umb-actions-child { + list-style: none; + display: block; + margin: 0px; +} + +.umb-actions-child li { + display: block; +} + +.umb-actions-child a { + display: block; + clear: both; + text-decoration: none; + padding-left: 10px; +} +.umb-actions-child li .menu-label { + font-size: 14px; + color: @black; + margin-left: 10px; +} + +.umb-actions-child li .menu-label small { + font-size: 12px; + display: block; + clear: right; + line-height: 14px; + color: @gray-6; + white-space: normal; + margin-top: 2px; +} +.umb-actions-child li a:hover .menuLabel small { + text-decoration: none !important +} +.umb-actions-child i { + font-size: 30px; + min-width: 30px; + text-align: center; + line-height: 24px; /* set line-height to ensure all icons use same line-height */ +} + +.umb-actions-child li.add { + margin-top: 20px; + border-top: 1px solid @gray-8; + padding-top: 20px; +} +.umb-actions-child li.add i { + opacity: 0.4; +} + + +// Tree icon colors +// ------------------------- + +.umb-tree i.icon.blue { + color: @blue; +} +.umb-tree i.icon.green { + color: @green; +} +.umb-tree i.icon.purple { + color: @purple; +} +.umb-tree i.icon.orange { + color: @orange; +} +.umb-tree i.icon.red { + color: @red; +} + + + + +// Loading Animation +// ------------------------ + +.umb-tree li div.l{ + width:100%; + height:1px; + overflow:hidden; + position: absolute; + left: 0; + bottom: 0; +} + +.umb-tree li div.l div { + .umb-loader; +} + +//loader defaults +.umb-tree .umb-loader{ + height: 10px; margin: 10px 10px 10px 10px; +} + + +/*body.touch .umb-tree .icon{font-size: 19px;}*/ +body.touch .umb-tree ins{font-size: 14px; visibility: visible; padding: 7px;} +body.touch .umb-tree li > div { + padding-top: 8px; + padding-bottom: 8px; + font-size: 110%; +} + +// change height of this if touch devices should have a different height of preloader. +body.touch .umb-tree li div.l div { + padding: 0; +} + +body.touch .umb-actions a { + padding: 7px 25px 7px 20px; + font-size: 110%; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index dd25fe473f..5ee1d6dba1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -1,448 +1,448 @@ -// -// Variables -// -------------------------------------------------- - - -// Global values -// -------------------------------------------------- - - -// Grays -// ------------------------- -@black: #000; -@blackLight: #1d1d1d; -@grayDarker: #222; -@grayDark: #343434; -@gray: #555; -@grayMed: #7f7f7f; -@grayLight: #d9d9d9; -@grayLighter: #f8f8f8; -@white: #fff; - - -// Accent colors -// ------------------------- -@blue: #2e8aea; -@blueDark: #0064cd; -@blueLight: #add8e6; -@green: #46a546; -@red: #9d261d; -@yellow: #ffc40d; -@orange: #DF7F48; -@pink: #c3325f; - - -// Colors -// ------------------------- - -@turquoise-d1: #00AEA2; -@turquoise: #03BFB3; -@turquoise-l1: #42CFC5; -@turquoise-l2: #81DED8; -@turquoise-l3: #C0F0ED; -@turquoise-washed: #F3FDFC; - -@purple-d2: #1D1333; -@purple-d1: #2E2246; -@purple: #413659; -@purple-l1: #675E7A; -@purple-l2: #8D869B; -@purple-l3: #B3AFBD; -@purple-washed: #F6F3FD; - -// UI Colors -@red-d1: #F02E28; -@red: #FE3E39; -@red-l1: #FE6561; -@red-l2: #FE8B88; -@red-l3: #FFB2B0; -@red-washed: #FFECEB; - -@yellow-d2: #F0AC00; -@yellow-d1: #FFC011; -@yellow: #FFCE38; -@yellow-l1: #FFD861; -@yellow-l2: #FFE28A; -@yellow-l3: #FFECB0; -@yellow-washed: #FFFAEB; - -@green-d1: #1FB572; -@green: #35C786; -@green-l1: #4ECF95; -@green-l2: #79E1B2; -@green-l3: #A6F0CF; -@green-washed: #EBFFF6; - -// Grayscale -@gray-1: #1E1C1C; -@gray-2: #303033; -@gray-3: #515054; -@gray-4: #68676B; -@gray-5: #817F85; -@gray-6: #A2A1A6; -@gray-7: #BBBABF; -@gray-8: #D8D7D9; -@gray-9: #E9E9EB; -@gray-10: #F3F3F5; - - -.red{color: @red;} -.blue{color: @blue;} -.black{color: @black;} -.turquoise{color: @turquoise;} -.turquoise-d1{color: @turquoise-d1;} - - -//icon colors for tree icons -.color-red, .color-red i{color: @red-d1 !important;} -.color-blue, .color-blue i{color: @turquoise-d1 !important;} -.color-orange, .color-orange i{color: #d9631e !important;} -.color-green, .color-green i{color: @green-d1 !important;} -.color-yellow, .color-yellow i{color: @yellow-d1 !important;} - -/* Colors based on http://zavoloklom.github.io/material-design-color-palette/colors.html */ -.color-black, .color-black i { color: #000 !important; } -.color-blue-grey, .color-blue-grey i { color: #607d8b !important; } -.color-grey, .color-grey i { color: #9e9e9e !important; } -.color-brown, .color-brown i { color: #795548 !important; } -.color-blue, .color-blue i { color: #2196f3 !important; } -.color-light-blue, .color-light-blue i {color: #03a9f4 !important; } -.color-cyan, .color-cyan i { color: #00bcd4 !important; } -.color-green, .color-green i { color: #4caf50 !important; } -.color-light-green, .color-light-green i {color: #8bc34a !important; } -.color-lime, .color-lime i { color: #cddc39 !important; } -.color-yellow, .color-yellow i { color: #ffeb3b !important; } -.color-amber, .color-amber i { color: #ffc107 !important; } -.color-orange, .color-orange i { color: #ff9800 !important; } -.color-deep-orange, .color-deep-orange i { color: #ff5722 !important; } -.color-red, .color-red i { color: #f44336 !important; } -.color-pink, .color-pink i { color: #e91e63 !important; } -.color-purple,.color-purple i { color: #9c27b0 !important; } -.color-deep-purple, .color-deep-purple i { color: #673ab7 !important; } -.color-indigo, .color-indigo i { color: #3f51b5 !important; } - - -// Scaffolding -// ------------------------- -@appHeaderHeight: 55px; -@bodyBackground: @gray-10; -@textColor: @gray-2; - -@editorHeaderHeight: 80px; -@editorFooterHeight: 50px; - - -// Links -// ------------------------- -@linkColor: @black; -@linkColorHover: darken(@linkColor, 15%); - -// Typography -// ------------------------- -@sansFontFamily: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; -@serifFontFamily: Georgia, "Times New Roman", Times, serif; -@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace; - -@baseFontSize: 15px; -@baseFontFamily: @sansFontFamily; -@baseLineHeight: 20px; -@altFontFamily: @serifFontFamily; - -@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily -@headingsFontWeight: 500; // instead of browser default, bold -@headingsColor: inherit; // empty to use BS default, @textColor - - -// Component sizing -// ------------------------- -// Based on 14px font-size and 20px line-height - -@fontSizeLarge: @baseFontSize * 1.25; // ~18px -@fontSizeMedium: @baseFontSize * 1.15; // ~14px -@fontSizeSmall: @baseFontSize * 0.85; // ~12px -@fontSizeMini: @baseFontSize * 0.75; // ~11px - -@paddingLarge: 11px 19px; // 44px -@paddingSmall: 2px 10px; // 26px -@paddingMini: 0 6px; // 22px - - -// Disabled this to keep consistency throughout the backoffice UI. Untill a better solution is thought up, this will do. -@baseBorderRadius: 3px; // 2px; -@borderRadiusLarge: 3px; // 6px; -@borderRadiusSmall: 3px; // 3px; - - -// Tables -// ------------------------- -@tableBackground: @white; // overall background-color -@tableBackgroundAccent: @gray-10; // for striping -@tableBackgroundHover: @gray-10; // for hover -@tableBorder: @gray-9; // table and cell border - -// Buttons -// ------------------------- -@btnBackground: @gray-9; -@btnBackgroundHighlight: @gray-9; -@btnBorder: @gray-9; - -@btnPrimaryBackground: @green; -@btnPrimaryBackgroundHighlight: @green; - -@btnInfoBackground: @purple-l1; -@btnInfoBackgroundHighlight: @purple-l1; - -@btnSuccessBackground: @green; -@btnSuccessBackgroundHighlight: @green; - -@btnWarningBackground: @red-l1; -@btnWarningBackgroundHighlight: @red-l1; - -@btnDangerBackground: @red-l1; -@btnDangerBackgroundHighlight: @red-l1; - -@btnInverseBackground: @gray-2; -@btnInverseBackgroundHighlight: @gray-2; - -@btnNeutralBackground: @gray-9; -@btnNeutralBackgroundHighlight: @gray-9; - - -// Forms -// ------------------------- -@inputBackground: @white; -@inputBorder: @gray-7; -@inputBorderRadius: 0; -@inputDisabledBackground: @gray-10; -@formActionsBackground: @gray-9; -@inputHeight: @baseLineHeight + 12px; // base line-height + 8px vertical padding + 2px top/bottom border -@controlRequiredColor: @red; - - -// Tabs -// ------------------------- - -@tabsBorderRadius: @baseBorderRadius; - -// Dropdowns -// ------------------------- -@dropdownBackground: @white; -@dropdownBorder: none; -@dropdownBorderRadius: @baseBorderRadius; -@dropdownDividerTop: @gray-8; -@dropdownDividerBottom: @white; - -@dropdownLinkColor: @gray-2; -@dropdownLinkColorHover: @white; -@dropdownLinkColorActive: @white; - -@dropdownLinkBackgroundActive: @linkColor; -@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive; - - - -// COMPONENT VARIABLES -// -------------------------------------------------- - -// Drawer -@drawerWidth: 400px; - - -// Z-index master list -// ------------------------- -// Used for a bird's eye view of components dependent on the z-axis -// Try to avoid customizing these :) -@zIndexTree: 100; -@zindexDropdown: 1000; -@zindexPopover: 1010; -@zindexTooltip: 1030; -@zindexFixedNavbar: 1030; -@zindexModalBackdrop: 1040; -@zindexModal: 1050; - -@zindexUmbOverlay: 7500; -@zindexOverlayBackdrop: 2000; - -// Sticky bar has a z-index of "500", which is set from javascript in directive -// so set z-index of cropper should be lower to be behind sticky bar. -@zindexCropperOverlay: 499; - -// Sprite icons path -// ------------------------- -@iconSpritePath: "../img/glyphicons-halflings.png"; -@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png"; - - -// Input placeholder text color -// ------------------------- -@placeholderText: @gray-8; - - -// Hr border color -// ------------------------- -@hrBorder: @gray-10; - - -// Horizontal forms & lists -// ------------------------- -@horizontalComponentOffset: 180px; - - -// Wells -// ------------------------- -@wellBackground: @gray-10; - - -// Navbar -// ------------------------- -@navbarCollapseWidth: 979px; -@navbarCollapseDesktopWidth: @navbarCollapseWidth + 1; - -@navbarHeight: 40px; -@navbarBackgroundHighlight: @white; -@navbarBackground: darken(@navbarBackgroundHighlight, 5%); -@navbarBorder: darken(@navbarBackground, 12%); - -@navbarText: @gray-4; -@navbarLinkColor: @gray-4; -@navbarLinkColorHover: @gray-2; -@navbarLinkColorActive: @gray-3; -@navbarLinkBackgroundHover: transparent; -@navbarLinkBackgroundActive: darken(@navbarBackground, 5%); - -@navbarBrandColor: @navbarLinkColor; - -// Inverted navbar -@navbarInverseBackground: @gray-1; -@navbarInverseBackgroundHighlight: @gray-2; -@navbarInverseBorder: @gray-2; - -@navbarInverseText: @gray-8; -@navbarInverseLinkColor: @gray-8; -@navbarInverseLinkColorHover: @white; -@navbarInverseLinkColorActive: @navbarInverseLinkColorHover; -@navbarInverseLinkBackgroundHover: transparent; -@navbarInverseLinkBackgroundActive: @navbarInverseBackground; - -@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%); -@navbarInverseSearchBackgroundFocus: @white; -@navbarInverseSearchBorder: @navbarInverseBackground; -@navbarInverseSearchPlaceholderColor: @gray-7; - -@navbarInverseBrandColor: @navbarInverseLinkColor; - - -// Pagination -// ------------------------- -@paginationBackground: @white; -@paginationBorder: @gray-8; -@paginationActiveBackground: @gray-10; - - -// Hero unit -// ------------------------- -@heroUnitBackground: @gray-10; -@heroUnitHeadingColor: inherit; -@heroUnitLeadColor: inherit; - - -// alerts -// ------------------------- -@warningText: @white; -@warningBackground: @yellow-d2; -@warningBorder: transparent; - -@errorText: @white; -@errorBackground: @red-d1; -@errorBorder: transparent; - -@successText: @white; -@successBackground: @green-d1; -@successBorder: transparent; - -@infoText: @white; -@infoBackground: @turquoise-d1; -@infoBorder: transparent; - -@alertBorderRadius: 0; - -// SD: Had to duplicate the above but prefix with 'form' inversed colors -// because we cannot share the above alert colors with forms otherwise we end up with white -// text and giant red backgrounds. - -// Form states -// ------------------------- -@formWarningText: @warningBackground; -@formWarningBackground: lighten(@warningBackground, 38%); -@formWarningBorder: darken(spin(@warningBackground, -10), 3%); - -@formErrorText: @errorBackground; -@formErrorBackground: lighten(@errorBackground, 55%); -@formErrorBorder: darken(spin(@errorBackground, -10), 3%); - -@formSuccessText: @successBackground; -@formSuccessBackground: lighten(@successBackground, 48%); -@formSuccessBorder: darken(spin(@successBackground, -10), 5%); - -@formInfoText: @infoBackground; -@formInfoBackground: lighten(@infoBackground, 41%); -@formInfoBorder: darken(spin(@infoBackground, -10), 7%); - - -// Tooltips and popovers -// ------------------------- -@tooltipColor: @white; -@tooltipBackground: @black; -@tooltipArrowWidth: 5px; -@tooltipArrowColor: @tooltipBackground; - -@popoverBackground: @white; -@popoverArrowWidth: 10px; -@popoverArrowColor: @white; -@popoverTitleBackground: darken(@popoverBackground, 3%); - -// Special enhancement for popovers -@popoverArrowOuterWidth: @popoverArrowWidth + 1; -@popoverArrowOuterColor: @gray-7; - - - -// GRID -// -------------------------------------------------- - - -// Default 940px grid -// ------------------------- -@gridColumns: 12; -@gridColumnWidth: 60px; -@gridGutterWidth: 0px; -@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); - -// 1200px min -@gridColumnWidth1200: 70px; -@gridGutterWidth1200: 30px; -@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1)); - -// 768px-979px -@gridColumnWidth768: 42px; -@gridGutterWidth768: 20px; -@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1)); - - -// Fluid grid -// ------------------------- -@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth); -@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth); - -// 1200px min -@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200); -@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200); - -// 768px-979px -@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768); -@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768); - -// SORTABLE -// -------------------------------------------------- -@sortableHelperBg: @turquoise-l2; -@sortablePlaceholderBg : @turquoise; +// +// Variables +// -------------------------------------------------- + + +// Global values +// -------------------------------------------------- + + +// Grays +// ------------------------- +@black: #000; +@blackLight: #1d1d1d; +@grayDarker: #222; +@grayDark: #343434; +@gray: #555; +@grayMed: #7f7f7f; +@grayLight: #d9d9d9; +@grayLighter: #f8f8f8; +@white: #fff; + + +// Accent colors +// ------------------------- +@blue: #2e8aea; +@blueDark: #0064cd; +@blueLight: #add8e6; +@green: #46a546; +@red: #9d261d; +@yellow: #ffc40d; +@orange: #DF7F48; +@pink: #c3325f; + + +// Colors +// ------------------------- + +@turquoise-d1: #00AEA2; +@turquoise: #03BFB3; +@turquoise-l1: #42CFC5; +@turquoise-l2: #81DED8; +@turquoise-l3: #C0F0ED; +@turquoise-washed: #F3FDFC; + +@purple-d2: #1D1333; +@purple-d1: #2E2246; +@purple: #413659; +@purple-l1: #675E7A; +@purple-l2: #8D869B; +@purple-l3: #B3AFBD; +@purple-washed: #F6F3FD; + +// UI Colors +@red-d1: #F02E28; +@red: #FE3E39; +@red-l1: #FE6561; +@red-l2: #FE8B88; +@red-l3: #FFB2B0; +@red-washed: #FFECEB; + +@yellow-d2: #F0AC00; +@yellow-d1: #FFC011; +@yellow: #FFCE38; +@yellow-l1: #FFD861; +@yellow-l2: #FFE28A; +@yellow-l3: #FFECB0; +@yellow-washed: #FFFAEB; + +@green-d1: #1FB572; +@green: #35C786; +@green-l1: #4ECF95; +@green-l2: #79E1B2; +@green-l3: #A6F0CF; +@green-washed: #EBFFF6; + +// Grayscale +@gray-1: #1E1C1C; +@gray-2: #303033; +@gray-3: #515054; +@gray-4: #68676B; +@gray-5: #817F85; +@gray-6: #A2A1A6; +@gray-7: #BBBABF; +@gray-8: #D8D7D9; +@gray-9: #E9E9EB; +@gray-10: #F3F3F5; + + +.red{color: @red;} +.blue{color: @blue;} +.black{color: @black;} +.turquoise{color: @turquoise;} +.turquoise-d1{color: @turquoise-d1;} + + +//icon colors for tree icons +.color-red, .color-red i{color: @red-d1 !important;} +.color-blue, .color-blue i{color: @turquoise-d1 !important;} +.color-orange, .color-orange i{color: #d9631e !important;} +.color-green, .color-green i{color: @green-d1 !important;} +.color-yellow, .color-yellow i{color: @yellow-d1 !important;} + +/* Colors based on http://zavoloklom.github.io/material-design-color-palette/colors.html */ +.color-black, .color-black i { color: #000 !important; } +.color-blue-grey, .color-blue-grey i { color: #607d8b !important; } +.color-grey, .color-grey i { color: #9e9e9e !important; } +.color-brown, .color-brown i { color: #795548 !important; } +.color-blue, .color-blue i { color: #2196f3 !important; } +.color-light-blue, .color-light-blue i {color: #03a9f4 !important; } +.color-cyan, .color-cyan i { color: #00bcd4 !important; } +.color-green, .color-green i { color: #4caf50 !important; } +.color-light-green, .color-light-green i {color: #8bc34a !important; } +.color-lime, .color-lime i { color: #cddc39 !important; } +.color-yellow, .color-yellow i { color: #ffeb3b !important; } +.color-amber, .color-amber i { color: #ffc107 !important; } +.color-orange, .color-orange i { color: #ff9800 !important; } +.color-deep-orange, .color-deep-orange i { color: #ff5722 !important; } +.color-red, .color-red i { color: #f44336 !important; } +.color-pink, .color-pink i { color: #e91e63 !important; } +.color-purple,.color-purple i { color: #9c27b0 !important; } +.color-deep-purple, .color-deep-purple i { color: #673ab7 !important; } +.color-indigo, .color-indigo i { color: #3f51b5 !important; } + + +// Scaffolding +// ------------------------- +@appHeaderHeight: 55px; +@bodyBackground: @gray-10; +@textColor: @gray-2; + +@editorHeaderHeight: 80px; +@editorFooterHeight: 50px; + + +// Links +// ------------------------- +@linkColor: @black; +@linkColorHover: darken(@linkColor, 15%); + +// Typography +// ------------------------- +@sansFontFamily: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; +@serifFontFamily: Georgia, "Times New Roman", Times, serif; +@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace; + +@baseFontSize: 15px; +@baseFontFamily: @sansFontFamily; +@baseLineHeight: 20px; +@altFontFamily: @serifFontFamily; + +@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily +@headingsFontWeight: 500; // instead of browser default, bold +@headingsColor: inherit; // empty to use BS default, @textColor + + +// Component sizing +// ------------------------- +// Based on 14px font-size and 20px line-height + +@fontSizeLarge: @baseFontSize * 1.25; // ~18px +@fontSizeMedium: @baseFontSize * 1.15; // ~14px +@fontSizeSmall: @baseFontSize * 0.85; // ~12px +@fontSizeMini: @baseFontSize * 0.75; // ~11px + +@paddingLarge: 11px 19px; // 44px +@paddingSmall: 2px 10px; // 26px +@paddingMini: 0 6px; // 22px + + +// Disabled this to keep consistency throughout the backoffice UI. Untill a better solution is thought up, this will do. +@baseBorderRadius: 3px; // 2px; +@borderRadiusLarge: 3px; // 6px; +@borderRadiusSmall: 3px; // 3px; + + +// Tables +// ------------------------- +@tableBackground: @white; // overall background-color +@tableBackgroundAccent: @gray-10; // for striping +@tableBackgroundHover: @gray-10; // for hover +@tableBorder: @gray-9; // table and cell border + +// Buttons +// ------------------------- +@btnBackground: @gray-9; +@btnBackgroundHighlight: @gray-9; +@btnBorder: @gray-9; + +@btnPrimaryBackground: @green; +@btnPrimaryBackgroundHighlight: @green; + +@btnInfoBackground: @purple-l1; +@btnInfoBackgroundHighlight: @purple-l1; + +@btnSuccessBackground: @green; +@btnSuccessBackgroundHighlight: @green; + +@btnWarningBackground: @red-l1; +@btnWarningBackgroundHighlight: @red-l1; + +@btnDangerBackground: @red-l1; +@btnDangerBackgroundHighlight: @red-l1; + +@btnInverseBackground: @gray-2; +@btnInverseBackgroundHighlight: @gray-2; + +@btnNeutralBackground: @gray-9; +@btnNeutralBackgroundHighlight: @gray-9; + + +// Forms +// ------------------------- +@inputBackground: @white; +@inputBorder: @gray-7; +@inputBorderRadius: 0; +@inputDisabledBackground: @gray-10; +@formActionsBackground: @gray-9; +@inputHeight: @baseLineHeight + 12px; // base line-height + 8px vertical padding + 2px top/bottom border +@controlRequiredColor: @red; + + +// Tabs +// ------------------------- + +@tabsBorderRadius: @baseBorderRadius; + +// Dropdowns +// ------------------------- +@dropdownBackground: @white; +@dropdownBorder: none; +@dropdownBorderRadius: @baseBorderRadius; +@dropdownDividerTop: @gray-8; +@dropdownDividerBottom: @white; + +@dropdownLinkColor: @gray-2; +@dropdownLinkColorHover: @white; +@dropdownLinkColorActive: @white; + +@dropdownLinkBackgroundActive: @linkColor; +@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive; + + + +// COMPONENT VARIABLES +// -------------------------------------------------- + +// Drawer +@drawerWidth: 400px; + + +// Z-index master list +// ------------------------- +// Used for a bird's eye view of components dependent on the z-axis +// Try to avoid customizing these :) +@zIndexTree: 100; +@zindexDropdown: 1000; +@zindexPopover: 1010; +@zindexTooltip: 1030; +@zindexFixedNavbar: 1030; +@zindexModalBackdrop: 1040; +@zindexModal: 1050; + +@zindexUmbOverlay: 7500; +@zindexOverlayBackdrop: 2000; + +// Sticky bar has a z-index of "500", which is set from javascript in directive +// so set z-index of cropper should be lower to be behind sticky bar. +@zindexCropperOverlay: 499; + +// Sprite icons path +// ------------------------- +@iconSpritePath: "../img/glyphicons-halflings.png"; +@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png"; + + +// Input placeholder text color +// ------------------------- +@placeholderText: @gray-8; + + +// Hr border color +// ------------------------- +@hrBorder: @gray-10; + + +// Horizontal forms & lists +// ------------------------- +@horizontalComponentOffset: 180px; + + +// Wells +// ------------------------- +@wellBackground: @gray-10; + + +// Navbar +// ------------------------- +@navbarCollapseWidth: 979px; +@navbarCollapseDesktopWidth: @navbarCollapseWidth + 1; + +@navbarHeight: 40px; +@navbarBackgroundHighlight: @white; +@navbarBackground: darken(@navbarBackgroundHighlight, 5%); +@navbarBorder: darken(@navbarBackground, 12%); + +@navbarText: @gray-4; +@navbarLinkColor: @gray-4; +@navbarLinkColorHover: @gray-2; +@navbarLinkColorActive: @gray-3; +@navbarLinkBackgroundHover: transparent; +@navbarLinkBackgroundActive: darken(@navbarBackground, 5%); + +@navbarBrandColor: @navbarLinkColor; + +// Inverted navbar +@navbarInverseBackground: @gray-1; +@navbarInverseBackgroundHighlight: @gray-2; +@navbarInverseBorder: @gray-2; + +@navbarInverseText: @gray-8; +@navbarInverseLinkColor: @gray-8; +@navbarInverseLinkColorHover: @white; +@navbarInverseLinkColorActive: @navbarInverseLinkColorHover; +@navbarInverseLinkBackgroundHover: transparent; +@navbarInverseLinkBackgroundActive: @navbarInverseBackground; + +@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%); +@navbarInverseSearchBackgroundFocus: @white; +@navbarInverseSearchBorder: @navbarInverseBackground; +@navbarInverseSearchPlaceholderColor: @gray-7; + +@navbarInverseBrandColor: @navbarInverseLinkColor; + + +// Pagination +// ------------------------- +@paginationBackground: @white; +@paginationBorder: @gray-8; +@paginationActiveBackground: @gray-10; + + +// Hero unit +// ------------------------- +@heroUnitBackground: @gray-10; +@heroUnitHeadingColor: inherit; +@heroUnitLeadColor: inherit; + + +// alerts +// ------------------------- +@warningText: @white; +@warningBackground: @yellow-d2; +@warningBorder: transparent; + +@errorText: @white; +@errorBackground: @red-d1; +@errorBorder: transparent; + +@successText: @white; +@successBackground: @green-d1; +@successBorder: transparent; + +@infoText: @white; +@infoBackground: @turquoise-d1; +@infoBorder: transparent; + +@alertBorderRadius: 0; + +// SD: Had to duplicate the above but prefix with 'form' inversed colors +// because we cannot share the above alert colors with forms otherwise we end up with white +// text and giant red backgrounds. + +// Form states +// ------------------------- +@formWarningText: @warningBackground; +@formWarningBackground: lighten(@warningBackground, 38%); +@formWarningBorder: darken(spin(@warningBackground, -10), 3%); + +@formErrorText: @errorBackground; +@formErrorBackground: lighten(@errorBackground, 55%); +@formErrorBorder: darken(spin(@errorBackground, -10), 3%); + +@formSuccessText: @successBackground; +@formSuccessBackground: lighten(@successBackground, 48%); +@formSuccessBorder: darken(spin(@successBackground, -10), 5%); + +@formInfoText: @infoBackground; +@formInfoBackground: lighten(@infoBackground, 41%); +@formInfoBorder: darken(spin(@infoBackground, -10), 7%); + + +// Tooltips and popovers +// ------------------------- +@tooltipColor: @white; +@tooltipBackground: @black; +@tooltipArrowWidth: 5px; +@tooltipArrowColor: @tooltipBackground; + +@popoverBackground: @white; +@popoverArrowWidth: 10px; +@popoverArrowColor: @white; +@popoverTitleBackground: darken(@popoverBackground, 3%); + +// Special enhancement for popovers +@popoverArrowOuterWidth: @popoverArrowWidth + 1; +@popoverArrowOuterColor: @gray-7; + + + +// GRID +// -------------------------------------------------- + + +// Default 940px grid +// ------------------------- +@gridColumns: 12; +@gridColumnWidth: 60px; +@gridGutterWidth: 0px; +@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); + +// 1200px min +@gridColumnWidth1200: 70px; +@gridGutterWidth1200: 30px; +@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1)); + +// 768px-979px +@gridColumnWidth768: 42px; +@gridGutterWidth768: 20px; +@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1)); + + +// Fluid grid +// ------------------------- +@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth); +@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth); + +// 1200px min +@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200); +@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200); + +// 768px-979px +@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768); +@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768); + +// SORTABLE +// -------------------------------------------------- +@sortableHelperBg: @turquoise-l2; +@sortablePlaceholderBg : @turquoise; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js index e1262fdea8..e788e6fc9b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.controller.js @@ -1,44 +1,44 @@ -/** - * @ngdoc controller - * @name Umbraco.DashboardController - * @function - * - * @description - * Controls the dashboards of the application - * - */ - -function DashboardController($scope, $routeParams, dashboardResource, localizationService) { - - $scope.page = {}; - $scope.page.nameLocked = true; - $scope.page.loading = true; - - $scope.dashboard = {}; - localizationService.localize("sections_" + $routeParams.section).then(function(name){ - $scope.dashboard.name = name; - }); - - dashboardResource.getDashboard($routeParams.section).then(function(tabs){ - $scope.dashboard.tabs = tabs; - - // set first tab to active - if($scope.dashboard.tabs && $scope.dashboard.tabs.length > 0) { - $scope.dashboard.tabs[0].active = true; - } - - $scope.page.loading = false; - }); - - $scope.changeTab = function(tab) { - $scope.dashboard.tabs.forEach(function(tab) { - tab.active = false; - }); - tab.active = true; - }; - -} - - -//register it -angular.module('umbraco').controller("Umbraco.DashboardController", DashboardController); +/** + * @ngdoc controller + * @name Umbraco.DashboardController + * @function + * + * @description + * Controls the dashboards of the application + * + */ + +function DashboardController($scope, $routeParams, dashboardResource, localizationService) { + + $scope.page = {}; + $scope.page.nameLocked = true; + $scope.page.loading = true; + + $scope.dashboard = {}; + localizationService.localize("sections_" + $routeParams.section).then(function(name){ + $scope.dashboard.name = name; + }); + + dashboardResource.getDashboard($routeParams.section).then(function(tabs){ + $scope.dashboard.tabs = tabs; + + // set first tab to active + if($scope.dashboard.tabs && $scope.dashboard.tabs.length > 0) { + $scope.dashboard.tabs[0].active = true; + } + + $scope.page.loading = false; + }); + + $scope.changeTab = function(tab) { + $scope.dashboard.tabs.forEach(function(tab) { + tab.active = false; + }); + tab.active = true; + }; + +} + + +//register it +angular.module('umbraco').controller("Umbraco.DashboardController", DashboardController); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html index 98cb2295c8..c4e13f9289 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html @@ -1,43 +1,43 @@ -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -
    - -
    -

    {{property.caption}}

    -
    -
    - -
    -

    {{property.caption}}

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

    {{property.caption}}

    +
    +
    + +
    +

    {{property.caption}}

    + +
    + +
    + +
    + +
    + +
    + +
    +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index 7116a6e6e2..1dc69d3a0d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -1,412 +1,412 @@ -angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", - function ($scope, $cookies, $location, currentUserResource, formHelper, mediaHelper, umbRequestHelper, Upload, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService, $q) { - - $scope.invitedUser = null; - $scope.invitedUserPasswordModel = { - password: "", - confirmPassword: "", - buttonState: "", - passwordPolicies: null, - passwordPolicyText: "" - } - $scope.loginStates = { - submitButton: "init" - } - $scope.avatarFile = { - filesHolder: null, - uploadStatus: null, - uploadProgress: 0, - maxFileSize: Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB", - acceptedFileTypes: mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes), - uploaded: false - } - $scope.togglePassword = function () { - var elem = $("form[name='loginForm'] input[name='password']"); - elem.attr("type", (elem.attr("type") === "text" ? "password" : "text")); - } - - function init() { - // Check if it is a new user - var inviteVal = $location.search().invite; - if (inviteVal && (inviteVal === "1" || inviteVal === "2")) { - - $q.all([ - //get the current invite user - authResource.getCurrentInvitedUser().then(function (data) { - $scope.invitedUser = data; - }, - function () { - //it failed so we should remove the search - $location.search('invite', null); - }), - //get the membership provider config for password policies - authResource.getMembershipProviderConfig().then(function (data) { - $scope.invitedUserPasswordModel.passwordPolicies = data; - - //localize the text - localizationService.localize("errorHandling_errorInPasswordFormat", - [ - $scope.invitedUserPasswordModel.passwordPolicies.minPasswordLength, - $scope.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars - ]).then(function (data) { - $scope.invitedUserPasswordModel.passwordPolicyText = data; - }); - }) - ]).then(function () { - - $scope.inviteStep = Number(inviteVal); - - }); - } - } - - $scope.changeAvatar = function (files, event) { - if (files && files.length > 0) { - upload(files[0]); - } - }; - - $scope.getStarted = function () { - $location.search('invite', null); - $scope.submit(true); - } - - function upload(file) { - - $scope.avatarFile.uploadProgress = 0; - - Upload.upload({ - url: umbRequestHelper.getApiUrl("currentUserApiBaseUrl", "PostSetAvatar"), - fields: {}, - file: file - }).progress(function (evt) { - - if ($scope.avatarFile.uploadStatus !== "done" && $scope.avatarFile.uploadStatus !== "error") { - // set uploading status on file - $scope.avatarFile.uploadStatus = "uploading"; - - // calculate progress in percentage - var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); - - // set percentage property on file - $scope.avatarFile.uploadProgress = progressPercentage; - } - - }).success(function (data, status, headers, config) { - - $scope.avatarFile.uploadProgress = 100; - - // set done status on file - $scope.avatarFile.uploadStatus = "done"; - - $scope.invitedUser.avatars = data; - - $scope.avatarFile.uploaded = true; - - }).error(function (evt, status, headers, config) { - - // set status done - $scope.avatarFile.uploadStatus = "error"; - - // If file not found, server will return a 404 and display this message - if (status === 404) { - $scope.avatarFile.serverErrorMessage = "File not found"; - } - else if (status == 400) { - //it's a validation error - $scope.avatarFile.serverErrorMessage = evt.message; - } - else { - //it's an unhandled error - //if the service returns a detailed error - if (evt.InnerException) { - $scope.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage; - - //Check if its the common "too large file" exception - if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { - $scope.avatarFile.serverErrorMessage = "File too large to upload"; - } - - } else if (evt.Message) { - $scope.avatarFile.serverErrorMessage = evt.Message; - } - } - }); - } - - $scope.inviteSavePassword = function () { - - if (formHelper.submitForm({ scope: $scope })) { - - $scope.invitedUserPasswordModel.buttonState = "busy"; - - currentUserResource.performSetInvitedUserPassword($scope.invitedUserPasswordModel.password) - .then(function (data) { - - //success - formHelper.resetForm({ scope: $scope }); - $scope.invitedUserPasswordModel.buttonState = "success"; - //set the user and set them as logged in - $scope.invitedUser = data; - userService.setAuthenticationSuccessful(data); - - $scope.inviteStep = 2; - - }, function (err) { - - //error - formHelper.handleError(err); - - $scope.invitedUserPasswordModel.buttonState = "error"; - - }); - } - }; - - var setFieldFocus = function (form, field) { - $timeout(function () { - $("form[name='" + form + "'] input[name='" + field + "']").focus(); - }); - } - - var twoFactorloginDialog = null; - function show2FALoginDialog(view, callback) { - if (!twoFactorloginDialog) { - twoFactorloginDialog = dialogService.open({ - - //very special flag which means that global events cannot close this dialog - manualClose: true, - template: view, - modalClass: "login-overlay", - animation: "slide", - show: true, - callback: callback, - - }); - } - } - - function resetInputValidation() { - $scope.confirmPassword = ""; - $scope.password = ""; - $scope.login = ""; - if ($scope.loginForm) { - $scope.loginForm.username.$setValidity('auth', true); - $scope.loginForm.password.$setValidity('auth', true); - } - if ($scope.requestPasswordResetForm) { - $scope.requestPasswordResetForm.email.$setValidity("auth", true); - } - if ($scope.setPasswordForm) { - $scope.setPasswordForm.password.$setValidity('auth', true); - $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); - } - } - - $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; - - $scope.showLogin = function () { - $scope.errorMsg = ""; - resetInputValidation(); - $scope.view = "login"; - setFieldFocus("loginForm", "username"); - } - - $scope.showRequestPasswordReset = function () { - $scope.errorMsg = ""; - resetInputValidation(); - $scope.view = "request-password-reset"; - $scope.showEmailResetConfirmation = false; - setFieldFocus("requestPasswordResetForm", "email"); - } - - $scope.showSetPassword = function () { - $scope.errorMsg = ""; - resetInputValidation(); - $scope.view = "set-password"; - setFieldFocus("setPasswordForm", "password"); - } - - var d = new Date(); - var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday"); - var konamiMode = $cookies.konamiLogin; - if (konamiMode == "1") { - $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; - } else { - localizationService.localize("login_greeting" + d.getDay()).then(function (label) { - $scope.greeting = label; - }); // weekday[d.getDay()]; - } - $scope.errorMsg = ""; - - $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl; - $scope.externalLoginProviders = externalLoginInfo.providers; - $scope.externalLoginInfo = externalLoginInfo; - $scope.resetPasswordCodeInfo = resetPasswordCodeInfo; - $scope.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage; - - $scope.activateKonamiMode = function () { - if ($cookies.konamiLogin == "1") { - // somehow I can't update the cookie value using $cookies, so going native - document.cookie = "konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; - document.location.reload(); - } else { - document.cookie = "konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;"; - $scope.$apply(function () { - $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; - }); - } - } - - $scope.loginSubmit = function (login, password) { - - //TODO: Do validation properly like in the invite password update - - //if the login and password are not empty we need to automatically - // validate them - this is because if there are validation errors on the server - // then the user has to change both username & password to resubmit which isn't ideal, - // so if they're not empty, we'll just make sure to set them to valid. - if (login && password && login.length > 0 && password.length > 0) { - $scope.loginForm.username.$setValidity('auth', true); - $scope.loginForm.password.$setValidity('auth', true); - } - - if ($scope.loginForm.$invalid) { - return; - } - - $scope.loginStates.submitButton = "busy"; - - userService.authenticate(login, password) - .then(function (data) { - $scope.loginStates.submitButton = "success"; - $scope.submit(true); - }, - function (reason) { - - //is Two Factor required? - if (reason.status === 402) { - $scope.errorMsg = "Additional authentication required"; - show2FALoginDialog(reason.data.twoFactorView, $scope.submit); - } - else { - $scope.loginStates.submitButton = "error"; - $scope.errorMsg = reason.errorMsg; - - //set the form inputs to invalid - $scope.loginForm.username.$setValidity("auth", false); - $scope.loginForm.password.$setValidity("auth", false); - } - }); - - //setup a watch for both of the model values changing, if they change - // while the form is invalid, then revalidate them so that the form can - // be submitted again. - $scope.loginForm.username.$viewChangeListeners.push(function () { - if ($scope.loginForm.$invalid) { - $scope.loginForm.username.$setValidity('auth', true); - $scope.loginForm.password.$setValidity('auth', true); - } - }); - $scope.loginForm.password.$viewChangeListeners.push(function () { - if ($scope.loginForm.$invalid) { - $scope.loginForm.username.$setValidity('auth', true); - $scope.loginForm.password.$setValidity('auth', true); - } - }); - }; - - $scope.requestPasswordResetSubmit = function (email) { - - //TODO: Do validation properly like in the invite password update - - if (email && email.length > 0) { - $scope.requestPasswordResetForm.email.$setValidity('auth', true); - } - - $scope.showEmailResetConfirmation = false; - - if ($scope.requestPasswordResetForm.$invalid) { - return; - } - - $scope.errorMsg = ""; - - authResource.performRequestPasswordReset(email) - .then(function () { - //remove the email entered - $scope.email = ""; - $scope.showEmailResetConfirmation = true; - }, function (reason) { - $scope.errorMsg = reason.errorMsg; - $scope.requestPasswordResetForm.email.$setValidity("auth", false); - }); - - $scope.requestPasswordResetForm.email.$viewChangeListeners.push(function () { - if ($scope.requestPasswordResetForm.email.$invalid) { - $scope.requestPasswordResetForm.email.$setValidity('auth', true); - } - }); - }; - - $scope.setPasswordSubmit = function (password, confirmPassword) { - - $scope.showSetPasswordConfirmation = false; - - if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) { - $scope.setPasswordForm.password.$setValidity('auth', true); - $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); - } - - if ($scope.setPasswordForm.$invalid) { - return; - } - - //TODO: All of this logic can/should be shared! We should do validation the nice way instead of all of this manual stuff, see: inviteSavePassword - authResource.performSetPassword($scope.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, $scope.resetPasswordCodeInfo.resetCodeModel.resetCode) - .then(function () { - $scope.showSetPasswordConfirmation = true; - $scope.resetComplete = true; - - //reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again - resetPasswordCodeInfo.resetCodeModel = null; - - }, function (reason) { - if (reason.data && reason.data.Message) { - $scope.errorMsg = reason.data.Message; - } - else { - $scope.errorMsg = reason.errorMsg; - } - $scope.setPasswordForm.password.$setValidity("auth", false); - $scope.setPasswordForm.confirmPassword.$setValidity("auth", false); - }); - - $scope.setPasswordForm.password.$viewChangeListeners.push(function () { - if ($scope.setPasswordForm.password.$invalid) { - $scope.setPasswordForm.password.$setValidity('auth', true); - } - }); - $scope.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () { - if ($scope.setPasswordForm.confirmPassword.$invalid) { - $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); - } - }); - } - - - //Now, show the correct panel: - - if ($scope.resetPasswordCodeInfo.resetCodeModel) { - $scope.showSetPassword(); - } - else if ($scope.resetPasswordCodeInfo.errors.length > 0) { - $scope.view = "password-reset-code-expired"; - } - else { - $scope.showLogin(); - } - - init(); - - }); +angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", + function ($scope, $cookies, $location, currentUserResource, formHelper, mediaHelper, umbRequestHelper, Upload, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService, $q) { + + $scope.invitedUser = null; + $scope.invitedUserPasswordModel = { + password: "", + confirmPassword: "", + buttonState: "", + passwordPolicies: null, + passwordPolicyText: "" + } + $scope.loginStates = { + submitButton: "init" + } + $scope.avatarFile = { + filesHolder: null, + uploadStatus: null, + uploadProgress: 0, + maxFileSize: Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB", + acceptedFileTypes: mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes), + uploaded: false + } + $scope.togglePassword = function () { + var elem = $("form[name='loginForm'] input[name='password']"); + elem.attr("type", (elem.attr("type") === "text" ? "password" : "text")); + } + + function init() { + // Check if it is a new user + var inviteVal = $location.search().invite; + if (inviteVal && (inviteVal === "1" || inviteVal === "2")) { + + $q.all([ + //get the current invite user + authResource.getCurrentInvitedUser().then(function (data) { + $scope.invitedUser = data; + }, + function () { + //it failed so we should remove the search + $location.search('invite', null); + }), + //get the membership provider config for password policies + authResource.getMembershipProviderConfig().then(function (data) { + $scope.invitedUserPasswordModel.passwordPolicies = data; + + //localize the text + localizationService.localize("errorHandling_errorInPasswordFormat", + [ + $scope.invitedUserPasswordModel.passwordPolicies.minPasswordLength, + $scope.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars + ]).then(function (data) { + $scope.invitedUserPasswordModel.passwordPolicyText = data; + }); + }) + ]).then(function () { + + $scope.inviteStep = Number(inviteVal); + + }); + } + } + + $scope.changeAvatar = function (files, event) { + if (files && files.length > 0) { + upload(files[0]); + } + }; + + $scope.getStarted = function () { + $location.search('invite', null); + $scope.submit(true); + } + + function upload(file) { + + $scope.avatarFile.uploadProgress = 0; + + Upload.upload({ + url: umbRequestHelper.getApiUrl("currentUserApiBaseUrl", "PostSetAvatar"), + fields: {}, + file: file + }).progress(function (evt) { + + if ($scope.avatarFile.uploadStatus !== "done" && $scope.avatarFile.uploadStatus !== "error") { + // set uploading status on file + $scope.avatarFile.uploadStatus = "uploading"; + + // calculate progress in percentage + var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); + + // set percentage property on file + $scope.avatarFile.uploadProgress = progressPercentage; + } + + }).success(function (data, status, headers, config) { + + $scope.avatarFile.uploadProgress = 100; + + // set done status on file + $scope.avatarFile.uploadStatus = "done"; + + $scope.invitedUser.avatars = data; + + $scope.avatarFile.uploaded = true; + + }).error(function (evt, status, headers, config) { + + // set status done + $scope.avatarFile.uploadStatus = "error"; + + // If file not found, server will return a 404 and display this message + if (status === 404) { + $scope.avatarFile.serverErrorMessage = "File not found"; + } + else if (status == 400) { + //it's a validation error + $scope.avatarFile.serverErrorMessage = evt.message; + } + else { + //it's an unhandled error + //if the service returns a detailed error + if (evt.InnerException) { + $scope.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage; + + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { + $scope.avatarFile.serverErrorMessage = "File too large to upload"; + } + + } else if (evt.Message) { + $scope.avatarFile.serverErrorMessage = evt.Message; + } + } + }); + } + + $scope.inviteSavePassword = function () { + + if (formHelper.submitForm({ scope: $scope })) { + + $scope.invitedUserPasswordModel.buttonState = "busy"; + + currentUserResource.performSetInvitedUserPassword($scope.invitedUserPasswordModel.password) + .then(function (data) { + + //success + formHelper.resetForm({ scope: $scope }); + $scope.invitedUserPasswordModel.buttonState = "success"; + //set the user and set them as logged in + $scope.invitedUser = data; + userService.setAuthenticationSuccessful(data); + + $scope.inviteStep = 2; + + }, function (err) { + + //error + formHelper.handleError(err); + + $scope.invitedUserPasswordModel.buttonState = "error"; + + }); + } + }; + + var setFieldFocus = function (form, field) { + $timeout(function () { + $("form[name='" + form + "'] input[name='" + field + "']").focus(); + }); + } + + var twoFactorloginDialog = null; + function show2FALoginDialog(view, callback) { + if (!twoFactorloginDialog) { + twoFactorloginDialog = dialogService.open({ + + //very special flag which means that global events cannot close this dialog + manualClose: true, + template: view, + modalClass: "login-overlay", + animation: "slide", + show: true, + callback: callback, + + }); + } + } + + function resetInputValidation() { + $scope.confirmPassword = ""; + $scope.password = ""; + $scope.login = ""; + if ($scope.loginForm) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + if ($scope.requestPasswordResetForm) { + $scope.requestPasswordResetForm.email.$setValidity("auth", true); + } + if ($scope.setPasswordForm) { + $scope.setPasswordForm.password.$setValidity('auth', true); + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + } + + $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; + + $scope.showLogin = function () { + $scope.errorMsg = ""; + resetInputValidation(); + $scope.view = "login"; + setFieldFocus("loginForm", "username"); + } + + $scope.showRequestPasswordReset = function () { + $scope.errorMsg = ""; + resetInputValidation(); + $scope.view = "request-password-reset"; + $scope.showEmailResetConfirmation = false; + setFieldFocus("requestPasswordResetForm", "email"); + } + + $scope.showSetPassword = function () { + $scope.errorMsg = ""; + resetInputValidation(); + $scope.view = "set-password"; + setFieldFocus("setPasswordForm", "password"); + } + + var d = new Date(); + var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday"); + var konamiMode = $cookies.konamiLogin; + if (konamiMode == "1") { + $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; + } else { + localizationService.localize("login_greeting" + d.getDay()).then(function (label) { + $scope.greeting = label; + }); // weekday[d.getDay()]; + } + $scope.errorMsg = ""; + + $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl; + $scope.externalLoginProviders = externalLoginInfo.providers; + $scope.externalLoginInfo = externalLoginInfo; + $scope.resetPasswordCodeInfo = resetPasswordCodeInfo; + $scope.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage; + + $scope.activateKonamiMode = function () { + if ($cookies.konamiLogin == "1") { + // somehow I can't update the cookie value using $cookies, so going native + document.cookie = "konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; + document.location.reload(); + } else { + document.cookie = "konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;"; + $scope.$apply(function () { + $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; + }); + } + } + + $scope.loginSubmit = function (login, password) { + + //TODO: Do validation properly like in the invite password update + + //if the login and password are not empty we need to automatically + // validate them - this is because if there are validation errors on the server + // then the user has to change both username & password to resubmit which isn't ideal, + // so if they're not empty, we'll just make sure to set them to valid. + if (login && password && login.length > 0 && password.length > 0) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + + if ($scope.loginForm.$invalid) { + return; + } + + $scope.loginStates.submitButton = "busy"; + + userService.authenticate(login, password) + .then(function (data) { + $scope.loginStates.submitButton = "success"; + $scope.submit(true); + }, + function (reason) { + + //is Two Factor required? + if (reason.status === 402) { + $scope.errorMsg = "Additional authentication required"; + show2FALoginDialog(reason.data.twoFactorView, $scope.submit); + } + else { + $scope.loginStates.submitButton = "error"; + $scope.errorMsg = reason.errorMsg; + + //set the form inputs to invalid + $scope.loginForm.username.$setValidity("auth", false); + $scope.loginForm.password.$setValidity("auth", false); + } + }); + + //setup a watch for both of the model values changing, if they change + // while the form is invalid, then revalidate them so that the form can + // be submitted again. + $scope.loginForm.username.$viewChangeListeners.push(function () { + if ($scope.loginForm.$invalid) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + }); + $scope.loginForm.password.$viewChangeListeners.push(function () { + if ($scope.loginForm.$invalid) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + }); + }; + + $scope.requestPasswordResetSubmit = function (email) { + + //TODO: Do validation properly like in the invite password update + + if (email && email.length > 0) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + + $scope.showEmailResetConfirmation = false; + + if ($scope.requestPasswordResetForm.$invalid) { + return; + } + + $scope.errorMsg = ""; + + authResource.performRequestPasswordReset(email) + .then(function () { + //remove the email entered + $scope.email = ""; + $scope.showEmailResetConfirmation = true; + }, function (reason) { + $scope.errorMsg = reason.errorMsg; + $scope.requestPasswordResetForm.email.$setValidity("auth", false); + }); + + $scope.requestPasswordResetForm.email.$viewChangeListeners.push(function () { + if ($scope.requestPasswordResetForm.email.$invalid) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + }); + }; + + $scope.setPasswordSubmit = function (password, confirmPassword) { + + $scope.showSetPasswordConfirmation = false; + + if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) { + $scope.setPasswordForm.password.$setValidity('auth', true); + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + + if ($scope.setPasswordForm.$invalid) { + return; + } + + //TODO: All of this logic can/should be shared! We should do validation the nice way instead of all of this manual stuff, see: inviteSavePassword + authResource.performSetPassword($scope.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, $scope.resetPasswordCodeInfo.resetCodeModel.resetCode) + .then(function () { + $scope.showSetPasswordConfirmation = true; + $scope.resetComplete = true; + + //reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again + resetPasswordCodeInfo.resetCodeModel = null; + + }, function (reason) { + if (reason.data && reason.data.Message) { + $scope.errorMsg = reason.data.Message; + } + else { + $scope.errorMsg = reason.errorMsg; + } + $scope.setPasswordForm.password.$setValidity("auth", false); + $scope.setPasswordForm.confirmPassword.$setValidity("auth", false); + }); + + $scope.setPasswordForm.password.$viewChangeListeners.push(function () { + if ($scope.setPasswordForm.password.$invalid) { + $scope.setPasswordForm.password.$setValidity('auth', true); + } + }); + $scope.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () { + if ($scope.setPasswordForm.confirmPassword.$invalid) { + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + }); + } + + + //Now, show the correct panel: + + if ($scope.resetPasswordCodeInfo.resetCodeModel) { + $scope.showSetPassword(); + } + else if ($scope.resetPasswordCodeInfo.errors.length > 0) { + $scope.view = "password-reset-code-expired"; + } + else { + $scope.showLogin(); + } + + init(); + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 7d878558b6..e8e8ccf6a3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -1,254 +1,254 @@ -
    - -
    - - - - - - + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.controller.js index 6fac34259a..46abf5c88a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.controller.js @@ -1,22 +1,22 @@ -/** - * @ngdoc controller - * @name Umbraco.Dialogs.LegacyDeleteController - * @function - * - * @description - * The controller for deleting content - */ -function YsodController($scope, legacyResource, treeService, navigationService) { - - if ($scope.error && $scope.error.data && $scope.error.data.StackTrace) { - //trim whitespace - $scope.error.data.StackTrace = $scope.error.data.StackTrace.trim(); - } - - $scope.closeDialog = function() { - $scope.dismiss(); - }; - -} - -angular.module("umbraco").controller("Umbraco.Dialogs.YsodController", YsodController); +/** + * @ngdoc controller + * @name Umbraco.Dialogs.LegacyDeleteController + * @function + * + * @description + * The controller for deleting content + */ +function YsodController($scope, legacyResource, treeService, navigationService) { + + if ($scope.error && $scope.error.data && $scope.error.data.StackTrace) { + //trim whitespace + $scope.error.data.StackTrace = $scope.error.data.StackTrace.trim(); + } + + $scope.closeDialog = function() { + $scope.dismiss(); + }; + +} + +angular.module("umbraco").controller("Umbraco.Dialogs.YsodController", YsodController); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.html index 4d8a000d43..1734867945 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.html @@ -1,28 +1,28 @@ -
    - - - - +
    + + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js index fcc8e8c2ed..273dabb72f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/legacy.controller.js @@ -1,34 +1,34 @@ -/** - * @ngdoc controller - * @name Umbraco.LegacyController - * @function - * - * @description - * A controller to control the legacy iframe injection - * -*/ -function LegacyController($scope, $routeParams, $element) { - - var url = decodeURIComponent($routeParams.url.replace(/javascript\:/gi, "")); - //split into path and query - var urlParts = url.split("?"); - var extIndex = urlParts[0].lastIndexOf("."); - var ext = extIndex === -1 ? "" : urlParts[0].substr(extIndex); - //path cannot be a js file - if (ext !== ".js" || ext === "") { - //path cannot contain any of these chars - var toClean = "*(){}[];:<>\\|'\""; - for (var i = 0; i < toClean.length; i++) { - var reg = new RegExp("\\" + toClean[i], "g"); - urlParts[0] = urlParts[0].replace(reg, ""); - } - //join cleaned path and query back together - url = urlParts[0] + (urlParts.length === 1 ? "" : ("?" + urlParts[1])); - $scope.legacyPath = url; - } - else { - throw "Invalid url"; - } -} - +/** + * @ngdoc controller + * @name Umbraco.LegacyController + * @function + * + * @description + * A controller to control the legacy iframe injection + * +*/ +function LegacyController($scope, $routeParams, $element) { + + var url = decodeURIComponent($routeParams.url.replace(/javascript\:/gi, "")); + //split into path and query + var urlParts = url.split("?"); + var extIndex = urlParts[0].lastIndexOf("."); + var ext = extIndex === -1 ? "" : urlParts[0].substr(extIndex); + //path cannot be a js file + if (ext !== ".js" || ext === "") { + //path cannot contain any of these chars + var toClean = "*(){}[];:<>\\|'\""; + for (var i = 0; i < toClean.length; i++) { + var reg = new RegExp("\\" + toClean[i], "g"); + urlParts[0] = urlParts[0].replace(reg, ""); + } + //join cleaned path and query back together + url = urlParts[0] + (urlParts.length === 1 ? "" : ("?" + urlParts[1])); + $scope.legacyPath = url; + } + else { + throw "Invalid url"; + } +} + angular.module("umbraco").controller('Umbraco.LegacyController', LegacyController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/legacy.html b/src/Umbraco.Web.UI.Client/src/views/common/legacy.html index 1862466644..2fd41be41c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/legacy.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/legacy.html @@ -1,3 +1,3 @@ -
    - +
    +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js index 4f78632be5..86132fe8f3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/login.controller.js @@ -1,24 +1,24 @@ -/** This controller is simply here to launch the login dialog when the route is explicitly changed to /login */ -angular.module('umbraco').controller("Umbraco.LoginController", function (eventsService, $scope, userService, $location, $rootScope) { - - userService._showLoginDialog(); - - var evtOn = eventsService.on("app.ready", function(evt, data){ - $scope.avatar = "assets/img/application/logo.png"; - - var path = "/"; - - //check if there's a returnPath query string, if so redirect to it - var locationObj = $location.search(); - if (locationObj.returnPath) { - path = decodeURIComponent(locationObj.returnPath); - } - - $location.url(path); - }); - - $scope.$on('$destroy', function () { - eventsService.unsubscribe(evtOn); - }); - -}); +/** This controller is simply here to launch the login dialog when the route is explicitly changed to /login */ +angular.module('umbraco').controller("Umbraco.LoginController", function (eventsService, $scope, userService, $location, $rootScope) { + + userService._showLoginDialog(); + + var evtOn = eventsService.on("app.ready", function(evt, data){ + $scope.avatar = "assets/img/application/logo.png"; + + var path = "/"; + + //check if there's a returnPath query string, if so redirect to it + var locationObj = $location.search(); + if (locationObj.returnPath) { + path = decodeURIComponent(locationObj.returnPath); + } + + $location.url(path); + }); + + $scope.$on('$destroy', function () { + eventsService.unsubscribe(evtOn); + }); + +}); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/login.html b/src/Umbraco.Web.UI.Client/src/views/common/login.html index a81cd1e9ae..d490d395eb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/login.html @@ -1,3 +1,3 @@ -
    - +
    +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.html index a1f6ff8ffe..401acca2c5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.html @@ -1,27 +1,27 @@ -
    - - - - - - - -

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

    +
    +
    + +
    + + + + + + + + + + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html index c4941d6fb4..8f16bc40d4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.html @@ -1,63 +1,63 @@ -
    - -
    - - -
    - - - - - - - - There are no macros available to insert - - -
    - -
    - -
    {{model.selectedMacro.name}}
    - -
      -
    • - - - - - - - -
    • -
    - - - There are no parameters for this macro - - -
    -
    - -
    +
    + +
    + + +
    + + + + + + + + There are no macros available to insert + + +
    + +
    + +
    {{model.selectedMacro.name}}
    + +
      +
    • + + + + + + + +
    • +
    + + + There are no parameters for this macro + + +
    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index e6e3742c6a..a6131f55cf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -1,391 +1,391 @@ -//used for the media picker dialog -angular.module("umbraco") - .controller("Umbraco.Overlays.MediaPickerController", - function($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, $cookies, localStorageService, localizationService) { - - if (!$scope.model.title) { - localizationService.localize("defaultdialogs_selectMedia").then(function(value){ - $scope.model.title = value; - }); - } - - var dialogOptions = $scope.model; - - $scope.disableFolderSelect = dialogOptions.disableFolderSelect; - $scope.onlyImages = dialogOptions.onlyImages; - $scope.showDetails = dialogOptions.showDetails; - $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; - $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; - $scope.cropSize = dialogOptions.cropSize; - $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); - $scope.lockedFolder = true; - - var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; - var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); - if ($scope.onlyImages) { - $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); - } else { - // Use whitelist of allowed file types if provided - if (allowedUploadFiles !== '') { - $scope.acceptedFileTypes = allowedUploadFiles; - } else { - // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles - $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); - } - } - - $scope.maxFileSize = umbracoSettings.maxFileSize + "KB"; - - $scope.model.selectedImages = []; - - $scope.acceptedMediatypes = []; - mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: '', - }; - - //preload selected item - $scope.target = undefined; - if (dialogOptions.currentTarget) { - $scope.target = dialogOptions.currentTarget; - } - - function onInit() { - if ($scope.startNodeId !== -1) { - entityResource.getById($scope.startNodeId, "media") - .then(function (ent) { - $scope.startNodeId = ent.id; - run(); - }); - } else { - run(); - } - } - - function run() { - //default root item - if (!$scope.target) { - if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { - entityResource.getById($scope.lastOpenedNode, "media") - .then(ensureWithinStartNode, gotoStartNode); - } else { - gotoStartNode(); - } - } else { - //if a target is specified, go look it up - generally this target will just contain ids not the actual full - //media object so we need to look it up - var id = $scope.target.udi ? $scope.target.udi : $scope.target.id - var altText = $scope.target.altText; - mediaResource.getById(id) - .then(function (node) { - $scope.target = node; - if (ensureWithinStartNode(node)) { - selectImage(node); - $scope.target.url = mediaHelper.resolveFile(node); - $scope.target.altText = altText; - $scope.openDetailsDialog(); - } - }, - gotoStartNode); - } - } - - $scope.upload = function(v) { - angular.element(".umb-file-dropzone-directive .file-select").click(); - }; - - $scope.dragLeave = function(el, event) { - $scope.activeDrag = false; - }; - - $scope.dragEnter = function(el, event) { - $scope.activeDrag = true; - }; - - $scope.submitFolder = function() { - if ($scope.newFolderName) { - $scope.creatingFolder = true; - mediaResource - .addFolder($scope.newFolderName, $scope.currentFolder.id) - .then(function(data) { - //we've added a new folder so lets clear the tree cache for that specific item - treeService.clearCache({ - cacheKey: "__media", //this is the main media tree cache key - childrenOf: data.parentId //clear the children of the parent - }); - $scope.creatingFolder = false; - $scope.gotoFolder(data); - $scope.showFolderInput = false; - $scope.newFolderName = ""; - }); - } else { - $scope.showFolderInput = false; - } - }; - - $scope.enterSubmitFolder = function(event) { - if (event.keyCode === 13) { - $scope.submitFolder(); - event.stopPropagation(); - } - }; - - $scope.gotoFolder = function(folder) { - if (!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); - } - - if (!folder) { - folder = { id: -1, name: "Media", icon: "icon-folder" }; - } - - if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media") - .then(function(anc) { - $scope.path = _.filter(anc, - function(f) { - return f.path.indexOf($scope.startNodeId) !== -1; - }); - }); - - } else { - $scope.path = []; - } - - mediaTypeHelper.getAllowedImagetypes(folder.id) - .then(function (types) { - $scope.acceptedMediatypes = types; - }); - - $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual; - - $scope.currentFolder = folder; - localStorageService.set("umbLastOpenedMediaNodeId", folder.id); - return getChildren(folder.id); - }; - - $scope.clickHandler = function(image, event, index) { - if (image.isFolder) { - if ($scope.disableFolderSelect) { - $scope.gotoFolder(image); - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - selectImage(image); - } - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - if ($scope.showDetails) { - - $scope.target = image; - - // handle both entity and full media object - if (image.image) { - $scope.target.url = image.image; - } else { - $scope.target.url = mediaHelper.resolveFile(image); - } - - $scope.openDetailsDialog(); - } else { - selectImage(image); - } - } - }; - - $scope.clickItemName = function(item) { - if (item.isFolder) { - $scope.gotoFolder(item); - } - }; - - function selectImage(image) { - if (image.selected) { - for (var i = 0; $scope.model.selectedImages.length > i; i++) { - var imageInSelection = $scope.model.selectedImages[i]; - if (image.key === imageInSelection.key) { - image.selected = false; - $scope.model.selectedImages.splice(i, 1); - } - } - } else { - if (!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); - } - image.selected = true; - $scope.model.selectedImages.push(image); - } - } - - function deselectAllImages(images) { - for (var i = 0; i < images.length; i++) { - var image = images[i]; - image.selected = false; - } - images.length = 0; - } - - $scope.onUploadComplete = function(files) { - $scope.gotoFolder($scope.currentFolder).then(function() { - if (files.length === 1 && $scope.model.selectedImages.length === 0) { - selectImage($scope.images[$scope.images.length - 1]); - } - }); - }; - - $scope.onFilesQueue = function() { - $scope.activeDrag = false; - }; - - function ensureWithinStartNode(node) { - // make sure that last opened node is on the same path as start node - var nodePath = node.path.split(","); - - if (nodePath.indexOf($scope.startNodeId.toString()) !== -1) { - $scope.gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder" }); - return true; - } else { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - return false; - } - } - - function gotoStartNode(err) { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - } - - $scope.openDetailsDialog = function() { - - $scope.mediaPickerDetailsOverlay = {}; - $scope.mediaPickerDetailsOverlay.show = true; - - $scope.mediaPickerDetailsOverlay.submit = function(model) { - $scope.model.selectedImages.push($scope.target); - $scope.model.submit($scope.model); - - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - }; - - $scope.mediaPickerDetailsOverlay.close = function(oldModel) { - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - }; - }; - - var debounceSearchMedia = _.debounce(function() { - $scope.$apply(function() { - if ($scope.searchOptions.filter) { - searchMedia(); - } else { - // reset pagination - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: '' - }; - getChildren($scope.currentFolder.id); - } - }); - }, 500); - - $scope.changeSearch = function() { - $scope.loading = true; - debounceSearchMedia(); - }; - - $scope.changePagination = function(pageNumber) { - $scope.loading = true; - $scope.searchOptions.pageNumber = pageNumber; - searchMedia(); - }; - - function searchMedia() { - $scope.loading = true; - entityResource.getPagedDescendants($scope.startNodeId, "Media", $scope.searchOptions) - .then(function(data) { - // update image data to work with image grid - angular.forEach(data.items, - function(mediaItem) { - // set thumbnail and src - mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); - mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); - // set properties to match a media object - if (mediaItem.metaData && - mediaItem.metaData.umbracoWidth && - mediaItem.metaData.umbracoHeight) { - - mediaItem.properties = [ - { - alias: "umbracoWidth", - value: mediaItem.metaData.umbracoWidth.Value - }, - { - alias: "umbracoHeight", - value: mediaItem.metaData.umbracoHeight.Value - } - ]; - } - }); - // update images - $scope.images = data.items ? data.items : []; - // update pagination - if (data.pageNumber > 0) - $scope.searchOptions.pageNumber = data.pageNumber; - if (data.pageSize > 0) - $scope.searchOptions.pageSize = data.pageSize; - $scope.searchOptions.totalItems = data.totalItems; - $scope.searchOptions.totalPages = data.totalPages; - // set already selected images to selected - preSelectImages(); - $scope.loading = false; - }); - } - - function getChildren(id) { - $scope.loading = true; - return mediaResource.getChildren(id) - .then(function(data) { - $scope.searchOptions.filter = ""; - $scope.images = data.items ? data.items : []; - // set already selected images to selected - preSelectImages(); - $scope.loading = false; - }); - } - - function preSelectImages() { - for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { - var folderImage = $scope.images[folderImageIndex]; - var imageIsSelected = false; - - if ($scope.model && angular.isArray($scope.model.selectedImages)) { - for (var selectedImageIndex = 0; - selectedImageIndex < $scope.model.selectedImages.length; - selectedImageIndex++) { - var selectedImage = $scope.model.selectedImages[selectedImageIndex]; - - if (folderImage.key === selectedImage.key) { - imageIsSelected = true; - } - } - } - - if (imageIsSelected) { - folderImage.selected = true; - } - } - } - - onInit(); - - }); +//used for the media picker dialog +angular.module("umbraco") + .controller("Umbraco.Overlays.MediaPickerController", + function($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, $cookies, localStorageService, localizationService) { + + if (!$scope.model.title) { + localizationService.localize("defaultdialogs_selectMedia").then(function(value){ + $scope.model.title = value; + }); + } + + var dialogOptions = $scope.model; + + $scope.disableFolderSelect = dialogOptions.disableFolderSelect; + $scope.onlyImages = dialogOptions.onlyImages; + $scope.showDetails = dialogOptions.showDetails; + $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; + $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; + $scope.cropSize = dialogOptions.cropSize; + $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); + $scope.lockedFolder = true; + + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; + var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); + if ($scope.onlyImages) { + $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); + } else { + // Use whitelist of allowed file types if provided + if (allowedUploadFiles !== '') { + $scope.acceptedFileTypes = allowedUploadFiles; + } else { + // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles + $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); + } + } + + $scope.maxFileSize = umbracoSettings.maxFileSize + "KB"; + + $scope.model.selectedImages = []; + + $scope.acceptedMediatypes = []; + mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) + .then(function(types) { + $scope.acceptedMediatypes = types; + }); + + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '', + }; + + //preload selected item + $scope.target = undefined; + if (dialogOptions.currentTarget) { + $scope.target = dialogOptions.currentTarget; + } + + function onInit() { + if ($scope.startNodeId !== -1) { + entityResource.getById($scope.startNodeId, "media") + .then(function (ent) { + $scope.startNodeId = ent.id; + run(); + }); + } else { + run(); + } + } + + function run() { + //default root item + if (!$scope.target) { + if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { + entityResource.getById($scope.lastOpenedNode, "media") + .then(ensureWithinStartNode, gotoStartNode); + } else { + gotoStartNode(); + } + } else { + //if a target is specified, go look it up - generally this target will just contain ids not the actual full + //media object so we need to look it up + var id = $scope.target.udi ? $scope.target.udi : $scope.target.id + var altText = $scope.target.altText; + mediaResource.getById(id) + .then(function (node) { + $scope.target = node; + if (ensureWithinStartNode(node)) { + selectImage(node); + $scope.target.url = mediaHelper.resolveFile(node); + $scope.target.altText = altText; + $scope.openDetailsDialog(); + } + }, + gotoStartNode); + } + } + + $scope.upload = function(v) { + angular.element(".umb-file-dropzone-directive .file-select").click(); + }; + + $scope.dragLeave = function(el, event) { + $scope.activeDrag = false; + }; + + $scope.dragEnter = function(el, event) { + $scope.activeDrag = true; + }; + + $scope.submitFolder = function() { + if ($scope.newFolderName) { + $scope.creatingFolder = true; + mediaResource + .addFolder($scope.newFolderName, $scope.currentFolder.id) + .then(function(data) { + //we've added a new folder so lets clear the tree cache for that specific item + treeService.clearCache({ + cacheKey: "__media", //this is the main media tree cache key + childrenOf: data.parentId //clear the children of the parent + }); + $scope.creatingFolder = false; + $scope.gotoFolder(data); + $scope.showFolderInput = false; + $scope.newFolderName = ""; + }); + } else { + $scope.showFolderInput = false; + } + }; + + $scope.enterSubmitFolder = function(event) { + if (event.keyCode === 13) { + $scope.submitFolder(); + event.stopPropagation(); + } + }; + + $scope.gotoFolder = function(folder) { + if (!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + + if (!folder) { + folder = { id: -1, name: "Media", icon: "icon-folder" }; + } + + if (folder.id > 0) { + entityResource.getAncestors(folder.id, "media") + .then(function(anc) { + $scope.path = _.filter(anc, + function(f) { + return f.path.indexOf($scope.startNodeId) !== -1; + }); + }); + + } else { + $scope.path = []; + } + + mediaTypeHelper.getAllowedImagetypes(folder.id) + .then(function (types) { + $scope.acceptedMediatypes = types; + }); + + $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual; + + $scope.currentFolder = folder; + localStorageService.set("umbLastOpenedMediaNodeId", folder.id); + return getChildren(folder.id); + }; + + $scope.clickHandler = function(image, event, index) { + if (image.isFolder) { + if ($scope.disableFolderSelect) { + $scope.gotoFolder(image); + } else { + eventsService.emit("dialogs.mediaPicker.select", image); + selectImage(image); + } + } else { + eventsService.emit("dialogs.mediaPicker.select", image); + if ($scope.showDetails) { + + $scope.target = image; + + // handle both entity and full media object + if (image.image) { + $scope.target.url = image.image; + } else { + $scope.target.url = mediaHelper.resolveFile(image); + } + + $scope.openDetailsDialog(); + } else { + selectImage(image); + } + } + }; + + $scope.clickItemName = function(item) { + if (item.isFolder) { + $scope.gotoFolder(item); + } + }; + + function selectImage(image) { + if (image.selected) { + for (var i = 0; $scope.model.selectedImages.length > i; i++) { + var imageInSelection = $scope.model.selectedImages[i]; + if (image.key === imageInSelection.key) { + image.selected = false; + $scope.model.selectedImages.splice(i, 1); + } + } + } else { + if (!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + image.selected = true; + $scope.model.selectedImages.push(image); + } + } + + function deselectAllImages(images) { + for (var i = 0; i < images.length; i++) { + var image = images[i]; + image.selected = false; + } + images.length = 0; + } + + $scope.onUploadComplete = function(files) { + $scope.gotoFolder($scope.currentFolder).then(function() { + if (files.length === 1 && $scope.model.selectedImages.length === 0) { + selectImage($scope.images[$scope.images.length - 1]); + } + }); + }; + + $scope.onFilesQueue = function() { + $scope.activeDrag = false; + }; + + function ensureWithinStartNode(node) { + // make sure that last opened node is on the same path as start node + var nodePath = node.path.split(","); + + if (nodePath.indexOf($scope.startNodeId.toString()) !== -1) { + $scope.gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder" }); + return true; + } else { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + return false; + } + } + + function gotoStartNode(err) { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + } + + $scope.openDetailsDialog = function() { + + $scope.mediaPickerDetailsOverlay = {}; + $scope.mediaPickerDetailsOverlay.show = true; + + $scope.mediaPickerDetailsOverlay.submit = function(model) { + $scope.model.selectedImages.push($scope.target); + $scope.model.submit($scope.model); + + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + + $scope.mediaPickerDetailsOverlay.close = function(oldModel) { + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + }; + + var debounceSearchMedia = _.debounce(function() { + $scope.$apply(function() { + if ($scope.searchOptions.filter) { + searchMedia(); + } else { + // reset pagination + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '' + }; + getChildren($scope.currentFolder.id); + } + }); + }, 500); + + $scope.changeSearch = function() { + $scope.loading = true; + debounceSearchMedia(); + }; + + $scope.changePagination = function(pageNumber) { + $scope.loading = true; + $scope.searchOptions.pageNumber = pageNumber; + searchMedia(); + }; + + function searchMedia() { + $scope.loading = true; + entityResource.getPagedDescendants($scope.startNodeId, "Media", $scope.searchOptions) + .then(function(data) { + // update image data to work with image grid + angular.forEach(data.items, + function(mediaItem) { + // set thumbnail and src + mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); + mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); + // set properties to match a media object + if (mediaItem.metaData && + mediaItem.metaData.umbracoWidth && + mediaItem.metaData.umbracoHeight) { + + mediaItem.properties = [ + { + alias: "umbracoWidth", + value: mediaItem.metaData.umbracoWidth.Value + }, + { + alias: "umbracoHeight", + value: mediaItem.metaData.umbracoHeight.Value + } + ]; + } + }); + // update images + $scope.images = data.items ? data.items : []; + // update pagination + if (data.pageNumber > 0) + $scope.searchOptions.pageNumber = data.pageNumber; + if (data.pageSize > 0) + $scope.searchOptions.pageSize = data.pageSize; + $scope.searchOptions.totalItems = data.totalItems; + $scope.searchOptions.totalPages = data.totalPages; + // set already selected images to selected + preSelectImages(); + $scope.loading = false; + }); + } + + function getChildren(id) { + $scope.loading = true; + return mediaResource.getChildren(id) + .then(function(data) { + $scope.searchOptions.filter = ""; + $scope.images = data.items ? data.items : []; + // set already selected images to selected + preSelectImages(); + $scope.loading = false; + }); + } + + function preSelectImages() { + for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { + var folderImage = $scope.images[folderImageIndex]; + var imageIsSelected = false; + + if ($scope.model && angular.isArray($scope.model.selectedImages)) { + for (var selectedImageIndex = 0; + selectedImageIndex < $scope.model.selectedImages.length; + selectedImageIndex++) { + var selectedImage = $scope.model.selectedImages[selectedImageIndex]; + + if (folderImage.key === selectedImage.key) { + imageIsSelected = true; + } + } + } + + if (imageIsSelected) { + folderImage.selected = true; + } + } + } + + onInit(); + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html index 6795caf790..482485c4b2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html @@ -1,150 +1,150 @@ -
    - -
    - -
    - - - - - - -
    - - -
    - -
    - -
    - -
    -
    - - - - - - - -
    - - -
    - - - - - -
    - - - -
    - -
    - - -
    - -
    - -
    - Preview -
    - - - - -
    - -
    - -
    - - -
    - -
    - - -
    - - -
    - -
    +
    + +
    + +
    + + + + + + +
    + + +
    + +
    + +
    + +
    +
    + + + + + + + +
    + + +
    + + + + + +
    + + + +
    + +
    + + +
    + +
    + +
    + Preview +
    + + + + +
    + +
    + +
    + + +
    + +
    + + +
    + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.controller.js index 482732e3a3..83567ee1fa 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.controller.js @@ -1,64 +1,64 @@ -//used for the member picker dialog -angular.module("umbraco").controller("Umbraco.Overlays.MemberGroupPickerController", - function($scope, eventsService, entityResource, searchService, $log, localizationService) { - - $scope.dialogTreeApi = {}; - $scope.multiPicker = $scope.model.multiPicker; - - function activate() { - - if(!$scope.model.title) { - localizationService.localize("defaultdialogs_selectMemberGroup").then(function(value){ - $scope.model.title = value; - }); - } - - if ($scope.multiPicker) { - $scope.model.selectedMemberGroups = []; - } else { - $scope.model.selectedMemberGroup = ""; - } - - } - - function selectMemberGroup(id) { - $scope.model.selectedMemberGroup = id; - } - - function selectMemberGroups(id) { - $scope.model.selectedMemberGroups.push(id); - } - - /** Method used for selecting a node */ - function select(text, id) { - - if ($scope.model.multiPicker) { - selectMemberGroups(id); - } - else { - selectMemberGroup(id); - $scope.model.submit($scope.model); - } - } - - function nodeSelectHandler(args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - eventsService.emit("dialogs.memberGroupPicker.select", args); - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - select(args.node.name, args.node.id); - - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - - $scope.onTreeInit = function () { - $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); - }; - - activate(); - - }); +//used for the member picker dialog +angular.module("umbraco").controller("Umbraco.Overlays.MemberGroupPickerController", + function($scope, eventsService, entityResource, searchService, $log, localizationService) { + + $scope.dialogTreeApi = {}; + $scope.multiPicker = $scope.model.multiPicker; + + function activate() { + + if(!$scope.model.title) { + localizationService.localize("defaultdialogs_selectMemberGroup").then(function(value){ + $scope.model.title = value; + }); + } + + if ($scope.multiPicker) { + $scope.model.selectedMemberGroups = []; + } else { + $scope.model.selectedMemberGroup = ""; + } + + } + + function selectMemberGroup(id) { + $scope.model.selectedMemberGroup = id; + } + + function selectMemberGroups(id) { + $scope.model.selectedMemberGroups.push(id); + } + + /** Method used for selecting a node */ + function select(text, id) { + + if ($scope.model.multiPicker) { + selectMemberGroups(id); + } + else { + selectMemberGroup(id); + $scope.model.submit($scope.model); + } + } + + function nodeSelectHandler(args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + eventsService.emit("dialogs.memberGroupPicker.select", args); + + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + select(args.node.name, args.node.id); + + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + + $scope.onTreeInit = function () { + $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); + }; + + activate(); + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.html index a416abf64b..ab481d5f0d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.html @@ -1,13 +1,13 @@ -
    - - + + - - -
    + on-init="onTreeInit()" + enablecheckboxes="{{model.multiPicker}}"> + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index ecf1bfe548..da6d2582e3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -1,616 +1,616 @@ -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", - function ($scope, - $q, - entityResource, - eventsService, - $log, - searchService, - angularHelper, - $timeout, - localizationService, - treeService, - contentResource, - mediaResource, - memberResource, - languageResource) { - - //used as the result selection - $scope.model.selection = []; - - //the tree object when it loads - var tree = null; - - // Search and listviews is only working for content, media and member section - var searchableSections = ["content", "media", "member"]; - // tracks all expanded paths so when the language is switched we can resync it with the already loaded paths - var expandedPaths = []; - - var vm = this; - vm.treeReady = false; - vm.dialogTreeApi = {}; - vm.initDialogTree = initDialogTree; - vm.section = $scope.model.section; - vm.treeAlias = $scope.model.treeAlias; - vm.multiPicker = $scope.model.multiPicker; - vm.hideHeader = (typeof $scope.model.hideHeader) === "boolean" ? $scope.model.hideHeader : true; - // if you need to load a not initialized tree set this value to false - default is true - vm.onlyInitialized = $scope.model.onlyInitialized; - vm.searchInfo = { - searchFromId: $scope.model.startNodeId, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - vm.startNodeId = $scope.model.startNodeId; - //Used for toggling an empty-state message - //Some trees can have no items (dictionary & forms email templates) - vm.hasItems = true; - vm.emptyStateMessage = $scope.model.emptyStateMessage; - vm.languages = []; - vm.selectedLanguage = {}; - vm.languageSelectorIsOpen = false; - vm.showLanguageSelector = $scope.model.showLanguageSelector; - // Allow the entity type to be passed in but defaults to Document for backwards compatibility. - vm.entityType = $scope.model.entityType ? $scope.model.entityType : "Document"; - vm.enableSearh = searchableSections.indexOf(vm.section) !== -1; - - - vm.toggleLanguageSelector = toggleLanguageSelector; - vm.selectLanguage = selectLanguage; - vm.onSearchResults = onSearchResults; - vm.hideSearch = hideSearch; - vm.closeMiniListView = closeMiniListView; - vm.selectListViewNode = selectListViewNode; - - function initDialogTree() { - vm.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler); - //TODO: Also deal with unexpanding!! - vm.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler); - vm.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); - } - - /** - * Performs the initialization of this component - */ - function onInit () { - - // load languages - languageResource.getAll().then(function (languages) { - vm.languages = languages; - - // set the default language - vm.languages.forEach(function (language) { - if (language.isDefault) { - vm.selectedLanguage = language; - vm.languageSelectorIsOpen = false; - } - }); - }); - - if (vm.treeAlias === "content") { - vm.entityType = "Document"; - if (!$scope.model.title) { - localizationService.localize("defaultdialogs_selectContent").then(function(value){ - $scope.model.title = value; - }); - } - } - else if (vm.treeAlias === "member" || vm.section) { - vm.entityType = "Member"; - if (!$scope.model.title) { - localizationService.localize("defaultdialogs_selectMember").then(function(value){ - $scope.model.title = value; - }) - } - } - else if (vm.treeAlias === "media" || vm.section === "media") { - vm.entityType = "Media"; - if (!$scope.model.title) { - localizationService.localize("defaultdialogs_selectMedia").then(function(value){ - $scope.model.title = value; - }); - } - } - - //TODO: Seems odd this logic is here, i don't think it needs to be and should just exist on the property editor using this - if ($scope.model.minNumber) { - $scope.model.minNumber = parseInt($scope.model.minNumber, 10); - } - if ($scope.model.maxNumber) { - $scope.model.maxNumber = parseInt($scope.model.maxNumber, 10); - } - - //if a alternative startnode is used, we need to check if it is a container - if (vm.enableSearh && - vm.startNodeId && - vm.startNodeId !== -1 && - vm.startNodeId !== "-1") { - entityResource.getById(vm.startNodeId, vm.entityType).then(function (node) { - if (node.metaData.IsContainer) { - openMiniListView(node); - } - initTree(); - }); - } - else { - initTree(); - } - - //Configures filtering - if ($scope.model.filter) { - - $scope.model.filterExclude = false; - $scope.model.filterAdvanced = false; - - //used advanced filtering - if (angular.isFunction($scope.model.filter)) { - $scope.model.filterAdvanced = true; - } - else if (angular.isObject($scope.model.filter)) { - $scope.model.filterAdvanced = true; - } - else { - if ($scope.model.filter.startsWith("!")) { - $scope.model.filterExclude = true; - $scope.model.filter = $scope.model.filter.substring(1); - } - - //used advanced filtering - if ($scope.model.filter.startsWith("{")) { - $scope.model.filterAdvanced = true; - //convert to object - $scope.model.filter = angular.fromJson($scope.model.filter); - } - } - } - } - - /** - * Updates the tree's query parameters - */ - function initTree() { - //create the custom query string param for this tree - var queryParams = {}; - if (vm.startNodeId) { - queryParams["startNodeId"] = $scope.model.startNodeId; - } - if (vm.selectedLanguage && vm.selectedLanguage.id) { - queryParams["culture"] = vm.selectedLanguage.culture; - } - var queryString = $.param(queryParams); //create the query string from the params object - - if (!queryString) { - vm.customTreeParams = $scope.model.customTreeParams; - } - else { - vm.customTreeParams = queryString; - if ($scope.model.customTreeParams) { - vm.customTreeParams += "&" + $scope.model.customTreeParams; - } - } - - vm.treeReady = true; - } - - function selectLanguage(language) { - vm.selectedLanguage = language; - // close the language selector - vm.languageSelectorIsOpen = false; - - initTree(); //this will reset the tree params and the tree directive will pick up the changes in a $watch - - //execute after next digest because the internal watch on the customtreeparams needs to be bound now that we've changed it - $timeout(function () { - //reload the tree with it's updated querystring args - vm.dialogTreeApi.load(vm.section).then(function () { - - //create the list of promises - var promises = []; - for (var i = 0; i < expandedPaths.length; i++) { - promises.push(vm.dialogTreeApi.syncTree({ path: expandedPaths[i], activate: false })); - } - //execute them sequentially - angularHelper.executeSequentialPromises(promises); - }); - }); - }; - - function toggleLanguageSelector() { - vm.languageSelectorIsOpen = !vm.languageSelectorIsOpen; - }; - - function nodeExpandedHandler(args) { - - //store the reference to the expanded node path - if (args.node) { - treeService._trackExpandedPaths(args.node, expandedPaths); - } - - // open mini list view for list views - if (args.node.metaData.isContainer) { - openMiniListView(args.node); - } - - if (angular.isArray(args.children)) { - - //iterate children - _.each(args.children, - function (child) { - - //now we need to look in the already selected search results and - // toggle the check boxes for those ones that are listed - var exists = _.find(vm.searchInfo.selectedSearchResults, - function (selected) { - return child.id == selected.id; - }); - if (exists) { - child.selected = true; - } - }); - - //check filter - performFiltering(args.children); - } - } - - //gets the tree object when it loads - function treeLoadedHandler(args) { - //args.tree contains children (args.tree.root.children) - vm.hasItems = args.tree.root.children.length > 0; - - tree = args.tree; - } - - //wires up selection - function nodeSelectHandler(args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.isSearchResult) { - //check if the item selected was a search result from a list view - - //unselect - select(args.node.name, args.node.id); - - //remove it from the list view children - var listView = args.node.parent(); - listView.children = _.reject(listView.children, - function (child) { - return child.id == args.node.id; - }); - - //remove it from the custom tracked search result list - vm.searchInfo.selectedSearchResults = _.reject(vm.searchInfo.selectedSearchResults, - function (i) { - return i.id == args.node.id; - }); - } - else { - eventsService.emit("dialogs.treePickerController.select", args); - - if (args.node.filtered) { - return; - } - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - if ($scope.model.select) { - $scope.model.select(args.node); - } - else { - select(args.node.name, args.node.id); - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - - } - } - - /** Method used for selecting a node */ - function select(text, id, entity) { - //if we get the root, we just return a constructed entity, no need for server data - if (id < 0) { - - var rootNode = { - alias: null, - icon: "icon-folder", - id: id, - name: text - }; - - if (vm.multiPicker) { - if (entity) { - multiSelectItem(entity); - } - else { - multiSelectItem(rootNode); - } - } - else { - $scope.model.selection.push(rootNode); - $scope.model.submit($scope.model); - } - } - else { - - if (vm.multiPicker) { - - if (entity) { - multiSelectItem(entity); - } - else { - //otherwise we have to get it from the server - entityResource.getById(id, vm.entityType).then(function (ent) { - multiSelectItem(ent); - }); - } - - } - else { - - hideSearch(); - - //if an entity has been passed in, use it - if (entity) { - $scope.model.selection.push(entity); - $scope.model.submit($scope.model); - } - else { - //otherwise we have to get it from the server - entityResource.getById(id, vm.entityType).then(function (ent) { - $scope.model.selection.push(ent); - $scope.model.submit($scope.model); - }); - } - } - } - } - - function multiSelectItem(item) { - - var found = false; - var foundIndex = 0; - - if ($scope.model.selection.length > 0) { - for (i = 0; $scope.model.selection.length > i; i++) { - var selectedItem = $scope.model.selection[i]; - if (selectedItem.id === item.id) { - found = true; - foundIndex = i; - } - } - } - - if (found) { - $scope.model.selection.splice(foundIndex, 1); - } - else { - $scope.model.selection.push(item); - } - - } - - function performFiltering(nodes) { - - if (!$scope.model.filter) { - return; - } - - //remove any list view search nodes from being filtered since these are special nodes that always must - // be allowed to be clicked on - nodes = _.filter(nodes, - function (n) { - return !angular.isObject(n.metaData.listViewNode); - }); - - if ($scope.model.filterAdvanced) { - - //filter either based on a method or an object - var filtered = angular.isFunction($scope.model.filter) - ? _.filter(nodes, $scope.model.filter) - : _.where(nodes, $scope.model.filter); - - angular.forEach(filtered, - function (value, key) { - value.filtered = true; - if ($scope.model.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push($scope.model.filterCssClass); - } - }); - } - else { - var a = $scope.model.filter.toLowerCase().replace(/\s/g, '').split(','); - angular.forEach(nodes, - function (value, key) { - - var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; - - if (!$scope.model.filterExclude && !found || $scope.model.filterExclude && found) { - value.filtered = true; - - if ($scope.model.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push($scope.model.filterCssClass); - } - } - }); - } - } - - function openMiniListView(node) { - vm.miniListView = node; - } - - function multiSubmit(result) { - entityResource.getByIds(result, vm.entityType).then(function (ents) { - $scope.submit(ents); - }); - } - - /** method to select a search result */ - function selectResult(evt, result) { - - if (result.filtered) { - return; - } - - result.selected = result.selected === true ? false : true; - - //since result = an entity, we'll pass it in so we don't have to go back to the server - select(result.name, result.id, result); - - //add/remove to our custom tracked list of selected search results - if (result.selected) { - vm.searchInfo.selectedSearchResults.push(result); - } - else { - vm.searchInfo.selectedSearchResults = _.reject(vm.searchInfo.selectedSearchResults, - function (i) { - return i.id == result.id; - }); - } - - //ensure the tree node in the tree is checked/unchecked if it already exists there - if (tree) { - var found = treeService.getDescendantNode(tree.root, result.id); - if (found) { - found.selected = result.selected; - } - } - } - - function hideSearch() { - - //Traverse the entire displayed tree and update each node to sync with the selected search results - if (tree) { - - //we need to ensure that any currently displayed nodes that get selected - // from the search get updated to have a check box! - function checkChildren(children) { - _.each(children, - function (child) { - //check if the id is in the selection, if so ensure it's flagged as selected - var exists = _.find(vm.searchInfo.selectedSearchResults, - function (selected) { - return child.id == selected.id; - }); - //if the curr node exists in selected search results, ensure it's checked - if (exists) { - child.selected = true; - } - //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result - else if (child.metaData.isSearchResult) { - //if this tree node is under a list view it means that the node was added - // to the tree dynamically under the list view that was searched, so we actually want to remove - // it all together from the tree - var listView = child.parent(); - listView.children = _.reject(listView.children, - function (c) { - return c.id == child.id; - }); - } - - //check if the current node is a list view and if so, check if there's any new results - // that need to be added as child nodes to it based on search results selected - if (child.metaData.isContainer) { - - child.cssClasses = _.reject(child.cssClasses, - function (c) { - return c === 'tree-node-slide-up-hide-active'; - }); - - var listViewResults = _.filter(vm.searchInfo.selectedSearchResults, - function (i) { - return i.parentId == child.id; - }); - _.each(listViewResults, - function (item) { - var childExists = _.find(child.children, - function (c) { - return c.id == item.id; - }); - if (!childExists) { - var parent = child; - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return parent; - } - }); - } - }); - } - - //recurse - if (child.children && child.children.length > 0) { - checkChildren(child.children); - } - }); - } - - checkChildren(tree.root.children); - } - - - vm.searchInfo.showSearch = false; - vm.searchInfo.searchFromId = vm.startNodeId; - vm.searchInfo.searchFromName = null; - vm.searchInfo.results = []; - } - - function onSearchResults(results) { - - //filter all items - this will mark an item as filtered - performFiltering(results); - - //now actually remove all filtered items so they are not even displayed - results = _.filter(results, - function (item) { - return !item.filtered; - }); - - vm.searchInfo.results = results; - - //sync with the curr selected results - _.each(vm.searchInfo.results, - function (result) { - var exists = _.find($scope.model.selection, - function (selectedId) { - return result.id == selectedId; - }); - if (exists) { - result.selected = true; - } - }); - - vm.searchInfo.showSearch = true; - } - - function selectListViewNode(node) { - select(node.name, node.id); - //toggle checked state - node.selected = node.selected === true ? false : true; - } - - function closeMiniListView() { - vm.miniListView = undefined; - } - - //initialize - onInit(); - - }); +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", + function ($scope, + $q, + entityResource, + eventsService, + $log, + searchService, + angularHelper, + $timeout, + localizationService, + treeService, + contentResource, + mediaResource, + memberResource, + languageResource) { + + //used as the result selection + $scope.model.selection = []; + + //the tree object when it loads + var tree = null; + + // Search and listviews is only working for content, media and member section + var searchableSections = ["content", "media", "member"]; + // tracks all expanded paths so when the language is switched we can resync it with the already loaded paths + var expandedPaths = []; + + var vm = this; + vm.treeReady = false; + vm.dialogTreeApi = {}; + vm.initDialogTree = initDialogTree; + vm.section = $scope.model.section; + vm.treeAlias = $scope.model.treeAlias; + vm.multiPicker = $scope.model.multiPicker; + vm.hideHeader = (typeof $scope.model.hideHeader) === "boolean" ? $scope.model.hideHeader : true; + // if you need to load a not initialized tree set this value to false - default is true + vm.onlyInitialized = $scope.model.onlyInitialized; + vm.searchInfo = { + searchFromId: $scope.model.startNodeId, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + } + vm.startNodeId = $scope.model.startNodeId; + //Used for toggling an empty-state message + //Some trees can have no items (dictionary & forms email templates) + vm.hasItems = true; + vm.emptyStateMessage = $scope.model.emptyStateMessage; + vm.languages = []; + vm.selectedLanguage = {}; + vm.languageSelectorIsOpen = false; + vm.showLanguageSelector = $scope.model.showLanguageSelector; + // Allow the entity type to be passed in but defaults to Document for backwards compatibility. + vm.entityType = $scope.model.entityType ? $scope.model.entityType : "Document"; + vm.enableSearh = searchableSections.indexOf(vm.section) !== -1; + + + vm.toggleLanguageSelector = toggleLanguageSelector; + vm.selectLanguage = selectLanguage; + vm.onSearchResults = onSearchResults; + vm.hideSearch = hideSearch; + vm.closeMiniListView = closeMiniListView; + vm.selectListViewNode = selectListViewNode; + + function initDialogTree() { + vm.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler); + //TODO: Also deal with unexpanding!! + vm.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler); + vm.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); + } + + /** + * Performs the initialization of this component + */ + function onInit () { + + // load languages + languageResource.getAll().then(function (languages) { + vm.languages = languages; + + // set the default language + vm.languages.forEach(function (language) { + if (language.isDefault) { + vm.selectedLanguage = language; + vm.languageSelectorIsOpen = false; + } + }); + }); + + if (vm.treeAlias === "content") { + vm.entityType = "Document"; + if (!$scope.model.title) { + localizationService.localize("defaultdialogs_selectContent").then(function(value){ + $scope.model.title = value; + }); + } + } + else if (vm.treeAlias === "member" || vm.section) { + vm.entityType = "Member"; + if (!$scope.model.title) { + localizationService.localize("defaultdialogs_selectMember").then(function(value){ + $scope.model.title = value; + }) + } + } + else if (vm.treeAlias === "media" || vm.section === "media") { + vm.entityType = "Media"; + if (!$scope.model.title) { + localizationService.localize("defaultdialogs_selectMedia").then(function(value){ + $scope.model.title = value; + }); + } + } + + //TODO: Seems odd this logic is here, i don't think it needs to be and should just exist on the property editor using this + if ($scope.model.minNumber) { + $scope.model.minNumber = parseInt($scope.model.minNumber, 10); + } + if ($scope.model.maxNumber) { + $scope.model.maxNumber = parseInt($scope.model.maxNumber, 10); + } + + //if a alternative startnode is used, we need to check if it is a container + if (vm.enableSearh && + vm.startNodeId && + vm.startNodeId !== -1 && + vm.startNodeId !== "-1") { + entityResource.getById(vm.startNodeId, vm.entityType).then(function (node) { + if (node.metaData.IsContainer) { + openMiniListView(node); + } + initTree(); + }); + } + else { + initTree(); + } + + //Configures filtering + if ($scope.model.filter) { + + $scope.model.filterExclude = false; + $scope.model.filterAdvanced = false; + + //used advanced filtering + if (angular.isFunction($scope.model.filter)) { + $scope.model.filterAdvanced = true; + } + else if (angular.isObject($scope.model.filter)) { + $scope.model.filterAdvanced = true; + } + else { + if ($scope.model.filter.startsWith("!")) { + $scope.model.filterExclude = true; + $scope.model.filter = $scope.model.filter.substring(1); + } + + //used advanced filtering + if ($scope.model.filter.startsWith("{")) { + $scope.model.filterAdvanced = true; + //convert to object + $scope.model.filter = angular.fromJson($scope.model.filter); + } + } + } + } + + /** + * Updates the tree's query parameters + */ + function initTree() { + //create the custom query string param for this tree + var queryParams = {}; + if (vm.startNodeId) { + queryParams["startNodeId"] = $scope.model.startNodeId; + } + if (vm.selectedLanguage && vm.selectedLanguage.id) { + queryParams["culture"] = vm.selectedLanguage.culture; + } + var queryString = $.param(queryParams); //create the query string from the params object + + if (!queryString) { + vm.customTreeParams = $scope.model.customTreeParams; + } + else { + vm.customTreeParams = queryString; + if ($scope.model.customTreeParams) { + vm.customTreeParams += "&" + $scope.model.customTreeParams; + } + } + + vm.treeReady = true; + } + + function selectLanguage(language) { + vm.selectedLanguage = language; + // close the language selector + vm.languageSelectorIsOpen = false; + + initTree(); //this will reset the tree params and the tree directive will pick up the changes in a $watch + + //execute after next digest because the internal watch on the customtreeparams needs to be bound now that we've changed it + $timeout(function () { + //reload the tree with it's updated querystring args + vm.dialogTreeApi.load(vm.section).then(function () { + + //create the list of promises + var promises = []; + for (var i = 0; i < expandedPaths.length; i++) { + promises.push(vm.dialogTreeApi.syncTree({ path: expandedPaths[i], activate: false })); + } + //execute them sequentially + angularHelper.executeSequentialPromises(promises); + }); + }); + }; + + function toggleLanguageSelector() { + vm.languageSelectorIsOpen = !vm.languageSelectorIsOpen; + }; + + function nodeExpandedHandler(args) { + + //store the reference to the expanded node path + if (args.node) { + treeService._trackExpandedPaths(args.node, expandedPaths); + } + + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, + function (child) { + + //now we need to look in the already selected search results and + // toggle the check boxes for those ones that are listed + var exists = _.find(vm.searchInfo.selectedSearchResults, + function (selected) { + return child.id == selected.id; + }); + if (exists) { + child.selected = true; + } + }); + + //check filter + performFiltering(args.children); + } + } + + //gets the tree object when it loads + function treeLoadedHandler(args) { + //args.tree contains children (args.tree.root.children) + vm.hasItems = args.tree.root.children.length > 0; + + tree = args.tree; + } + + //wires up selection + function nodeSelectHandler(args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.isSearchResult) { + //check if the item selected was a search result from a list view + + //unselect + select(args.node.name, args.node.id); + + //remove it from the list view children + var listView = args.node.parent(); + listView.children = _.reject(listView.children, + function (child) { + return child.id == args.node.id; + }); + + //remove it from the custom tracked search result list + vm.searchInfo.selectedSearchResults = _.reject(vm.searchInfo.selectedSearchResults, + function (i) { + return i.id == args.node.id; + }); + } + else { + eventsService.emit("dialogs.treePickerController.select", args); + + if (args.node.filtered) { + return; + } + + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + if ($scope.model.select) { + $scope.model.select(args.node); + } + else { + select(args.node.name, args.node.id); + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + + } + } + + /** Method used for selecting a node */ + function select(text, id, entity) { + //if we get the root, we just return a constructed entity, no need for server data + if (id < 0) { + + var rootNode = { + alias: null, + icon: "icon-folder", + id: id, + name: text + }; + + if (vm.multiPicker) { + if (entity) { + multiSelectItem(entity); + } + else { + multiSelectItem(rootNode); + } + } + else { + $scope.model.selection.push(rootNode); + $scope.model.submit($scope.model); + } + } + else { + + if (vm.multiPicker) { + + if (entity) { + multiSelectItem(entity); + } + else { + //otherwise we have to get it from the server + entityResource.getById(id, vm.entityType).then(function (ent) { + multiSelectItem(ent); + }); + } + + } + else { + + hideSearch(); + + //if an entity has been passed in, use it + if (entity) { + $scope.model.selection.push(entity); + $scope.model.submit($scope.model); + } + else { + //otherwise we have to get it from the server + entityResource.getById(id, vm.entityType).then(function (ent) { + $scope.model.selection.push(ent); + $scope.model.submit($scope.model); + }); + } + } + } + } + + function multiSelectItem(item) { + + var found = false; + var foundIndex = 0; + + if ($scope.model.selection.length > 0) { + for (i = 0; $scope.model.selection.length > i; i++) { + var selectedItem = $scope.model.selection[i]; + if (selectedItem.id === item.id) { + found = true; + foundIndex = i; + } + } + } + + if (found) { + $scope.model.selection.splice(foundIndex, 1); + } + else { + $scope.model.selection.push(item); + } + + } + + function performFiltering(nodes) { + + if (!$scope.model.filter) { + return; + } + + //remove any list view search nodes from being filtered since these are special nodes that always must + // be allowed to be clicked on + nodes = _.filter(nodes, + function (n) { + return !angular.isObject(n.metaData.listViewNode); + }); + + if ($scope.model.filterAdvanced) { + + //filter either based on a method or an object + var filtered = angular.isFunction($scope.model.filter) + ? _.filter(nodes, $scope.model.filter) + : _.where(nodes, $scope.model.filter); + + angular.forEach(filtered, + function (value, key) { + value.filtered = true; + if ($scope.model.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push($scope.model.filterCssClass); + } + }); + } + else { + var a = $scope.model.filter.toLowerCase().replace(/\s/g, '').split(','); + angular.forEach(nodes, + function (value, key) { + + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; + + if (!$scope.model.filterExclude && !found || $scope.model.filterExclude && found) { + value.filtered = true; + + if ($scope.model.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push($scope.model.filterCssClass); + } + } + }); + } + } + + function openMiniListView(node) { + vm.miniListView = node; + } + + function multiSubmit(result) { + entityResource.getByIds(result, vm.entityType).then(function (ents) { + $scope.submit(ents); + }); + } + + /** method to select a search result */ + function selectResult(evt, result) { + + if (result.filtered) { + return; + } + + result.selected = result.selected === true ? false : true; + + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); + + //add/remove to our custom tracked list of selected search results + if (result.selected) { + vm.searchInfo.selectedSearchResults.push(result); + } + else { + vm.searchInfo.selectedSearchResults = _.reject(vm.searchInfo.selectedSearchResults, + function (i) { + return i.id == result.id; + }); + } + + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); + if (found) { + found.selected = result.selected; + } + } + } + + function hideSearch() { + + //Traverse the entire displayed tree and update each node to sync with the selected search results + if (tree) { + + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! + function checkChildren(children) { + _.each(children, + function (child) { + //check if the id is in the selection, if so ensure it's flagged as selected + var exists = _.find(vm.searchInfo.selectedSearchResults, + function (selected) { + return child.id == selected.id; + }); + //if the curr node exists in selected search results, ensure it's checked + if (exists) { + child.selected = true; + } + //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result + else if (child.metaData.isSearchResult) { + //if this tree node is under a list view it means that the node was added + // to the tree dynamically under the list view that was searched, so we actually want to remove + // it all together from the tree + var listView = child.parent(); + listView.children = _.reject(listView.children, + function (c) { + return c.id == child.id; + }); + } + + //check if the current node is a list view and if so, check if there's any new results + // that need to be added as child nodes to it based on search results selected + if (child.metaData.isContainer) { + + child.cssClasses = _.reject(child.cssClasses, + function (c) { + return c === 'tree-node-slide-up-hide-active'; + }); + + var listViewResults = _.filter(vm.searchInfo.selectedSearchResults, + function (i) { + return i.parentId == child.id; + }); + _.each(listViewResults, + function (item) { + var childExists = _.find(child.children, + function (c) { + return c.id == item.id; + }); + if (!childExists) { + var parent = child; + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return parent; + } + }); + } + }); + } + + //recurse + if (child.children && child.children.length > 0) { + checkChildren(child.children); + } + }); + } + + checkChildren(tree.root.children); + } + + + vm.searchInfo.showSearch = false; + vm.searchInfo.searchFromId = vm.startNodeId; + vm.searchInfo.searchFromName = null; + vm.searchInfo.results = []; + } + + function onSearchResults(results) { + + //filter all items - this will mark an item as filtered + performFiltering(results); + + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, + function (item) { + return !item.filtered; + }); + + vm.searchInfo.results = results; + + //sync with the curr selected results + _.each(vm.searchInfo.results, + function (result) { + var exists = _.find($scope.model.selection, + function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); + + vm.searchInfo.showSearch = true; + } + + function selectListViewNode(node) { + select(node.name, node.id); + //toggle checked state + node.selected = node.selected === true ? false : true; + } + + function closeMiniListView() { + vm.miniListView = undefined; + } + + //initialize + onInit(); + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index eb9c84dc81..ef9b063739 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -1,60 +1,60 @@ -
    -
    - -
    -
    -
    {{vm.selectedLanguage.name}}
    -   -
    - -
    - -
    - - -
    - - - - - - {{ vm.emptyStateMessage }} - - -
    - - -
    - -
    - - - - -
    +
    +
    + +
    +
    +
    {{vm.selectedLanguage.name}}
    +   +
    + +
    + +
    + + +
    + + + + + + {{ vm.emptyStateMessage }} + + +
    + + +
    + +
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js index 0499059aca..f632547bfc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js @@ -1,33 +1,33 @@ -angular.module("umbraco") - .controller("Umbraco.Overlays.YsodController", function ($scope, legacyResource, treeService, navigationService, localizationService) { - - function onInit() { - - if(!$scope.model.title) { - localizationService.localize("errors_receivedErrorFromServer").then(function(value){ - $scope.model.title = value; - }); - } - - if ($scope.model.error && $scope.model.error.data && $scope.model.error.data.StackTrace) { - //trim whitespace - $scope.model.error.data.StackTrace = $scope.model.error.data.StackTrace.trim(); - } - - if ($scope.model.error && $scope.model.error.data) { - $scope.model.error.data.InnerExceptions = []; - var ex = $scope.model.error.data.InnerException; - while (ex) { - if (ex.StackTrace) { - ex.StackTrace = ex.StackTrace.trim(); - } - $scope.model.error.data.InnerExceptions.push(ex); - ex = ex.InnerException; - } - } - - } - - onInit(); - - }); +angular.module("umbraco") + .controller("Umbraco.Overlays.YsodController", function ($scope, legacyResource, treeService, navigationService, localizationService) { + + function onInit() { + + if(!$scope.model.title) { + localizationService.localize("errors_receivedErrorFromServer").then(function(value){ + $scope.model.title = value; + }); + } + + if ($scope.model.error && $scope.model.error.data && $scope.model.error.data.StackTrace) { + //trim whitespace + $scope.model.error.data.StackTrace = $scope.model.error.data.StackTrace.trim(); + } + + if ($scope.model.error && $scope.model.error.data) { + $scope.model.error.data.InnerExceptions = []; + var ex = $scope.model.error.data.InnerException; + while (ex) { + if (ex.StackTrace) { + ex.StackTrace = ex.StackTrace.trim(); + } + $scope.model.error.data.InnerExceptions.push(ex); + ex = ex.InnerException; + } + } + + } + + onInit(); + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html index 9a4ff7cb81..fa0639fdd0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html @@ -1,27 +1,27 @@ -
    - -

    {{model.error.errorMsg}}

    -

    {{model.error.data.ExceptionMessage || model.error.data.Message}}

    - -
    -
    - Exception Details: -
    - {{model.error.data.ExceptionType}}: {{model.error.data.ExceptionMessage}} -
    - -
    -
    - Stacktrace: -
    -
    {{model.error.data.StackTrace}}
    -
    - -
    -
    - Inner Exception: -
    -
    {{e.ExceptionType}}: {{e.ExceptionMessage}}
    -
    {{e.StackTrace}}
    -
    -
    +
    + +

    {{model.error.errorMsg}}

    +

    {{model.error.data.ExceptionMessage || model.error.data.Message}}

    + +
    +
    + Exception Details: +
    + {{model.error.data.ExceptionType}}: {{model.error.data.ExceptionMessage}} +
    + +
    +
    + Stacktrace: +
    +
    {{model.error.data.StackTrace}}
    +
    + +
    +
    + Inner Exception: +
    +
    {{e.ExceptionType}}: {{e.ExceptionMessage}}
    +
    {{e.StackTrace}}
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html index 18020ffd59..0bb4b1ed2e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html @@ -1,16 +1,16 @@ -
    -
    -

    {{menuDialogTitle}}

    -
    - - +
    +
    +

    {{menuDialogTitle}}

    +
    + +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index b04eeeda2f..907a45b08f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -1,51 +1,51 @@ -
    - - - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html index 46643152a6..a7a88a1d22 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html @@ -1,33 +1,33 @@ -
    -
    - -
    - -
    +
    +
    + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-control-group.html b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-control-group.html index d1356066c2..aae96c4edf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-control-group.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-control-group.html @@ -1,14 +1,14 @@ -
    -
    -
    - -
    -
    -
    +
    +
    +
    + +
    +
    +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-pane.html b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-pane.html index b9cda6bb26..7c94769d2d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-pane.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-pane.html @@ -1,3 +1,3 @@ -
    - +
    +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-panel.html b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-panel.html index 62460be6d5..a963eff309 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-panel.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-panel.html @@ -1,3 +1,3 @@ -
    - -
    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html b/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html index 9962e2dd84..d8245abbe7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html @@ -1,21 +1,21 @@ -
    - -
    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html index 5eb55144b2..ebe829ea52 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html @@ -1,10 +1,10 @@ -
    -

    {{caption}}

    - -
    -
    - Cancel - Ok -
    -
    -
    +
    +

    {{caption}}

    + +
    +
    + Cancel + Ok +
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html index 8c5c7fcc4a..5245384aac 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html @@ -1,70 +1,70 @@ -
    -
    - -
    -
    -
    - -
    - - -
    -
    - -
    -
    - -
    - - -
    -
    - - -
    -
    - - -
    - {{item[column.alias]}} -
    - - - - - -
    -
    -
    -
    -
    - - - There are no items show in the list. - -
    +
    +
    + +
    +
    +
    + +
    + + +
    +
    + +
    +
    + +
    + + +
    +
    + + +
    +
    + + +
    + {{item[column.alias]}} +
    + + + + + +
    +
    +
    +
    +
    + + + There are no items show in the list. + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js index 919c4623ef..6970a7ba36 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js @@ -1,75 +1,75 @@ -/** - * @ngdoc controller - * @name Umbraco.Editors.Content.CreateController - * @function - * - * @description - * The controller for the content creation dialog - */ -function contentCreateController($scope, - $routeParams, - contentTypeResource, - iconHelper, - $location, - navigationService, +/** + * @ngdoc controller + * @name Umbraco.Editors.Content.CreateController + * @function + * + * @description + * The controller for the content creation dialog + */ +function contentCreateController($scope, + $routeParams, + contentTypeResource, + iconHelper, + $location, + navigationService, blueprintConfig) { - function initialize() { - contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { - $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); - }); - - $scope.selectContentType = true; - $scope.selectBlueprint = false; - $scope.allowBlank = blueprintConfig.allowBlank; + function initialize() { + contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { + $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); + }); + + $scope.selectContentType = true; + $scope.selectBlueprint = false; + $scope.allowBlank = blueprintConfig.allowBlank; } - function close() { - navigationService.hideMenu(); + function close() { + navigationService.hideMenu(); } - function createBlank(docType) { - $location - .path("/content/content/edit/" + $scope.currentNode.id) + function createBlank(docType) { + $location + .path("/content/content/edit/" + $scope.currentNode.id) .search("doctype", docType.alias) - .search("create", "true"); - close(); + .search("create", "true"); + close(); } - function createOrSelectBlueprintIfAny(docType) { - var blueprintIds = _.keys(docType.blueprints || {}); - $scope.docType = docType; - if (blueprintIds.length) { - if (blueprintConfig.skipSelect) { - createFromBlueprint(blueprintIds[0]); - } else { - $scope.selectContentType = false; - $scope.selectBlueprint = true; - } - } else { - createBlank(docType); - } - } - - function createFromBlueprint(blueprintId) { - $location - .path("/content/content/edit/" + $scope.currentNode.id) + function createOrSelectBlueprintIfAny(docType) { + var blueprintIds = _.keys(docType.blueprints || {}); + $scope.docType = docType; + if (blueprintIds.length) { + if (blueprintConfig.skipSelect) { + createFromBlueprint(blueprintIds[0]); + } else { + $scope.selectContentType = false; + $scope.selectBlueprint = true; + } + } else { + createBlank(docType); + } + } + + function createFromBlueprint(blueprintId) { + $location + .path("/content/content/edit/" + $scope.currentNode.id) .search("doctype", $scope.docType.alias) .search("create", "true") - .search("blueprintId", blueprintId); - close(); + .search("blueprintId", blueprintId); + close(); } - + $scope.createBlank = createBlank; $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny; - $scope.createFromBlueprint = createFromBlueprint; - - initialize(); -} - -angular.module("umbraco").controller("Umbraco.Editors.Content.CreateController", contentCreateController); - -angular.module("umbraco").value("blueprintConfig", { + $scope.createFromBlueprint = createFromBlueprint; + + initialize(); +} + +angular.module("umbraco").controller("Umbraco.Editors.Content.CreateController", contentCreateController); + +angular.module("umbraco").value("blueprintConfig", { skipSelect: false, - allowBlank: true + allowBlank: true }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js index 3c112c292b..6e2865046d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js @@ -1,76 +1,76 @@ -/** - * @ngdoc controller - * @name Umbraco.Editors.ContentDeleteController - * @function - * - * @description - * The controller for deleting content - */ -function ContentDeleteController($scope, $timeout, contentResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) { +/** + * @ngdoc controller + * @name Umbraco.Editors.ContentDeleteController + * @function + * + * @description + * The controller for deleting content + */ +function ContentDeleteController($scope, $timeout, contentResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) { - /** - * Used to toggle UI elements during delete operations - * @param {any} isDeleting + /** + * Used to toggle UI elements during delete operations + * @param {any} isDeleting */ - function toggleDeleting(isDeleting) { - $scope.currentNode.loading = isDeleting; - $scope.busy = isDeleting; + function toggleDeleting(isDeleting) { + $scope.currentNode.loading = isDeleting; + $scope.busy = isDeleting; } - - $scope.performDelete = function() { - - // stop from firing again on double-click - if ($scope.busy) { return false; } - - toggleDeleting(true); - - contentResource.deleteById($scope.currentNode.id).then(function () { - - //get the root node before we remove it - var rootNode = treeService.getTreeRoot($scope.currentNode); - - treeService.removeNode($scope.currentNode); + + $scope.performDelete = function() { + + // stop from firing again on double-click + if ($scope.busy) { return false; } + + toggleDeleting(true); + + contentResource.deleteById($scope.currentNode.id).then(function () { + + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + + treeService.removeNode($scope.currentNode); toggleDeleting(false); - + if (rootNode) { $timeout(function () { - //ensure the recycle bin has child nodes now - var recycleBin = treeService.getDescendantNode(rootNode, -20); + //ensure the recycle bin has child nodes now + var recycleBin = treeService.getDescendantNode(rootNode, -20); if (recycleBin) { - treeService.syncTree({ node: recycleBin, path: treeService.getPath(recycleBin), forceReload: true }); - } - }, 500); - } - - //if the current edited item is the same one as we're deleting, we need to navigate elsewhere - if (editorState.current && editorState.current.id == $scope.currentNode.id) { - - //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent - var location = "/content"; - if ($scope.currentNode.parentId.toString() !== "-1") - location = "/content/content/edit/" + $scope.currentNode.parentId; - - $location.path(location); - } - - navigationService.hideMenu(); - }, function(err) { - - toggleDeleting(false); - - //check if response is ysod - if (err.status && err.status >= 500) { - dialogService.ysodDialog(err); - } - }); - - }; - - $scope.cancel = function() { + treeService.syncTree({ node: recycleBin, path: treeService.getPath(recycleBin), forceReload: true }); + } + }, 500); + } + + //if the current edited item is the same one as we're deleting, we need to navigate elsewhere + if (editorState.current && editorState.current.id == $scope.currentNode.id) { + + //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent + var location = "/content"; + if ($scope.currentNode.parentId.toString() !== "-1") + location = "/content/content/edit/" + $scope.currentNode.parentId; + + $location.path(location); + } + + navigationService.hideMenu(); + }, function(err) { + + toggleDeleting(false); + + //check if response is ysod + if (err.status && err.status >= 500) { + dialogService.ysodDialog(err); + } + }); + + }; + + $scope.cancel = function() { toggleDeleting(false); - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.Content.DeleteController", ContentDeleteController); + navigationService.hideDialog(); + }; +} + +angular.module("umbraco").controller("Umbraco.Editors.Content.DeleteController", ContentDeleteController); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.emptyrecyclebin.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.emptyrecyclebin.controller.js index 23b872674b..ee55b1ced4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.emptyrecyclebin.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.emptyrecyclebin.controller.js @@ -1,38 +1,38 @@ -/** - * @ngdoc controller - * @name Umbraco.Editors.Content.EmptyRecycleBinController - * @function - * - * @description - * The controller for deleting content - */ -function ContentEmptyRecycleBinController($scope, contentResource, treeService, navigationService, notificationsService, $route) { - - $scope.busy = false; - - $scope.performDelete = function() { - - //(used in the UI) - $scope.busy = true; - $scope.currentNode.loading = true; - - contentResource.emptyRecycleBin($scope.currentNode.id).then(function (result) { - - $scope.busy = false; - $scope.currentNode.loading = false; - - treeService.removeChildNodes($scope.currentNode); - navigationService.hideMenu(); - - //reload the current view - $route.reload(); - }); - - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.Content.EmptyRecycleBinController", ContentEmptyRecycleBinController); +/** + * @ngdoc controller + * @name Umbraco.Editors.Content.EmptyRecycleBinController + * @function + * + * @description + * The controller for deleting content + */ +function ContentEmptyRecycleBinController($scope, contentResource, treeService, navigationService, notificationsService, $route) { + + $scope.busy = false; + + $scope.performDelete = function() { + + //(used in the UI) + $scope.busy = true; + $scope.currentNode.loading = true; + + contentResource.emptyRecycleBin($scope.currentNode.id).then(function (result) { + + $scope.busy = false; + $scope.currentNode.loading = false; + + treeService.removeChildNodes($scope.currentNode); + navigationService.hideMenu(); + + //reload the current view + $route.reload(); + }); + + }; + + $scope.cancel = function() { + navigationService.hideDialog(); + }; +} + +angular.module("umbraco").controller("Umbraco.Editors.Content.EmptyRecycleBinController", ContentEmptyRecycleBinController); 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 ee39f98234..7501508a48 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/copy.html @@ -1,94 +1,94 @@ -
    -
    -
    - -
    -
    -
    {{error.errorMsg}}
    -
    {{error.data.message}}
    -
    -
    - -
    -
    - {{currentNode.name}} was copied to - {{target.name}} -
    - -
    - -

    - Choose where to copy {{currentNode.name}} to in the tree structure below -

    - -
    -
    -
    - -
    - -
    - - - -
    - - - - -
    - +
    +
    + +
    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    +
    + +
    +
    + {{currentNode.name}} was copied to + {{target.name}} +
    + +
    + +

    + Choose where to copy {{currentNode.name}} to in the tree structure below +

    + +
    +
    +
    + +
    + +
    + + + +
    + + + + +
    + - -
    -
    - - - - - - - - - - - - - - - - -
    -
    -
    - - + on-init="onTreeInit()" + enablelistviewexpand="true" + enablecheckboxes="true"> +
    +
    +
    + + + + + + + + + + + + + + + + +
    +
    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/content/create.html b/src/Umbraco.Web.UI.Client/src/views/content/create.html index c624023145..931737bcf0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/create.html @@ -1,55 +1,55 @@ - - -