├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── advanced-reviews-dotnet-core.yml │ └── advanced-reviews-dotnet-framework.yml ├── .gitignore ├── Advanced.CMS.AdvancedReviews.sln ├── Directory.build.targets ├── LICENSE ├── NuGet.config ├── README.md ├── assets ├── content-review.png ├── default emails │ ├── mail_edit.txt │ └── mail_preview.txt ├── documentation │ ├── approval_1.gif │ ├── approval_2_decline.gif │ ├── approval_3_approve_prompt.gif │ ├── configure_approval.gif │ ├── external_review_email_example.png │ ├── external_review_share_dialog.png │ ├── external_reviewer.gif │ ├── pin_answer_1.gif │ ├── pin_answer_2.gif │ ├── pin_answer_3.gif │ ├── pin_review_1.gif │ ├── pin_review_2.gif │ ├── pin_review_3.gif │ ├── preview_unpublished_content.gif │ ├── project_overview.png │ ├── screenshot_manipulation.gif │ └── sliding_panel.gif ├── logo.png ├── samples │ ├── abbie.jpg │ ├── administrator.jpg │ └── eddie.jpg └── thumbnail.png ├── build.cmd ├── build ├── CopyZipFiles.targets ├── common.props ├── database │ ├── Alloy.mdf │ ├── DefaultSiteContent.episerverdata │ └── commerce.Commerce.mdf ├── dependencies.props ├── nuspec.props ├── pack.ps1 └── version.props ├── global.json ├── pack.cmd ├── setup.cmd ├── site.cmd ├── src ├── Advanced.CMS.AdvancedReviews │ ├── Advanced.CMS.AdvancedReviews.csproj │ ├── Advanced.CMS.AdvancedReviews.nuspec │ ├── AdvancedReviewsEndpointRoutingExtension.cs │ ├── ExternalReviewLoginController.cs │ ├── ImageProxyController.cs │ ├── ServiceCollectionExtensions.cs │ ├── Views │ │ └── ExternalReviewLogin │ │ │ └── Index.cshtml │ ├── logo.png │ └── readme.txt ├── Advanced.CMS.ApprovalReviews │ ├── Advanced.CMS.ApprovalReviews.csproj │ ├── ApprovalOptions.cs │ ├── ApprovalReview.cs │ ├── ApprovalReviewStore.cs │ ├── AvatarsService │ │ ├── ICustomAvatarResolver.cs │ │ └── ReviewAvatarsController.cs │ ├── ClientResources │ │ ├── ShowReviewCommand.js │ │ ├── advancedReviewService.js │ │ ├── approveChangesInitializer.js │ │ ├── commandsProvider.js │ │ ├── content-review.png │ │ ├── initializer.js │ │ ├── notificationsInitializer.js │ │ ├── onPageEditingInitializer.js │ │ ├── rejectChangesInitializer.js │ │ └── styles.css │ ├── DdsApprovalReviewsRepository.cs │ ├── EmbededLanguages │ │ ├── advancedapprovalreviews_DE.xml │ │ ├── advancedapprovalreviews_EN.xml │ │ ├── advancedapprovalreviews_FI.xml │ │ ├── advancedapprovalreviews_NO.xml │ │ └── advancedapprovalreviews_SV.xml │ ├── IApprovalReviewsRepository.cs │ ├── IdenticonGenerator.cs │ ├── Notifications │ │ ├── ReviewContentNotificationFormatter.cs │ │ ├── ReviewContentNotificationModel.cs │ │ └── ReviewsNotifier.cs │ ├── React │ │ ├── .gitignore │ │ ├── .storybook │ │ │ ├── externalResources.json │ │ │ ├── fake-advanced-review-service.ts │ │ │ ├── fake_OPE.html │ │ │ ├── resources.json │ │ │ └── screenshots.json │ │ ├── .yarn │ │ │ ├── plugins │ │ │ │ └── @yarnpkg │ │ │ │ │ └── plugin-interactive-tools.cjs │ │ │ └── releases │ │ │ │ └── yarn-file.cjs │ │ ├── .yarnrc.yml │ │ ├── dojo.d.ts │ │ ├── eslint.config.mjs │ │ ├── externalResources.d.ts │ │ ├── package.json │ │ ├── resources.d.ts │ │ ├── review-avatars │ │ │ ├── alfred.jpg │ │ │ ├── john.jpg │ │ │ └── lina.jpg │ │ ├── setup.ts │ │ ├── src │ │ │ ├── _variables.scss │ │ │ ├── admin │ │ │ │ ├── admin-plugin.scss │ │ │ │ └── admin-plugin.tsx │ │ │ ├── comment │ │ │ │ ├── comment.scss │ │ │ │ └── comment.tsx │ │ │ ├── common │ │ │ │ ├── context-menu.tsx │ │ │ │ ├── drop-down-menu.scss │ │ │ │ └── drop-down-menu.tsx │ │ │ ├── confirmation │ │ │ │ └── confirmation.tsx │ │ │ ├── details │ │ │ │ ├── review-details.scss │ │ │ │ └── review-details.tsx │ │ │ ├── drawable-preview │ │ │ │ ├── drawable-preview.stories.tsx.js │ │ │ │ └── drawable-preview.tsx │ │ │ ├── editable-external-reviews │ │ │ │ ├── confirm-name-dialog.stories.tsx │ │ │ │ ├── confirm-name-dialog.tsx │ │ │ │ └── editable-external-review-component.tsx │ │ │ ├── external-reviews-manage-links │ │ │ │ ├── external-review-links-store.ts │ │ │ │ ├── external-review-manage-links-edit.tsx │ │ │ │ ├── external-review-manage-links-widget.tsx │ │ │ │ ├── external-review-manage-links.scss │ │ │ │ ├── external-review-manage-links.stories.tsx │ │ │ │ ├── external-review-manage-links.tsx │ │ │ │ ├── external-review-share-dialog.scss │ │ │ │ ├── external-review-share-dialog.stories.tsx │ │ │ │ ├── external-review-share-dialog.tsx │ │ │ │ └── fake-review-links-store.ts │ │ │ ├── iframe-overlay │ │ │ │ └── iframe-overlay.tsx │ │ │ ├── iframe-with-pins │ │ │ │ ├── iframe-with-pins.stories.tsx │ │ │ │ └── iframe-with-pins.tsx │ │ │ ├── location-comment │ │ │ │ ├── location-comment.scss │ │ │ │ └── location-comment.tsx │ │ │ ├── new-review-dialog │ │ │ │ ├── new-review-dialog.scss │ │ │ │ ├── new-review-dialog.stories.tsx │ │ │ │ └── new-review-dialog.tsx │ │ │ ├── pin-collection │ │ │ │ ├── pin-collection.scss │ │ │ │ └── pin-collection.tsx │ │ │ ├── pin-navigator │ │ │ │ ├── pin-navigator.scss │ │ │ │ └── pin-navigator.tsx │ │ │ ├── pin │ │ │ │ ├── pin.scss │ │ │ │ ├── pin.stories.tsx │ │ │ │ └── pin.tsx │ │ │ ├── position-calculator │ │ │ │ ├── offset.ts │ │ │ │ ├── position-calculator.test.ts │ │ │ │ └── position-calculator.ts │ │ │ ├── review-component-widget │ │ │ │ └── review-component-widget.tsx │ │ │ ├── reviews-sliding-panel │ │ │ │ ├── reviews-sliding-panel.scss │ │ │ │ ├── reviews-sliding-panel.stories.tsx │ │ │ │ └── reviews-sliding-panel.tsx │ │ │ ├── screenshot-dialog │ │ │ │ ├── screenshot-dialog.scss │ │ │ │ ├── screenshot-dialog.stories.tsx │ │ │ │ └── screenshot-dialog.tsx │ │ │ └── store │ │ │ │ ├── priority-icon-mappings.ts │ │ │ │ └── review-store.tsx │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ ├── vite-admin.config.ts │ │ ├── vite-env.d.ts │ │ ├── vite-external-review-manage-links-widget.config.ts │ │ ├── vite-external.config.ts │ │ ├── vite-widget.config.ts │ │ ├── vite.amd.config.ts │ │ ├── vite.config.ts │ │ └── yarn.lock │ ├── ReviewLocationPreviewPluginController.cs │ ├── ReviewUrlGenerator.cs │ ├── ServiceCollectionExtensions.cs │ ├── SiteUriResolver.cs │ ├── StartPageResolver.cs │ ├── advanced-cms-approval-reviews.Views │ │ └── Views │ │ │ └── ReviewLocationPreviewPlugin │ │ │ └── Index.cshtml │ └── module.config ├── Advanced.CMS.ExternalReviews │ ├── Advanced.CMS.ExternalReviews.csproj │ ├── AdvancedReviewsMenuProvider.cs │ ├── AdvancedReviewsModuleViewModel.cs │ ├── ApprovalReviewsShellModule.cs │ ├── BlocksPreview │ │ ├── BlockPreviewController.cs │ │ ├── BlockPreviewInitialization.cs │ │ ├── BlockPreviewViewModel.cs │ │ └── BlocksTemplateCoordinator.cs │ ├── ClientResources │ │ ├── external-review-service.js │ │ └── initializer.js │ ├── ContentExtensions.cs │ ├── CustomContentLoaderInitialization.cs │ ├── DraftContentAreaPreview │ │ ├── ContentAccessEvaluatorDecorator.cs │ │ └── PublishedStateAssessorDecorator.cs │ ├── DraftContentLoader.cs │ ├── EditReview │ │ ├── ConvertEditLinksFilter.cs │ │ ├── PageEditController.cs │ │ ├── PageEditPartialRouter.cs │ │ └── PropertyResolver.cs │ ├── EmbededLanguages │ │ ├── advancedexternalreviews_DE.xml │ │ ├── advancedexternalreviews_EN.xml │ │ ├── advancedexternalreviews_FI.xml │ │ ├── advancedexternalreviews_NO.xml │ │ └── advancedexternalreviews_SV.xml │ ├── ExternalReviewOptions.cs │ ├── ExternalReviewState.cs │ ├── ExternalReviewUrlGenerator.cs │ ├── ExternalReviewsInitializationModule.cs │ ├── ExternalReviewsShellModule.cs │ ├── InternalApiControllerFeatureProvider.cs │ ├── ManageLinks │ │ ├── AdvancedReviewsComponentProvider.cs │ │ └── ExternalReviewLinksManageComponent.cs │ ├── PagePreviewPartialRouter.cs │ ├── PinCodeSecurity │ │ ├── ExternalLinkPinCodeSecurityHandler.cs │ │ └── PinCodeHashGenerator.cs │ ├── PreviewPageTemplateDescriptor.cs │ ├── PreviewUrlResolver.cs │ ├── ProjectContentResolver.cs │ ├── Properties │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── RemoveExpiredTokensJob.cs │ ├── Resources │ │ ├── edit.txt │ │ ├── mail_edit.txt │ │ └── mail_preview.txt │ ├── ReviewLinksRepository │ │ ├── ExternalReviewLink.cs │ │ ├── ExternalReviewLinkBuilder.cs │ │ ├── ExternalReviewLinkDds.cs │ │ ├── ExternalReviewLinkExtension.cs │ │ ├── ExternalReviewLinksRepository.cs │ │ ├── ExternalReviewStore.cs │ │ └── IExternalReviewLinksRepository.cs │ ├── ServiceCollectionExtensions.cs │ ├── UrlPath.cs │ ├── advanced-cms-external-reviews.Views │ │ └── Views │ │ │ ├── BlockPreview │ │ │ └── Index.cshtml │ │ │ └── PageEdit │ │ │ └── Index.cshtml │ └── module.config └── Alloy.Sample │ ├── .gitignore │ ├── Alloy.Sample.csproj │ ├── Business │ ├── Channels │ │ ├── DisplayResolutionBase.cs │ │ ├── DisplayResolutions.cs │ │ ├── MobileChannel.cs │ │ └── WebChannel.cs │ ├── CommerceAutoMigrateUrl.cs │ ├── ContentExtensions.cs │ ├── ContentLocator.cs │ ├── EditorDescriptors │ │ ├── ContactPageSelectionFactory.cs │ │ └── ContactPageSelector.cs │ ├── IModifyLayout.cs │ ├── Initialization │ │ └── CustomizedRenderingInitialization.cs │ ├── PageContextActionFilter.cs │ ├── PageTypeExtensions.cs │ ├── PageViewContextFactory.cs │ ├── Rendering │ │ ├── AlloyContentAreaRenderer.cs │ │ ├── ErrorHandlingContentRenderer.cs │ │ ├── IContainerPage.cs │ │ ├── ICustomCssInContentArea.cs │ │ ├── SiteViewEngineLocationExpander.cs │ │ └── TemplateCoordinator.cs │ ├── SiteInitialization.cs │ └── UIDescriptors │ │ └── ContainerPageUIDescriptor.cs │ ├── Components │ ├── ContactBlockViewComponent.cs │ ├── ImageFileViewComponent.cs │ ├── PageListBlockViewComponent.cs │ └── VideoFileViewComponent.cs │ ├── Controllers │ ├── DefaultPageController.cs │ ├── DummyProductController.cs │ ├── MyVariationController.cs │ ├── PageControllerBase.cs │ ├── PreviewController.cs │ ├── RegisterController.cs │ ├── RouteAttributeController.cs │ ├── SearchPageController.cs │ └── StartPageController.cs │ ├── Extensions │ ├── HttpContextExtensions.cs │ ├── ServiceCollectionExtensions.cs │ └── ViewContextExtension.cs │ ├── Global.cs │ ├── Helpers │ ├── CategorizableExtensions.cs │ ├── HtmlHelpers.cs │ └── UrlHelpers.cs │ ├── Infrastructure │ ├── AdministratorRegistrationPageMiddleware.cs │ └── RegisterFirstAdminWithLocalRequestAttribute.cs │ ├── Models │ ├── Blocks │ │ ├── ButtonBlock.cs │ │ ├── ContactBlock.cs │ │ ├── EditorialBlock.cs │ │ ├── JumbotronBlock.cs │ │ ├── PageListBlock.cs │ │ ├── SiteBlockData.cs │ │ ├── SiteLogotypeBlock.cs │ │ ├── TeaserBlock.cs │ │ └── _ReadMe.txt │ ├── Commerce │ │ └── DummyProduct.cs │ ├── LoginViewModel.cs │ ├── Media │ │ ├── GenericMedia.cs │ │ ├── ImageFile.cs │ │ ├── VectorImageFile.cs │ │ └── VideoFile.cs │ ├── Pages │ │ ├── AllPropertiesTestPage.cs │ │ ├── ArticlePage.cs │ │ ├── ContactPage.cs │ │ ├── ContainerPage.cs │ │ ├── IHasRelatedContent.cs │ │ ├── ISearchPage.cs │ │ ├── LandingPage.cs │ │ ├── NewsPage.cs │ │ ├── ProductPage.cs │ │ ├── SearchPage.cs │ │ ├── SitePageData.cs │ │ ├── StandardPage.cs │ │ ├── StartPage.cs │ │ ├── TestContentPage.cs │ │ └── _ReadMe.txt │ ├── Register │ │ └── RegisterViewModel.cs │ ├── SiteContentType.cs │ ├── SiteImageUrl.cs │ └── ViewModels │ │ ├── ContactBlockModel.cs │ │ ├── ContentRenderingErrorModel.cs │ │ ├── IPageViewModel.cs │ │ ├── ImageViewModel.cs │ │ ├── LayoutModel.cs │ │ ├── PageListModel.cs │ │ ├── PageViewModel.cs │ │ ├── PreviewModel.cs │ │ ├── SearchContentModel.cs │ │ └── VideoViewModel.cs │ ├── Program.cs │ ├── Properties │ ├── AssemblyInfo.cs │ └── launchSettings.json │ ├── ProvisionDatabase_MVC.cs │ ├── Resources │ └── LanguageFiles │ │ ├── ContentTypeNames.xml │ │ ├── Display.xml │ │ ├── EditorHints.xml │ │ ├── GroupNames.xml │ │ ├── PropertyNames.xml │ │ ├── Views.xml │ │ └── _ReadMe.txt │ ├── ServiceCollectionExtensions.cs │ ├── Startup.cs │ ├── Views │ ├── ArticlePage │ │ └── Index.cshtml │ ├── DummyProduct │ │ └── Index.cshtml │ ├── LandingPage │ │ └── Index.cshtml │ ├── MyVariation │ │ └── Index.cshtml │ ├── NewsPage │ │ └── Index.cshtml │ ├── Preview │ │ └── Index.cshtml │ ├── ProductPage │ │ └── Index.cshtml │ ├── Register │ │ └── Index.cshtml │ ├── SearchPage │ │ └── Index.cshtml │ ├── Shared │ │ ├── Blocks │ │ │ ├── ButtonBlock.cshtml │ │ │ ├── EditorialBlock.cshtml │ │ │ ├── JumbotronBlockWide.cshtml │ │ │ ├── NoRenderer.cshtml │ │ │ ├── SiteLogotypeBlock.cshtml │ │ │ ├── TeaserBlock.cshtml │ │ │ └── TeaserBlockWide.cshtml │ │ ├── Breadcrumbs.cshtml │ │ ├── Components │ │ │ ├── ContactBlock │ │ │ │ └── Default.cshtml │ │ │ ├── ImageFile │ │ │ │ └── Default.cshtml │ │ │ ├── PageListBlock │ │ │ │ └── Default.cshtml │ │ │ ├── TestContentPage │ │ │ │ └── Index.cshtml │ │ │ └── VideoFile │ │ │ │ └── Default.cshtml │ │ ├── DisplayTemplates │ │ │ ├── ContactPage.cshtml │ │ │ ├── DateTime.cshtml │ │ │ ├── StringsCollection.cshtml │ │ │ └── _ReadMe.txt │ │ ├── Footer.cshtml │ │ ├── Header.cshtml │ │ ├── Layouts │ │ │ ├── _LeftNavigation.cshtml │ │ │ ├── _Root.cshtml │ │ │ └── _TwoPlusOne.cshtml │ │ ├── PagePartials │ │ │ ├── ContactPage.cshtml │ │ │ ├── ContactPageWide.cshtml │ │ │ ├── Page.cshtml │ │ │ └── PageWide.cshtml │ │ ├── Readonly.cshtml │ │ ├── SubNavigation.cshtml │ │ ├── TemplateError.cshtml │ │ └── TemplateHint.cshtml │ ├── StandardPage │ │ └── Index.cshtml │ ├── StartPage │ │ └── Index.cshtml │ ├── _ReadMe.txt │ ├── _ViewImports.cshtml │ └── _viewstart.cshtml │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── bundleconfig.json │ ├── docker-compose.yml │ ├── docker │ ├── Sql.Dockerfile │ ├── Web.Dockerfile │ ├── build-script │ │ ├── attach_db.sh │ │ └── wait_sqlserver_start_and_attachdb.sh │ ├── build.ps1 │ └── run-script │ │ ├── build.sh │ │ ├── build_web_only.sh │ │ ├── run.sh │ │ ├── stop.sh │ │ └── stop_web_only.sh │ ├── favicon.ico │ ├── module.config │ ├── modulesbin │ └── .gitkeep │ └── wwwroot │ ├── ClientResources │ ├── Images │ │ └── icons │ │ │ └── layoutIcons24x24.png │ ├── Scripts │ │ └── Editors │ │ │ └── StringList.js │ └── Styles │ │ ├── LayoutIcons.css │ │ └── Styles.css │ ├── css │ ├── bootstrap-collapse.js │ ├── bootstrap-responsive.css │ ├── bootstrap.css │ ├── editmode.css │ ├── editor.css │ ├── media.css │ └── style.css │ ├── gfx │ ├── New_FDT_Press_Contact_.JPG │ ├── carouselbackground.png │ ├── contact.jpg │ ├── exampelspan4.png │ ├── experts.png │ ├── fallows-media-wide.jpg │ ├── leader.png │ ├── leader2.png │ ├── logotype.png │ ├── meet.jpg │ ├── page-type-thumbnail-article.png │ ├── page-type-thumbnail-contact.png │ ├── page-type-thumbnail-product.png │ ├── page-type-thumbnail-standard.png │ ├── page-type-thumbnail.png │ ├── person.png │ ├── plan.jpg │ ├── play.png │ ├── playInactive.png │ ├── productLandingv2.png │ ├── searchbutton.png │ ├── searchbuttonsmall.png │ └── track.jpg │ ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png │ ├── js │ ├── bootstrap.js │ └── jquery.js │ └── jwplayer │ ├── jwplayer.js │ ├── player.swf │ ├── preview.jpg │ └── video.mp4 ├── test.cmd └── test ├── Advanced.CMS.AdvancedReviews.IntegrationTests.Basic ├── Advanced.CMS.AdvancedReviews.IntegrationTests.Basic.csproj ├── ExternalLinksForProjectTest.cs ├── IntegrationTestCollection.cs ├── NoEveryoneAccess.cs ├── PreviewingLastContentVersion.cs ├── PublishedPageWithDraftReferencesTests.cs ├── SinglePageWithLanguagesTests.cs ├── SiteFixture.cs ├── TinyMceShowDraftLinksTests.cs └── TokenExpiration.cs ├── Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity ├── Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity.csproj ├── IntegrationTestCollection.cs ├── ObjectExtensions.cs ├── SinglePageWithPinSecurityTests.cs └── SiteFixtureWithPinSecurity.cs ├── Advanced.CMS.AdvancedReviews.IntegrationTests ├── Advanced.CMS.AdvancedReviews.IntegrationTests.csproj ├── Assets │ └── db_template.mdf ├── CommonFixture.cs ├── SiteFixtureBase.cs ├── TestEnvironment.cs ├── TestScenarioBuilder.cs └── TestScenarioBuilderFactory.cs ├── Advanced.CMS.IntegrationTests ├── Advanced.CMS.IntegrationTests.csproj ├── CmsDatabaseFixture.cs ├── DatabaseFixture.cs ├── DatabaseHelper.cs ├── FakeUserMiddleware.cs ├── IdentitySchema.sql ├── ServiceMocks │ ├── MockServiceCollectionExtensions.cs │ ├── MockableContentAccessChecker.cs │ ├── MockableContentLoaderService.cs │ ├── MockableContentRepository.cs │ ├── MockableContentService.cs │ ├── MockableCurrentProject.cs │ ├── MockableProjectLoaderService.cs │ └── MockableProjectService.cs ├── SolutionPathUtility.cs ├── SwitchableDatabaseMode.cs ├── UIServiceFixture.cs └── UnitTestWebHostingEnvironment.cs └── sites └── TestSite ├── .gitignore ├── Controllers ├── StandardPageController.cs └── StartPageController.cs ├── Global.cs ├── Models ├── EditorialBlock.cs ├── GenericFile.cs ├── ImageFile.cs ├── SitePageData.cs ├── StandardPage.cs └── StartPage.cs ├── Program.cs ├── Properties └── launchSettings.json ├── SiteViewEngineLocationExpander.cs ├── SkipAntiforgeryFilterProvider.cs ├── Startup.cs ├── TestSite.csproj ├── Views ├── Shared │ └── Blocks │ │ └── EditorialBlock.cshtml ├── StandardPage │ └── Index.cshtml └── StartPage │ └── Index.cshtml ├── appsettings.Development.json ├── appsettings.json ├── module.config └── wwwroot └── Dummy.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.{cs,svc,asax,aspx,cshtml,csproj,sln}] 11 | charset = utf-8-bom 12 | 13 | [*.{config,csproj,nuspec,xml}] 14 | indent_size = 2 15 | insert_final_newline = false 16 | 17 | [*.sln] 18 | indent_style = tab 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | # Ignore these files 24 | [{LICENSE,build/database/*,src/alloy/App_Data/**,src/alloy/modulesbin/**,src/alloy/obj/**,src/alloy/Static/**}] 25 | indent_style = unset 26 | indent_size = unset 27 | trim_trailing_whitespace = unset 28 | insert_final_newline = unset 29 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ## Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | 4 | ## Visual Studio and .NET text files 5 | *.ascx text 6 | *.aspx text 7 | *.cmd text 8 | *.config text 9 | *.cs text diff=csharp 10 | *.cshtml text 11 | *.csproj text eol=crlf 12 | *.master text 13 | *.msbuild text 14 | *.ps1 text 15 | *.psm1 text 16 | *.sln text eol=crlf 17 | *.sql text 18 | *.targets text 19 | *.transform text 20 | *.xml text 21 | 22 | ## Common web text files 23 | *.css text 24 | *.htm text 25 | *.html text 26 | *.js text 27 | *.less text 28 | *.md text 29 | 30 | ## Bash and Linux text files 31 | *.sh text eol=lf 32 | 33 | ## Visual Studio and .NET binary files 34 | *.dll binary 35 | *.snk binary 36 | 37 | ## Common web binary files 38 | *.gif binary 39 | *.ico binary 40 | *.jpeg binary 41 | *.jpg binary 42 | *.png binary 43 | -------------------------------------------------------------------------------- /.github/workflows/advanced-reviews-dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: Build .net core 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup .NET Core 18 | uses: actions/setup-dotnet@v4 19 | with: 20 | dotnet-version: 6.0.100 21 | - name: Set up Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: "20.9.0" 25 | - name: Install client dependencies 26 | run: setup.cmd 27 | shell: cmd 28 | - name: Build 29 | run: build.cmd Release 30 | shell: cmd 31 | - name: Test 32 | run: test.cmd Release 33 | shell: cmd 34 | - name: Pack 35 | run: pack.cmd Release 36 | shell: cmd 37 | - name: Archive production artifacts 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: nuget 41 | path: | 42 | artifacts/packages/*.nupkg 43 | -------------------------------------------------------------------------------- /.github/workflows/advanced-reviews-dotnet-framework.yml: -------------------------------------------------------------------------------- 1 | name: Build .net framework 2 | 3 | on: 4 | push: 5 | branches: [ net4_master ] 6 | pull_request: 7 | branches: [ net4_master ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup .NET Core 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 5.0.104 21 | - name: Install server dependencies 22 | run: dotnet restore advanced-reviews.sln --verbosity Detailed 23 | - name: Set up Node.js 24 | uses: actions/setup-node@v3 25 | with: 26 | node-version: "16" 27 | - name: Install client dependencies 28 | run: setup.cmd 29 | shell: cmd 30 | - name: Build 31 | run: dotnet build advanced-reviews.sln --configuration Release --no-restore 32 | - name: Pack 33 | run: pack.cmd 34 | shell: cmd 35 | - name: Archive production artifacts 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: nuget 39 | path: | 40 | *.nupkg 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Repository specific files 2 | 3 | App_Code/ 4 | App_Data/ 5 | 6 | License.config 7 | 8 | ## Ignore Visual Studio temporary files and build results. 9 | 10 | # User-specific files 11 | *.user 12 | 13 | # Build results 14 | [Bb]in/ 15 | [Oo]bj/ 16 | [Ll]og/ 17 | 18 | # Visual Studio 2015 cache/options directory 19 | .vs/ 20 | 21 | # NuGet Packages 22 | [Pp]ackages/ 23 | 24 | .idea/ 25 | .vscode 26 | 27 | node_modules/ 28 | 29 | *.nupkg 30 | advanced-cms-approval-reviews.zip 31 | advanced-cms-external-reviews.zip 32 | 33 | dist/ 34 | out/ 35 | 36 | -------------------------------------------------------------------------------- /Directory.build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/content-review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/content-review.png -------------------------------------------------------------------------------- /assets/default emails/mail_edit.txt: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | Please review the following content: 4 | [#link#] 5 | 6 | This is interactive version of the review. You can click on the page to add your comments. 7 | 8 | This message contains confidential information and is intended only for the individual named. If you are not the named addressee, you should not disseminate, distribute or copy this email. Please notify the sender immediately by email if you have received this email by mistake and delete this email from your system. 9 | 10 | Best regards 11 | Alloy team -------------------------------------------------------------------------------- /assets/default emails/mail_preview.txt: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | Please review the following content: 4 | [#link#] 5 | 6 | This message contains confidential information and is intended only for the individual named. If you are not the named addressee, you should not disseminate, distribute or copy this email. Please notify the sender immediately by email if you have received this email by mistake and delete this email from your system. 7 | 8 | Best regards 9 | Alloy team -------------------------------------------------------------------------------- /assets/documentation/approval_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/approval_1.gif -------------------------------------------------------------------------------- /assets/documentation/approval_2_decline.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/approval_2_decline.gif -------------------------------------------------------------------------------- /assets/documentation/approval_3_approve_prompt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/approval_3_approve_prompt.gif -------------------------------------------------------------------------------- /assets/documentation/configure_approval.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/configure_approval.gif -------------------------------------------------------------------------------- /assets/documentation/external_review_email_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/external_review_email_example.png -------------------------------------------------------------------------------- /assets/documentation/external_review_share_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/external_review_share_dialog.png -------------------------------------------------------------------------------- /assets/documentation/external_reviewer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/external_reviewer.gif -------------------------------------------------------------------------------- /assets/documentation/pin_answer_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/pin_answer_1.gif -------------------------------------------------------------------------------- /assets/documentation/pin_answer_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/pin_answer_2.gif -------------------------------------------------------------------------------- /assets/documentation/pin_answer_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/pin_answer_3.gif -------------------------------------------------------------------------------- /assets/documentation/pin_review_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/pin_review_1.gif -------------------------------------------------------------------------------- /assets/documentation/pin_review_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/pin_review_2.gif -------------------------------------------------------------------------------- /assets/documentation/pin_review_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/pin_review_3.gif -------------------------------------------------------------------------------- /assets/documentation/preview_unpublished_content.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/preview_unpublished_content.gif -------------------------------------------------------------------------------- /assets/documentation/project_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/project_overview.png -------------------------------------------------------------------------------- /assets/documentation/screenshot_manipulation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/screenshot_manipulation.gif -------------------------------------------------------------------------------- /assets/documentation/sliding_panel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/documentation/sliding_panel.gif -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/logo.png -------------------------------------------------------------------------------- /assets/samples/abbie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/samples/abbie.jpg -------------------------------------------------------------------------------- /assets/samples/administrator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/samples/administrator.jpg -------------------------------------------------------------------------------- /assets/samples/eddie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/samples/eddie.jpg -------------------------------------------------------------------------------- /assets/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/assets/thumbnail.png -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Set Release or Debug configuration. 4 | IF "%1"=="Release" (set CONFIGURATION=Release) ELSE (set CONFIGURATION=Debug) 5 | ECHO Building in %CONFIGURATION% 6 | 7 | REM Build the C# solution. 8 | CALL dotnet build -c %CONFIGURATION% 9 | IF %errorlevel% NEQ 0 EXIT /B %errorlevel% 10 | 11 | REM Build client side 12 | CD src\Advanced.CMS.ApprovalReviews\React 13 | CALL yarn build 14 | IF %errorlevel% NEQ 0 EXIT /B %errorlevel% 15 | CD ..\..\..\ 16 | -------------------------------------------------------------------------------- /build/CopyZipFiles.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /build/common.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Advanced.CMS.AdvancedReviews 5 | advanced-cms 6 | advanced-cms 7 | © 2019-2022 advanced-cms 8 | github.com/advanced-cms 9 | github.com/advanced-cms/LICENSE 10 | review preview content approval external 11 | true 12 | true 13 | true 14 | true 15 | true 16 | 17 | 18 | -------------------------------------------------------------------------------- /build/database/Alloy.mdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/build/database/Alloy.mdf -------------------------------------------------------------------------------- /build/database/DefaultSiteContent.episerverdata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/build/database/DefaultSiteContent.episerverdata -------------------------------------------------------------------------------- /build/database/commerce.Commerce.mdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/build/database/commerce.Commerce.mdf -------------------------------------------------------------------------------- /build/nuspec.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | $(ProjectDir)..\..\ 5 | $(VersionPrefix)$(VersionSuffix) 6 | Configuration=$(Configuration);PackageVersion=$(PackageVersion);CmsUIVersion=$(CmsUIVersion);CmsUINextMajorVersion=$(CmsUINextMajorVersion); 7 | $(SolutionDir)artifacts\packages\ 8 | NU5100;NU5125 9 | 10 | 11 | -------------------------------------------------------------------------------- /build/version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.3.10 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.100", 4 | "architecture": "x64", 5 | "rollForward": "minor" 6 | } 7 | } -------------------------------------------------------------------------------- /pack.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | 4 | SET CONFIGURATION=Debug 5 | 6 | IF "%2"=="Release" (SET CONFIGURATION=Release) 7 | 8 | @REM CALL build.cmd Release 9 | powershell ./build/pack.ps1 -version %1 -configuration %CONFIGURATION% 10 | 11 | EXIT /B %errorlevel% 12 | -------------------------------------------------------------------------------- /setup.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | SET AlloyMVC=src\Alloy.Sample 4 | 5 | IF EXIST %AlloyMVC%\App_Data ( 6 | ECHO Remove all files from the app data folder 7 | DEL %AlloyMVC%\App_Data\*.* /F /Q || Exit /B 1 8 | ) ELSE ( 9 | MKDIR %AlloyMVC%\App_Data || Exit /B 1 10 | ) 11 | 12 | REM Copy the database files to the site. 13 | XCOPY /y/i build\Database\DefaultSiteContent.episerverdata %AlloyMVC%\App_Data\ || Exit /B 1 14 | XCOPY /y/i/k build\database\Alloy.mdf %AlloyMVC%\App_Data\ || Exit /B 1 15 | XCOPY /y/i/k build\database\commerce.Commerce.mdf %AlloyMVC%\App_Data\ || Exit /B 1 16 | 17 | CD src\Advanced.CMS.ApprovalReviews\React 18 | CALL yarn install 19 | IF %errorlevel% NEQ 0 EXIT /B %errorlevel% 20 | CD ..\..\..\ 21 | 22 | EXIT /B %ERRORLEVEL% 23 | -------------------------------------------------------------------------------- /site.cmd: -------------------------------------------------------------------------------- 1 | call SET ASPNETCORE_ENVIRONMENT=Development 2 | call SET SolutionDir=%~dp0 3 | call dotnet run --project src\\Alloy.Sample\\Alloy.Sample.csproj -------------------------------------------------------------------------------- /src/Advanced.CMS.AdvancedReviews/Advanced.CMS.AdvancedReviews.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net6.0 6 | Library 7 | Advanced.CMS.AdvancedReviews.nuspec 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Advanced.CMS.AdvancedReviews/AdvancedReviewsEndpointRoutingExtension.cs: -------------------------------------------------------------------------------- 1 | using Advanced.CMS.ExternalReviews; 2 | using EPiServer.ServiceLocation; 3 | using EPiServer.Web.Routing; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Routing; 7 | using Microsoft.AspNetCore.Routing.Constraints; 8 | 9 | namespace Advanced.CMS.AdvancedReviews; 10 | 11 | internal class AdvancedReviewsEndpointRoutingExtension : IEndpointRoutingExtension 12 | { 13 | public void MapEndpoints(IEndpointRouteBuilder endpointRouteBuilder) 14 | { 15 | var options = endpointRouteBuilder.ServiceProvider.GetInstance(); 16 | 17 | endpointRouteBuilder.MapControllerRoute("ImageProxy", "/ImageProxy/{token}/{contentLink}", 18 | new { controller = "ImageProxy", action = "Index" }); 19 | 20 | endpointRouteBuilder.MapControllerRoute("ExternalReviewLogin", 21 | $"/{options.PinCodeSecurity.ExternalReviewLoginUrl}", 22 | new { controller = "ExternalReviewLogin", action = "Index" }); 23 | 24 | endpointRouteBuilder.MapControllerRoute("ExternalReviewLoginSubmit", 25 | $"/{options.PinCodeSecurity.ExternalReviewLoginUrl}", 26 | new { controller = "ExternalReviewLogin", action = "Submit" }, 27 | new { httpMethod = new HttpMethodRouteConstraint(HttpMethods.Post) }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Advanced.CMS.AdvancedReviews/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Advanced.CMS.AdvancedReviews/logo.png -------------------------------------------------------------------------------- /src/Advanced.CMS.AdvancedReviews/readme.txt: -------------------------------------------------------------------------------- 1 | Advanced.CMS.AdvancedReviews 2 | 3 | 4 | Installation 5 | ============ 6 | 7 | 8 | In order to start using AdvancedReviews you need to add it explicitly to your site. 9 | Please add the following statement to your Startup.cs 10 | 11 | 12 | public class Startup 13 | { 14 | ... 15 | public void ConfigureServices(IServiceCollection services) 16 | { 17 | ... 18 | services.AddAdvancedReviews(); 19 | ... 20 | } 21 | ... 22 | } 23 | 24 | AddAdvancedReviews extension method also accepts optional parameter of Action which 25 | lets you configure the add-on according to your needs. 26 | Full documentation can be found here: https://github.com/advanced-cms/advanced-reviews 27 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/Advanced.CMS.ApprovalReviews.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | Library 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/ApprovalOptions.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.ServiceLocation; 2 | 3 | namespace Advanced.CMS.ApprovalReviews; 4 | 5 | [Options] 6 | public class ApprovalOptions 7 | { 8 | public NotificationsOptions Notifications { get; private set; } = new(); 9 | } 10 | 11 | public class NotificationsOptions 12 | { 13 | public bool NotificationsEnabled { get; set; } = true; 14 | } 15 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/ApprovalReview.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.Data; 3 | using EPiServer.Data.Dynamic; 4 | 5 | namespace Advanced.CMS.ApprovalReviews; 6 | 7 | [EPiServerDataStore(AutomaticallyCreateStore = true, AutomaticallyRemapStore = true)] 8 | public class ApprovalReview : IDynamicData 9 | { 10 | public Identity Id { get; set; } 11 | 12 | [EPiServerDataIndex] 13 | public ContentReference ContentLink { get; set; } 14 | 15 | public string SerializedReview { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/AvatarsService/ICustomAvatarResolver.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced.CMS.ApprovalReviews.AvatarsService; 2 | 3 | /// 4 | /// Service responsible for returning unique avatar based on username 5 | /// 6 | public interface ICustomAvatarResolver 7 | { 8 | /// 9 | /// Returns user photo based on username 10 | /// Image should be a square. Recommended image size is 100x100 pixels 11 | /// 12 | /// Requested user name 13 | /// User avatar 14 | byte[] GetImage(string userName); 15 | } 16 | 17 | /// 18 | /// Empty implementation for 19 | /// Always returns "null" when resolving user 20 | /// 21 | internal class NullCustomAvatarResolver : ICustomAvatarResolver 22 | { 23 | public byte[] GetImage(string userName) 24 | { 25 | return null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/ClientResources/commandsProvider.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dijit/form/ToggleButton", 4 | "epi-cms/component/command/_GlobalToolbarCommandProvider", 5 | "advanced-cms-approval-reviews/ShowReviewCommand" 6 | ], function ( 7 | declare, 8 | ToggleButton, 9 | _GlobalToolbarCommandProvider, 10 | ShowReviewCommand) { 11 | return declare([_GlobalToolbarCommandProvider], { 12 | 13 | constructor: function () { 14 | this.inherited(arguments); 15 | 16 | var showReviewCommand = new ShowReviewCommand({ }); 17 | this.addToLeading(showReviewCommand, 18 | { 19 | showLabel: false, 20 | widget: ToggleButton, 21 | 'class': 'epi-mediumButton epi-review-button' 22 | }); 23 | } 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/ClientResources/content-review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Advanced.CMS.ApprovalReviews/ClientResources/content-review.png -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/ClientResources/notificationsInitializer.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/topic", 3 | "dojo/when", 4 | "epi/dependency", 5 | "epi/shell/StickyViewSelector", 6 | "epi/shell/TypeDescriptorManager" 7 | ], function ( 8 | topic, 9 | when, 10 | dependency, 11 | StickyViewSelector, 12 | TypeDescriptorManager 13 | ) { 14 | function initialize() { 15 | } 16 | 17 | return { 18 | initialize: initialize 19 | }; 20 | }); 21 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/ClientResources/styles.css: -------------------------------------------------------------------------------- 1 | .Sleek .dijitToolbar .dijitToggleButton.epi-review-button { 2 | margin-right: 8px 3 | } 4 | 5 | .epi-review-icon { 6 | background: url(content-review.png) 0 0 no-repeat; 7 | height: 24px; 8 | width: 24px; 9 | } 10 | 11 | 12 | .dijitChecked.epi-mediumButton .epi-review-icon { 13 | background: url(content-review.png) -24px 0 no-repeat; 14 | } 15 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/Notifications/ReviewContentNotificationModel.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | 3 | namespace Advanced.CMS.ApprovalReviews.Notifications 4 | { 5 | public class ReviewContentNotificationModel 6 | { 7 | public ContentReference ContentLink { get; set; } 8 | 9 | public string SenderDisplayName { get; set; } 10 | 11 | public string Title { get; set; } 12 | 13 | public string Text { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/.gitignore: -------------------------------------------------------------------------------- 1 | storybook_static/ 2 | dist/ 3 | stats.html 4 | 5 | .yarn/* 6 | !.yarn/patches 7 | !.yarn/plugins 8 | !.yarn/releases 9 | !.yarn/sdks 10 | !.yarn/versions -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: true 4 | 5 | nodeLinker: node-modules 6 | 7 | yarnPath: .yarn/releases/yarn-file.cjs 8 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/review-avatars/alfred.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Advanced.CMS.ApprovalReviews/React/review-avatars/alfred.jpg -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/review-avatars/john.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Advanced.CMS.ApprovalReviews/React/review-avatars/john.jpg -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/review-avatars/lina.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Advanced.CMS.ApprovalReviews/React/review-avatars/lina.jpg -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/setup.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, beforeAll, vi } from "vitest"; 2 | 3 | const originalError = console.error; 4 | beforeAll(() => { 5 | vi.spyOn(console, "error").mockImplementation((...args) => { 6 | if (typeof args[0] === "string" && args[0].includes("Warning")) { 7 | return; 8 | } 9 | return originalError.call(console, args); 10 | }); 11 | }); 12 | 13 | afterAll(() => { 14 | // @ts-ignore 15 | console.error.mockReset(); 16 | }); 17 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/_variables.scss: -------------------------------------------------------------------------------- 1 | $primary500: #1456f1; 2 | $secondary700: #a33727; 3 | $surface50: #ffffff; 4 | $surface500: #c2c2c2; 5 | $surface600: #979797; 6 | $surface700: #818181; 7 | $surface800: #606060; 8 | $success500: #4caf50; 9 | $reminder500: #ffeb3b; 10 | $error500: #f44336; 11 | $mdc-theme-primary: $primary500; 12 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/admin/admin-plugin.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .root { 7 | width: 50%; 8 | margin: auto; 9 | margin-top: 50px; 10 | } 11 | 12 | .reviews-list { 13 | display: table; 14 | } 15 | 16 | .reviews-list .list, 17 | .reviews-list .details { 18 | display: table-cell; 19 | vertical-align: top; 20 | } 21 | 22 | .reviews-list .list { 23 | width: 150px; 24 | min-width: 150px; 25 | } 26 | 27 | .reviews-list .main-link { 28 | font-weight: bold; 29 | width: 100%; 30 | display: inline-block; 31 | padding: 2px; 32 | border-bottom: 1px solid #c0c0c0; 33 | } 34 | 35 | .reviews-list .list > li { 36 | margin-bottom: 15px; 37 | } 38 | 39 | .reviews-list .delete { 40 | display: none; 41 | float: right; 42 | } 43 | 44 | .reviews-list .list .row:hover .delete { 45 | display: inline-block; 46 | } 47 | 48 | .reviews-list .details { 49 | padding-left: 10px; 50 | } 51 | 52 | .reviews-list .details > div { 53 | font-family: Courier; 54 | word-break: break-word; 55 | } 56 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/common/drop-down-menu.scss: -------------------------------------------------------------------------------- 1 | .menu-button { 2 | cursor: pointer; 3 | } 4 | 5 | .epi-context-menu { 6 | max-height: none !important; //TODO: Remove this hack until UI framework is upgraded 7 | } 8 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/confirmation/confirmation.tsx: -------------------------------------------------------------------------------- 1 | import "@material/react-dialog/index.scss"; 2 | 3 | import Dialog, { DialogButton, DialogContent, DialogFooter, DialogTitle } from "@material/react-dialog"; 4 | import React from "react"; 5 | 6 | interface ConfirmationDialogProps { 7 | title: string; 8 | description: string; 9 | okName: string; 10 | cancelName: string; 11 | open: boolean; 12 | onCloseDialog(action: boolean): void; 13 | } 14 | 15 | const ConfirmationDialog = ({ 16 | title, 17 | description, 18 | okName, 19 | cancelName, 20 | open, 21 | onCloseDialog, 22 | }: ConfirmationDialogProps) => { 23 | return ( 24 | onCloseDialog(action === "save")} 29 | > 30 | {title} 31 | {description} 32 | 33 | 34 | {cancelName} 35 | 36 | 37 | {okName} 38 | 39 | 40 | 41 | ); 42 | }; 43 | 44 | export default ConfirmationDialog; 45 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/details/review-details.scss: -------------------------------------------------------------------------------- 1 | .review-details { 2 | position: relative; 3 | 4 | .comments-list { 5 | margin: 10px 0; 6 | min-height: 100px; 7 | max-height: 500px; 8 | 9 | .comment { 10 | padding: 10px 0; 11 | 12 | .message { 13 | .date { 14 | margin-top: 2px; 15 | } 16 | } 17 | 18 | .screenshot { 19 | margin-top: -3px; 20 | } 21 | } 22 | } 23 | 24 | .first-comment { 25 | margin-top: 30px; 26 | margin-bottom: 20px; 27 | border-bottom: 1px solid #ddd; 28 | 29 | .message { 30 | .date { 31 | margin-top: 2px; 32 | } 33 | } 34 | 35 | .screenshot { 36 | margin-top: -3px; 37 | } 38 | } 39 | 40 | .actions { 41 | float: right; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/editable-external-reviews/confirm-name-dialog.stories.tsx: -------------------------------------------------------------------------------- 1 | import { action } from "@storybook/addon-actions"; 2 | import { storiesOf } from "@storybook/react"; 3 | import React from "react"; 4 | 5 | import ConfirmDialog from "./confirm-name-dialog"; 6 | 7 | storiesOf("External editable reviews", module).add("Confirm user name dialog", () => { 8 | return ; 9 | }); 10 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/external-reviews-manage-links/external-review-share-dialog.scss: -------------------------------------------------------------------------------- 1 | @import "@material/react-dialog/index"; 2 | @import "@material/react-material-icon/index"; 3 | @import "@material/react-floating-label/index"; 4 | 5 | .share-dialog-content { 6 | width: 550px; 7 | 8 | .text-field-container { 9 | margin-bottom: 10px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/external-reviews-manage-links/external-review-share-dialog.stories.tsx: -------------------------------------------------------------------------------- 1 | import { action } from "@storybook/addon-actions"; 2 | import { storiesOf } from "@storybook/react"; 3 | import React from "react"; 4 | 5 | import res from "../../.storybook/externalResources.json"; 6 | import ShareDialog from "./external-review-share-dialog"; 7 | 8 | storiesOf("External reviews", module).add("Share dialog", () => { 9 | return ( 10 | 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/location-comment/location-comment.scss: -------------------------------------------------------------------------------- 1 | .panel-container, 2 | .review-dialog { 3 | .location-comment-field { 4 | width: 100%; 5 | margin-top: 10px; 6 | 7 | textarea { 8 | resize: none; 9 | min-height: auto; 10 | height: 130px; 11 | } 12 | } 13 | 14 | .attach-screenshot { 15 | position: absolute; 16 | right: 0; 17 | } 18 | } 19 | 20 | .panel-container .attach-screenshot { 21 | bottom: 90px; 22 | } 23 | 24 | .review-dialog .attach-screenshot { 25 | top: 10px; 26 | } 27 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/new-review-dialog/new-review-dialog.scss: -------------------------------------------------------------------------------- 1 | @import "@material/react-dialog/index"; 2 | 3 | .mdc-dialog { 4 | &.review-dialog { 5 | z-index: 750; 6 | 7 | .mdc-dialog__title { 8 | margin-top: 0; 9 | margin-bottom: 0; 10 | font-size: 1.25rem; 11 | line-height: 1rem; 12 | padding: 0 12px; 13 | 14 | &:before { 15 | height: inherit; 16 | } 17 | } 18 | 19 | .mdc-dialog__content { 20 | padding: 0 12px; 21 | position: relative; 22 | 23 | .attach-screenshot { 24 | right: 10px; 25 | bottom: 105px; 26 | } 27 | } 28 | 29 | .header { 30 | display: flex; 31 | justify-content: space-between; 32 | margin-bottom: 10px; 33 | 34 | .review-actions { 35 | margin-top: -3px; 36 | } 37 | } 38 | 39 | .dialog-grid { 40 | width: 300px; 41 | height: 150px; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/pin-collection/pin-collection.scss: -------------------------------------------------------------------------------- 1 | @import "../_variables"; 2 | @import url("https://fonts.googleapis.com/icon?family=Material+Icons"); 3 | 4 | .review-overlay { 5 | position: absolute; 6 | top: 0; 7 | z-index: 1000; 8 | background-color: $reminder500; 9 | opacity: 0.5; 10 | width: 100%; 11 | height: 100%; 12 | overflow-y: auto; 13 | } 14 | 15 | .mdc-menu-surface { 16 | //TODO: menu-surface overlapped when in dialog 17 | z-index: 1500 !important; 18 | 19 | .mdc-list { 20 | .mdc-list-item { 21 | cursor: pointer; 22 | 23 | .mdc-list-item__graphic { 24 | margin-right: 10px; 25 | } 26 | } 27 | } 28 | } 29 | 30 | .user-picker { 31 | position: absolute; 32 | left: 0; 33 | bottom: 0; 34 | } 35 | 36 | //TODO: menu-surface float? 37 | .mdc-menu-surface--anchor { 38 | display: inline-block; 39 | } 40 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/pin-navigator/pin-navigator.scss: -------------------------------------------------------------------------------- 1 | @import "@material/react-material-icon/index"; 2 | 3 | .pin-navigator { 4 | .next-prev-icon { 5 | padding: 0; 6 | height: 32px; 7 | width: 32px; 8 | 9 | &:before, 10 | &:after { 11 | background-color: transparent; 12 | } 13 | } 14 | 15 | .pager { 16 | line-height: 50px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/pin/pin.scss: -------------------------------------------------------------------------------- 1 | @import "../_variables"; 2 | 3 | .review-location { 4 | position: absolute; 5 | box-sizing: border-box; 6 | -moz-box-sizing: border-box; 7 | font-size: 11px; 8 | cursor: pointer; 9 | user-select: none; 10 | 11 | &.highlighted, 12 | &:hover { 13 | -webkit-filter: drop-shadow(0px 0px 7px $surface800); 14 | filter: drop-shadow(0px 0px 7px $surface800); 15 | } 16 | 17 | circle { 18 | fill: $error500; 19 | stroke: $surface800; 20 | } 21 | 22 | &.done { 23 | circle { 24 | fill: $surface700; 25 | } 26 | } 27 | 28 | &.new { 29 | circle { 30 | fill: $success500; 31 | } 32 | } 33 | 34 | .priority-icon { 35 | width: 20px; 36 | height: 20px; 37 | font-size: 20px; 38 | color: $surface500; 39 | position: absolute; 40 | top: 4px; 41 | left: 4px; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/position-calculator/offset.ts: -------------------------------------------------------------------------------- 1 | export default function (node: HTMLElement, external: boolean) { 2 | const rect = node.getBoundingClientRect(); 3 | 4 | // In external edit scenario we need to add scrolling positions as the bounding rect is viewport-specific 5 | // However, in edit mode the iframe is inside .epi-editorViewport and the bounding rect does include the scroll position so there 6 | // is no need to add anything 7 | let domOffset: { x: number; y: number } = { x: 0, y: 0 }; 8 | if (external) { 9 | domOffset = { x: node.ownerDocument.defaultView.pageXOffset, y: node.ownerDocument.defaultView.pageYOffset }; 10 | } 11 | 12 | return { 13 | top: rect.top + domOffset.y, 14 | left: rect.left + domOffset.x, 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/reviews-sliding-panel/reviews-sliding-panel.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from "@storybook/react"; 2 | import { Provider } from "mobx-react"; 3 | import React from "react"; 4 | 5 | import FakeAdvancedReviewService from "../../.storybook/fake-advanced-review-service"; 6 | import resources from "../../.storybook/resources.json"; 7 | import { createStores } from "../store/review-store"; 8 | import SlidingPanel from "./reviews-sliding-panel"; 9 | 10 | const stores = createStores(new FakeAdvancedReviewService(), resources); 11 | 12 | storiesOf("Sliding panel", module).add("default", () => { 13 | stores.reviewStore.load(); 14 | return ( 15 | 16 | 17 | 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/screenshot-dialog/screenshot-dialog.scss: -------------------------------------------------------------------------------- 1 | @import "react-image-crop/lib/ReactCrop"; 2 | 3 | .mdc-dialog { 4 | //TODO: increase the size of this dialog? 5 | 6 | &.screenshot-picker-dialog { 7 | z-index: 800; 8 | 9 | .screenshot-picker { 10 | .screenshot-cropper { 11 | width: 560px; 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/src/store/priority-icon-mappings.ts: -------------------------------------------------------------------------------- 1 | import { Priority } from "./review-store"; 2 | 3 | const icons = {}; 4 | icons[Priority.Important] = "priority_high"; 5 | icons[Priority.Normal] = "assignment"; 6 | icons[Priority.Trivial] = "transit_enterexit"; 7 | 8 | export default icons; 9 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "resolveJsonModule": true, 5 | "esModuleInterop": true, 6 | "jsx": "react", 7 | "target": "es5", 8 | "sourceMap": true, 9 | "lib": ["dom", "es5", "es2017", "scripthost", "es2015.promise"] 10 | }, 11 | "exclude": ["**/*.test.ts"], 12 | "references": [ 13 | { 14 | "path": "./tsconfig.node.json" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/vite-admin.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import viteConfig from "./vite.config"; 3 | 4 | viteConfig.build.rollupOptions.input = "./src/admin/admin-plugin.tsx"; 5 | export default defineConfig(viteConfig as any); 6 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/vite-external-review-manage-links-widget.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import viteConfig from "./vite.amd.config"; 3 | viteConfig.build.rollupOptions.input = "./src/external-reviews-manage-links/external-review-manage-links-widget.tsx"; 4 | export default defineConfig(viteConfig as any); 5 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/vite-external.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | const path = require("path"); 3 | import viteConfig from "./vite.config"; 4 | 5 | viteConfig.build.rollupOptions.input = "./src/editable-external-reviews/editable-external-review-component.tsx"; 6 | viteConfig.build.rollupOptions.output.dir = path.resolve(__dirname, "../../Advanced.CMS.ExternalReviews/ClientResources/dist"); 7 | export default defineConfig(viteConfig as any); 8 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/vite-widget.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import viteConfig from "./vite.amd.config"; 3 | viteConfig.build.rollupOptions.input = "./src/review-component-widget/review-component-widget.tsx"; 4 | export default defineConfig(viteConfig as any); 5 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/React/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react"; 2 | import eslint from "vite-plugin-eslint"; 3 | import { visualizer } from "rollup-plugin-visualizer"; 4 | 5 | const isWatch = process.argv.includes("--watch"); 6 | const isProductionBuild = process.argv.includes("build") && !isWatch; 7 | 8 | const plugins = [react(), visualizer() as any]; 9 | 10 | const turnOffLinkBuildErrors = isWatch; 11 | plugins.push( 12 | eslint({ 13 | fix: !isProductionBuild, 14 | emitError: !turnOffLinkBuildErrors, 15 | failOnError: !turnOffLinkBuildErrors, 16 | }), 17 | ); 18 | 19 | export default { 20 | plugins: plugins, 21 | build: { 22 | rollupOptions: { 23 | input: "", 24 | output: { 25 | entryFileNames: "[name].js", 26 | assetFileNames: `[name].[ext]`, 27 | dir: "../ClientResources/dist", 28 | }, 29 | }, 30 | sourcemap: true, 31 | }, 32 | test: { 33 | globals: true, 34 | environment: "jsdom", 35 | setupFiles: "./setup.ts", 36 | css: false, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/ReviewUrlGenerator.cs: -------------------------------------------------------------------------------- 1 | using Advanced.CMS.ApprovalReviews.AvatarsService; 2 | using EPiServer.Shell; 3 | 4 | namespace Advanced.CMS.ApprovalReviews; 5 | 6 | public class ReviewUrlGenerator 7 | { 8 | public string AvatarUrl => Paths.ToResource("advanced-cms-approval-reviews", $"ReviewAvatars/{nameof(ReviewAvatarsController.Index)}"); 9 | //TODO: minor, why this does not work??? var controllerPath = _httpContextAccessor.HttpContext.GetControllerPath(typeof(ReviewLocationPreviewPluginController), "Index"); 10 | public string ReviewLocationPluginUrl => Paths.ToResource("advanced-cms-approval-reviews" ,"ReviewLocationPreviewPlugin"); 11 | } 12 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Advanced.CMS.ApprovalReviews.AvatarsService; 2 | using Advanced.CMS.ApprovalReviews.Notifications; 3 | using EPiServer.Notification; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Advanced.CMS.ApprovalReviews; 7 | 8 | public static class ServiceCollectionExtensions 9 | { 10 | public static IServiceCollection AddApprovalReviews(this IServiceCollection services) 11 | { 12 | services.AddTransient(); 13 | services.AddTransient(); 14 | services.AddTransient(); 15 | services.AddTransient(); 16 | services.AddTransient(); 17 | services.AddTransient(); 18 | services.AddSingleton(); 19 | services.AddSingleton(); 20 | 21 | return services; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/SiteUriResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EPiServer.Core; 3 | using EPiServer.Web; 4 | 5 | namespace Advanced.CMS.ApprovalReviews; 6 | 7 | public interface ISiteUriResolver 8 | { 9 | Uri GetUri(ContentReference contentReference); 10 | } 11 | 12 | internal class SiteUriResolver : ISiteUriResolver 13 | { 14 | public Uri GetUri(ContentReference contentReference) 15 | { 16 | return SiteDefinition.Current.SiteUrl; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/StartPageResolver.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.ServiceLocation; 3 | using EPiServer.Web; 4 | using EPiServer.Web.Routing; 5 | 6 | namespace Advanced.CMS.ApprovalReviews; 7 | 8 | public interface IStartPageUrlResolver 9 | { 10 | string GetUrl(ContentReference contentReference, string languageBranch); 11 | } 12 | 13 | internal class StartPageUrlResolver: IStartPageUrlResolver 14 | { 15 | private readonly IUrlResolver _urlResolver; 16 | private readonly ISiteDefinitionResolver _siteDefinitionResolver; 17 | 18 | public StartPageUrlResolver(IUrlResolver urlResolver, ISiteDefinitionResolver siteDefinitionResolver) 19 | { 20 | _urlResolver = urlResolver; 21 | _siteDefinitionResolver = siteDefinitionResolver; 22 | } 23 | 24 | public string GetUrl(ContentReference contentReference, string languageBranch) 25 | { 26 | var site = _siteDefinitionResolver.GetByContent(contentReference, true); 27 | return _urlResolver.GetUrl(site?.StartPage ?? ContentReference.StartPage, languageBranch); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/advanced-cms-approval-reviews.Views/Views/ReviewLocationPreviewPlugin/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model Advanced.CMS.ApprovalReviews.ViewModel 2 | @using EPiServer.Shell 3 | @using EPiServer.Shell.Navigation 4 | @using EPiServer.Shell.Navigation.Internal 5 | 6 | @{ 7 | Layout = string.Empty; 8 | } 9 | 10 | 11 | 12 | 13 | Advanced approval review 14 | 15 | 16 | @Html.Raw(Html.CreatePlatformNavigationMenu()) 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ApprovalReviews/module.config: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/Advanced.CMS.ExternalReviews.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | Library 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | <_ContentIncludedByDefault Remove="advanced-cms-external-reviews.Views\Views\ExternalReviewLogin\Index.cshtml" /> 22 | 23 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/AdvancedReviewsModuleViewModel.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework.Web.Resources; 2 | using EPiServer.Shell.Modules; 3 | 4 | namespace Advanced.CMS.ExternalReviews; 5 | 6 | internal class AdvancedReviewsModuleViewModel : ModuleViewModel 7 | { 8 | public AdvancedReviewsModuleViewModel(ShellModule shellModule, IClientResourceService clientResourceService, ExternalReviewOptions options) : 9 | base(shellModule, clientResourceService) 10 | { 11 | Options = options; 12 | } 13 | 14 | public string Language { get; set; } 15 | public string AvatarUrl { get; set; } 16 | public ExternalReviewOptions Options { get; } 17 | } 18 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/ApprovalReviewsShellModule.cs: -------------------------------------------------------------------------------- 1 | using Advanced.CMS.ApprovalReviews; 2 | using EPiServer.Framework.Web.Resources; 3 | using EPiServer.Security; 4 | using EPiServer.ServiceLocation; 5 | using EPiServer.Shell.Modules; 6 | using EPiServer.Shell.Profile.Internal; 7 | 8 | namespace Advanced.CMS.ExternalReviews; 9 | 10 | public class ApprovalReviewsShellModule : ShellModule 11 | { 12 | public ApprovalReviewsShellModule(string name, string routeBasePath, string resourceBasePath) 13 | : base(name, routeBasePath, resourceBasePath) 14 | { 15 | } 16 | 17 | /// 18 | public override ModuleViewModel CreateViewModel(ModuleTable moduleTable, IClientResourceService clientResourceService) 19 | { 20 | var options = ServiceLocator.Current.GetInstance(); 21 | var reviewUrlGenerator = ServiceLocator.Current.GetInstance(); 22 | var principal = ServiceLocator.Current.GetInstance(); 23 | var currentUiCulture = ServiceLocator.Current.GetInstance(); 24 | var model = new AdvancedReviewsModuleViewModel(this, clientResourceService, options) 25 | { 26 | Language = currentUiCulture.Get(principal.Principal.Identity.Name).Name, 27 | AvatarUrl = reviewUrlGenerator.AvatarUrl 28 | }; 29 | return model; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/BlocksPreview/BlockPreviewInitialization.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework; 2 | using EPiServer.Framework.Initialization; 3 | 4 | namespace Advanced.CMS.ExternalReviews.BlocksPreview; 5 | 6 | /// 7 | /// Module for support external blocks rendering. 8 | /// 9 | [ModuleDependency(typeof(EPiServer.Web.InitializationModule))] 10 | internal class BlockPreviewInitialization : IInitializableModule 11 | { 12 | public void Initialize(InitializationEngine context) 13 | { 14 | // context.Locate.TemplateResolver() 15 | // .TemplateResolved += BlocksTemplateCoordinator.OnTemplateResolved; 16 | } 17 | 18 | public void Uninitialize(InitializationEngine context) 19 | { 20 | // ServiceLocator.Current.GetInstance() 21 | // .TemplateResolved -= BlocksTemplateCoordinator.OnTemplateResolved; 22 | } 23 | 24 | public void Preload(string[] parameters) 25 | { 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/BlocksPreview/BlockPreviewViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using EPiServer.Core; 3 | using EPiServer.Framework.Localization; 4 | using EPiServer.ServiceLocation; 5 | 6 | namespace Advanced.CMS.ExternalReviews.BlocksPreview; 7 | 8 | public class BlockPreviewViewModel 9 | { 10 | public IContent PreviewContent { get; set; } 11 | 12 | public BlockPreviewViewModel() 13 | { 14 | var localizationService = ServiceLocator.Current.GetInstance(); 15 | Areas = new List(); 16 | NotFound = string.Format(localizationService.GetString("/preview/norendereratall"), PreviewContent.Name); 17 | } 18 | 19 | public List Areas { get; } 20 | 21 | public string NotFound { get; set; } 22 | 23 | public class PreviewArea 24 | { 25 | public bool Supported { get; set; } 26 | public string AreaName { get; set; } 27 | public string AreaTag { get; set; } 28 | public ContentArea ContentArea { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/ClientResources/initializer.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "epi/routes", 4 | "epi/shell/store/JsonRest", 5 | "epi/shell/store/Throttle", 6 | "epi/_Module" 7 | ], function ( 8 | declare, 9 | routes, 10 | JsonRest, 11 | Throttle, 12 | _Module 13 | ) { 14 | return declare([_Module], { 15 | initialize: function () { 16 | this.inherited(arguments); 17 | 18 | var options = this._settings.options || {}; 19 | if (!options.isEnabled) { 20 | return; 21 | } 22 | 23 | var registry = this.resolveDependency("epi.storeregistry"); 24 | 25 | //Register store 26 | registry.add("externalreviews", 27 | new Throttle( 28 | new JsonRest({ 29 | preventCache: true, 30 | target: this._getRestPath("externalreviewstore")//, 31 | //idProperty: "contentLink" 32 | }) 33 | ) 34 | ); 35 | }, 36 | 37 | _getRestPath: function (name) { 38 | return routes.getRestPath({ moduleArea: "advanced-cms-external-reviews", storeName: name }); 39 | } 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/DraftContentAreaPreview/ContentAccessEvaluatorDecorator.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Principal; 2 | using EPiServer.Core; 3 | using EPiServer.Security; 4 | 5 | namespace Advanced.CMS.ExternalReviews.DraftContentAreaPreview; 6 | 7 | internal class ContentAccessEvaluatorDecorator : IContentAccessEvaluator 8 | { 9 | private readonly IContentAccessEvaluator _defaultContentAccessEvaluator; 10 | private readonly ExternalReviewState _externalReviewState; 11 | 12 | public ContentAccessEvaluatorDecorator(IContentAccessEvaluator defaultContentAccessEvaluator, ExternalReviewState externalReviewState) 13 | { 14 | _defaultContentAccessEvaluator = defaultContentAccessEvaluator; 15 | _externalReviewState = externalReviewState; 16 | } 17 | 18 | public bool HasAccess(IContent content, IPrincipal principal, AccessLevel access) 19 | { 20 | if (_externalReviewState.IsInExternalReviewContext) 21 | { 22 | return true; 23 | } 24 | 25 | return _defaultContentAccessEvaluator.HasAccess(content, principal, access); 26 | } 27 | 28 | public AccessLevel GetAccessLevel(IContent content, IPrincipal principal) 29 | { 30 | if (_externalReviewState.IsInExternalReviewContext) 31 | { 32 | return AccessLevel.Read; 33 | } 34 | 35 | return _defaultContentAccessEvaluator.GetAccessLevel(content, principal); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/DraftContentAreaPreview/PublishedStateAssessorDecorator.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | 3 | namespace Advanced.CMS.ExternalReviews.DraftContentAreaPreview; 4 | 5 | internal class PublishedStateAssessorDecorator : IPublishedStateAssessor 6 | { 7 | private readonly IPublishedStateAssessor _defaultService; 8 | private readonly ExternalReviewState _externalReviewState; 9 | 10 | public PublishedStateAssessorDecorator(IPublishedStateAssessor defaultService, ExternalReviewState externalReviewState) 11 | { 12 | _defaultService = defaultService; 13 | _externalReviewState = externalReviewState; 14 | } 15 | 16 | public bool IsPublished(IContent content, PublishedStateCondition condition) 17 | { 18 | if (_externalReviewState.IsInExternalReviewContext) 19 | { 20 | if (content is PageData) 21 | { 22 | return true; 23 | } 24 | 25 | if (_externalReviewState.CustomLoaded.Contains(content.ContentLink.ToString())) 26 | { 27 | var cachedContent = _externalReviewState.GetCachedContent(content.ContentLink); 28 | if (cachedContent != null) 29 | { 30 | return true; 31 | } 32 | } 33 | } 34 | 35 | return _defaultService.IsPublished(content, condition); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/ExternalReviewUrlGenerator.cs: -------------------------------------------------------------------------------- 1 | using Advanced.CMS.ExternalReviews.EditReview; 2 | using EPiServer.Core; 3 | using EPiServer.Shell; 4 | 5 | namespace Advanced.CMS.ExternalReviews; 6 | 7 | internal class ExternalReviewUrlGenerator 8 | { 9 | private readonly ExternalReviewState _externalReviewState; 10 | public string ReviewsUrl => Paths.ToResource("advanced-cms-external-reviews", $"PageEdit/{nameof(PageEditController.Index)}"); 11 | public string AddPinUrl => Paths.ToResource("advanced-cms-external-reviews", $"PageEdit/{nameof(PageEditController.AddPin)}"); 12 | public string RemovePinUrl => Paths.ToResource("advanced-cms-external-reviews", $"PageEdit/{nameof(PageEditController.RemovePin)}"); 13 | 14 | public ExternalReviewUrlGenerator(ExternalReviewState externalReviewState) 15 | { 16 | _externalReviewState = externalReviewState; 17 | } 18 | 19 | public string GetProxiedImageUrl(ContentReference contentLink) 20 | { 21 | return $"/ImageProxy/{_externalReviewState.Token}/{contentLink}"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/ExternalReviewsShellModule.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework.Web.Resources; 2 | using EPiServer.ServiceLocation; 3 | using EPiServer.Shell.Modules; 4 | 5 | namespace Advanced.CMS.ExternalReviews; 6 | 7 | public class ExternalReviewsShellModule : ShellModule 8 | { 9 | public ExternalReviewsShellModule(string name, string routeBasePath, string resourceBasePath) 10 | : base(name, routeBasePath, resourceBasePath) 11 | { 12 | } 13 | 14 | /// 15 | public override ModuleViewModel CreateViewModel(ModuleTable moduleTable, IClientResourceService clientResourceService) 16 | { 17 | var options = ServiceLocator.Current.GetInstance(); 18 | return new AdvancedReviewsModuleViewModel(this, clientResourceService, options); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/InternalApiControllerFeatureProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.Controllers; 5 | 6 | namespace Advanced.CMS.ExternalReviews; 7 | 8 | internal sealed class InternalApiControllerFeatureProvider : ControllerFeatureProvider 9 | { 10 | protected override bool IsController(TypeInfo typeInfo) 11 | { 12 | if (!typeInfo.IsClass || typeInfo.IsAbstract || typeInfo.IsNested || typeInfo.ContainsGenericParameters) 13 | { 14 | return false; 15 | } 16 | 17 | // Only controllers in EPiServer. dlls 18 | if (typeInfo.Assembly.FullName == null || !typeInfo.Assembly.FullName.StartsWith("Advanced.CMS.", StringComparison.OrdinalIgnoreCase)) 19 | { 20 | return false; 21 | } 22 | 23 | return typeInfo.IsAssignableTo(typeof(Controller)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/PinCodeSecurity/PinCodeHashGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | 4 | namespace Advanced.CMS.ExternalReviews.PinCodeSecurity 5 | { 6 | internal static class PinCodeHashGenerator 7 | { 8 | public static string Hash(string pinCode, string token) 9 | { 10 | var value = token.Substring(0, 4) + pinCode + token.Substring(5, 4); 11 | 12 | var builder = new StringBuilder(); 13 | 14 | using (var hash = SHA256.Create()) 15 | { 16 | var result = hash.ComputeHash(Encoding.UTF8.GetBytes(value)); 17 | foreach (var b in result) 18 | { 19 | builder.Append(b.ToString("x2")); 20 | } 21 | } 22 | 23 | return builder.ToString(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/PreviewPageTemplateDescriptor.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced.CMS.ExternalReviews; 2 | 3 | /*[TemplateDescriptor(Inherited = true, 4 | AvailableWithoutTag=false, 5 | TagString="ExternalPagePreview", 6 | Default=false, 7 | TemplateTypeCategory = TemplateTypeCategories.MvcController)] 8 | internal class PreviewPageTemplateDescriptor : PageController 9 | { 10 | public ViewResult Index(PageData currentPage) 11 | { 12 | return View("~/Views/PagePreview.cshtml", currentPage); 13 | } 14 | }*/ 15 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/RemoveExpiredTokensJob.cs: -------------------------------------------------------------------------------- 1 | using Advanced.CMS.ExternalReviews.ReviewLinksRepository; 2 | using EPiServer.PlugIn; 3 | using EPiServer.Scheduler; 4 | using EPiServer.ServiceLocation; 5 | 6 | namespace Advanced.CMS.ExternalReviews; 7 | 8 | [ScheduledPlugIn(DisplayName = "Remove Expired Tokens Job", GUID = "ee619008-3e76-4886-b3c7-aa025a0c2603")] 9 | internal class RemoveExpiredTokensJob : ScheduledJobBase 10 | { 11 | public override string Execute() 12 | { 13 | OnStatusChanged($"Starting execution of {GetType()}"); 14 | 15 | var repository = ServiceLocator.Current.GetInstance(); 16 | var removedLinksCount = repository.RemoveExpiredLinks(); 17 | 18 | return $"Removed {removedLinksCount} links"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/Resources/edit.txt: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | Please review the folowing content: 4 | [#link#] 5 | 6 | This is interactive version of the review. You can click on the page to add your comments. 7 | 8 | This message contains confidential information and is intended only for the individual named. If you are not the named addressee, you should not disseminate, distribute or copy this email. Please notify the sender immediately by email if you have received this email by mistake and delete this email from your system. -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/Resources/mail_edit.txt: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | Please review the following content: 4 | [#link#] 5 | 6 | This is interactive version of the review. You can click on the page to add your comments. 7 | 8 | This message contains confidential information and is intended only for the individual named. If you are not the named addressee, you should not disseminate, distribute or copy this email. Please notify the sender immediately by email if you have received this email by mistake and delete this email from your system. 9 | 10 | Best regards 11 | Alloy team -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/Resources/mail_preview.txt: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | Please review the following content: 4 | [#link#] 5 | 6 | This message contains confidential information and is intended only for the individual named. If you are not the named addressee, you should not disseminate, distribute or copy this email. Please notify the sender immediately by email if you have received this email by mistake and delete this email from your system. 7 | 8 | Best regards 9 | Alloy team -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/ReviewLinksRepository/ExternalReviewLink.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EPiServer.Core; 3 | 4 | namespace Advanced.CMS.ExternalReviews.ReviewLinksRepository; 5 | 6 | public class ExternalReviewLink 7 | { 8 | public ContentReference ContentLink { get; set; } 9 | public string DisplayName { get; set; } 10 | public bool IsEditable { get; set; } 11 | public int? ProjectId { get; set; } 12 | public string Token { get; set; } 13 | public DateTime ValidTo { get; set; } 14 | public string LinkUrl { get; set; } 15 | public string PinCode { get; set; } 16 | public string ProjectName { get; set; } 17 | public string[] VisitorGroups { get; set; } 18 | } 19 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/ReviewLinksRepository/ExternalReviewLinkDds.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EPiServer.Core; 3 | using EPiServer.Data; 4 | using EPiServer.Data.Dynamic; 5 | 6 | namespace Advanced.CMS.ExternalReviews.ReviewLinksRepository; 7 | 8 | [EPiServerDataStore(AutomaticallyCreateStore = true, AutomaticallyRemapStore = true)] 9 | internal class ExternalReviewLinkDds : IDynamicData 10 | { 11 | public Identity Id { get; set; } 12 | 13 | [EPiServerDataIndex] 14 | public ContentReference ContentLink { get; set; } 15 | 16 | public string DisplayName { get; set; } 17 | 18 | public bool IsEditable { get; set; } 19 | 20 | public int? ProjectId { get; set; } 21 | 22 | [EPiServerDataIndex] 23 | public string Token { get; set; } 24 | 25 | public DateTime ValidTo { get; set; } 26 | 27 | public string PinCode { get; set; } 28 | 29 | public string[] VisitorGroups { get; set; } 30 | } 31 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/ReviewLinksRepository/ExternalReviewLinkExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Advanced.CMS.ExternalReviews.ReviewLinksRepository 4 | { 5 | public static class ExternalReviewLinkExtension 6 | { 7 | public static bool IsExpired(this ExternalReviewLink externalReviewLink) 8 | { 9 | return externalReviewLink.ValidTo < DateTime.Now; 10 | } 11 | 12 | public static bool IsEditableLink(this ExternalReviewLink externalReviewLink) 13 | { 14 | return externalReviewLink != null && 15 | !externalReviewLink.IsExpired() && 16 | externalReviewLink.IsEditable; 17 | } 18 | 19 | public static bool IsPreviewableLink(this ExternalReviewLink externalReviewLink) 20 | { 21 | return externalReviewLink != null && 22 | !externalReviewLink.IsExpired() && 23 | !externalReviewLink.IsEditable; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/ReviewLinksRepository/IExternalReviewLinksRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using EPiServer.Core; 4 | 5 | namespace Advanced.CMS.ExternalReviews.ReviewLinksRepository; 6 | 7 | public interface IExternalReviewLinksRepository 8 | { 9 | IEnumerable GetLinksForContent(ContentReference contentLink, int? projectId); 10 | ExternalReviewLink GetContentByToken(string token); 11 | int RemoveExpiredLinks(); 12 | bool HasPinCode(string token); 13 | ExternalReviewLink AddLink(ContentReference contentLink, bool isEditable, TimeSpan validTo, int? projectId); 14 | 15 | /// 16 | /// Update link 17 | /// 18 | /// 19 | /// 20 | /// New PIN code. If null then PIN is not updated 21 | /// Link display name, when empty then fallback to token 22 | /// Impersonate with the following visitor groups ids 23 | ExternalReviewLink UpdateLink(string token, DateTime? validTo, string pinCode, string displayName, string[] visitorGroups); 24 | 25 | void DeleteLink(string token); 26 | } 27 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/UrlPath.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using EPiServer; 3 | 4 | namespace Advanced.CMS.ExternalReviews 5 | { 6 | public static class UrlPath 7 | { 8 | public static string Combine(string url, params string[] pathFragments) 9 | { 10 | if (pathFragments.Length == 0) 11 | { 12 | return url; 13 | } 14 | 15 | 16 | var urlBuilder = new UrlBuilder(url); 17 | urlBuilder.Path = 18 | string.Join( 19 | "/", 20 | new[] { urlBuilder.Path.TrimEnd('/', '\\') }. 21 | Concat(pathFragments. 22 | Where(x => x.Length > 0). 23 | Select(x => x.TrimStart('/', '\\')))); 24 | 25 | return urlBuilder.ToString(); 26 | } 27 | 28 | public static string EnsureStartsWithSlash(string url) 29 | { 30 | url = url.Trim('/', '\\'); 31 | return $"/{url}"; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/advanced-cms-external-reviews.Views/Views/BlockPreview/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model BlockPreviewViewModel 2 | @using Advanced.CMS.ExternalReviews.BlocksPreview 3 | @using EPiServer.Shell.Web.Mvc.Html 4 | 5 | 6 | 7 | 8 | Content review 9 | 10 | 11 | 26 | 27 | 28 |

Preview for: @Model.PreviewContent.Name

29 | 30 | @foreach (var area in Model.Areas) 31 | { 32 | if (area.Supported) 33 | { 34 |
@string.Format(Html.Translate("/preview/heading").ToString(), Model.PreviewContent.Name, Html.Translate(area.AreaName))
35 |
36 | @(@Html.DisplayFor(x => area.ContentArea)) 37 |
38 | } 39 | } 40 | 41 | @if (!Model.Areas.Any()) 42 | { 43 | @await Html.PartialAsync("TemplateHint", Model.NotFound) 44 | } 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/Advanced.CMS.ExternalReviews/module.config: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Alloy.Sample/.gitignore: -------------------------------------------------------------------------------- 1 | modules/_protected/CMS/** 2 | modules/_protected/EPiServer.Cms.TinyMce/** 3 | modules/_protected/EPiServer.Cms.UI.Admin/** 4 | modules/_protected/EPiServer.Cms.UI.Settings/** 5 | modules/_protected/EPiServer.Cms.UI.VisitorGroups/** 6 | modules/_protected/Commerce/** 7 | modules/_protected/EPiServer.Commerce.Shell/** 8 | modules/_protected/EPiServer.Commerce.UI.Admin/** 9 | modules/_protected/episerver-telemetry-ui/** 10 | modules/_protected/EPiServer.Commerce.UI.CustomerService/** 11 | modules/_protected/Shell/** 12 | wwwroot/js/script.min.js 13 | wwwroot/css/css.min.css 14 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/Channels/MobileChannel.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Web; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Wangkanai.Detection; 5 | 6 | namespace Alloy.Sample.Business.Channels 7 | { 8 | // 9 | //Defines the 'Mobile' content channel 10 | // 11 | public class MobileChannel : DisplayChannel 12 | { 13 | public const string Name = "mobile"; 14 | 15 | public override string ChannelName 16 | { 17 | get 18 | { 19 | return Name; 20 | } 21 | } 22 | 23 | public override string ResolutionId 24 | { 25 | get 26 | { 27 | return typeof(IphoneVerticalResolution).FullName; 28 | } 29 | } 30 | 31 | //CMS-16684: ASPNET Core doesn't natively support checking device, we need to reimplement this 32 | public override bool IsActive(HttpContext context) 33 | { 34 | var detection = context.RequestServices.GetRequiredService(); 35 | return detection.Device.Type == DeviceType.Mobile; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/Channels/WebChannel.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Web; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Wangkanai.Detection; 5 | 6 | namespace Alloy.Sample.Business.Channels 7 | { 8 | /// 9 | /// Defines the 'Web' content channel 10 | /// 11 | public class WebChannel : DisplayChannel 12 | { 13 | public override string ChannelName 14 | { 15 | get 16 | { 17 | return "web"; 18 | } 19 | } 20 | 21 | //CMS-16684: ASPNET Core doesn't natively support checking device, we need to reimplement this 22 | public override bool IsActive(HttpContext context) 23 | { 24 | var detection = context.RequestServices.GetRequiredService(); 25 | return detection.Device.Type == DeviceType.Desktop; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/CommerceAutoMigrateUrl.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Commerce.Internal.Migration; 2 | using EPiServer.Framework; 3 | using EPiServer.Framework.Initialization; 4 | using EPiServer.ServiceLocation; 5 | using Microsoft.Extensions.DependencyInjection.Extensions; 6 | 7 | namespace Alloy.Sample.Business; 8 | 9 | [ModuleDependency(typeof(MigrationInitializationModule))] 10 | public class CommerceAutoMigrateUrlInitializer : IConfigurableModule 11 | { 12 | public void ConfigureContainer(ServiceConfigurationContext context) 13 | { 14 | context.Services.RemoveAll(typeof(MigrateActionUrlResolver)); 15 | context.Services.AddSingleton((MigrateActionUrlResolver) ( 16 | ( _, action) => $"EPiServer/Commerce/Migrate/{action}")); 17 | } 18 | 19 | public void Initialize(InitializationEngine context) 20 | { 21 | } 22 | 23 | public void Uninitialize(InitializationEngine context) 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/EditorDescriptors/ContactPageSelectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using EPiServer.ServiceLocation; 4 | using EPiServer.Shell.ObjectEditing; 5 | 6 | namespace Alloy.Sample.Business.EditorDescriptors 7 | { 8 | /// 9 | /// Provides a list of options corresponding to ContactPage pages on the site 10 | /// 11 | /// 12 | [ServiceConfiguration] 13 | public class ContactPageSelectionFactory : ISelectionFactory 14 | { 15 | private readonly ContentLocator _contentLocator; 16 | 17 | public ContactPageSelectionFactory(ContentLocator contentLocator) 18 | { 19 | _contentLocator = contentLocator; 20 | } 21 | 22 | public IEnumerable GetSelections(ExtendedMetadata metadata) 23 | { 24 | var contactPages = _contentLocator.GetContactPages(); 25 | 26 | return new List(contactPages.Select(c => new SelectItem { Value = c.PageLink, Text = c.Name })); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/EditorDescriptors/ContactPageSelector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using EPiServer.Core; 4 | using EPiServer.Shell.ObjectEditing; 5 | using EPiServer.Shell.ObjectEditing.EditorDescriptors; 6 | 7 | namespace Alloy.Sample.Business.EditorDescriptors 8 | { 9 | /// 10 | /// Registers an editor to select a ContactPage for a PageReference property using a dropdown 11 | /// 12 | [EditorDescriptorRegistration(TargetType = typeof(PageReference), UIHint = Global.SiteUIHints.Contact)] 13 | public class ContactPageSelector : EditorDescriptor 14 | { 15 | public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable attributes) 16 | { 17 | SelectionFactoryType = typeof(ContactPageSelectionFactory); 18 | 19 | ClientEditingClass = "epi-cms/contentediting/editors/SelectionEditor"; 20 | 21 | base.ModifyMetadata(metadata, attributes); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/IModifyLayout.cs: -------------------------------------------------------------------------------- 1 | using Alloy.Sample.Models.ViewModels; 2 | 3 | namespace Alloy.Sample.Business 4 | { 5 | /// 6 | /// Defines a method which may be invoked by PageContextActionFilter allowing controllers 7 | /// to modify common layout properties of the view model. 8 | /// 9 | interface IModifyLayout 10 | { 11 | void ModifyLayout(LayoutModel layoutModel); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/PageTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EPiServer.DataAbstraction; 3 | using EPiServer.ServiceLocation; 4 | 5 | namespace Alloy.Sample.Business 6 | { 7 | /// 8 | /// Provides extension methods for types intended to be used when working with page types 9 | /// 10 | public static class PageTypeExtensions 11 | { 12 | /// 13 | /// Returns the definition for a specific page type 14 | /// 15 | /// 16 | /// 17 | public static PageType GetPageType(this Type pageType) 18 | { 19 | var pageTypeRepository = ServiceLocator.Current.GetInstance>(); 20 | 21 | return pageTypeRepository.Load(pageType); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/Rendering/IContainerPage.cs: -------------------------------------------------------------------------------- 1 | namespace Alloy.Sample.Business.Rendering 2 | { 3 | /// 4 | /// Marker interface for content types which should not be handled by DefaultPageController. 5 | /// 6 | interface IContainerPage 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/Rendering/ICustomCssInContentArea.cs: -------------------------------------------------------------------------------- 1 | namespace Alloy.Sample.Business.Rendering 2 | { 3 | /// 4 | /// Defines a property for CSS class(es) which will be added to the class 5 | /// attribute of containing elements when rendered in a content area with a size tag. 6 | /// 7 | interface ICustomCssInContentArea 8 | { 9 | string ContentAreaCssClass { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/Rendering/SiteViewEngineLocationExpander.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Mvc.Razor; 3 | 4 | namespace Alloy.Sample.Business.Rendering 5 | { 6 | 7 | public class SiteViewEngineLocationExpander : IViewLocationExpander 8 | { 9 | private static readonly string[] AdditionalPartialViewFormats = new[] 10 | { 11 | TemplateCoordinator.BlockFolder + "{0}.cshtml", 12 | TemplateCoordinator.PagePartialsFolder + "{0}.cshtml" 13 | }; 14 | 15 | public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) 16 | { 17 | foreach (var location in viewLocations) 18 | { 19 | yield return location; 20 | } 21 | 22 | for (int i = 0; i < AdditionalPartialViewFormats.Length; i++) 23 | { 24 | yield return AdditionalPartialViewFormats[i]; 25 | } 26 | } 27 | public void PopulateValues(ViewLocationExpanderContext context) { } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/SiteInitialization.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Commerce.Initialization; 2 | using EPiServer.Commerce.Routing; 3 | using EPiServer.Framework; 4 | using EPiServer.Framework.Initialization; 5 | using EPiServer.ServiceLocation; 6 | 7 | namespace Alloy.Sample.Business; 8 | 9 | [ModuleDependency(typeof(InitializationModule))] 10 | public class SiteInitialization : IConfigurableModule 11 | { 12 | public void ConfigureContainer(ServiceConfigurationContext context) 13 | { 14 | 15 | } 16 | 17 | public void Initialize(InitializationEngine context) 18 | { 19 | CatalogRouteHelper.MapDefaultHierarchialRouter(false); 20 | } 21 | 22 | public void Uninitialize(InitializationEngine context) 23 | { 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Business/UIDescriptors/ContainerPageUIDescriptor.cs: -------------------------------------------------------------------------------- 1 | using Alloy.Sample.Models.Pages; 2 | using EPiServer.Shell; 3 | 4 | namespace Alloy.Sample.Business.UIDescriptors 5 | { 6 | /// 7 | /// Describes how the UI should appear for content. 8 | /// 9 | [UIDescriptorRegistration] 10 | public class ContainerPageUIDescriptor : UIDescriptor 11 | { 12 | public ContainerPageUIDescriptor() 13 | : base(ContentTypeCssClassNames.Container) 14 | { 15 | DefaultView = CmsViewNames.AllPropertiesView; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Components/ImageFileViewComponent.cs: -------------------------------------------------------------------------------- 1 | using Alloy.Sample.Models.Media; 2 | using Alloy.Sample.Models.ViewModels; 3 | using EPiServer.Web.Mvc; 4 | using EPiServer.Web.Routing; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace Alloy.Sample.Components 8 | { 9 | /// 10 | /// Controller for the image file. 11 | /// 12 | public class ImageFileViewComponent : PartialContentComponent 13 | { 14 | private readonly UrlResolver _urlResolver; 15 | 16 | public ImageFileViewComponent(UrlResolver urlResolver) 17 | { 18 | _urlResolver = urlResolver; 19 | } 20 | 21 | /// 22 | /// The index action for the image file. Creates the view model and renders the view. 23 | /// 24 | /// The current image file. 25 | protected override IViewComponentResult InvokeComponent(ImageFile currentContent) 26 | { 27 | var model = new ImageViewModel 28 | { 29 | Url = _urlResolver.GetUrl(currentContent.ContentLink), 30 | Name = currentContent.Name, 31 | Copyright = currentContent.Copyright 32 | }; 33 | 34 | return View(model); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Components/VideoFileViewComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Alloy.Sample.Models.Media; 3 | using Alloy.Sample.Models.ViewModels; 4 | using EPiServer.Core; 5 | using EPiServer.Web.Mvc; 6 | using EPiServer.Web.Routing; 7 | using Microsoft.AspNetCore.Mvc; 8 | 9 | namespace Alloy.Sample.Components 10 | { 11 | /// 12 | /// Controller for the video file. 13 | /// 14 | public class VideoFileViewComponent : PartialContentComponent 15 | { 16 | private readonly UrlResolver _urlResolver; 17 | 18 | public VideoFileViewComponent(UrlResolver urlResolver) 19 | { 20 | _urlResolver = urlResolver; 21 | } 22 | 23 | /// 24 | /// The index action for the video file. Creates the view model and renders the view. 25 | /// 26 | /// The current video file. 27 | protected override IViewComponentResult InvokeComponent(VideoFile currentContent) 28 | { 29 | var model = new VideoViewModel 30 | { 31 | Url = _urlResolver.GetUrl(currentContent.ContentLink), 32 | PreviewImageUrl = ContentReference.IsNullOrEmpty(currentContent.PreviewImage) ? String.Empty : _urlResolver.GetUrl(currentContent.PreviewImage), 33 | }; 34 | 35 | return View(model); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Controllers/DummyProductController.cs: -------------------------------------------------------------------------------- 1 | using Alloy.Sample.Models.Commerce; 2 | using EPiServer.Web.Mvc; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace Alloy.Sample.Controllers; 6 | 7 | public class DummyProductController : ContentController 8 | { 9 | public ActionResult Index(DummyProduct currentContent) 10 | { 11 | return View(currentContent); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Controllers/MyVariationController.cs: -------------------------------------------------------------------------------- 1 | using Alloy.Sample.Models.Commerce; 2 | using EPiServer.Web.Mvc; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace Alloy.Sample.Controllers; 6 | 7 | public class MyVariationController : ContentController 8 | { 9 | public ActionResult Index(MyVariation currentContent) 10 | { 11 | return View(currentContent); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Controllers/RouteAttributeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace Alloy.Sample.Controllers 4 | { 5 | public class RouteAttributeController : ControllerBase 6 | { 7 | public const string AttributeRoute = "goto-foo"; 8 | 9 | [Route(AttributeRoute)] 10 | public IActionResult Index() => Content("bar"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Controllers/SearchPageController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Alloy.Sample.Models.Pages; 3 | using Alloy.Sample.Models.ViewModels; 4 | using EPiServer.Shell.Search; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace Alloy.Sample.Controllers 8 | { 9 | public class SearchPageController : PageControllerBase 10 | { 11 | private SearchProvidersManager _searchProvidersManager; 12 | 13 | public SearchPageController(SearchProvidersManager searchProvidersManager) 14 | { 15 | _searchProvidersManager = searchProvidersManager; 16 | } 17 | 18 | public ViewResult Index(SearchPage currentPage, string q) 19 | { 20 | var providers = _searchProvidersManager.GetEnabledProvidersByPriority("CMS/pages", true); 21 | 22 | var hits = providers.SelectMany(p => p.Search(new Query(q))).ToList(); 23 | 24 | var model = new SearchContentModel(currentPage) 25 | { 26 | Hits = hits.Select(x => new SearchContentModel.SearchHit 27 | { 28 | Url = x.Url, 29 | Excerpt = x.PreviewText, 30 | Title = x.Title 31 | }), 32 | NumberOfHits = hits.Count, 33 | SearchServiceDisabled = false, 34 | SearchedQuery = q 35 | }; 36 | 37 | return View(model); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Controllers/StartPageController.cs: -------------------------------------------------------------------------------- 1 | using Alloy.Sample.Models.Pages; 2 | using Alloy.Sample.Models.ViewModels; 3 | using EPiServer.Web; 4 | using EPiServer.Web.Mvc; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace Alloy.Sample.Controllers 8 | { 9 | public class StartPageController : PageControllerBase 10 | { 11 | public IActionResult Index(StartPage currentPage) 12 | { 13 | var model = PageViewModel.Create(currentPage); 14 | 15 | if (SiteDefinition.Current.StartPage.CompareToIgnoreWorkID(currentPage.ContentLink)) // Check if it is the StartPage or just a page of the StartPage type. 16 | { 17 | //Connect the view models logotype property to the start page's to make it editable 18 | var editHints = ViewData.GetEditHints, StartPage>(); 19 | editHints.AddConnection(m => m.Layout.Logotype, p => p.SiteLogotype); 20 | editHints.AddConnection(m => m.Layout.ProductPages, p => p.ProductPageLinks); 21 | editHints.AddConnection(m => m.Layout.CompanyInformationPages, p => p.CompanyInformationPageLinks); 22 | editHints.AddConnection(m => m.Layout.NewsPages, p => p.NewsPageLinks); 23 | editHints.AddConnection(m => m.Layout.CustomerZonePages, p => p.CustomerZonePageLinks); 24 | } 25 | 26 | return View(model); 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Extensions/HttpContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace Alloy.Sample.Extensions 5 | { 6 | public static class HttpContextExtensions 7 | { 8 | private const string NullIpAddress = "::1"; 9 | private static bool? _isLocalRequest = null; 10 | 11 | public static bool IsLocalRequest(this HttpContext httpContext) 12 | { 13 | if (!_isLocalRequest.HasValue) 14 | { 15 | var connection = httpContext.Connection; 16 | 17 | _isLocalRequest = connection.RemoteIpAddress.IsSet() ? connection.LocalIpAddress.IsSet() 18 | //Is local is same as remote, then we are local 19 | ? connection.RemoteIpAddress.Equals(connection.LocalIpAddress) 20 | //else we are remote if the remote IP address is not a loopback address 21 | : IPAddress.IsLoopback(connection.RemoteIpAddress) 22 | : true; 23 | } 24 | 25 | return _isLocalRequest.Value; 26 | } 27 | 28 | private static bool IsSet(this IPAddress address) 29 | { 30 | return address != null && address.ToString() != NullIpAddress; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Infrastructure/RegisterFirstAdminWithLocalRequestAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Filters; 4 | 5 | namespace Alloy.Sample.Infrastructure 6 | { 7 | [AttributeUsage(AttributeTargets.Class, Inherited = true)] 8 | public class RegisterFirstAdminWithLocalRequestAttribute : Attribute, IAuthorizationFilter 9 | { 10 | public void OnAuthorization(AuthorizationFilterContext context) 11 | { 12 | if (AdministratorRegistrationPageMiddleware.IsEnabled == false) 13 | { 14 | context.Result = new NotFoundResult(); 15 | return; 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Blocks/ButtonBlock.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using EPiServer; 3 | using EPiServer.DataAbstraction; 4 | 5 | namespace Alloy.Sample.Models.Blocks 6 | { 7 | /// 8 | /// Used to insert a link which is styled as a button 9 | /// 10 | [SiteContentType(GUID = "426CF12F-1F01-4EA0-922F-0778314DDAF0")] 11 | [SiteImageUrl] 12 | public class ButtonBlock : SiteBlockData 13 | { 14 | [Display(Order = 1, GroupName = SystemTabNames.Content)] 15 | [Required] 16 | public virtual string ButtonText { get; set; } 17 | 18 | [Display(Order = 2, GroupName = SystemTabNames.Content)] 19 | [Required] 20 | public virtual Url ButtonLink { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Blocks/EditorialBlock.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using EPiServer.Core; 3 | using EPiServer.DataAbstraction; 4 | using EPiServer.DataAnnotations; 5 | 6 | namespace Alloy.Sample.Models.Blocks 7 | { 8 | /// 9 | /// Used to insert editorial content edited using a rich-text editor 10 | /// 11 | [SiteContentType( 12 | GUID = "67F617A4-2175-4360-975E-75EDF2B924A7", 13 | GroupName = SystemTabNames.Content)] 14 | [SiteImageUrl] 15 | public class EditorialBlock : SiteBlockData 16 | { 17 | [Display(GroupName = SystemTabNames.Content)] 18 | [CultureSpecific] 19 | public virtual XhtmlString MainBody { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Blocks/SiteBlockData.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Alloy.Sample.Models.Blocks 3 | { 4 | /// 5 | /// Base class for all block types on the site 6 | /// 7 | public abstract class SiteBlockData : EPiServer.Core.BlockData 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Blocks/_ReadMe.txt: -------------------------------------------------------------------------------- 1 | This folder contains all block types. 2 | 3 | Blocks should be named with a suffix of "Block", such as "TeaserBlock" or "NewsListBlock". 4 | 5 | Default block controls should be named with a suffix of "Control", 6 | such as "TeaserBlockControl" or "NewsListBlockControl". -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Commerce/DummyProduct.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Commerce.Catalog.ContentTypes; 2 | using EPiServer.Commerce.Catalog.DataAnnotations; 3 | 4 | namespace Alloy.Sample.Models.Commerce; 5 | 6 | [CatalogContentType( 7 | DisplayName = "DummyProduct", 8 | GUID = "4965d65c-9415-43dd-ad9c-3d4d080fd27d", 9 | Description = "")] 10 | public class DummyProduct : ProductContent 11 | { 12 | public virtual XhtmlString HtmlContent { get; set; } 13 | 14 | public virtual ContentArea RelatedProducts { get; set; } 15 | } 16 | 17 | [CatalogContentType(GUID = "8d664789-3e96-409e-b418-baf807241f7c", MetaClassName = "My_Variation")] 18 | public class MyVariation : VariationContent 19 | { 20 | public virtual XhtmlString HtmlContent { get; set; } 21 | 22 | public virtual ContentArea RelatedProducts { get; set; } 23 | } 24 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Alloy.Sample.Models 4 | { 5 | public class LoginViewModel 6 | { 7 | [Required] 8 | public string Username { get; set; } 9 | [Required] 10 | public string Password { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Media/GenericMedia.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EPiServer.Core; 3 | using EPiServer.DataAnnotations; 4 | 5 | namespace Alloy.Sample.Models.Media 6 | { 7 | [ContentType(GUID = "EE3BD195-7CB0-4756-AB5F-E5E223CD9820")] 8 | public class GenericMedia : MediaData 9 | { 10 | /// 11 | /// Gets or sets the description. 12 | /// 13 | public virtual String Description { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Media/ImageFile.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.DataAnnotations; 3 | using EPiServer.Framework.DataAnnotations; 4 | 5 | namespace Alloy.Sample.Models.Media 6 | { 7 | [ContentType(GUID = "0A89E464-56D4-449F-AEA8-2BF774AB8730")] 8 | [MediaDescriptor(ExtensionString = "jpg,jpeg,jpe,ico,gif,bmp,png")] 9 | public class ImageFile : ImageData 10 | { 11 | /// 12 | /// Gets or sets the copyright. 13 | /// 14 | /// 15 | /// The copyright. 16 | /// 17 | public virtual string Copyright { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Media/VectorImageFile.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.DataAnnotations; 3 | using EPiServer.Framework.Blobs; 4 | using EPiServer.Framework.DataAnnotations; 5 | 6 | namespace Alloy.Sample.Models.Media 7 | { 8 | [ContentType(GUID = "F522B459-EB27-462C-B216-989FC7FF9448")] 9 | [MediaDescriptor(ExtensionString = "svg")] 10 | public class VectorImageFile : ImageData 11 | { 12 | /// 13 | /// Gets the generated thumbnail for this media. 14 | /// 15 | public override Blob Thumbnail 16 | { 17 | get { return BinaryData; } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Media/VideoFile.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using EPiServer.Core; 3 | using EPiServer.DataAnnotations; 4 | using EPiServer.Framework.DataAnnotations; 5 | using EPiServer.Web; 6 | 7 | namespace Alloy.Sample.Models.Media 8 | { 9 | [ContentType(GUID = "85468104-E06F-47E5-A317-FC9B83D3CBA6")] 10 | [MediaDescriptor(ExtensionString = "flv,mp4,webm")] 11 | public class VideoFile : VideoData 12 | { 13 | /// 14 | /// Gets or sets the copyright. 15 | /// 16 | public virtual string Copyright { get; set; } 17 | 18 | /// 19 | /// Gets or sets the URL to the preview image. 20 | /// 21 | [UIHint(UIHint.Image)] 22 | public virtual ContentReference PreviewImage { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/ArticlePage.cs: -------------------------------------------------------------------------------- 1 | namespace Alloy.Sample.Models.Pages 2 | { 3 | /// 4 | /// Used primarily for publishing news articles on the website 5 | /// 6 | [SiteContentType( 7 | GroupName = Global.GroupNames.News, 8 | GUID = "AEECADF2-3E89-4117-ADEB-F8D43565D2F4")] 9 | [SiteImageUrl(Global.StaticGraphicsFolderPath + "page-type-thumbnail-article.png")] 10 | public class ArticlePage : StandardPage 11 | { 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/ContactPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using Alloy.Sample.Business.Rendering; 3 | using EPiServer.Core; 4 | using EPiServer.Web; 5 | 6 | namespace Alloy.Sample.Models.Pages 7 | { 8 | /// 9 | /// Represents contact details for a contact person 10 | /// 11 | [SiteContentType( 12 | GUID = "F8D47655-7B50-4319-8646-3369BA9AF05B", 13 | GroupName = Global.GroupNames.Specialized)] 14 | [SiteImageUrl(Global.StaticGraphicsFolderPath + "page-type-thumbnail-contact.png")] 15 | public class ContactPage : SitePageData, IContainerPage 16 | { 17 | [Display(GroupName = Global.GroupNames.Contact)] 18 | [UIHint(UIHint.Image)] 19 | public virtual ContentReference Image { get; set; } 20 | 21 | [Display(GroupName = Global.GroupNames.Contact)] 22 | public virtual string Phone { get; set; } 23 | 24 | [Display(GroupName = Global.GroupNames.Contact)] 25 | [EmailAddress] 26 | public virtual string Email { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/ContainerPage.cs: -------------------------------------------------------------------------------- 1 | using Alloy.Sample.Business.Rendering; 2 | 3 | namespace Alloy.Sample.Models.Pages 4 | { 5 | /// 6 | /// Used to logically group pages in the page tree 7 | /// 8 | [SiteContentType( 9 | GUID = "D178950C-D20E-4A46-90BD-5338B2424745", 10 | GroupName = Global.GroupNames.Specialized)] 11 | [SiteImageUrl] 12 | public class ContainerPage : SitePageData, IContainerPage 13 | { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/IHasRelatedContent.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | 3 | namespace Alloy.Sample.Models.Pages 4 | { 5 | public interface IHasRelatedContent 6 | { 7 | ContentArea RelatedContentArea { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/ISearchPage.cs: -------------------------------------------------------------------------------- 1 | namespace Alloy.Sample.Models.Pages 2 | { 3 | /// 4 | /// Marker interface for search implementation 5 | /// 6 | public interface ISearchPage 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/LandingPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using EPiServer.Core; 3 | using EPiServer.DataAbstraction; 4 | using EPiServer.DataAnnotations; 5 | 6 | namespace Alloy.Sample.Models.Pages 7 | { 8 | /// 9 | /// Used for campaign or landing pages, commonly used for pages linked in online advertising such as AdWords 10 | /// 11 | [SiteContentType( 12 | GUID = "DBED4258-8213-48DB-A11F-99C034172A54", 13 | GroupName = Global.GroupNames.Specialized)] 14 | [SiteImageUrl] 15 | public class LandingPage : SitePageData 16 | { 17 | [Display( 18 | GroupName = SystemTabNames.Content, 19 | Order=310)] 20 | [CultureSpecific] 21 | public virtual ContentArea MainContentArea { get; set; } 22 | 23 | public override void SetDefaultValues(ContentType contentType) 24 | { 25 | base.SetDefaultValues(contentType); 26 | 27 | HideSiteFooter = true; 28 | HideSiteHeader = true; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/NewsPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using Alloy.Sample.Business; 3 | using Alloy.Sample.Models.Blocks; 4 | using EPiServer.DataAbstraction; 5 | using EPiServer.Filters; 6 | using EPiServer.Framework.Localization; 7 | using EPiServer.ServiceLocation; 8 | 9 | namespace Alloy.Sample.Models.Pages 10 | { 11 | /// 12 | /// Presents a news section including a list of the most recent articles on the site 13 | /// 14 | [SiteContentType(GUID = "638D8271-5CA3-4C72-BABC-3E8779233263")] 15 | [SiteImageUrl] 16 | public class NewsPage : StandardPage 17 | { 18 | [Display( 19 | GroupName = SystemTabNames.Content, 20 | Order = 305)] 21 | public virtual PageListBlock NewsList { get; set; } 22 | 23 | public override void SetDefaultValues(ContentType contentType) 24 | { 25 | base.SetDefaultValues(contentType); 26 | 27 | NewsList.Count = 20; 28 | NewsList.Heading = ServiceLocator.Current.GetInstance().GetString("/newspagetemplate/latestnews"); 29 | NewsList.IncludeIntroduction = true; 30 | NewsList.IncludePublishDate = true; 31 | NewsList.Recursive = true; 32 | NewsList.PageTypeFilter = typeof(ArticlePage).GetPageType(); 33 | NewsList.SortOrder = FilterSortOrder.PublishedDescending; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/ProductPage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using Alloy.Sample.Models.Blocks; 4 | using EPiServer.Core; 5 | using EPiServer.DataAbstraction; 6 | using EPiServer.DataAnnotations; 7 | 8 | namespace Alloy.Sample.Models.Pages 9 | { 10 | /// 11 | /// Used to present a single product 12 | /// 13 | [SiteContentType( 14 | GUID = "17583DCD-3C11-49DD-A66D-0DEF0DD601FC", 15 | GroupName = Global.GroupNames.Products)] 16 | [SiteImageUrl(Global.StaticGraphicsFolderPath + "page-type-thumbnail-product.png")] 17 | [AvailableContentTypes( 18 | Availability = Availability.Specific, 19 | IncludeOn = new[] { typeof(StartPage) })] 20 | public class ProductPage : StandardPage, IHasRelatedContent 21 | { 22 | [Required] 23 | [Display(Order = 305)] 24 | [UIHint(Global.SiteUIHints.StringsCollection)] 25 | [CultureSpecific] 26 | public virtual IList UniqueSellingPoints { get; set; } 27 | 28 | [Display( 29 | GroupName = SystemTabNames.Content, 30 | Order = 330)] 31 | [CultureSpecific] 32 | [AllowedTypes(new[] { typeof(IContentData) },new[] { typeof(JumbotronBlock) })] 33 | public virtual ContentArea RelatedContentArea { get; set; } 34 | 35 | public virtual ContentReference LinkItem { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/SearchPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using Alloy.Sample.Models.Blocks; 3 | using EPiServer.Core; 4 | using EPiServer.DataAbstraction; 5 | using EPiServer.DataAnnotations; 6 | 7 | namespace Alloy.Sample.Models.Pages 8 | { 9 | /// 10 | /// Used to provide on-site search 11 | /// 12 | [SiteContentType( 13 | GUID = "AAC25733-1D21-4F82-B031-11E626C91E30", 14 | GroupName = Global.GroupNames.Specialized)] 15 | [SiteImageUrl] 16 | public class SearchPage : SitePageData, IHasRelatedContent, ISearchPage 17 | { 18 | [Display( 19 | GroupName = SystemTabNames.Content, 20 | Order = 310)] 21 | [CultureSpecific] 22 | [AllowedTypes(new[] { typeof(IContentData) }, new[] { typeof(JumbotronBlock) })] 23 | public virtual ContentArea RelatedContentArea { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/StandardPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using EPiServer.Core; 3 | using EPiServer.DataAbstraction; 4 | using EPiServer.DataAnnotations; 5 | 6 | namespace Alloy.Sample.Models.Pages 7 | { 8 | /// 9 | /// Used for the pages mainly consisting of manually created content such as text, images, and blocks 10 | /// 11 | [SiteContentType(GUID = "9CCC8A41-5C8C-4BE0-8E73-520FF3DE8267")] 12 | [SiteImageUrl(Global.StaticGraphicsFolderPath + "page-type-thumbnail-standard.png")] 13 | public class StandardPage : SitePageData 14 | { 15 | [Display( 16 | GroupName = SystemTabNames.Content, 17 | Order = 310)] 18 | [CultureSpecific] 19 | public virtual XhtmlString MainBody { get; set; } 20 | 21 | [Display( 22 | GroupName = SystemTabNames.Content, 23 | Order = 320)] 24 | public virtual ContentArea MainContentArea { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/TestContentPage.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.DataAnnotations; 3 | 4 | namespace Alloy.Sample.Models.Pages 5 | { 6 | [SiteContentType(GUID = "111CF12F-1F01-4EA0-922F-0778314DDAF0")] 7 | public class TestContentBlock : BlockData 8 | { 9 | public virtual string Title { get; set; } 10 | } 11 | 12 | [SiteContentType(GUID = "222CF12F-1F01-4EA0-922F-0778314DDAF0")] 13 | public class TestContentPage : SitePageData 14 | { 15 | public virtual string Title { get; set; } 16 | 17 | [AllowedTypes(typeof(TestContentBlock))] 18 | public virtual ContentReference Reference { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Pages/_ReadMe.txt: -------------------------------------------------------------------------------- 1 | This folder contains all page types. 2 | 3 | Pages should be named with a suffix of "Page", such as "StandardPage" or "ProductPage". 4 | 5 | Default page templates should be named with a suffix of "Template", 6 | such as "StandardPageTemplate" or "ProductPageTemplate". 7 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/Register/RegisterViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Alloy.Sample.Models.Register 4 | { 5 | public class RegisterViewModel 6 | { 7 | [Required] 8 | [Display(Name = "Username")] 9 | [RegularExpression(@"^[a-zA-Z0-9_-]+$", ErrorMessage = "Username can only contain letters a-z, numbers, underscores and hyphens.")] 10 | [StringLength(20, ErrorMessage ="The {0} field can not be more than {1} characters long.")] 11 | public string Username { get; set; } 12 | 13 | [Required] 14 | [EmailAddress] 15 | [Display(Name = "Email")] 16 | public string Email { get; set; } 17 | 18 | [Required] 19 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 20 | [DataType(DataType.Password)] 21 | [Display(Name = "Password")] 22 | public string Password { get; set; } 23 | 24 | [DataType(DataType.Password)] 25 | [Display(Name = "Confirm password")] 26 | [System.ComponentModel.DataAnnotations.Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 27 | public string ConfirmPassword { get; set; } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/SiteContentType.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.DataAnnotations; 2 | 3 | namespace Alloy.Sample.Models 4 | { 5 | /// 6 | /// Attribute used for site content types to set default attribute values 7 | /// 8 | public class SiteContentType : ContentTypeAttribute 9 | { 10 | public SiteContentType() 11 | { 12 | GroupName = Global.GroupNames.Default; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/SiteImageUrl.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.DataAnnotations; 2 | 3 | namespace Alloy.Sample.Models 4 | { 5 | /// 6 | /// Attribute to set the default thumbnail for site page and block types 7 | /// 8 | public class SiteImageUrl : ImageUrlAttribute 9 | { 10 | /// 11 | /// The parameterless constructor will initialize a SiteImageUrl attribute with a default thumbnail 12 | /// 13 | public SiteImageUrl() : base("/gfx/page-type-thumbnail.png") 14 | { 15 | 16 | } 17 | 18 | public SiteImageUrl(string path) : base(path) 19 | { 20 | 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/ViewModels/ContactBlockModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using Alloy.Sample.Models.Pages; 3 | using EPiServer.Core; 4 | using EPiServer.Web; 5 | using Microsoft.AspNetCore.Html; 6 | 7 | namespace Alloy.Sample.Models.ViewModels 8 | { 9 | public class ContactBlockModel 10 | { 11 | [UIHint(UIHint.Image)] 12 | public ContentReference Image { get; set; } 13 | public string Heading { get; set; } 14 | public string LinkText { get; set; } 15 | public IHtmlContent LinkUrl { get; set; } 16 | public bool ShowLink { get; set; } 17 | public ContactPage ContactPage { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/ViewModels/ContentRenderingErrorModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EPiServer; 3 | using EPiServer.Core; 4 | 5 | namespace Alloy.Sample.Models.ViewModels 6 | { 7 | public class ContentRenderingErrorModel 8 | { 9 | public ContentRenderingErrorModel(IContentData contentData, Exception exception) 10 | { 11 | var content = contentData as IContent; 12 | if(content != null) 13 | { 14 | ContentName = content.Name; 15 | } 16 | else 17 | { 18 | ContentName = string.Empty; 19 | } 20 | 21 | ContentTypeName = contentData.GetOriginalType().Name; 22 | 23 | Exception = exception; 24 | } 25 | 26 | public string ContentName { get; set; } 27 | public string ContentTypeName { get; set; } 28 | public Exception Exception { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/ViewModels/IPageViewModel.cs: -------------------------------------------------------------------------------- 1 | using Alloy.Sample.Models.Pages; 2 | using EPiServer.Core; 3 | 4 | namespace Alloy.Sample.Models.ViewModels 5 | { 6 | /// 7 | /// Defines common characteristics for view models for pages, including properties used by layout files. 8 | /// 9 | /// 10 | /// Views which should handle several page types (T) can use this interface as model type rather than the 11 | /// concrete PageViewModel class, utilizing the that this interface is covariant. 12 | /// 13 | public interface IPageViewModel where T : SitePageData 14 | { 15 | T CurrentPage { get; } 16 | LayoutModel Layout { get; set; } 17 | IContent Section { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/ViewModels/ImageViewModel.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Alloy.Sample.Models.ViewModels 3 | { 4 | /// 5 | /// View model for the image file 6 | /// 7 | public class ImageViewModel 8 | { 9 | /// 10 | /// Gets or sets the URL to the image. 11 | /// 12 | public string Url { get; set; } 13 | 14 | /// 15 | /// Gets or sets the name of the image. 16 | /// 17 | public string Name { get; set; } 18 | 19 | /// 20 | /// Gets or sets the copyright information of the image. 21 | /// 22 | public string Copyright { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/ViewModels/LayoutModel.cs: -------------------------------------------------------------------------------- 1 | using Alloy.Sample.Models.Blocks; 2 | using EPiServer.SpecializedProperties; 3 | using Microsoft.AspNetCore.Html; 4 | 5 | namespace Alloy.Sample.Models.ViewModels 6 | { 7 | public class LayoutModel 8 | { 9 | public SiteLogotypeBlock Logotype { get; set; } 10 | public IHtmlContent LogotypeLinkUrl { get; set; } 11 | public bool HideHeader { get; set; } 12 | public bool HideFooter { get; set; } 13 | public LinkItemCollection ProductPages { get; set; } 14 | public LinkItemCollection CompanyInformationPages { get; set; } 15 | public LinkItemCollection NewsPages { get; set; } 16 | public LinkItemCollection CustomerZonePages { get; set; } 17 | public bool LoggedIn { get; set; } 18 | public HtmlString LoginUrl { get; set; } 19 | public HtmlString LogOutUrl { get; set; } 20 | public HtmlString SearchActionUrl { get; set; } 21 | 22 | public bool IsInReadonlyMode {get;set;} 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/ViewModels/PageListModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Alloy.Sample.Models.Blocks; 3 | using EPiServer.Core; 4 | 5 | namespace Alloy.Sample.Models.ViewModels 6 | { 7 | public class PageListModel 8 | { 9 | public PageListModel(PageListBlock block) 10 | { 11 | Heading = block.Heading; 12 | ShowIntroduction = block.IncludeIntroduction; 13 | ShowPublishDate = block.IncludePublishDate; 14 | } 15 | public string Heading { get; set; } 16 | public IEnumerable Pages { get; set; } 17 | public bool ShowIntroduction { get; set; } 18 | public bool ShowPublishDate { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/ViewModels/PageViewModel.cs: -------------------------------------------------------------------------------- 1 | using Alloy.Sample.Models.Pages; 2 | using EPiServer.Core; 3 | 4 | namespace Alloy.Sample.Models.ViewModels 5 | { 6 | public class PageViewModel : IPageViewModel where T : SitePageData 7 | { 8 | public PageViewModel(T currentPage) 9 | { 10 | CurrentPage = currentPage; 11 | } 12 | 13 | public T CurrentPage { get; private set; } 14 | public LayoutModel Layout { get; set; } 15 | public IContent Section { get; set; } 16 | } 17 | 18 | public static class PageViewModel 19 | { 20 | /// 21 | /// Returns a PageViewModel of type . 22 | /// 23 | /// 24 | /// Convenience method for creating PageViewModels without having to specify the type as methods can use type inference while constructors cannot. 25 | /// 26 | public static PageViewModel Create(T page) where T : SitePageData 27 | { 28 | return new PageViewModel(page); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/ViewModels/PreviewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Alloy.Sample.Models.Pages; 3 | using EPiServer.Core; 4 | 5 | namespace Alloy.Sample.Models.ViewModels 6 | { 7 | public class PreviewModel : PageViewModel 8 | { 9 | public PreviewModel(SitePageData currentPage, IContent previewContent) 10 | : base(currentPage) 11 | { 12 | PreviewContent = previewContent; 13 | Areas = new List(); 14 | } 15 | 16 | public IContent PreviewContent { get; set; } 17 | public List Areas { get; set; } 18 | 19 | public class PreviewArea 20 | { 21 | public bool Supported { get; set; } 22 | public string AreaName { get; set; } 23 | public string AreaTag { get; set; } 24 | public ContentArea ContentArea { get; set; } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/ViewModels/SearchContentModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Alloy.Sample.Models.Pages; 3 | 4 | namespace Alloy.Sample.Models.ViewModels 5 | { 6 | public class SearchContentModel : PageViewModel 7 | { 8 | public SearchContentModel(SearchPage currentPage) : base(currentPage) 9 | { 10 | } 11 | 12 | public bool SearchServiceDisabled { get; set; } 13 | public string SearchedQuery { get; set; } 14 | public int NumberOfHits { get; set; } 15 | public IEnumerable Hits { get; set; } 16 | 17 | public class SearchHit 18 | { 19 | public string Title { get; set; } 20 | public string Url { get; set; } 21 | public string Excerpt { get; set; } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Models/ViewModels/VideoViewModel.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Alloy.Sample.Models.ViewModels 3 | { 4 | /// 5 | /// View model for the video file 6 | /// 7 | public class VideoViewModel 8 | { 9 | /// 10 | /// Gets or sets the URL to the video. 11 | /// 12 | public string Url { get; set; } 13 | 14 | /// 15 | /// Gets or sets the URL to a preview image for the video. 16 | /// 17 | public string PreviewImageUrl { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | using Serilog; 4 | using Serilog.Events; 5 | 6 | namespace Alloy.Sample 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | Log.Logger = new LoggerConfiguration() 13 | .MinimumLevel.Warning() 14 | .WriteTo.File("app_data/log.txt", rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: LogEventLevel.Warning) 15 | .WriteTo.Console() 16 | .CreateLogger(); 17 | 18 | CreateHostBuilder(args).Build().Run(); 19 | } 20 | 21 | public static IHostBuilder CreateHostBuilder(string[] args) => 22 | Host.CreateDefaultBuilder(args) 23 | .ConfigureCmsDefaults() 24 | .ConfigureLogging(logging => 25 | { 26 | logging.AddSerilog(); 27 | }) 28 | .UseSerilog() 29 | .ConfigureWebHostDefaults(webBuilder => 30 | { 31 | webBuilder.UseStartup(); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: ComVisible(false)] 5 | [assembly: CLSCompliant(false)] 6 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57729/", 7 | "sslPort": 44333 8 | } 9 | }, 10 | "profiles": { 11 | "Kestrel (Env: Development)": { 12 | "commandName": "Project", 13 | "launchBrowser": true, 14 | "applicationUrl": "http://localhost:8000/", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "IIS Express (Env: Development)": { 20 | "commandName": "IISExpress", 21 | "launchBrowser": false, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Resources/LanguageFiles/Display.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Mobile 7 | 8 | 9 | Web 10 | 11 | 12 | 13 | Full 14 | Narrow 15 | Wide 16 | 17 | 18 | Android vertical (480x800) 19 | iPad horizontal (1024x768) 20 | iPhone vertical (320x568) 21 | Standard (1366x768) 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Resources/LanguageFiles/EditorHints.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Button Text 8 | 9 | 10 | 11 | 12 | This block type does not have a renderer for this type of content area. 13 | 14 | 15 | 16 | 17 | The block '{0}' when displayed as {1} 18 | The block '{0}' cannot be displayed as {1} 19 | No renderer found for '{0}' 20 | 21 | 22 | Error while rendering {0} {1} 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Resources/LanguageFiles/GroupNames.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Default 7 | 8 | 9 | News 10 | 11 | 12 | Products 13 | 14 | 15 | SEO 16 | 17 | 18 | Site settings 19 | 20 | 21 | Specialized 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Resources/LanguageFiles/Views.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | E-mail 6 | No contact selected 7 | Phone 8 | 9 |
10 | The Company 11 | Customer Zone 12 | Log in 13 | Log out 14 | News & Events 15 | Products 16 |
17 | 18 | Search 19 | 20 | 21 | Latest news 22 | News list will be empty since no list root has been set 23 | 24 | 25 | EPiServer Search is not configured or is not active for this website. 26 | hits 27 | Search result 28 | resulted in 29 | Search 30 | Your search for 31 | no 32 | 33 |
34 |
-------------------------------------------------------------------------------- /src/Alloy.Sample/Resources/LanguageFiles/_ReadMe.txt: -------------------------------------------------------------------------------- 1 | All language files in this folder are included in the LocalizationService. 2 | 3 | The path to this folder is configured in EPiServerFramework.config: 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Alloy.Sample/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using EPiServer.Framework.Hosting; 3 | using EPiServer.Web.Hosting; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Alloy.Sample 7 | { 8 | internal static class InternalServiceCollectionExtensions 9 | { 10 | /// 11 | public static IServiceCollection AddUIMappedFileProviders(this IServiceCollection services, string applicationRootPath, string uiSolutionRelativePath) 12 | { 13 | var uiSolutionFolder = Path.Combine(applicationRootPath, uiSolutionRelativePath); 14 | services.Configure(c => 15 | { 16 | c.BasePathFileProviders.Add(new MappingPhysicalFileProvider("/EPiServer/advanced-cms-external-reviews", string.Empty, Path.Combine(uiSolutionFolder, @"src\Advanced.CMS.ExternalReviews"))); 17 | c.BasePathFileProviders.Add(new MappingPhysicalFileProvider("/EPiServer/advanced-cms-approval-reviews", string.Empty, Path.Combine(uiSolutionFolder, @"src\Advanced.CMS.ApprovalReviews"))); 18 | }); 19 | return services; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/ArticlePage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using Alloy.Sample 2 | @model Alloy.Sample.Models.ViewModels.PageViewModel 3 | 4 | @{ Layout = "~/Views/Shared/Layouts/_LeftNavigation.cshtml"; } 5 | 6 |

x.CurrentPage.PageName)>@Model.CurrentPage.PageName

7 |

x.CurrentPage.MetaDescription)>@Model.CurrentPage.MetaDescription

8 |
9 |
x.CurrentPage.MainBody)> 10 | @Html.DisplayFor(m => m.CurrentPage.MainBody) 11 |
12 |
13 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row", Tag = Global.ContentAreaTags.TwoThirdsWidth }) 14 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/DummyProduct/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = null; 3 | } 4 | 5 | @model Alloy.Sample.Models.Commerce.DummyProduct 6 | 7 |

@Model.Name

8 |
@Html.PropertyFor(x => x.HtmlContent)
9 |
@Html.PropertyFor(x => x.RelatedProducts)
10 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/LandingPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using Alloy.Sample 2 | @model Alloy.Sample.Models.ViewModels.PageViewModel 3 |
4 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row equal-height", tag = Global.ContentAreaTags.FullWidth }) 5 |
6 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/MyVariation/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = null; 3 | } 4 | 5 | @model Alloy.Sample.Models.Commerce.MyVariation 6 | 7 |

@Model.Name

8 |
@Html.PropertyFor(x => x.HtmlContent)
9 |
@Html.PropertyFor(x => x.RelatedProducts)
10 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/NewsPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using Alloy.Sample 2 | @model Alloy.Sample.Models.ViewModels.PageViewModel 3 | 4 | @{ Layout = "~/Views/Shared/Layouts/_LeftNavigation.cshtml"; } 5 | 6 |

x.CurrentPage.PageName)>@Model.CurrentPage.PageName

7 |

x.CurrentPage.MetaDescription)>@Model.CurrentPage.MetaDescription

8 |
9 |
x.CurrentPage.MainBody)> 10 | @Html.DisplayFor(m => m.CurrentPage.MainBody) 11 |
12 |
13 | @Html.PropertyFor(x => x.CurrentPage.NewsList) 14 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row", Tag = Global.ContentAreaTags.TwoThirdsWidth }) 15 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Preview/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.ViewModels.PreviewModel 2 | 3 | @foreach(var area in Model.Areas) 4 | { 5 | if(area.Supported) 6 | { 7 | @await Html.PartialAsync("TemplateHint", string.Format(System.Globalization.CultureInfo.CurrentUICulture, LocalizationService.Current.GetString("/preview/heading"), Model.PreviewContent.Name, LocalizationService.Current.GetString(area.AreaName))) 8 |
9 | @Html.DisplayFor(x => area.ContentArea, new {Tag = area.AreaTag}) 10 |
11 | } 12 | else 13 | { 14 | @await Html.PartialAsync("TemplateHint", string.Format(System.Globalization.CultureInfo.CurrentUICulture, LocalizationService.Current.GetString("/preview/norenderer"), Model.PreviewContent.Name, LocalizationService.Current.GetString(area.AreaName))) 15 | } 16 | } 17 | 18 | @if(!Model.Areas.Any()) 19 | { 20 | @await Html.PartialAsync("TemplateHint", string.Format(System.Globalization.CultureInfo.CurrentUICulture, LocalizationService.Current.GetString("/preview/norendereratall"), Model.PreviewContent.Name)) 21 | } 22 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/ProductPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using Alloy.Sample 2 | @using Alloy.Sample.Helpers 3 | @model Alloy.Sample.Models.ViewModels.PageViewModel 4 | 5 | @{ Layout = "~/Views/Shared/Layouts/_TwoPlusOne.cshtml"; } 6 | 7 |

x.CurrentPage.PageName)>@Model.CurrentPage.PageName

8 |

x.CurrentPage.MetaDescription)>@Model.CurrentPage.MetaDescription

9 |
10 |
x.CurrentPage.MainBody)> 11 | @Html.DisplayFor(m => m.CurrentPage.MainBody) 12 |
13 |
14 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row", Tag = Global.ContentAreaTags.TwoThirdsWidth }) 15 | 16 | @section RelatedContent 17 | { 18 |
x.CurrentPage.PageImage)> 19 | 20 |
21 | 22 |
23 |

x.CurrentPage.PageName)>@Model.CurrentPage.PageName

24 | @Html.PropertyFor(x => x.CurrentPage.UniqueSellingPoints) 25 |
26 | 27 | @Html.PropertyFor(x => x.CurrentPage.RelatedContentArea, new { CssClass = "row", Tag = Global.ContentAreaTags.OneThirdWidth }) 28 | } 29 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Blocks/ButtonBlock.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.Blocks.ButtonBlock 2 | 3 | m.ButtonText)> 4 | @{ 5 | var buttonText = string.IsNullOrWhiteSpace(Model.ButtonText) 6 | ? LocalizationService.Current.GetString("/blocks/buttonblockcontrol/buttondefaulttext") 7 | : Model.ButtonText; 8 | } 9 | @buttonText 10 | 11 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Blocks/EditorialBlock.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.Blocks.EditorialBlock 2 | 3 |
x.MainBody)> 4 | @Html.DisplayFor(x => Model.MainBody) 5 |
6 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Blocks/JumbotronBlockWide.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.Blocks.JumbotronBlock 2 | 3 |
4 |
5 | @Html.PropertyFor(m=>m.Image) 6 |
7 | 8 |
9 |

m.Heading)>@Model.Heading

10 |

m.SubHeading)>@Model.SubHeading

11 | m.ButtonText)>@Model.ButtonText 12 |
13 |
14 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Blocks/NoRenderer.cshtml: -------------------------------------------------------------------------------- 1 | @await Html.PartialAsync("TemplateHint", Html.Translate("/blocks/norenderer/message").ToString()) 2 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Blocks/SiteLogotypeBlock.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.Blocks.SiteLogotypeBlock 2 | 3 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Blocks/TeaserBlock.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Core 2 | @using Alloy.Sample.Extensions 3 | @using Alloy.Sample.Helpers 4 | 5 | @model Alloy.Sample.Models.Blocks.TeaserBlock 6 | 7 |
8 | @*Link the teaser block only if a link has been set and not displayed in preview*@ 9 | @using (Html.BeginConditionalLink 10 | (!ContentReference.IsNullOrEmpty(Model.Link) && !(Html.ViewContext.IsPreviewMode()), 11 | Url.PageLinkUrl(Model.Link), 12 | Model.Heading)) 13 | { 14 |

x.Heading)>@Model.Heading

15 |

x.Text)>@Model.Text

16 |
x.Image)>
17 | } 18 | 19 |
20 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Blocks/TeaserBlockWide.cshtml: -------------------------------------------------------------------------------- 1 | @using Alloy.Sample.Extensions 2 | @using Alloy.Sample.Helpers 3 | @model Alloy.Sample.Models.Blocks.TeaserBlock 4 | 5 |
6 | @*Link the teaser block only if a link has been set and not displayed in preview*@ 7 | @using(Html.BeginConditionalLink( 8 | !ContentReference.IsNullOrEmpty(Model.Link) && !(Html.ViewContext.IsPreviewMode()), 9 | Url.PageLinkUrl(Model.Link), 10 | Model.Heading)) 11 | { 12 |
13 |
x.Image)> 14 | 15 |
16 |
17 |

x.Heading)>@Model.Heading

18 |

x.Text)>@Model.Text

19 |
20 |
21 | } 22 |
23 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Components/ContactBlock/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.ViewModels.ContactBlockModel 2 | 3 |
4 | @Html.PropertyFor(x => x.Image) 5 |

x.Heading)>@Model.Heading

6 | @Html.PropertyFor(x => x.ContactPage) 7 | @if(Model.ShowLink) 8 | { 9 | x.LinkText)> 10 | @Model.LinkText 11 | 12 | } 13 |
14 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Components/ImageFile/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.ViewModels.ImageViewModel 2 | 3 | @Model.Name 4 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Components/PageListBlock/Default.cshtml: -------------------------------------------------------------------------------- 1 | @using Alloy.Sample.Helpers 2 | @using Alloy.Sample.Models.Pages 3 | @model Alloy.Sample.Models.ViewModels.PageListModel 4 | @Html.FullRefreshPropertiesMetaData(new[] { "IncludePublishDate", "IncludeIntroduction", "Count", "SortOrder", "Root", "PageTypeFilter", "CategoryFilter", "Recursive" }) 5 |

x.Heading)>@Model.Heading

6 |
7 | 8 | @foreach(var page in Model.Pages) 9 | { 10 |
11 |

12 | @Html.PageLink(page) 13 |

14 | @if(Model.ShowPublishDate && page.StartPublish.HasValue) 15 | { 16 |

@Html.DisplayFor(x => page.StartPublish)

17 | } 18 | @if(Model.ShowIntroduction && page is SitePageData) 19 | { 20 | var withTeaserText = (SitePageData) page; 21 |

@withTeaserText.TeaserText

22 | } 23 |
24 |
25 | } 26 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Components/TestContentPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer 2 | @using EPiServer.ServiceLocation 3 | @using Alloy.Sample.Models.Pages 4 | @model Alloy.Sample.Models.ViewModels.PageViewModel 5 | 6 | @{ 7 | var contentLoader = ServiceLocator.Current.GetInstance (); 8 | var block = contentLoader.Get(Model.CurrentPage.Reference); 9 | var block2 = contentLoader.Get(Model.CurrentPage.Reference); 10 | } 11 | @Model.CurrentPage.Title 12 | 13 | 14 | Display for: 15 | @Html.PropertyFor(m => m.CurrentPage.PageImage) 16 | 17 | @block.Title 18 | @block2.Title 19 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Components/VideoFile/Default.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Framework.Web.Resources 2 | @model Alloy.Sample.Models.ViewModels.VideoViewModel 3 | @{ 4 | ClientResources.RequireScript(Href("~/jwplayer/jwplayer.js")); 5 | 6 | //The video element's ID needs to be unique in order for several video blocks and possible the same video block, to work on the same page 7 | var containerId = "video-container-" + Guid.NewGuid().GetHashCode(); 8 | } 9 | @Html.FullRefreshPropertiesMetaData(new []{"Url"}) 10 |
m.Url)> 11 |
12 |
13 |
14 | 15 | 30 |
31 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/DisplayTemplates/ContactPage.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.Pages.ContactPage 2 | 3 |

4 | @if(Model != null) 5 | { 6 | @Model.PageName
7 | @Html.Translate("/contact/phone")@: : @Model.Phone
8 | @Html.Translate("/contact/email")@: : @Html.DisplayFor(m => m.Email) 9 | } 10 | else 11 | { 12 | @Html.Translate("/contact/noneselected") 13 | } 14 |

15 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/DisplayTemplates/DateTime.cshtml: -------------------------------------------------------------------------------- 1 | @model DateTime 2 | @Model.ToString("d MMMM yyyy") 3 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/DisplayTemplates/StringsCollection.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | @if(Model != null && Model.Any()) 3 | { 4 |
    5 | @foreach(var stringValue in Model) 6 | { 7 |
  • @stringValue
  • 8 | } 9 |
10 | } 11 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/DisplayTemplates/_ReadMe.txt: -------------------------------------------------------------------------------- 1 | The views in this folder are used when rendering properties using Html.DisplayFor and Html.PropertyFor. 2 | Display templates are selected based on the type name of the property and, optionally, by UIHint and DataType attributes added to the property. 3 | Note that the CMS adds a number of view templates which do not exist in this folder but found through a view engine which the CMS adds at start up. 4 | Those view templates can be found in \Application\Util\Views\Shared\DisplayTemplates. Views in this folder takes precedence meaning 5 | that we can override those templates, which is currently done for content areas. -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Layouts/_LeftNavigation.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.ViewModels.IPageViewModel 2 | 3 | @{ Layout = "~/Views/Shared/Layouts/_Root.cshtml"; } 4 | 5 | @{await Html.RenderPartialAsync("Breadcrumbs", Model);} 6 | 7 |
8 |
9 |
10 |
11 | @{await Html.RenderPartialAsync("SubNavigation", Model);} 12 | @RenderSection("RelatedContent", false) 13 |
14 |
15 |
16 | 17 |
18 | @RenderBody() 19 |
20 |
21 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Layouts/_TwoPlusOne.cshtml: -------------------------------------------------------------------------------- 1 | @using Alloy.Sample 2 | @using Alloy.Sample.Models.Pages 3 | @model Alloy.Sample.Models.ViewModels.IPageViewModel 4 | @{ Layout = "~/Views/Shared/Layouts/_Root.cshtml"; } 5 | 6 | @{await Html.RenderPartialAsync("Breadcrumbs");} 7 | 8 |
9 | 10 |
11 | @RenderBody() 12 |
13 | 14 |
15 | @if (IsSectionDefined("RelatedContent")) 16 | { 17 | @RenderSection("RelatedContent") 18 | } 19 | else if (Model.CurrentPage is IHasRelatedContent) 20 | { 21 | @Html.PropertyFor(x => ((IHasRelatedContent)x.CurrentPage).RelatedContentArea, new { CssClass = "row", Tag = Global.ContentAreaTags.OneThirdWidth }) 22 | } 23 |
24 |
25 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/PagePartials/ContactPage.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.Pages.ContactPage 2 | 3 |
4 | 5 |

@Model.PageName

6 |

@Model.TeaserText

7 |

8 | @Html.Translate("/contact/email"): @Html.DisplayFor(x => x.Email)
9 | @Html.Translate("/contact/phone"): @Model.Phone 10 |

11 |
12 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/PagePartials/ContactPageWide.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.Pages.ContactPage 2 |
3 |
4 |
5 | 6 |
7 |

@Model.PageName

8 |

@Model.TeaserText

9 |

10 | @Html.Translate("/contact/email"): @Html.DisplayFor(x => x.Email)
11 | @Html.Translate("/contact/phone"): @Model.Phone 12 |

13 |
14 |
15 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/PagePartials/Page.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Core 2 | @using Alloy.Sample.Helpers 3 | @model Alloy.Sample.Models.Pages.SitePageData 4 | 5 |
6 | @using(Html.BeginConditionalLink(Model.HasTemplate(), Url.PageLinkUrl(Model), Model.PageName)) 7 | { 8 |

@Model.PageName

9 |

@Model.TeaserText

10 | @Html.DisplayFor(m => m.PageImage) 11 | } 12 |
13 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/PagePartials/PageWide.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Core 2 | @using Alloy.Sample.Helpers 3 | @model Alloy.Sample.Models.Pages.SitePageData 4 |
5 | @using(Html.BeginConditionalLink(Model.HasTemplate(), Url.PageLinkUrl(Model), Model.PageName)) 6 | { 7 |
8 |
9 | @Html.DisplayFor(m => m.PageImage) 10 |
11 |

@Model.PageName

12 |

@Model.TeaserText

13 |
14 | } 15 |
16 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/Readonly.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.ViewModels.IPageViewModel 2 | 3 |
4 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/TemplateError.cshtml: -------------------------------------------------------------------------------- 1 | @model Alloy.Sample.Models.ViewModels.ContentRenderingErrorModel 2 |
3 |

@string.Format(System.Globalization.CultureInfo.CurrentUICulture, LocalizationService.Current.GetString("/renderingerror/heading"), Model.ContentTypeName, Model.ContentName)

4 |

5 | @Model.Exception.Message
6 | @Model.Exception.StackTrace 7 |

8 |
9 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/Shared/TemplateHint.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 |

@Model

3 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/StandardPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using Alloy.Sample 2 | @model Alloy.Sample.Models.ViewModels.PageViewModel 3 | 4 | @{ Layout = "~/Views/Shared/Layouts/_LeftNavigation.cshtml"; } 5 | 6 |

x.CurrentPage.PageName)>@Model.CurrentPage.PageName

7 |

x.CurrentPage.MetaDescription)>@Model.CurrentPage.MetaDescription

8 |
9 |
x.CurrentPage.MainBody)> 10 | @Html.DisplayFor(m => m.CurrentPage.MainBody) 11 |
12 |
13 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row", Tag = Global.ContentAreaTags.TwoThirdsWidth }) 14 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/StartPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using Alloy.Sample 2 | @model Alloy.Sample.Models.ViewModels.PageViewModel 3 | 4 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row equal-height", tag = Global.ContentAreaTags.FullWidth }) 5 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/_ReadMe.txt: -------------------------------------------------------------------------------- 1 | View locations in Alloy follows a number of conventions in addition to the default ASP.NET MVC conventions: 2 | * Views for pages and blocks with their own controllers use standard ASP.NET MVC conventions - /.cshtml 3 | * Page types which don't have their own controller are mapped to /Index.cshtml by DefaultPageController 4 | * Views for block types which don't have their own controllers are found in Shared/Blocks 5 | * Partial views for page types which don't have their own controllers for partial requests are found in Shared/PagePartials -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Framework.Localization 2 | @using EPiServer.Web.Mvc.Html 3 | @using EPiServer.Shell.Web.Mvc.Html 4 | @using EPiServer.Core 5 | @using EPiServer.Web 6 | @using EPiServer.Web.Mvc 7 | @using EPiServer.Web.Routing 8 | @using Microsoft.AspNetCore.Mvc.Razor 9 | @using Microsoft.AspNetCore.Html 10 | 11 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 12 | -------------------------------------------------------------------------------- /src/Alloy.Sample/Views/_viewstart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/Layouts/_Root.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /src/Alloy.Sample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "EPiServer": "Information", 7 | "Microsoft.Hosting.Lifetime": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Alloy.Sample/bundleconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputFileName": "wwwroot/css/css.min.css", 4 | "inputFiles": [ 5 | "wwwroot/css/bootstrap.css", 6 | "wwwroot/css/bootstrap-responsive.css", 7 | "wwwroot/css/media.css", 8 | "wwwroot/css/style.css", 9 | "wwwroot/css/editmode.css" 10 | ] 11 | }, 12 | { 13 | "outputFileName": "wwwroot/js/script.min.js", 14 | "inputFiles": [ 15 | "wwwroot/js/jquery.js", 16 | "wwwroot/js/bootstrap.js" 17 | ], 18 | "minify": { 19 | "enabled": true, 20 | "renameLocals": true 21 | }, 22 | "sourceMap": false 23 | } 24 | ] -------------------------------------------------------------------------------- /src/Alloy.Sample/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | sql: 4 | build: 5 | dockerfile: ./docker/Sql.Dockerfile 6 | context: . 7 | # comment out below lines if you want to expose sql server port for using with other tool like sql server managemnent studio 8 | # ports: 9 | # - "1433:1433" 10 | environment: 11 | SA_PASSWORD: ${sa_password} 12 | ACCEPT_EULA: "Y" 13 | DATABASE_NAME: ${database_name} 14 | image: alloy/db:${sql_tag} 15 | web: 16 | build: 17 | dockerfile: ./docker/Web.Dockerfile 18 | context: . 19 | ports: 20 | - "${site_port}:8000" 21 | environment: 22 | ConnectionStrings__EPiServerDB: "Server=sql;Database=${database_name};User Id=sa;Password=${sa_password};MultipleActiveResultSets=True;" 23 | depends_on: 24 | - sql 25 | image: alloy/web:${web_tag} 26 | -------------------------------------------------------------------------------- /src/Alloy.Sample/docker/Sql.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/mssql/server:2019-latest AS base 2 | 3 | USER root 4 | 5 | ENV ACCEPT_EULA=Y 6 | ENV MSSQL_TCP_PORT=1433 7 | EXPOSE 1433 8 | 9 | WORKDIR /src 10 | COPY ./docker/build-script/attach_db.sh /docker/attach_db.sh 11 | COPY ./App_Data/Alloy.mdf /docker/Alloy.mdf 12 | 13 | RUN chmod -R 777 /docker/. 14 | 15 | ENTRYPOINT /docker/attach_db.sh & /opt/mssql/bin/sqlservr 16 | -------------------------------------------------------------------------------- /src/Alloy.Sample/docker/Web.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base 2 | WORKDIR /app 3 | EXPOSE 8000 4 | 5 | FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build 6 | WORKDIR /src 7 | COPY . . 8 | RUN dotnet restore "Alloy.Sample.csproj" 9 | 10 | RUN dotnet build "Alloy.Sample.csproj" -c Release -o /app/build 11 | 12 | FROM build AS publish 13 | RUN dotnet publish "Alloy.Sample.csproj" -c Release -o /app/publish 14 | COPY ./docker/build-script/wait_sqlserver_start_and_attachdb.sh /app/publish/wait_sqlserver_start_and_attachdb.sh 15 | COPY ./App_Data/DefaultSiteContent.episerverdata /app/publish/App_Data/DefaultSiteContent.episerverdata 16 | 17 | FROM base AS final 18 | WORKDIR /app 19 | COPY --from=publish /app/publish . 20 | #wait sql server container start and attach alloy database then start web 21 | ENTRYPOINT ./wait_sqlserver_start_and_attachdb.sh 22 | -------------------------------------------------------------------------------- /src/Alloy.Sample/docker/build-script/attach_db.sh: -------------------------------------------------------------------------------- 1 | sleep 30s 2 | /opt/mssql-tools/bin/sqlcmd -S . -U sa -P ${SA_PASSWORD} \ 3 | -Q "CREATE DATABASE [${DATABASE_NAME}] ON (FILENAME ='/docker/Alloy.mdf') FOR ATTACH" -------------------------------------------------------------------------------- /src/Alloy.Sample/docker/build-script/wait_sqlserver_start_and_attachdb.sh: -------------------------------------------------------------------------------- 1 | 2 | sleep 120s 3 | echo "Connection string:" $ConnectionStrings__EPiServerDB 4 | echo "Site port:" $site_port 5 | dotnet Alloy.Sample.dll 6 | -------------------------------------------------------------------------------- /src/Alloy.Sample/docker/run-script/build.sh: -------------------------------------------------------------------------------- 1 | docker load -i alloy-db.tar 2 | docker load -i alloy-web.tar -------------------------------------------------------------------------------- /src/Alloy.Sample/docker/run-script/build_web_only.sh: -------------------------------------------------------------------------------- 1 | docker load -i alloy-web.tar -------------------------------------------------------------------------------- /src/Alloy.Sample/docker/run-script/run.sh: -------------------------------------------------------------------------------- 1 | docker-compose -f docker-compose.yml up -d -------------------------------------------------------------------------------- /src/Alloy.Sample/docker/run-script/stop.sh: -------------------------------------------------------------------------------- 1 | docker-compose -f docker-compose.yml down -------------------------------------------------------------------------------- /src/Alloy.Sample/docker/run-script/stop_web_only.sh: -------------------------------------------------------------------------------- 1 | docker-compose rm -f -s -v web -------------------------------------------------------------------------------- /src/Alloy.Sample/favicon.ico: -------------------------------------------------------------------------------- 1 | h( ...>>>Ɋ9777000$$$666!!!### -------------------------------------------------------------------------------- /src/Alloy.Sample/module.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Alloy.Sample/modulesbin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/modulesbin/.gitkeep -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/ClientResources/Images/icons/layoutIcons24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/ClientResources/Images/icons/layoutIcons24x24.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/ClientResources/Styles/LayoutIcons.css: -------------------------------------------------------------------------------- 1 | .Sleek .epi-icon__layout--full { 2 | background: url('../Images/icons/layoutIcons24x24.png') 0px -24px no-repeat; 3 | height: 24px; 4 | width: 24px; 5 | } 6 | .Sleek .epi-icon__layout--half { 7 | background: url('../Images/icons/layoutIcons24x24.png') 0px -48px no-repeat; 8 | height: 24px; 9 | width: 24px; 10 | } 11 | .Sleek .epi-icon__layout--two-thirds { 12 | background: url('../Images/icons/layoutIcons24x24.png') 0px -72px no-repeat; 13 | height: 24px; 14 | width: 24px; 15 | } 16 | .Sleek .epi-icon__layout--one-third { 17 | background: url('../Images/icons/layoutIcons24x24.png') 0px -96px no-repeat; 18 | height: 24px; 19 | width: 24px; 20 | } 21 | .Sleek .epi-icon__layout--one-quarter { 22 | background: url('../Images/icons/layoutIcons24x24.png') 0px -120px no-repeat; 23 | height: 24px; 24 | width: 24px; 25 | } 26 | -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/ClientResources/Styles/Styles.css: -------------------------------------------------------------------------------- 1 | @import url("LayoutIcons.css"); 2 | 3 | .epiStringList .dijitTextArea { 4 | width: 250px; 5 | } 6 | 7 | .epiStringList .epiStringListError .dijitTextArea { 8 | border: solid 1px #d46464; 9 | } 10 | -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/css/editmode.css: -------------------------------------------------------------------------------- 1 | /* CSS specific to edit mode, such as help content displayed to the editor */ 2 | 3 | .alert-info 4 | { 5 | border-color: #B8C0C5; 6 | color: black; 7 | font-family: Verdana; 8 | font-size: 1em; 9 | font-style: italic; 10 | background-color: #B8C0C5; 11 | box-shadow: 3px 3px 5px #CCC; 12 | text-align: center; 13 | } 14 | 15 | .alert-error p { 16 | text-align: left; 17 | } 18 | 19 | .alert-error .heading { 20 | font-weight: bold; 21 | color: #ff0000; 22 | } 23 | 24 | .alert-error .details { 25 | font-size: 0.8em; 26 | max-height: 100px; 27 | overflow: scroll; 28 | } 29 | 30 | .header.dim { 31 | margin: 2% 0; 32 | opacity: 0.3; 33 | } -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/css/editor.css: -------------------------------------------------------------------------------- 1 | /* Styles used by the TinyMCE editor */ 2 | 3 | h2 {EditMenuName:Header 2;} 4 | h3 {EditMenuName:Header 3;} 5 | 6 | /*Block Preview*/ 7 | .alert-info { 8 | background-color: #FFF8AA; 9 | border-color: #858585; 10 | color: #000000; 11 | font-family: Verdana; 12 | font-size: 12px; 13 | } 14 | 15 | .header.dim { 16 | margin: 2% 0; 17 | opacity: 0.3; 18 | } -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/New_FDT_Press_Contact_.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/New_FDT_Press_Contact_.JPG -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/carouselbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/carouselbackground.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/contact.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/contact.jpg -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/exampelspan4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/exampelspan4.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/experts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/experts.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/fallows-media-wide.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/fallows-media-wide.jpg -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/leader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/leader.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/leader2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/leader2.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/logotype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/logotype.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/meet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/meet.jpg -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/page-type-thumbnail-article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/page-type-thumbnail-article.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/page-type-thumbnail-contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/page-type-thumbnail-contact.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/page-type-thumbnail-product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/page-type-thumbnail-product.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/page-type-thumbnail-standard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/page-type-thumbnail-standard.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/page-type-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/page-type-thumbnail.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/person.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/plan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/plan.jpg -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/play.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/playInactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/playInactive.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/productLandingv2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/productLandingv2.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/searchbutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/searchbutton.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/searchbuttonsmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/searchbuttonsmall.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/gfx/track.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/gfx/track.jpg -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/jwplayer/player.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/jwplayer/player.swf -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/jwplayer/preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/jwplayer/preview.jpg -------------------------------------------------------------------------------- /src/Alloy.Sample/wwwroot/jwplayer/video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/src/Alloy.Sample/wwwroot/jwplayer/video.mp4 -------------------------------------------------------------------------------- /test.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | 4 | SET PATH=.\.ci\tools\;%PATH% 5 | 6 | REM Set Release or Debug configuration. 7 | IF "%1"=="Release" (set CONFIGURATION=Release) ELSE (set CONFIGURATION=Debug) 8 | ECHO Testing in %CONFIGURATION% 9 | 10 | ECHO Running c# tests 11 | CALL dotnet test test\Advanced.CMS.AdvancedReviews.IntegrationTests.Basic\Advanced.CMS.AdvancedReviews.IntegrationTests.Basic.csproj -c %CONFIGURATION% 12 | CALL dotnet test test\Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity\Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity.csproj -c %CONFIGURATION% 13 | IF %errorlevel% NEQ 0 EXIT /B %errorlevel% 14 | 15 | EXIT /B %ERRORLEVEL% 16 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.Basic/Advanced.CMS.AdvancedReviews.IntegrationTests.Basic.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net6.0 6 | false 7 | True 8 | False 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.Basic/IntegrationTestCollection.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.ServiceLocation; 2 | using Xunit; 3 | 4 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests.Basic; 5 | 6 | [CollectionDefinition(Name, DisableParallelization = true)] 7 | public class IntegrationTestCollection : ICollectionFixture 8 | { 9 | public const string Name = "Advanced Reviews Test Collection"; 10 | } 11 | 12 | [Collection(IntegrationTestCollection.Name)] 13 | public class IntegrationTestCollectionBaseClassFixture : IClassFixture 14 | { 15 | protected readonly SiteFixture _siteFixture; 16 | protected readonly TestScenarioBuilderFactory _testScenarioBuilderFactory; 17 | 18 | public IntegrationTestCollectionBaseClassFixture(SiteFixture siteFixture) 19 | { 20 | _siteFixture = siteFixture; 21 | _testScenarioBuilderFactory = ServiceLocator.Current.GetInstance(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.Basic/NoEveryoneAccess.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests.Basic; 7 | 8 | public class NoEveryoneAccess : IntegrationTestCollectionBaseClassFixture 9 | { 10 | public NoEveryoneAccess(SiteFixture siteFixture) : base(siteFixture) 11 | { 12 | } 13 | 14 | [Fact] 15 | public async Task When_Creating_Token_For_Page_Without_Everyone_Access_It_Returns_200() 16 | { 17 | var testEnvironment = _testScenarioBuilderFactory.GetBuilder().Init().WithoutEveryoneAccess().WithViewPin().Build(); 18 | 19 | var message = new HttpRequestMessage(HttpMethod.Get, testEnvironment.ExternalReviewLink.LinkUrl); 20 | var response = await _siteFixture.Client.SendAsync(message); 21 | await response.Content.ReadAsStringAsync(); 22 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.Basic/PreviewingLastContentVersion.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests.Basic; 2 | /* 3 | GIVEN: 4 | - draft page 5 | - external view link to page 6 | WHEN: 7 | - page is referencing draft content 8 | THEN: 9 | - should always show latest page version 10 | */ 11 | 12 | public class PreviewingLastContentVersion 13 | { 14 | // create REFERENCED page draft 15 | 16 | // create MAIN draft page 17 | 18 | // reference REFERENCED page in MAIN page ContentArea 19 | 20 | // create external link for MAIN page 21 | 22 | // ASSERT REFERENCED page is visible 23 | 24 | // publish REFERENCED page 25 | 26 | // ASSERT REFERENCED page is visible 27 | 28 | // update REFERENCED page, but don't publish 29 | 30 | // ASSERT REFERENCED page is visible 31 | } 32 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.Basic/PublishedPageWithDraftReferencesTests.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests.Basic; 2 | /* 3 | GIVEN: 4 | - draft/published page with ContentArea, ContentReference, LinkItemCollection 5 | - external view link to page 6 | WHEN: 7 | - page is referencing unpublished content in properties 8 | THEN: 9 | - should show draft references 10 | */ 11 | 12 | public class PublishedPageWithDraftReferencesTests 13 | { 14 | // create 3 draft pages 15 | 16 | // create MAIN draft page 17 | 18 | // reference 3 draft pages in ContentArea, ContentReference and LinkItemCollection 19 | 20 | // create external link 21 | 22 | // ASSERT draft content is visible on page 23 | 24 | // publish MAIN page 25 | 26 | // ASSERT draft content is visible on page 27 | 28 | } 29 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.Basic/SinglePageWithLanguagesTests.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests.Basic; 2 | /* 3 | GIVEN: 4 | - draft page 5 | - external view link to page 6 | WHEN: 7 | - page is translated 8 | THEN: 9 | - should load proper draft version 10 | */ 11 | 12 | public class SinglePageWithLanguagesTests 13 | { 14 | // create draft page 15 | 16 | // create external link for page 17 | 18 | // translate page 19 | 20 | // create external link for translated page 21 | 22 | // ASSERT main branch link shows proper data 23 | 24 | // ASSERT translated page shows proper data 25 | 26 | // publish main branch 27 | 28 | // change main branch data 29 | 30 | // publish translated page 31 | 32 | // change translated page data 33 | 34 | // ASSERT main branch link shows proper data 35 | 36 | // ASSERT translated page shows proper data 37 | } 38 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.Basic/SiteFixture.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests.Basic; 2 | 3 | public class SiteFixture : SiteFixtureBase 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.Basic/TinyMceShowDraftLinksTests.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests.Basic; 2 | /* 3 | GIVEN: 4 | - draft page with TinyMce 5 | - external view link to page 6 | WHEN: 7 | - TinyMce property contains unpublished images 8 | THEN: 9 | - should show draft content in TinyMce 10 | */ 11 | 12 | public class TinyMceShowDraftLinksTests 13 | { 14 | // create an image 15 | 16 | // create MAIN draft page 17 | 18 | // add image link to TinyMce 19 | 20 | // create external link 21 | 22 | // ASSERT draft image is visible 23 | } 24 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.Basic/TokenExpiration.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests.Basic; 7 | 8 | public class TokenExpiration : IntegrationTestCollectionBaseClassFixture 9 | { 10 | public TokenExpiration(SiteFixture siteFixture) : base(siteFixture) 11 | { 12 | } 13 | 14 | [Fact] 15 | public async Task When_DraftPage_Created_Link_Is_Expired_It_Returns_404() 16 | { 17 | var testEnvironment = _testScenarioBuilderFactory.GetBuilder().Init().WithViewPin().PinExpired().Build(); 18 | 19 | var messageAfterTokenExpiration = new HttpRequestMessage(HttpMethod.Get, testEnvironment.ExternalReviewLink.LinkUrl); 20 | var responseAfterTokenExpiration = await _siteFixture.Client.SendAsync(messageAfterTokenExpiration); 21 | await responseAfterTokenExpiration.Content.ReadAsStringAsync(); 22 | Assert.Equal(HttpStatusCode.NotFound, responseAfterTokenExpiration.StatusCode); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity/Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | false 6 | True 7 | False 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity/IntegrationTestCollection.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity; 4 | 5 | [CollectionDefinition(Name, DisableParallelization = true)] 6 | public class IntegrationTestCollectionWithPinSecurity : ICollectionFixture 7 | { 8 | public const string Name = "Advanced Reviews Test Collection With Pin Security"; 9 | } 10 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity; 6 | 7 | public static class ObjectExtensions 8 | { 9 | public static IDictionary AsDictionary(this object source, BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) 10 | { 11 | return (IDictionary)source.GetType().GetProperties(bindingAttr).ToDictionary 12 | ( 13 | propInfo => propInfo.Name, 14 | propInfo => (T)propInfo.GetValue(source, null)); 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity/SiteFixtureWithPinSecurity.cs: -------------------------------------------------------------------------------- 1 | using Advanced.CMS.ExternalReviews; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests.PinSecurity; 5 | 6 | public class SiteFixtureWithPinSecurity : SiteFixtureBase 7 | { 8 | public SiteFixtureWithPinSecurity() : base(OptionsCallback) 9 | { 10 | } 11 | 12 | private static void OptionsCallback(ExternalReviewOptions options) 13 | { 14 | options.PinCodeSecurity.Enabled = true; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests/Advanced.CMS.AdvancedReviews.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net6.0 6 | false 7 | True 8 | False 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests/Assets/db_template.mdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/advanced-cms/advanced-reviews/2cc087131f15c2b71bd85ac364c05fd1fed079a4/test/Advanced.CMS.AdvancedReviews.IntegrationTests/Assets/db_template.mdf -------------------------------------------------------------------------------- /test/Advanced.CMS.AdvancedReviews.IntegrationTests/TestEnvironment.cs: -------------------------------------------------------------------------------- 1 | using Advanced.CMS.ExternalReviews.ReviewLinksRepository; 2 | using TestSite.Models; 3 | 4 | namespace Advanced.CMS.AdvancedReviews.IntegrationTests; 5 | 6 | public class TestEnvironment 7 | { 8 | public TestEnvironment(StandardPage page, ExternalReviewLink externalReviewLink) 9 | { 10 | Page = page; 11 | ExternalReviewLink = externalReviewLink; 12 | } 13 | 14 | public StandardPage Page { get; set; } 15 | 16 | public ExternalReviewLink ExternalReviewLink { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /test/Advanced.CMS.IntegrationTests/Advanced.CMS.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | net6.0 6 | false 7 | True 8 | False 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/Advanced.CMS.IntegrationTests/CmsDatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced.CMS.IntegrationTests 2 | { 3 | public sealed class CmsDatabaseFixture : DatabaseFixture 4 | { 5 | public CmsDatabaseFixture(string databaseMdfTemplateFile, string destinationDatabaseFile) 6 | { 7 | CMS_MDF_FILE_PATH = databaseMdfTemplateFile; 8 | DESTINATION_MDF_PATH = destinationDatabaseFile; 9 | DropExistingDatabases(DESTINATION_MDF_PATH); 10 | CopyDatabaseFiles(); 11 | SetFolderAccess(); 12 | CreateDatabase(DESTINATION_MDF_PATH); 13 | } 14 | protected override string CMS_MDF_FILE_PATH { get; } 15 | protected override string DESTINATION_MDF_PATH { get; } 16 | protected override string CONNECTION_STRING_TEMPLATE { get; } 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/Advanced.CMS.IntegrationTests/ServiceMocks/MockableContentAccessChecker.cs: -------------------------------------------------------------------------------- 1 | using EPiServer; 2 | using EPiServer.Core; 3 | using EPiServer.Core.Internal; 4 | using EPiServer.Security; 5 | 6 | namespace Advanced.CMS.IntegrationTests.ServiceMocks; 7 | 8 | public class MockableContentAccessChecker : ContentAccessChecker 9 | { 10 | public bool Enabled { get; set; } 11 | 12 | public MockableContentAccessChecker(IContentLoader contentLoader, IPrincipalAccessor principalAccessor) : base( 13 | contentLoader, principalAccessor) 14 | { 15 | } 16 | 17 | public override bool HasSufficientAccess(ContentProvider provider, ContentReference contentLink, AccessLevel access) 18 | { 19 | return Enabled || base.HasSufficientAccess(provider, contentLink, access); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Advanced.CMS.IntegrationTests/ServiceMocks/MockableCurrentProject.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Cms.Shell.UI.Rest.Projects; 2 | using EPiServer.DataAbstraction; 3 | using EPiServer.ServiceLocation; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | namespace Advanced.CMS.IntegrationTests.ServiceMocks; 7 | 8 | public class MockableCurrentProject : CurrentProject 9 | { 10 | private int? projectId; 11 | 12 | public MockableCurrentProject(IHttpContextAccessor httpContextAccessor, 13 | ServiceAccessor projectRepositoryAccessor) : base(httpContextAccessor, 14 | projectRepositoryAccessor) 15 | { 16 | } 17 | 18 | public override int? ProjectId => projectId; 19 | 20 | public void SetProject(int? value) 21 | { 22 | projectId = value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/Advanced.CMS.IntegrationTests/ServiceMocks/MockableProjectLoaderService.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Cms.Shell.UI.Rest.Projects; 2 | using EPiServer.Cms.Shell.UI.Rest.Projects.Internal; 3 | using EPiServer.Data; 4 | using EPiServer.DataAbstraction; 5 | using EPiServer.ServiceLocation; 6 | 7 | namespace Advanced.CMS.IntegrationTests.ServiceMocks; 8 | 9 | public class MockableProjectLoaderService : ProjectLoaderService 10 | { 11 | public MockableProjectLoaderService(ProjectRepository projectRepository, 12 | ISiteConfigurationRepository siteConfigurationRepository, 13 | ProjectUIOptions projectUIOptions, IDatabaseMode databaseMode) : base(projectRepository, 14 | ServiceLocator.Current.GetInstance(), 15 | ServiceLocator.Current.GetInstance(), 16 | siteConfigurationRepository, projectUIOptions, databaseMode) 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/Advanced.CMS.IntegrationTests/ServiceMocks/MockableProjectService.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Cms.Shell.UI.Rest; 2 | using EPiServer.Cms.Shell.UI.Rest.Approvals.Internal; 3 | using EPiServer.Cms.Shell.UI.Rest.Projects; 4 | using EPiServer.Cms.Shell.UI.Rest.Projects.Internal; 5 | using EPiServer.Core; 6 | using EPiServer.Data; 7 | using EPiServer.DataAbstraction; 8 | using EPiServer.Framework.Localization; 9 | using EPiServer.ServiceLocation; 10 | 11 | namespace Advanced.CMS.IntegrationTests.ServiceMocks; 12 | 13 | public class MockableProjectService : ProjectService 14 | { 15 | public MockableProjectService(ProjectRepository projectRepository, ProjectPublisher projectPublisher, 16 | IContentChangeManager contentChangeManager, 17 | LanguageSelectorFactory languageSelectorFactory, 18 | ISiteConfigurationRepository siteConfigurationRepository, ApprovalService approvalService, 19 | LocalizationService localizationService, ProjectUIOptions projectUIOptions, IDatabaseMode databaseMode) : base( 20 | projectRepository, 21 | projectPublisher, ServiceLocator.Current.GetInstance(), contentChangeManager, 22 | languageSelectorFactory, ServiceLocator.Current.GetInstance(), 23 | siteConfigurationRepository, approvalService, localizationService, projectUIOptions, databaseMode) 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/Advanced.CMS.IntegrationTests/SolutionPathUtility.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. 3 | // Derived from Microsoft.AspNetCore.Mvc.TestCommon.SolutionPathUtility 4 | 5 | using System; 6 | using System.IO; 7 | 8 | namespace Advanced.CMS.IntegrationTests 9 | { 10 | public static class SolutionPathUtility 11 | { 12 | public static string GetSolutionPath(string solutionRelativePath, string solutionName) 13 | { 14 | var applicationBasePath = Environment.CurrentDirectory; // Microsoft.Extensions.PlatformAbstractions.PlatformServices.Default.Application.ApplicationBasePath; 15 | 16 | var directoryInfo = new DirectoryInfo(applicationBasePath); 17 | do 18 | { 19 | var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, solutionName)); 20 | if (solutionFileInfo.Exists) 21 | { 22 | return Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath)); 23 | } 24 | 25 | directoryInfo = directoryInfo.Parent; 26 | } 27 | while (directoryInfo.Parent != null); 28 | 29 | throw new Exception($"Solution root could not be located using current application directory {applicationBasePath}."); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Advanced.CMS.IntegrationTests/SwitchableDatabaseMode.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Data; 2 | 3 | namespace Advanced.CMS.IntegrationTests 4 | { 5 | public class SwitchableDatabaseMode : IDatabaseMode 6 | { 7 | private readonly IDatabaseMode _inner; 8 | 9 | public SwitchableDatabaseMode(IDatabaseMode inner) 10 | { 11 | _inner = inner; 12 | } 13 | 14 | public DatabaseMode? ManualMode { get; set; } 15 | 16 | public DatabaseMode DatabaseMode => ManualMode ?? _inner.DatabaseMode; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/sites/TestSite/.gitignore: -------------------------------------------------------------------------------- 1 | modules/_protected/CMS/** 2 | modules/_protected/EPiServer.Cms.TinyMce/** 3 | modules/_protected/EPiServer.Cms.UI.Admin/** 4 | modules/_protected/EPiServer.Cms.UI.Settings/** 5 | modules/_protected/EPiServer.Cms.UI.VisitorGroups/** 6 | modules/_protected/Shell/** 7 | -------------------------------------------------------------------------------- /test/sites/TestSite/Controllers/StandardPageController.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Web.Mvc; 2 | using Microsoft.AspNetCore.Mvc; 3 | using TestSite.Models; 4 | 5 | namespace TestSite.Controllers 6 | { 7 | public class StandardPageController : PageController 8 | { 9 | public IActionResult Index(StandardPage currentPage) 10 | { 11 | return View(currentPage); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/sites/TestSite/Controllers/StartPageController.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Web.Mvc; 2 | using Microsoft.AspNetCore.Mvc; 3 | using TestSite.Models; 4 | 5 | namespace TestSite.Controllers 6 | { 7 | public class StartPageController : PageController 8 | { 9 | public IActionResult Index(StartPage currentPage) 10 | { 11 | return View(currentPage); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/sites/TestSite/Models/EditorialBlock.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.DataAnnotations; 3 | 4 | namespace TestSite.Models; 5 | 6 | [ContentType(GUID = "541C1C77-0040-4D05-AF42-14C165D882C7")] 7 | public class EditorialBlock : BlockData 8 | { 9 | public virtual string MainBody { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /test/sites/TestSite/Models/GenericFile.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.DataAnnotations; 3 | using EPiServer.Framework.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations; 5 | 6 | namespace TestSite.Models 7 | { 8 | [ContentType] 9 | public class GenericFile : MediaData 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/sites/TestSite/Models/ImageFile.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.DataAnnotations; 3 | using EPiServer.Framework.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations; 5 | 6 | namespace TestSite.Models 7 | { 8 | [ContentType] 9 | [MediaDescriptor(ExtensionString = "jpg,jpeg,jpe,ico,gif,bmp,png")] 10 | public class ImageFile : ImageData 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/sites/TestSite/Models/StandardPage.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.DataAnnotations; 3 | 4 | namespace TestSite.Models 5 | { 6 | [ContentType(GUID = "85FC1C77-0040-4D05-AF42-14C165D882C7")] 7 | public class StandardPage : PageData 8 | { 9 | public virtual ContentArea ContentArea { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/sites/TestSite/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using EPiServer.DependencyInjection; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Hosting; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace TestSite 13 | { 14 | public class Program 15 | { 16 | public static void Main(string[] args) 17 | { 18 | CreateHostBuilder(args).Build().Run(); 19 | } 20 | 21 | public static IHostBuilder CreateHostBuilder(string[] args) => 22 | Host.CreateDefaultBuilder(args) 23 | .ConfigureCmsDefaults() 24 | .ConfigureWebHostDefaults(webBuilder => 25 | { 26 | webBuilder.UseStartup(); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/sites/TestSite/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:29966", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "TestSite": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/sites/TestSite/SiteViewEngineLocationExpander.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Mvc.Razor; 3 | 4 | namespace TestSite.Business.Rendering 5 | { 6 | public class SiteViewEngineLocationExpander : IViewLocationExpander 7 | { 8 | public const string BlockFolder = "~/Views/Shared/Blocks/"; 9 | public const string PagePartialsFolder = "~/Views/Shared/PagePartials/"; 10 | 11 | private static readonly string[] AdditionalPartialViewFormats = new[] 12 | { 13 | BlockFolder + "{0}.cshtml", 14 | PagePartialsFolder + "{0}.cshtml" 15 | }; 16 | 17 | public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) 18 | { 19 | foreach (var location in viewLocations) 20 | { 21 | yield return location; 22 | } 23 | 24 | for (int i = 0; i < AdditionalPartialViewFormats.Length; i++) 25 | { 26 | yield return AdditionalPartialViewFormats[i]; 27 | } 28 | } 29 | public void PopulateValues(ViewLocationExpanderContext context) { } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/sites/TestSite/SkipAntiforgeryFilterProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Mvc.Filters; 4 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 5 | 6 | namespace TestSite 7 | { 8 | public class SkipAntiforgeryFilterProvider : IFilterProvider 9 | { 10 | public int Order => int.MaxValue; 11 | 12 | public void OnProvidersExecuted(FilterProviderContext context) 13 | { 14 | } 15 | 16 | public void OnProvidersExecuting(FilterProviderContext context) 17 | { 18 | if (context == null) 19 | { 20 | throw new ArgumentNullException(nameof(context)); 21 | } 22 | 23 | var FilterDescriptor = new FilterDescriptor(SkipAntiforgeryPolicy.Instance, FilterScope.Last); 24 | var filterItem = new FilterItem(FilterDescriptor, SkipAntiforgeryPolicy.Instance); 25 | context.Results.Add(filterItem); 26 | } 27 | 28 | class SkipAntiforgeryPolicy : IAntiforgeryPolicy, IAsyncAuthorizationFilter 29 | { 30 | public static readonly SkipAntiforgeryPolicy Instance = new SkipAntiforgeryPolicy(); 31 | 32 | public Task OnAuthorizationAsync(AuthorizationFilterContext context) => Task.CompletedTask; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/sites/TestSite/TestSite.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | false 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/sites/TestSite/Views/Shared/Blocks/EditorialBlock.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Web.Mvc.Html 2 | @model TestSite.Models.EditorialBlock 3 | 4 |

AAAAAAAAAAAAAA

5 | 6 |
x.MainBody)> 7 | EEEEE 8 | @Html.DisplayFor(x => Model.MainBody) 9 |
10 | -------------------------------------------------------------------------------- /test/sites/TestSite/Views/StandardPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Web.Mvc.Html 2 | @model TestSite.Models.StandardPage 3 | 4 | 5 | 6 | 7 | 8 | @Model.Name 9 | 10 | 11 |

@Html.PropertyFor(x => x.Name)

12 | @if (Model.ContentArea != null) 13 | { 14 |

Fragments count = @Model.ContentArea.Count

15 | @Html.PropertyFor(x => x.ContentArea) 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/sites/TestSite/Views/StartPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Web.Mvc.Html 2 | @model TestSite.Models.StartPage 3 | 4 | 5 | 6 | 7 | 8 | @Model.MetaTitle 9 | 10 | 11 |

@Html.PropertyFor(x => x.Name)

12 | @Html.PropertyFor(x => x.MainContentArea) 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/sites/TestSite/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/sites/TestSite/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /test/sites/TestSite/module.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/sites/TestSite/wwwroot/Dummy.txt: -------------------------------------------------------------------------------- 1 | If wwwroot does not containe any files IWebHostingEnvironment.WebRootPath is null 2 | --------------------------------------------------------------------------------