diff --git a/webapp-java/build.gradle b/webapp-java/build.gradle
index 115ed51ba..ca6f90d57 100644
--- a/webapp-java/build.gradle
+++ b/webapp-java/build.gradle
@@ -35,7 +35,8 @@ task buildModules(type: MavenBuildTask) {
"dxa-module-googleanalytics",
"dxa-module-mediamanager",
"dxa-module-search",
- "dxa-module-51degrees"
+ "dxa-module-51degrees",
+ "dxa-module-ugc"
],
["dxa-module-test"]
]
diff --git a/webapp-java/dxa-module-51degrees/pom.xml b/webapp-java/dxa-module-51degrees/pom.xml
index 565321b4e..f0d74fe81 100644
--- a/webapp-java/dxa-module-51degrees/pom.xml
+++ b/webapp-java/dxa-module-51degrees/pom.xml
@@ -7,7 +7,7 @@
com.sdl.dxa
dxa-oss-parent
- 2.0.0
+ 2.1.0-SNAPSHOT
com.sdl.dxa.modules
diff --git a/webapp-java/dxa-module-audience-manager/pom.xml b/webapp-java/dxa-module-audience-manager/pom.xml
index bb730e246..b95fc37b4 100644
--- a/webapp-java/dxa-module-audience-manager/pom.xml
+++ b/webapp-java/dxa-module-audience-manager/pom.xml
@@ -5,7 +5,7 @@
com.sdl.dxa
dxa-oss-parent
- 2.0.0
+ 2.1.0-SNAPSHOT
com.sdl.dxa.modules
diff --git a/webapp-java/dxa-module-cid/pom.xml b/webapp-java/dxa-module-cid/pom.xml
index 428cbcf45..76fc09ae8 100644
--- a/webapp-java/dxa-module-cid/pom.xml
+++ b/webapp-java/dxa-module-cid/pom.xml
@@ -6,7 +6,7 @@
com.sdl.dxa
dxa-oss-parent
- 2.0.0
+ 2.1.0-SNAPSHOT
com.sdl.dxa.modules
diff --git a/webapp-java/dxa-module-cid/src/main/java/com/sdl/dxa/modules/cid/CidWebInitializer.java b/webapp-java/dxa-module-cid/src/main/java/com/sdl/dxa/modules/cid/CidWebInitializer.java
index 6cc7d2afd..767711d94 100644
--- a/webapp-java/dxa-module-cid/src/main/java/com/sdl/dxa/modules/cid/CidWebInitializer.java
+++ b/webapp-java/dxa-module-cid/src/main/java/com/sdl/dxa/modules/cid/CidWebInitializer.java
@@ -7,13 +7,12 @@
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
import java.util.Properties;
@Slf4j
public class CidWebInitializer implements WebApplicationInitializer {
@Override
- public void onStartup(ServletContext servletContext) throws ServletException {
+ public void onStartup(ServletContext servletContext) {
log.debug("Trying to initialize servlet for CID module");
Properties properties = InitializationUtils.loadDxaProperties();
@@ -34,8 +33,6 @@ public void onStartup(ServletContext servletContext) throws ServletException {
log.warn("Class {} for CID module is expected to be either Filter of Servlet but is", className);
}
- String sessionIdName = InitializationUtils.loadDxaProperties().getProperty("dxa.modules.cid.sessionid.name");
- servletContext.getSessionCookieConfig().setName(sessionIdName);
- log.info("Set default SESSIONID to {}", sessionIdName);
+ // session id rename has moved to DXA Web Initialization
}
}
diff --git a/webapp-java/dxa-module-cid/src/main/resources/dxa.modules.cid.properties b/webapp-java/dxa-module-cid/src/main/resources/dxa.modules.cid.properties
index 8d6b1b5bf..9d01fbcc1 100644
--- a/webapp-java/dxa-module-cid/src/main/resources/dxa.modules.cid.properties
+++ b/webapp-java/dxa-module-cid/src/main/resources/dxa.modules.cid.properties
@@ -1,4 +1,5 @@
dxa.modules.cid.mapping=/cid/*
-dxa.modules.cid.sessionid.name=DXA-SESSIONID
+# need to rename it in DXA because of CID bug
+dxa.web.sessionid.name=DXA-SESSIONID
#dxa.modules.cid.className=should be configured it in dxa.properties file because if version-specific
#dxa.modules.cid.appHostMapping=external path to the server which runs DXA for back-mapping for CID Service
\ No newline at end of file
diff --git a/webapp-java/dxa-module-context-expressions/pom.xml b/webapp-java/dxa-module-context-expressions/pom.xml
index 0682fdacc..86eb5aeac 100644
--- a/webapp-java/dxa-module-context-expressions/pom.xml
+++ b/webapp-java/dxa-module-context-expressions/pom.xml
@@ -7,7 +7,7 @@
com.sdl.dxa
dxa-oss-parent
- 2.0.0
+ 2.1.0-SNAPSHOT
com.sdl.dxa.modules
diff --git a/webapp-java/dxa-module-core/pom.xml b/webapp-java/dxa-module-core/pom.xml
index 3e95d1fcd..233a46bf6 100644
--- a/webapp-java/dxa-module-core/pom.xml
+++ b/webapp-java/dxa-module-core/pom.xml
@@ -6,7 +6,7 @@
com.sdl.dxa
dxa-oss-parent
- 2.0.0
+ 2.1.0-SNAPSHOT
com.sdl.dxa.modules
diff --git a/webapp-java/dxa-module-googleanalytics/pom.xml b/webapp-java/dxa-module-googleanalytics/pom.xml
index 1a64c1ed3..af702c917 100644
--- a/webapp-java/dxa-module-googleanalytics/pom.xml
+++ b/webapp-java/dxa-module-googleanalytics/pom.xml
@@ -5,7 +5,7 @@
com.sdl.dxa
dxa-oss-parent
- 2.0.0
+ 2.1.0-SNAPSHOT
com.sdl.dxa.modules
diff --git a/webapp-java/dxa-module-mediamanager/pom.xml b/webapp-java/dxa-module-mediamanager/pom.xml
index 22bbda770..2a03d141c 100644
--- a/webapp-java/dxa-module-mediamanager/pom.xml
+++ b/webapp-java/dxa-module-mediamanager/pom.xml
@@ -6,7 +6,7 @@
com.sdl.dxa
dxa-oss-parent
- 2.0.0
+ 2.1.0-SNAPSHOT
com.sdl.dxa.modules
diff --git a/webapp-java/dxa-module-search/pom.xml b/webapp-java/dxa-module-search/pom.xml
index d667ce9be..5e76a46f7 100644
--- a/webapp-java/dxa-module-search/pom.xml
+++ b/webapp-java/dxa-module-search/pom.xml
@@ -5,7 +5,7 @@
com.sdl.dxa
dxa-oss-parent
- 2.0.0
+ 2.1.0-SNAPSHOT
com.sdl.dxa.modules
diff --git a/webapp-java/dxa-module-smarttarget/pom.xml b/webapp-java/dxa-module-smarttarget/pom.xml
index c5bbad9a0..649c9c6f1 100644
--- a/webapp-java/dxa-module-smarttarget/pom.xml
+++ b/webapp-java/dxa-module-smarttarget/pom.xml
@@ -6,7 +6,7 @@
com.sdl.dxa
dxa-oss-parent
- 2.0.0
+ 2.1.0-SNAPSHOT
com.sdl.dxa.modules
diff --git a/webapp-java/dxa-module-smarttarget/src/test/java/com/sdl/dxa/modules/smarttarget/mapping/SmartTargetPageBuilderTest.java b/webapp-java/dxa-module-smarttarget/src/test/java/com/sdl/dxa/modules/smarttarget/mapping/SmartTargetPageBuilderTest.java
index 767b1937a..ceb2b4dfe 100644
--- a/webapp-java/dxa-module-smarttarget/src/test/java/com/sdl/dxa/modules/smarttarget/mapping/SmartTargetPageBuilderTest.java
+++ b/webapp-java/dxa-module-smarttarget/src/test/java/com/sdl/dxa/modules/smarttarget/mapping/SmartTargetPageBuilderTest.java
@@ -101,7 +101,7 @@ public void shouldHavePositiveePriority() {
public void shouldReturnNullIfPageModelIsNull() {
//when, then
//noinspection ConstantConditions
- assertNull(pageBuilder.buildPageModel(null, new PageModelData("", null, null, null, null, null, null)));
+ assertNull(pageBuilder.buildPageModel(null, new PageModelData("", "tcm", null, null, null, null, null, null)));
}
@Test
@@ -112,7 +112,7 @@ public void shouldNotChangePageModelWithoutSTRegionsOnPage() throws DxaException
PageModel expected = createPageModel(new RegionModelImpl("test"));
//when
- PageModel page2 = pageBuilder.buildPageModel(pageModel, new PageModelData("", null, null, null, null, null, null));
+ PageModel page2 = pageBuilder.buildPageModel(pageModel, new PageModelData("", "tcm", null, null, null, null, null, null));
//then
Assert.assertEquals(expected, pageModel);
@@ -134,7 +134,7 @@ public void shouldNotChangePageModelWithoutRegionsMetadata() throws DxaException
PageModel expected = createPageModel(new SmartTargetRegion("test"));
//when
- PageModel pageR2 = pageBuilder.buildPageModel(pageModel, new PageModelData("", null, null, null, null, null, ""));
+ PageModel pageR2 = pageBuilder.buildPageModel(pageModel, new PageModelData("", "tcm", null, null, null, null, null, ""));
//then
Assert.assertEquals(expected, pageModel);
@@ -148,7 +148,7 @@ private void shouldCallSubclassForSmartTargetAndProcessMetadata_R2(String maxIte
RegionModelData regionModelData = RegionModelData.builder().name("test").metadata(new ContentModelData() {{
put("maxItems", maxItemsValue);
}}).build();
- PageModelData pageModelData = new PageModelData("id", null, Collections.emptyMap(), null, "title", Lists.newArrayList(regionModelData), "");
+ PageModelData pageModelData = new PageModelData("id", "tcm", null, Collections.emptyMap(), null, "title", Lists.newArrayList(regionModelData), "");
SmartTargetPageModel stPageModel = Mockito.mock(SmartTargetPageModel.class);
Mockito.when(stPageModel.setAllowDuplicates(Matchers.anyBoolean())).thenReturn(stPageModel);
diff --git a/webapp-java/dxa-module-test/pom.xml b/webapp-java/dxa-module-test/pom.xml
index 87a1dfcf9..c3693bf32 100644
--- a/webapp-java/dxa-module-test/pom.xml
+++ b/webapp-java/dxa-module-test/pom.xml
@@ -7,7 +7,7 @@
com.sdl.dxa
dxa-oss-parent
- 2.0.0
+ 2.1.0-SNAPSHOT
com.sdl.dxa.modules
diff --git a/webapp-java/dxa-module-ugc/pom.xml b/webapp-java/dxa-module-ugc/pom.xml
new file mode 100644
index 000000000..c5736a733
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/pom.xml
@@ -0,0 +1,88 @@
+
+
+ 4.0.0
+
+ com.sdl.dxa
+ dxa-oss-parent
+ 2.1.0-SNAPSHOT
+
+
+ com.sdl.dxa.modules
+ dxa-module-ugc
+
+ DXA Modules - UGC
+ Implementation of DXA UGC module
+
+
+ https://github.com/sdl/dxa-modules
+ scm:git:git@github.com:sdl/dxa-modules.git
+ scm:git:git@github.com:sdl/dxa-modules.git
+ HEAD
+
+
+ 11.0.0-SNAPSHOT
+
+
+
+
+ com.sdl.dxa
+ dxa-common-api
+
+
+ com.sdl.dxa
+ dxa-tridion-provider
+
+
+
+ com.sdl.delivery
+ udp-cil-api
+ ${ugc-cil.version}
+
+
+ com.sdl.delivery
+ udp-ugc-cil-api
+ ${ugc-cil.version}
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+ javax.servlet
+ jstl
+
+
+
+
+ org.springframework
+ spring-test
+
+
+
+ junit
+ junit
+
+
+ org.mockito
+ mockito-all
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/UgcInitializer.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/UgcInitializer.java
new file mode 100644
index 000000000..7260ee996
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/UgcInitializer.java
@@ -0,0 +1,50 @@
+package com.sdl.dxa.modules.ugc;
+
+import com.sdl.delivery.ugc.client.comment.UgcCommentApi;
+import com.sdl.delivery.ugc.client.comment.impl.DefaultUgcCommentApi;
+import com.sdl.dxa.modules.ugc.model.entity.UgcComments;
+import com.sdl.dxa.modules.ugc.model.entity.UgcPostCommentForm;
+import com.sdl.dxa.modules.ugc.model.entity.UgcRegion;
+import com.sdl.webapp.common.api.mapping.views.AbstractModuleInitializer;
+import com.sdl.webapp.common.api.mapping.views.ModuleInfo;
+import com.sdl.webapp.common.api.mapping.views.RegisteredViewModel;
+import com.sdl.webapp.common.api.mapping.views.RegisteredViewModels;
+import com.sdl.webapp.common.api.model.page.DefaultPageModel;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+/**
+ *
Ugc Module initializer which initializes
+ * views registration in modules with {@link RegisteredViewModel} and {@link RegisteredViewModels}.
+ *
+ */
+@Slf4j
+@Configuration
+@ComponentScan("com.sdl.dxa.modules.ugc")
+public class UgcInitializer {
+
+ @Bean
+ public UgcCommentApi ugcCommentApi() {
+ return new DefaultUgcCommentApi();
+ }
+
+ //Todo: use UgcVoteCommentApi implementation when it becomes available
+
+ @Component
+ @RegisteredViewModels({
+ @RegisteredViewModel(viewName = "Comments", modelClass = UgcRegion.class),
+ @RegisteredViewModel(viewName = "UgcComments", modelClass = UgcComments.class),
+ @RegisteredViewModel(viewName = "UgcPostCommentForm", modelClass = UgcPostCommentForm.class, controllerName = "Ugc"),
+ @RegisteredViewModel(viewName = "GeneralPage", modelClass = DefaultPageModel.class)
+ })
+ @ModuleInfo(name = "UGC module", areaName = "Ugc", description = "Support for UGC views")
+ public static class UgcViewModuleInitializer extends AbstractModuleInitializer {
+ @Override
+ protected String getAreaName() {
+ return "Ugc";
+ }
+ }
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/UgcService.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/UgcService.java
new file mode 100644
index 000000000..efba18abb
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/UgcService.java
@@ -0,0 +1,142 @@
+package com.sdl.dxa.modules.ugc;
+
+import com.sdl.delivery.ugc.client.comment.UgcCommentApi;
+import com.sdl.delivery.ugc.client.comment.impl.SimpleCommentsFilter;
+import com.sdl.dxa.modules.ugc.data.Comment;
+import com.sdl.dxa.modules.ugc.data.User;
+import com.sdl.web.ugc.Status;
+import com.sdl.webapp.common.util.TcmUtils;
+import com.tridion.ambientdata.AmbientDataContext;
+import com.tridion.ambientdata.claimstore.ClaimStore;
+import lombok.extern.slf4j.Slf4j;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.ZonedDateTime;
+import java.util.*;
+
+/**
+ * Service providing methods to create and retrieve comments
+ */
+@Service
+@Slf4j
+public class UgcService {
+
+ private static final int maximumThreadsDepth = -1;
+
+ @Autowired
+ private UgcCommentApi ugcCommentApi;
+
+ //Todo: use UgcVoteCommentApi implementation when it becomes available
+
+ @Autowired
+ public UgcService() {
+ }
+
+ /**
+ * retrieves a list of {@link Comment} items for a given page
+ *
+ * @param publicationId Publication Id
+ * @param pageId Page Id
+ * @param descending Order
+ * @param statuses Limit to specific statuses
+ * @param top maximum number of comments to show
+ * @param skip number of comments to skip
+ * @return List of {@link Comment}
+ */
+ public List getComments(int publicationId, int pageId, boolean descending, Integer[] statuses, int top, int skip) {
+ final List statusStatuses = new ArrayList<>();
+ Arrays.stream(statuses).forEach(status -> statusStatuses.add(Status.getStatusForId(status)));
+ final SimpleCommentsFilter filter = new SimpleCommentsFilter()
+ .withTop(top)
+ .withSkip(skip)
+ .withDepth(maximumThreadsDepth)
+ .withStatuses(statusStatuses);
+
+ return convert(ugcCommentApi.retrieveThreadedComments(TcmUtils.buildPageTcmUri(publicationId, pageId), filter, descending, true));
+ }
+
+ /**
+ * Post {@link Comment} for a given page
+ *
+ * @param publicationId Publication Id
+ * @param pageId Page Id
+ * @param username User name
+ * @param email Email address
+ * @param content Post content
+ * @param parentId parent
+ * @param metadata Meta data
+ * @return {@link Comment}
+ */
+ public Comment postComment(int publicationId, int pageId, String username, String email, String content,
+ int parentId, Map metadata) {
+
+ try {
+ final ClaimStore claimStore = AmbientDataContext.getCurrentClaimStore();
+ if (claimStore != null) {
+ claimStore.put(new URI("taf:claim:contentdelivery:webservice:user"), username);
+ claimStore.put(new URI("taf:claim:contentdelivery:webservice:post:allowed"), true);
+ }
+ } catch (URISyntaxException e) {
+ log.error("Error while Storing Claims", e);
+ }
+ return convert(
+ ugcCommentApi.postComment(TcmUtils.buildPageTcmUri(publicationId, pageId), username, email, content, parentId, metadata));
+ }
+
+ private List convert(List comments) {
+ final List convertedComments = new ArrayList<>();
+ comments.forEach(comment -> convertedComments.add(convert(comment)));
+
+ return convertedComments;
+ }
+
+ private Comment convert(com.sdl.delivery.ugc.client.odata.edm.Comment comment) {
+ if (comment == null) {
+ return null;
+ }
+ final Comment c = new Comment();
+ c.setId(comment.getIdLong());
+ if (comment.getParent() != null) {
+ c.setParentId(comment.getParent().getIdLong());
+ }
+ c.setItemId(comment.getItemId());
+ c.setItemType(comment.getItemType());
+ c.setItemPublicationId(comment.getItemPublicationId());
+ c.setContent(comment.getContent());
+ c.setRating(comment.getScore());
+ c.setMetadata(comment.getMetadata());
+ if (comment.getUser() != null) {
+ c.setUser(convert(comment.getUser()));
+ }
+ if (comment.getCreationDate() != null) {
+ c.setCreationDate(convert(comment.getCreationDate()));
+ }
+ if (comment.getLastModifiedDate() != null) {
+ c.setLastModifiedDate(convert(comment.getLastModifiedDate()));
+ }
+ c.setChildren(convert(comment.getChildren()));
+
+ return c;
+ }
+
+ private DateTime convert(ZonedDateTime zonedDateTime) {
+ return new DateTime(
+ zonedDateTime.toInstant().toEpochMilli(),
+ DateTimeZone.forTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone())));
+ }
+
+ private User convert(com.sdl.delivery.ugc.client.odata.edm.User user) {
+ final User u = new User();
+ u.setId(user.getId());
+ u.setExternalId(user.getExternalId());
+ u.setName(user.getName());
+ u.setEmailAddress(user.getEmailAddress());
+ return u;
+ }
+
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/controllers/UgcApiController.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/controllers/UgcApiController.java
new file mode 100644
index 000000000..7860075a9
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/controllers/UgcApiController.java
@@ -0,0 +1,114 @@
+package com.sdl.dxa.modules.ugc.controllers;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.sdl.dxa.modules.ugc.UgcService;
+import com.sdl.dxa.modules.ugc.data.Comment;
+import com.sdl.dxa.modules.ugc.data.PostedComment;
+import com.sdl.dxa.modules.ugc.data.PubIdTitleLang;
+import com.sdl.webapp.common.controller.BaseController;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.springframework.web.bind.annotation.RequestMethod.GET;
+import static org.springframework.web.bind.annotation.RequestMethod.POST;
+
+
+/**
+ * Ugc API controller that handles requests to /api/comments/
.
+ */
+@Controller
+@RequestMapping(value = {"/api/comments", "/{path}/api/comments"})
+@Slf4j
+public class UgcApiController extends BaseController {
+
+ @Autowired
+ private UgcService ugcService;
+
+ @Autowired
+ public UgcApiController() {
+ }
+
+ /**
+ * handles get request
+ * listens to basepath/{publicationId}/{pageId}
+ *
+ * @param publicationId Publication Id
+ * @param pageId Page Id
+ * @param descending Sort descending
+ * @param status limit results to comments with a specific status
+ * @param top maximum number of comments to show
+ * @param skip number of comments to skip
+ * @return List of {@link Comment}
+ */
+ @RequestMapping(method = GET, value = "/{publicationId}/{pageId}",
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseBody
+ public List getComments(@PathVariable("publicationId") Integer publicationId,
+ @PathVariable("pageId") Integer pageId,
+ @RequestParam(value = "descending",
+ required = false,
+ defaultValue = "false") Boolean descending,
+ @RequestParam(value = "status[]",
+ required = false,
+ defaultValue = "0") Integer[] status,
+ @RequestParam(value = "top",
+ required = false,
+ defaultValue = "0") Integer top,
+ @RequestParam(value = "skip",
+ required = false,
+ defaultValue = "0") Integer skip) {
+ return ugcService.getComments(publicationId, pageId, descending, status, top, skip);
+ }
+
+ /**
+ * handles post request
+ * listens to basepath/add
+ *
+ * @param input {@link PostedComment}
+ * @return {@link Comment}
+ */
+ @RequestMapping(method = POST, value = "/add",
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseBody
+ public Comment postComment(@RequestBody PostedComment input) {
+ Map metadata = new HashMap<>();
+ metadata.put("publicationTitle", input.getPublicationTitle());
+ metadata.put("publicationUrl", input.getPublicationUrl());
+ metadata.put("itemTitle", input.getPageTitle());
+ metadata.put("itemUrl", input.getPageUrl());
+ metadata.put("language", input.getLanguage());
+ metadata.put("status", "0");
+
+ addPubIdTitleLangToCommentMetadata(input, metadata);
+
+ return ugcService.postComment(input.getPublicationId(),
+ input.getPageId(),
+ input.getUsername(),
+ input.getEmail(),
+ input.getContent(),
+ input.getParentId(),
+ metadata);
+ }
+
+ private void addPubIdTitleLangToCommentMetadata(@RequestBody PostedComment input, Map metadata) {
+ PubIdTitleLang pubIdTitleLang = new PubIdTitleLang();
+ pubIdTitleLang.setId(input.getPublicationId());
+ pubIdTitleLang.setLang(input.getLanguage());
+ pubIdTitleLang.setTitle(input.getPublicationTitle());
+
+ Gson gSon = new GsonBuilder().create();
+ String pubIdTitleLangJson = gSon.toJson(pubIdTitleLang);
+
+ metadata.put("pubIdTitleLang", pubIdTitleLangJson);
+ }
+
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/controllers/UgcController.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/controllers/UgcController.java
new file mode 100644
index 000000000..cca78417a
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/controllers/UgcController.java
@@ -0,0 +1,81 @@
+package com.sdl.dxa.modules.ugc.controllers;
+
+import com.sdl.dxa.modules.ugc.UgcService;
+import com.sdl.dxa.modules.ugc.data.Comment;
+import com.sdl.dxa.modules.ugc.model.entity.UgcComment;
+import com.sdl.dxa.modules.ugc.model.entity.UgcComments;
+import com.sdl.dxa.modules.ugc.model.entity.UgcPostCommentForm;
+import com.sdl.webapp.common.api.content.ContentProviderException;
+import com.sdl.webapp.common.api.model.EntityModel;
+import com.sdl.webapp.common.api.model.ViewModel;
+import com.sdl.webapp.common.controller.EntityController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Ugc entity controller that handles include requests to /system/mvc/Framework/Ugc/{entityId}
.
+ */
+@Controller
+@RequestMapping({"/system/mvc/Framework/Ugc"})
+public class UgcController extends EntityController {
+
+ @Autowired
+ private UgcService ugcService;
+
+ private static List createEntities(List comments) {
+
+ final List ugcComments = new ArrayList<>();
+ comments.forEach((Comment comment) -> ugcComments.add(createEntity(comment)));
+ return ugcComments;
+ }
+
+ private static UgcComment createEntity(Comment comment) {
+ final UgcComment ugcComment = new UgcComment();
+
+ ugcComment.setComments(createEntities(comment.getChildren()));
+ ugcComment.setCommentData(comment);
+ return ugcComment;
+ }
+
+ @Override
+ protected ViewModel enrichModel(ViewModel model, HttpServletRequest request) throws Exception {
+
+ if (model instanceof UgcComments) {
+ final ViewModel enrichedModel = super.enrichModel(model, request);
+ final UgcComments ugcComments = (UgcComments) (enrichedModel instanceof EntityModel ? enrichedModel : model);
+ final List comments = ugcService.getComments(ugcComments.getTarget().getPublicationId(),
+ ugcComments.getTarget().getItemId(), false, new Integer[0], 0, 0);
+ ugcComments.setComments(createEntities(comments));
+ return ugcComments;
+ }
+
+ if (model instanceof UgcPostCommentForm) {
+ final ViewModel enrichedModel = super.enrichModel(model, request);
+ final UgcPostCommentForm postForm = (UgcPostCommentForm) (enrichedModel instanceof EntityModel ? enrichedModel : model);
+
+ postForm.setFormUrl(context.getPage().getUrl());
+ }
+ return model;
+ }
+
+ /**
+ * Handles a request for an entity.
+ *
+ * @param request The request.
+ * @param entityId The entity id.
+ * @return The name of the entity view that should be rendered for this request.
+ * @throws ContentProviderException If an error occurs so that the entity cannot not be retrieved.
+ * @throws java.lang.Exception if any.
+ */
+ @RequestMapping({"Entity/{entityId}"})
+ public String handleGetEntity(HttpServletRequest request, @PathVariable String entityId) throws Exception {
+ return this.handleEntityRequest(request, entityId);
+ }
+
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/controllers/UgcPostFormController.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/controllers/UgcPostFormController.java
new file mode 100644
index 000000000..9dc05f54e
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/controllers/UgcPostFormController.java
@@ -0,0 +1,62 @@
+package com.sdl.dxa.modules.ugc.controllers;
+
+import com.sdl.dxa.modules.ugc.UgcService;
+import com.sdl.dxa.modules.ugc.model.entity.UgcPostCommentForm;
+import com.sdl.dxa.modules.ugc.model.validator.UgcPostCommentFormValidator;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+/**
+ * Ugc API controller that handles requests to /api/ugc/postcomment
.
+ */
+@Controller
+@RequestMapping(value = {"/api/ugc", "/{path}/api/ugc"})
+@Slf4j
+public class UgcPostFormController {
+
+ private static final String REDIRECT_PREFIX = "redirect:";
+
+ private final UgcPostCommentFormValidator ugcPostCommentFormValidator;
+
+ @Autowired
+ private UgcService ugcService;
+
+ @Autowired
+ public UgcPostFormController(UgcPostCommentFormValidator ugcPostCommentFormValidator) {
+ this.ugcPostCommentFormValidator = ugcPostCommentFormValidator;
+ }
+
+ /**
+ * handles post comment request
+ * listens to /api/ugc/postcomment
.
+ *
+ * @param form Posted Comment form
+ * @param bindingResult represents binding results
+ * @param redirectAttributes Redirect attributes
+ * @return redirect url
+ */
+ @RequestMapping(value = "/postcomment", method = RequestMethod.POST)
+ public String postComment(@ModelAttribute("entity") UgcPostCommentForm form, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
+ ugcPostCommentFormValidator.validate(form, bindingResult);
+
+ if (bindingResult.hasErrors()) {
+ log.trace("Comment Form for {} has {} errors", form.getTarget(), bindingResult.getErrorCount());
+ redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
+ return REDIRECT_PREFIX + form.getFormUrl();
+ }
+ log.trace("Comment Form for {} complete and valid",form.getTarget());
+
+ ugcService.postComment(form.getTarget().getPublicationId(), form.getTarget().getItemId(), form.getUserName(),
+ form.getEmailAddress(), form.getContent(), form.getParentId(), form.getMetadata());
+
+ log.trace("Comment on {} by {} posted succesful", form.getTarget(),form.getUserName());
+ return REDIRECT_PREFIX + form.getFormUrl();
+ }
+
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/Comment.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/Comment.java
new file mode 100644
index 000000000..4a906934f
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/Comment.java
@@ -0,0 +1,46 @@
+package com.sdl.dxa.modules.ugc.data;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+import org.joda.time.DateTime;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Ugc comment
+ */
+
+@Data
+public class Comment {
+ private long id;
+
+ @JsonIgnore
+ private long parentId;
+
+ private int itemPublicationId;
+
+ private int itemId;
+
+ private int itemType;
+
+// private CommentDate creationDate;
+ private DateTime creationDate;
+
+// private CommentDate lastModifiedDate;
+ private DateTime lastModifiedDate;
+
+ private String content;
+
+ private User user;
+
+ private List children;
+
+ @JsonIgnore
+ private int rating = 0;
+
+ @JsonIgnore
+ private Map metadata = new HashMap<>();
+
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/PostedComment.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/PostedComment.java
new file mode 100644
index 000000000..1b0eb7f09
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/PostedComment.java
@@ -0,0 +1,32 @@
+package com.sdl.dxa.modules.ugc.data;
+
+import lombok.Data;
+
+/**
+ * Ugc posted comment
+ */
+
+@Data
+public class PostedComment {
+ private int publicationId;
+
+ private int pageId;
+
+ private String username;
+
+ private String email;
+
+ private String content;
+
+ private int parentId = 0;
+
+ private String publicationTitle;
+
+ private String publicationUrl;
+
+ private String pageTitle;
+
+ private String pageUrl;
+
+ private String language;
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/PubIdTitleLang.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/PubIdTitleLang.java
new file mode 100644
index 000000000..c8962cbf6
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/PubIdTitleLang.java
@@ -0,0 +1,17 @@
+package com.sdl.dxa.modules.ugc.data;
+
+import lombok.Data;
+
+/**
+ * Publication Title and Language
+ */
+
+@Data
+public class PubIdTitleLang {
+
+ private int id;
+
+ private String title;
+
+ private String lang;
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/User.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/User.java
new file mode 100644
index 000000000..4782462d5
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/data/User.java
@@ -0,0 +1,17 @@
+package com.sdl.dxa.modules.ugc.data;
+
+import lombok.Data;
+
+/**
+ * Ugc User information
+ */
+@Data
+public class User {
+ private String id;
+
+ private String name;
+
+ private String emailAddress;
+
+ private String externalId;
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/mapping/UgcModelBuilder.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/mapping/UgcModelBuilder.java
new file mode 100644
index 000000000..9434f51ca
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/mapping/UgcModelBuilder.java
@@ -0,0 +1,283 @@
+package com.sdl.dxa.modules.ugc.mapping;
+
+import com.sdl.dxa.api.datamodel.model.ContentModelData;
+import com.sdl.dxa.api.datamodel.model.EntityModelData;
+import com.sdl.dxa.api.datamodel.model.PageModelData;
+import com.sdl.dxa.modules.ugc.model.entity.UgcComments;
+import com.sdl.dxa.modules.ugc.model.entity.UgcPostCommentForm;
+import com.sdl.dxa.modules.ugc.model.entity.UgcRegion;
+import com.sdl.dxa.tridion.mapping.EntityModelBuilder;
+import com.sdl.dxa.tridion.mapping.PageModelBuilder;
+import com.sdl.webapp.common.api.WebRequestContext;
+import com.sdl.webapp.common.api.localization.Localization;
+import com.sdl.webapp.common.api.model.*;
+import com.sdl.webapp.common.api.model.mvcdata.DefaultsMvcData;
+import com.sdl.webapp.common.api.model.mvcdata.MvcDataCreator;
+import com.sdl.webapp.common.exceptions.DxaException;
+import com.sdl.webapp.common.util.TcmUtils;
+import com.tridion.util.TCMURI;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builds {@linkplain PageModel Page Model} ando{@linkplain EntityModel Entity Model}.
+ */
+@Slf4j
+@Component
+public class UgcModelBuilder implements PageModelBuilder, EntityModelBuilder {
+
+ private static final String COMMENTS_CONFIG = "ugcConfig";
+ private static final String POST_FORM_CONFIG = "postFormConfig";
+
+ private static final String COMMENTS_REGION_KEY = "commentsRegion";
+ private static final String SHOW_COMMENTS_KEY = "showComments";
+ private static final String ALLOW_POST_KEY = "allowPost";
+
+ private static final String COMMENTS_REGION = "Comments";
+ private static final String COMMENTS_AREA = "Ugc";
+
+ private static final String SHOW_COMMENTS_EXT_DATA = "UgcShowComments";
+ private static final String POST_COMMENTS_EXT_DATA = "UgcPostComments";
+ private static final String COMMENTS_ENTITY_REGION_EXT_DATA = "CommentsEntityRegion";
+
+ private static final String COMMENTS_QUALIFIED_NAME = "Ugc:Ugc:UgcComments";
+ private static final String POST_FORM_QUALIFIED_NAME = "Ugc:Ugc:UgcPostCommentForm";
+
+ private final WebRequestContext webRequestContext;
+
+ @Autowired
+ public UgcModelBuilder(WebRequestContext webRequestContext) {
+ this.webRequestContext = webRequestContext;
+ }
+
+ private RegionModel findRegion(RegionModelSet regionModelSet, String regionName) {
+ for (RegionModel region : regionModelSet) {
+ if (region.getName().equals(regionName)) {
+ return region;
+ }
+ RegionModel childRegion = findRegion(region.getRegions(), regionName);
+ if (childRegion != null) {
+ return childRegion;
+ }
+ }
+ return null;
+ }
+
+ private UgcRegion createRegion(PageModel pageModel, String areaName, String regionName) {
+ UgcRegion ugcRegion = null;
+ if (!pageModel.getRegions().containsClass(UgcRegion.class)) {
+ try {
+ ugcRegion = new UgcRegion(regionName);
+ MvcData mvcData = MvcDataCreator.creator()
+ .fromQualifiedName(String.format("%s:%s", areaName, regionName))
+ .defaults(DefaultsMvcData.REGION)
+ .create();
+ ugcRegion.setMvcData(mvcData);
+ pageModel.getRegions().add(ugcRegion);
+ } catch (DxaException e) {
+ log.error("Creation of RegionModel {} failed", regionName, e);
+ }
+ } else {
+ ugcRegion = pageModel.getRegions().stream()
+ .filter(regionModel -> regionModel instanceof UgcRegion)
+ .map(regionModel -> (UgcRegion) regionModel)
+ .findFirst().orElse(null);
+ }
+ return ugcRegion;
+ }
+
+ private void addCommentsViews(PageModel pageModel, RegionModel region, Localization localization, RegionModel ugcRegion) {
+ final List regionEntities = new ArrayList<>();
+ for (EntityModel entity : region.getEntities()) {
+ if (entity.getExtensionData() == null) {
+ continue;
+ }
+ List entities = ugcRegion != null ? ugcRegion.getEntities() : regionEntities;
+ if (entity.getExtensionData().containsKey(COMMENTS_ENTITY_REGION_EXT_DATA)) {
+ // comment region specified for this entity so lets find it and use that
+ RegionModel targetRegion = findRegion(pageModel.getRegions(), (String) entity.getExtensionData().get(COMMENTS_ENTITY_REGION_EXT_DATA));
+ if (targetRegion != null && targetRegion != region) {
+ entities = targetRegion.getEntities();
+ } else if (targetRegion == null || targetRegion == region) {
+ entities = regionEntities;
+ }
+
+ if (entity.getExtensionData().containsKey(SHOW_COMMENTS_EXT_DATA) &&
+ (Boolean) entity.getExtensionData().get(SHOW_COMMENTS_EXT_DATA)) {
+ entities.add(createUgcCommentsEntity(localization, entity.getId(), TcmUtils.COMPONENT_ITEM_TYPE));
+ }
+ if (entity.getExtensionData().containsKey(POST_COMMENTS_EXT_DATA) && entity.getExtensionData().get(POST_COMMENTS_EXT_DATA) != null) {
+ entities.add(createUgcPostCommentEntity(localization, entity.getId(), TcmUtils.COMPONENT_ITEM_TYPE, (ContentModelData) entity.getExtensionData().get(POST_COMMENTS_EXT_DATA)));
+ }
+ }
+ }
+ // Add our ugc views to either the same region as the entity we have comments enabled for or the ugc "Comments" region if available
+ regionEntities.forEach(entity -> region.getEntities().add(entity));
+ region.getRegions().forEach(childRegion -> addCommentsViews(pageModel, childRegion, localization, ugcRegion));
+ }
+
+ private UgcComments createUgcCommentsEntity(Localization localization, String id, int itemType) {
+ final MvcData mvcData = MvcDataCreator.creator()
+ .fromQualifiedName(COMMENTS_QUALIFIED_NAME)
+ .defaults(DefaultsMvcData.ENTITY)
+ .create();
+ final UgcComments model = new UgcComments();
+ try {
+ model.setTarget(new TCMURI(TcmUtils.buildTcmUri(localization.getId(), id, itemType)));
+ } catch (ParseException e) {
+ log.error("Unable to process TCMURI '{}'.", TcmUtils.buildTcmUri(localization.getId(), id, itemType));
+ }
+ model.setMvcData(mvcData);
+ return model;
+ }
+
+ private UgcPostCommentForm createUgcPostCommentEntity(Localization localization, String id, int itemType, ContentModelData postFormConfig) {
+ final MvcData mvcData = MvcDataCreator.creator()
+ .fromQualifiedName(POST_FORM_QUALIFIED_NAME)
+ .defaults(DefaultsMvcData.ENTITY)
+ .create();
+ final UgcPostCommentForm model = new UgcPostCommentForm();
+ try {
+ model.setTarget(new TCMURI(TcmUtils.buildTcmUri(localization.getId(), id, itemType)));
+ } catch (ParseException e) {
+ log.error("Unable to process TCMURI '{}'.", TcmUtils.buildTcmUri(localization.getId(), id, itemType));
+ }
+ model.setMvcData(mvcData);
+ model.setUserNameLabel(getValue(postFormConfig, "userNameLabel", String.class));
+ model.setEmailAddressLabel(getValue(postFormConfig, "emailAddressLabel", String.class));
+ model.setContentLabel(getValue(postFormConfig, "contentLabel", String.class));
+ model.setSubmitButtonLabel(getValue(postFormConfig, "submitButtonLabel", String.class));
+ model.setNoContentMessage(getValue(postFormConfig, "noContentMessage", String.class));
+ model.setNoEmailAddressMessage(getValue(postFormConfig, "noEmailAddressMessage", String.class));
+ model.setNoUserNameMessage(getValue(postFormConfig, "noUserNameMessage", String.class));
+
+ return model;
+ }
+
+ private ContentModelData ugcMetadata(ContentModelData metadata) {
+ return metadata == null ? null : metadata.getAndCast(COMMENTS_CONFIG, ContentModelData.class);
+ }
+
+ private ContentModelData ugcPostFormMetadata(ContentModelData ugcMetadata) {
+ return ugcMetadata == null ? null : ugcMetadata.getAndCast(POST_FORM_CONFIG, ContentModelData.class);
+ }
+
+ private Boolean showComments(ContentModelData ugcMetadata) {
+ return getValue(ugcMetadata, SHOW_COMMENTS_KEY, Boolean.class);
+ }
+
+ private Boolean postComments(ContentModelData ugcMetadata) {
+ return getValue(ugcMetadata, ALLOW_POST_KEY, Boolean.class);
+ }
+
+ private T getValue(ContentModelData metadata, String name, Class type) {
+ if (metadata == null || !metadata.containsKey(name)) {
+ if (type == Boolean.class) {
+ //noinspection unchecked
+ return (T) (Boolean.valueOf("No"));
+ }
+ return null;
+ }
+ Object v = metadata.get(name);
+ if (v == null) {
+ if (type == Boolean.class) {
+ //noinspection unchecked
+ return (T) Boolean.FALSE;
+ }
+ return null;
+ }
+ if (type == Boolean.class) {
+ //noinspection unchecked
+ return (T) (v.toString().equalsIgnoreCase("yes") ? Boolean.TRUE : Boolean.FALSE);
+ }
+ //noinspection unchecked
+ return (T) v;
+ }
+
+ /**
+ * Extends the pagemodel with a UGC region and entities
+ *
+ * @param originalPageModel the strongly typed Page Model to build
+ * @param modelData the DXA R2 Data Model
+ */
+ @Nullable
+ @Override
+ public PageModel buildPageModel(@Nullable PageModel originalPageModel, @NotNull PageModelData modelData) {
+ if (originalPageModel == null) {
+ log.error("Original page model is null");
+ return null;
+ }
+
+ final ContentModelData metadata = modelData.getPageTemplate() == null ? null : modelData.getPageTemplate().getMetadata();
+ final ContentModelData ugcMetadata = ugcMetadata(metadata);
+ final Localization localization = webRequestContext.getLocalization();
+
+ String regionName = getValue(ugcMetadata, COMMENTS_REGION_KEY, String.class);
+ final RegionModel ugcRegion;
+ RegionModel ugcRegionModel;
+ if (StringUtils.isEmpty(regionName)) {
+ ugcRegionModel = findRegion(originalPageModel.getRegions(), COMMENTS_REGION);
+ if (ugcRegionModel == null) {
+ ugcRegionModel = createRegion(originalPageModel, COMMENTS_AREA, COMMENTS_REGION);
+ }
+ } else {
+ ugcRegionModel = findRegion(originalPageModel.getRegions(), regionName);
+ if (ugcRegionModel == null) {
+ log.error("Unable to locate region for comments '{}'.", regionName);
+ }
+ }
+
+ ugcRegion = ugcRegionModel;
+ originalPageModel.getRegions().forEach(region -> addCommentsViews(originalPageModel, region, localization, ugcRegion));
+
+ if (ugcRegion != null) {
+ if (showComments(ugcMetadata)) {
+ ugcRegion.getEntities().add(createUgcCommentsEntity(localization, originalPageModel.getId(), TcmUtils.PAGE_ITEM_TYPE));
+ }
+ if (postComments(ugcMetadata)) {
+ ugcRegion.getEntities().add(createUgcPostCommentEntity(localization, originalPageModel.getId(), TcmUtils.PAGE_ITEM_TYPE,
+ ugcPostFormMetadata(ugcMetadata)));
+ }
+ }
+ return originalPageModel;
+ }
+
+ /**
+ * Extends the Entity Model with Ugc extension data. Never returns null.
+ *
+ * @param originalEntityModel the strongly typed Entity Model to build. Is null for the first Entity Model Builder in the ModelBuilderPipelineImpl
+ * @param modelData the DXA R2 Data Model
+ * @param expectedClass required class of entity model, gets the priority if modelData contains MVC data
+ * @return the strongly typed Entity Model
+ */
+ @Override
+ public T buildEntityModel(@Nullable T originalEntityModel, EntityModelData modelData, @Nullable Class expectedClass) {
+ if (modelData != null && modelData.getComponentTemplate() != null ) {
+ final ContentModelData ugcMetadata = ugcMetadata(modelData.getComponentTemplate().getMetadata());
+ if (ugcMetadata != null) {
+ originalEntityModel.addExtensionData(SHOW_COMMENTS_EXT_DATA, showComments(ugcMetadata));
+ originalEntityModel.addExtensionData(POST_COMMENTS_EXT_DATA, (postComments(ugcMetadata) ? ugcPostFormMetadata(ugcMetadata) : null));
+ originalEntityModel.addExtensionData(COMMENTS_ENTITY_REGION_EXT_DATA, getValue(ugcMetadata, COMMENTS_REGION_KEY, String.class));
+ }
+ }
+ return originalEntityModel;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+
+
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcComment.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcComment.java
new file mode 100644
index 000000000..566553500
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcComment.java
@@ -0,0 +1,27 @@
+package com.sdl.dxa.modules.ugc.model.entity;
+
+import com.sdl.dxa.modules.ugc.data.Comment;
+import com.sdl.webapp.common.api.model.entity.AbstractEntityModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * EntityModel for a single Ugc comment
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class UgcComment extends AbstractEntityModel {
+
+ /**
+ * Comment Data
+ **/
+ private Comment commentData;
+
+ /**
+ * List of related comments
+ **/
+ private List comments;
+
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcComments.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcComments.java
new file mode 100644
index 000000000..9d7a1a2c5
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcComments.java
@@ -0,0 +1,29 @@
+package com.sdl.dxa.modules.ugc.model.entity;
+
+import com.sdl.webapp.common.api.mapping.semantic.annotations.SemanticMappingIgnore;
+import com.sdl.webapp.common.api.model.entity.AbstractEntityModel;
+import com.tridion.util.CMURI;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * EntityModel containing all comments related to a given page or component
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class UgcComments extends AbstractEntityModel {
+
+ /**
+ * Target CmUri for comments
+ **/
+ @SemanticMappingIgnore
+ private CMURI target;
+
+ /**
+ * List of comments
+ **/
+ @SemanticMappingIgnore
+ private List comments;
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcPostCommentForm.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcPostCommentForm.java
new file mode 100644
index 000000000..2458b4244
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcPostCommentForm.java
@@ -0,0 +1,129 @@
+package com.sdl.dxa.modules.ugc.model.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.sdl.dxa.caching.NeverCached;
+import com.sdl.webapp.common.api.mapping.semantic.annotations.SemanticMappingIgnore;
+import com.sdl.webapp.common.api.model.entity.AbstractEntityModel;
+import com.sdl.webapp.common.api.model.validation.DynamicCodeResolver;
+import com.sdl.webapp.common.api.model.validation.DynamicValidationMessage;
+import com.tridion.util.CMURI;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Map;
+
+/**
+ * EntityModel for Posted Comment model
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NeverCached(qualifier = "UgcPostCommentForm")
+public class UgcPostCommentForm extends AbstractEntityModel {
+
+ /**
+ * Holds the forms url, for redirecting purposes
+ */
+ @SemanticMappingIgnore
+ @JsonIgnore
+ private String formUrl;
+
+ /**
+ * Holds the form control value for username
+ */
+ @SemanticMappingIgnore
+ private String userName;
+
+ /**
+ * Holds the form control value for email address
+ **/
+ @SemanticMappingIgnore
+ private String emailAddress;
+
+
+
+ /**
+ * Holds the form control value for email address
+ **/
+ @SemanticMappingIgnore
+ private String content;
+
+ /**
+ * Metadata of comment to post
+ **/
+ @SemanticMappingIgnore
+ private Map metadata;
+
+ /**
+ * Parent id of comment to post
+ **/
+ @SemanticMappingIgnore
+ public int parentId = 0;
+
+ /**
+ * Label text for username input control on view
+ **/
+ private String userNameLabel;
+
+ /**
+ * Label text for email address input control on view
+ **/
+ private String emailAddressLabel;
+
+ /**
+ * Label text for content input control on view
+ **/
+ private String contentLabel;
+
+ /**
+ * Label text for submit button on view
+ **/
+ private String submitButtonLabel;
+
+ /**
+ * User name not specified message
+ **/
+ private String noUserNameMessage;
+
+ /**
+ * Email not specified message
+ **/
+ private String noEmailAddressMessage;
+
+
+ /**
+ * Content not specified message
+ **/
+ private String noContentMessage;
+
+ /**
+ * Target CmUri for comments
+ **/
+ @SemanticMappingIgnore
+ private CMURI target;
+
+ @DynamicValidationMessage(errorCode = "userName.empty")
+ public String getNoUserNameMessage() {
+ return noUserNameMessage;
+ }
+
+ @DynamicValidationMessage(errorCode = "emailAddress.empty")
+ public String getNoEmailAddressMessage() {
+ return noEmailAddressMessage;
+ }
+
+ @DynamicValidationMessage(errorCode = "content.empty")
+ public String getNoContentMessage() {
+ return noContentMessage;
+ }
+
+ /**
+ * Resolves error code using {@link DynamicCodeResolver}.
+ *
+ * @param code code to resolve
+ * @return resolved message
+ */
+ public String resolveErrorCode(String code) {
+ return DynamicCodeResolver.resolveCode(code, this);
+ }
+
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcRegion.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcRegion.java
new file mode 100644
index 000000000..d78f7d598
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/entity/UgcRegion.java
@@ -0,0 +1,47 @@
+package com.sdl.dxa.modules.ugc.model.entity;
+
+import com.sdl.webapp.common.api.model.MvcData;
+import com.sdl.webapp.common.api.model.RegionModel;
+import com.sdl.webapp.common.api.model.region.RegionModelImpl;
+import com.sdl.webapp.common.exceptions.DxaException;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * RegionModel for the Ugc Comment region
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class UgcRegion extends RegionModelImpl {
+ /**
+ * @param other other Region
+ */
+ public UgcRegion(RegionModel other) {
+ super(other);
+ }
+
+ /**
+ * @param name Region name
+ * @throws DxaException if name is empty or null
+ */
+ public UgcRegion(String name) throws DxaException {
+ super(name);
+ }
+
+ /**
+ * @param name region name
+ * @param qualifiedViewName view name
+ * @throws DxaException if name is empty or null
+ */
+ public UgcRegion(String name, String qualifiedViewName) throws DxaException {
+ super(name, qualifiedViewName);
+ }
+
+ /**
+ * @param mvcData {@link MvcData}
+ * @throws DxaException if name is empty or null
+ */
+ public UgcRegion(MvcData mvcData) throws DxaException {
+ super(mvcData);
+ }
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/validator/UgcPostCommentFormValidator.java b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/validator/UgcPostCommentFormValidator.java
new file mode 100644
index 000000000..bbd8a3f70
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/java/com/sdl/dxa/modules/ugc/model/validator/UgcPostCommentFormValidator.java
@@ -0,0 +1,38 @@
+package com.sdl.dxa.modules.ugc.model.validator;
+
+import com.sdl.dxa.modules.ugc.model.entity.UgcPostCommentForm;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+/**
+ * Validator, validating Ugc comment Posts
+ */
+@Component
+public class UgcPostCommentFormValidator implements Validator {
+
+ /**
+ * @param clazz supported form model class
+ * @return True/False
+ */
+ @Override
+ public boolean supports(Class> clazz) {
+ return UgcPostCommentForm.class.isAssignableFrom(clazz);
+ }
+
+ /**
+ * Validates posted form
+ *
+ * @param target form to validate
+ * @param errors errors found during validation
+ */
+ @Override
+ public void validate(Object target, Errors errors) {
+ final UgcPostCommentForm form = (UgcPostCommentForm) target;
+
+ ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "userName.empty", form.getNoUserNameMessage());
+ ValidationUtils.rejectIfEmptyOrWhitespace(errors, "emailAddress", "emailAddress.empty", form.getNoEmailAddressMessage());
+ ValidationUtils.rejectIfEmptyOrWhitespace(errors, "content", "content.empty", form.getNoContentMessage());
+ }
+}
diff --git a/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Entity/UgcComments.jsp b/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Entity/UgcComments.jsp
new file mode 100644
index 000000000..aec04868b
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Entity/UgcComments.jsp
@@ -0,0 +1,33 @@
+<%@ taglib prefix="dxa" uri="http://www.sdl.com/tridion-dxa" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+
Comments (${entity.comments.size()})
+
+
+
+
+
+
+
+
+
+ ${comment.commentData.user.name}
+ -
+ ${markup.formatDateDiff(comment.commentData.lastModifiedDate)}
+
+
+
${comment.commentData.content}
+
+
+
+
+
+
+
+
+
diff --git a/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Entity/UgcPostCommentForm.jsp b/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Entity/UgcPostCommentForm.jsp
new file mode 100644
index 000000000..7aaf86b7c
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Entity/UgcPostCommentForm.jsp
@@ -0,0 +1,41 @@
+<%@ taglib prefix="dxa" uri="http://www.sdl.com/tridion-dxa" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+
+
+
+
+
+
+
+ <%--@elvariable id="errors" type="java.util.ArrayList"--%>
+
+
+
+
+
+ ${entity.resolveErrorCode(error.code)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+ ${entity.submitButtonLabel}
+
+
+
\ No newline at end of file
diff --git a/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Page/GeneralPage.jsp b/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Page/GeneralPage.jsp
new file mode 100644
index 000000000..b79ad48fa
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Page/GeneralPage.jsp
@@ -0,0 +1,67 @@
+
+<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="dxa" uri="http://www.sdl.com/tridion-dxa" %>
+<%@ taglib prefix="xpm" uri="http://www.sdl.com/tridion-xpm" %>
+
+
+
+
+
+
+
+
+
+
+
+
+ ${pageModel.title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Region/Comments.jsp b/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Region/Comments.jsp
new file mode 100644
index 000000000..747dbf96e
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/main/resources/META-INF/resources/WEB-INF/Views/Ugc/Region/Comments.jsp
@@ -0,0 +1,8 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
+<%@ taglib prefix="dxa" uri="http://www.sdl.com/tridion-dxa" %>
+
+
+
+
+
+
diff --git a/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/UgcServiceTest.java b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/UgcServiceTest.java
new file mode 100644
index 000000000..1ae01ce7c
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/UgcServiceTest.java
@@ -0,0 +1,58 @@
+package com.sdl.dxa.modules.ugc;
+
+import com.sdl.delivery.ugc.client.comment.UgcCommentApi;
+import com.sdl.dxa.modules.ugc.data.Comment;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyMapOf;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class UgcServiceTest {
+
+
+ @Mock
+ private UgcCommentApi ugcCommentApi;
+
+ @Mock
+ private Comment comment;
+
+ @Mock
+ private UgcService ugcService;
+
+ @Test
+ public void shouldProcessGetComments() {
+ //given
+ List comments = new ArrayList<>();
+ comments.add(comment);
+ when(ugcService.getComments(any(int.class), any(int.class), any(boolean.class), any(Integer[].class), any(int.class), any(int.class))).thenReturn(comments);
+
+ //when
+ List result = ugcService.getComments(1,1,false,new Integer[]{},0,0);
+
+ //then
+ Assert.assertEquals(result,comments);
+
+ }
+
+ @Test
+ public void shouldProcessPostComment(){
+ //given
+ when(ugcService.postComment(any(int.class), any(int.class), any(String.class), any(String.class), any(String.class), any(int.class), anyMapOf(String.class, String.class))).thenReturn(comment);
+
+ //when
+ Comment result = ugcService.postComment(1,1,"userName","test@test.com","message",0, new HashMap<>());
+
+ //then
+ Assert.assertEquals(result,comment);
+ }
+}
\ No newline at end of file
diff --git a/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/controllers/UgControllerTest.java b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/controllers/UgControllerTest.java
new file mode 100644
index 000000000..ff591bf53
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/controllers/UgControllerTest.java
@@ -0,0 +1,61 @@
+package com.sdl.dxa.modules.ugc.controllers;
+
+import com.sdl.dxa.modules.ugc.UgcService;
+import com.sdl.dxa.modules.ugc.model.entity.UgcComment;
+import com.sdl.dxa.modules.ugc.model.entity.UgcComments;
+import com.sdl.webapp.common.api.model.ViewModel;
+import com.tridion.util.TCMURI;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class UgControllerTest {
+
+ @Mock
+ private UgcService ugcService;
+
+ @Mock
+ private List ugcComments;
+
+ @InjectMocks
+ @Spy
+ private UgcController controller;
+
+ @Test
+ public void shouldIgnoreModelIfItIsNotUgcComments() throws Exception {
+ //given
+ ViewModel model = mock(ViewModel.class);
+
+ //when
+ controller.enrichModel(model, null);
+
+ //then
+ verifyZeroInteractions(model);
+ }
+
+ @Test
+ public void shouldProcessModelIfItIsUgcComments() throws Exception {
+ //given
+ UgcComments model = mock(UgcComments.class);
+ ugcComments = new ArrayList<>();
+ when(model.getTarget()).thenReturn(new TCMURI("tcm:1-2-16"));
+ when(model.getComments()).thenReturn(ugcComments);
+ when(ugcService.getComments(any(int.class), any(int.class), any(boolean.class), any(Integer[].class), any(int.class), any(int.class))).thenReturn(new ArrayList<>());
+
+ //when
+ ViewModel result = controller.enrichModel(model, null);
+
+ //then
+ assertEquals(model.getClass(),result.getClass());
+ }
+}
\ No newline at end of file
diff --git a/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/controllers/UgcApiControllerTest.java b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/controllers/UgcApiControllerTest.java
new file mode 100644
index 000000000..a446abed8
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/controllers/UgcApiControllerTest.java
@@ -0,0 +1,101 @@
+package com.sdl.dxa.modules.ugc.controllers;
+
+import com.sdl.dxa.modules.ugc.UgcService;
+import com.sdl.dxa.modules.ugc.data.Comment;
+import com.sdl.dxa.modules.ugc.data.PostedComment;
+import com.sdl.dxa.modules.ugc.data.User;
+import com.sdl.webapp.common.api.WebRequestContext;
+import com.sdl.webapp.common.api.localization.Localization;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyMapOf;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+@ActiveProfiles("test")
+public class UgcApiControllerTest {
+
+ private static final int PAGE_ID = 12451;
+ private static final int PUBLICATION_ID = 4574;
+ private static final String COMMENT_TEXT = "comment";
+ private static final String USER_EMAIL = "email";
+ private static final String USER_NAME = "name";
+
+ @Mock
+ private WebRequestContext webRequestContext;
+
+ @Mock
+ private Localization localization;
+
+ @Mock
+ private List comments;
+
+ @Mock
+ private Comment comment;
+
+ @Mock
+ private UgcService ugcService;
+
+ @InjectMocks
+ @Spy
+ private UgcApiController ugcApiController;
+
+ @Before
+ public void init() {
+ comments = new ArrayList<>();
+ when(webRequestContext.getLocalization()).thenReturn(localization);
+ when(localization.getPath()).thenReturn("path");
+ when(ugcService.getComments(any(int.class), any(int.class), any(boolean.class), any(Integer[].class), any(int.class), any(int.class))).thenReturn(comments);
+ when(ugcService.postComment(any(int.class), any(int.class), any(String.class), any(String.class),
+ any(String.class), any(int.class), anyMapOf(String.class,String.class))).thenReturn(comment);
+ }
+
+ /**
+ *
+ */
+ @Test
+ public void shouldReturnComments() {
+ //given
+ //when
+ List retrievedComments = ugcApiController.getComments(PUBLICATION_ID, PAGE_ID, false, null, 0, 0);
+
+ //then
+ assertEquals(comments, retrievedComments);
+ }
+
+ @Test
+ public void testPostComment() {
+ //given
+ PostedComment postedComment = mock(PostedComment.class);
+ User user= mock(User.class);
+
+ when(postedComment.getUsername()).thenReturn(USER_NAME);
+ when(postedComment.getEmail()).thenReturn(USER_EMAIL);
+ when(postedComment.getContent()).thenReturn(COMMENT_TEXT);
+ when(comment.getUser()).thenReturn(user);
+ when(user.getName()).thenReturn(USER_NAME);
+ when(user.getEmailAddress()).thenReturn(USER_EMAIL);
+ when(comment.getContent()).thenReturn(COMMENT_TEXT);
+
+
+ //when
+ Comment result = ugcApiController.postComment(postedComment);
+ //then
+ assertEquals(USER_NAME, result.getUser().getName());
+ assertEquals(USER_EMAIL, result.getUser().getEmailAddress());
+
+ }
+}
\ No newline at end of file
diff --git a/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/mapping/UgcModelBuilderTest.java b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/mapping/UgcModelBuilderTest.java
new file mode 100644
index 000000000..3b14f858b
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/mapping/UgcModelBuilderTest.java
@@ -0,0 +1,158 @@
+package com.sdl.dxa.modules.ugc.mapping;
+
+import com.sdl.dxa.api.datamodel.model.*;
+import com.sdl.webapp.common.api.WebRequestContext;
+import com.sdl.webapp.common.api.localization.Localization;
+import com.sdl.webapp.common.api.model.EntityModel;
+import com.sdl.webapp.common.api.model.PageModel;
+import com.sdl.webapp.common.api.model.RegionModel;
+import com.sdl.webapp.common.api.model.RegionModelSet;
+import com.sdl.webapp.common.api.model.entity.AbstractEntityModel;
+import com.sdl.webapp.common.api.model.region.RegionModelImpl;
+import com.sdl.webapp.common.api.model.region.RegionModelSetImpl;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.mock;
+
+@RunWith(MockitoJUnitRunner.class)
+public class UgcModelBuilderTest {
+
+ private static final String COMMENTS_CONFIG = "ugcConfig";
+ private static final String POST_FORM_CONFIG = "postFormConfig";
+ private static final String COMMENTS_REGION_KEY = "commentsRegion";
+ private static final String SHOW_COMMENTS_KEY = "showComments";
+ private static final String ALLOW_POST_KEY = "allowPost";
+ private static final String SHOW_COMMENTS_EXT_DATA = "UgcShowComments";
+ private static final String POST_COMMENTS_EXT_DATA = "UgcPostComments";
+ private static final String COMMENTS_ENTITY_REGION_EXT_DATA = "CommentsEntityRegion";
+
+ @Mock
+ private WebRequestContext webRequestContext;
+
+ @Mock
+ private Localization localization;
+
+ @InjectMocks
+ private UgcModelBuilder builder;
+
+ @Test
+ public void shouldNotChangeEntityBecauseTemplateExtensionDataIsNullOrHasNoUgcMetadata() {
+ //given
+ TestEntity testEntity = new TestEntity();
+ EntityModelData entityModelData = new EntityModelData();
+ ComponentTemplateData componentTemplate = new ComponentTemplateData();
+ ContentModelData metadata = new ContentModelData();
+ entityModelData.setComponentTemplate(componentTemplate);
+
+ //when
+ EntityModel entityR2 = builder.buildEntityModel(testEntity, entityModelData, null);
+
+ //then
+ Assert.assertSame(testEntity, entityR2);
+
+ //when
+ componentTemplate.setMetadata(metadata);
+ entityR2 = builder.buildEntityModel(testEntity, entityModelData, null);
+ Assert.assertNull(entityR2.getExtensionData());
+
+ //then
+ Assert.assertSame(testEntity, entityR2);
+ Assert.assertNull(entityR2.getExtensionData());
+ }
+
+ /**
+ *
+ */
+ @Test
+ public void shouldChangeEntity() {
+ //given
+ TestEntity testEntity = new TestEntity();
+ EntityModelData entityModelData = getUgcEntityModelData();
+
+ //when
+ EntityModel entityR2 = builder.buildEntityModel(testEntity, entityModelData, null);
+
+ //then
+ Assert.assertEquals(entityR2.getExtensionData().get(SHOW_COMMENTS_EXT_DATA), true);
+ Assert.assertEquals(entityR2.getExtensionData().get(COMMENTS_ENTITY_REGION_EXT_DATA), "comment");
+ Assert.assertEquals(entityR2.getExtensionData().get(POST_COMMENTS_EXT_DATA), new ContentModelData());
+ }
+
+ @Test
+ public void shouldNotChangePageModel() {
+ //given
+ PageModel testPage = mock(PageModel.class);
+
+ PageModelData pageModelData = new PageModelData();
+ PageTemplateData pageTemplate = new PageTemplateData();
+ ContentModelData metadata = new ContentModelData();
+ ContentModelData ugcMetadata = new ContentModelData();
+ ugcMetadata.put(COMMENTS_REGION_KEY, "comment");
+ metadata.put(COMMENTS_CONFIG, ugcMetadata);
+
+ RegionModel regionmodel = mock(RegionModelImpl.class);
+ RegionModelSet regionModelSet = new RegionModelSetImpl();
+ regionModelSet.add(regionmodel);
+
+ TestEntity testEntity = new TestEntity();
+ EntityModel entityR2 = builder.buildEntityModel(testEntity, getUgcEntityModelData(), null);
+ ((TestEntity) entityR2).setId("2");
+
+ List regionEntities = new ArrayList<>();
+ regionEntities.add(entityR2);
+
+ pageTemplate.setMetadata(metadata);
+ pageModelData.setPageTemplate(pageTemplate);
+
+ when(webRequestContext.getLocalization()).thenReturn(localization);
+ when(localization.getPath()).thenReturn("path");
+ when(localization.getId()).thenReturn("1");
+ when(testPage.getRegions()).thenReturn(regionModelSet);
+ when(regionmodel.getEntities()).thenReturn(regionEntities);
+ when(regionmodel.getRegions()).thenReturn(new RegionModelSetImpl());
+ when(regionmodel.getName()).thenReturn("comment");
+
+
+ //when
+ PageModel pageR2 = builder.buildPageModel(testPage, pageModelData);
+
+ //then
+ Assert.assertSame(testPage, pageR2);
+ Assert.assertEquals(((RegionModel)testPage.getRegions().get(regionmodel.getClass()).toArray()[0]).getEntities().size(),3);
+
+
+ }
+
+ private EntityModelData getUgcEntityModelData() {
+ EntityModelData entityModelData = new EntityModelData();
+ ComponentTemplateData componentTemplate = new ComponentTemplateData();
+ ContentModelData metadata = new ContentModelData();
+ ContentModelData ugcMetadata = new ContentModelData();
+ ContentModelData ugcPostMetadata = new ContentModelData();
+
+ ugcMetadata.put(SHOW_COMMENTS_KEY, "yes");
+ ugcMetadata.put(ALLOW_POST_KEY, "yes");
+ ugcMetadata.put(COMMENTS_REGION_KEY, "comment");
+ ugcMetadata.put(POST_FORM_CONFIG, ugcPostMetadata);
+
+ metadata.put(COMMENTS_CONFIG, ugcMetadata);
+
+ componentTemplate.setMetadata(metadata);
+ entityModelData.setComponentTemplate(componentTemplate);
+ return entityModelData;
+ }
+
+ private static class TestEntity extends AbstractEntityModel {
+
+ }
+
+}
\ No newline at end of file
diff --git a/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/model/entity/UgcCommentTest.java b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/model/entity/UgcCommentTest.java
new file mode 100644
index 000000000..ef2e8352a
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/model/entity/UgcCommentTest.java
@@ -0,0 +1,117 @@
+package com.sdl.dxa.modules.ugc.model.entity;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sdl.dxa.DxaSpringInitialization;
+import com.sdl.dxa.modules.ugc.data.Comment;
+import com.sdl.dxa.modules.ugc.data.User;
+import com.sdl.webapp.common.util.ApplicationContextHolder;
+import com.sdl.webapp.common.util.TcmUtils;
+import org.joda.time.DateTime;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
+@ActiveProfiles("test")
+public class UgcCommentTest {
+ private static UgcComment ugcComment = new UgcComment();
+ private static Comment commentData = new Comment();
+ private static List comments = new ArrayList<>();
+ private static UgcComment otherComment = new UgcComment();
+ private static DateTime time = DateTime.now();
+ private static User user = new User();
+
+ static {
+
+ user.setEmailAddress("test@test.test");
+ user.setName("Tester");
+ user.setExternalId("ExternalId");
+ user.setId("Id");
+
+ commentData.setCreationDate(time);
+ commentData.setLastModifiedDate(time);
+ commentData.setContent("Comment");
+ commentData.setId(1);
+ commentData.setItemId(2);
+ commentData.setItemPublicationId(3);
+ commentData.setParentId(4);
+ commentData.setItemType(TcmUtils.COMPONENT_ITEM_TYPE);
+ commentData.setRating(5);
+ commentData.setUser(user);
+
+ otherComment.setCommentData(commentData);
+ otherComment.setComments(comments);
+
+ ugcComment.setCommentData(commentData);
+ ugcComment.setComments(comments);
+ }
+
+ @Test
+ public void shouldReturnCommentData() {
+ //given
+
+ //when
+
+ //then
+ assertEquals(commentData, ugcComment.getCommentData());
+ }
+
+ @Test
+ public void shouldReturnUserName() {
+ //given
+
+ //when
+
+ //then
+ assertEquals("Tester", ugcComment.getCommentData().getUser().getName());
+ }
+
+ @Test
+ public void shouldReturnComments() {
+ //given
+
+ //when
+
+ //then
+ assertEquals(comments, ugcComment.getComments());
+ }
+
+ @Test
+ public void shouldReturnCreationDate() {
+ //given
+
+ //when
+
+ //then
+ assertEquals(time, ugcComment.getCommentData().getCreationDate());
+ }
+
+
+ @Configuration
+ @Profile("test")
+ static class SpringContext {
+
+ @Bean
+ public ApplicationContextHolder applicationContextHolder() {
+ return new ApplicationContextHolder();
+ }
+
+ @Bean
+ public ObjectMapper objectMapper() {
+ return new DxaSpringInitialization().objectMapper();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/model/entity/UgcCommentsTest.java b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/model/entity/UgcCommentsTest.java
new file mode 100644
index 000000000..48a28779c
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/model/entity/UgcCommentsTest.java
@@ -0,0 +1,74 @@
+package com.sdl.dxa.modules.ugc.model.entity;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sdl.dxa.DxaSpringInitialization;
+import com.sdl.webapp.common.util.ApplicationContextHolder;
+import com.sdl.webapp.common.util.TcmUtils;
+import com.tridion.util.CMURI;
+import com.tridion.util.TCMURI;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
+@ActiveProfiles("test")
+public class UgcCommentsTest {
+
+ private static UgcComments ugcComments = new UgcComments();
+ private static UgcComment ugcComment = new UgcComment();
+ private static List comments = new ArrayList<>();
+ private static CMURI target;
+
+ static {
+ }
+
+ @Test
+ public void shouldReturnComments() {
+ //given
+ comments.add(ugcComment);
+ ugcComments.setComments(comments);
+ //when
+
+ //then
+ assertEquals(ugcComment, ugcComments.getComments().get(0));
+ }
+
+ @Test
+ public void setTarget() throws Exception {
+ //given
+ target = new TCMURI(TcmUtils.buildTcmUri(1, 2, TcmUtils.COMPONENT_ITEM_TYPE));
+ ugcComments.setTarget(target);
+ //when
+
+ //then
+ assertEquals(target, ugcComments.getTarget());
+ }
+
+ @Configuration
+ @Profile("test")
+ static class SpringContext {
+
+ @Bean
+ public ApplicationContextHolder applicationContextHolder() {
+ return new ApplicationContextHolder();
+ }
+
+ @Bean
+ public ObjectMapper objectMapper() {
+ return new DxaSpringInitialization().objectMapper();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/model/validator/UgcPostCommentFormValidatorTest.java b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/model/validator/UgcPostCommentFormValidatorTest.java
new file mode 100644
index 000000000..5e36185c1
--- /dev/null
+++ b/webapp-java/dxa-module-ugc/src/test/java/com/sdl/dxa/modules/ugc/model/validator/UgcPostCommentFormValidatorTest.java
@@ -0,0 +1,71 @@
+package com.sdl.dxa.modules.ugc.model.validator;
+
+import com.sdl.dxa.modules.ugc.model.entity.UgcPostCommentForm;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.validation.BeanPropertyBindingResult;
+import org.springframework.validation.BindingResult;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class UgcPostCommentFormValidatorTest {
+
+ @Mock
+ private UgcPostCommentFormValidator ugcPostCommentFormValidator;
+
+ @Test
+ public void shouldSupportUgcPostCommentFormClass() {
+ //given
+ ugcPostCommentFormValidator = new UgcPostCommentFormValidator();
+ //when
+ boolean result = ugcPostCommentFormValidator.supports(UgcPostCommentForm.class);
+ //then
+ Assert.assertTrue(result);
+ }
+
+ @Test
+ public void shouldNotSupportOtherClass() {
+ //given
+ ugcPostCommentFormValidator = new UgcPostCommentFormValidator();
+ //when
+ boolean result = ugcPostCommentFormValidator.supports(Object.class);
+ //then
+ Assert.assertFalse(result);
+ }
+
+ @Test
+ public void shouldFailOnValidation() {
+ //given
+ ugcPostCommentFormValidator = new UgcPostCommentFormValidator();
+ UgcPostCommentForm ugcPostCommentForm = mock(UgcPostCommentForm.class);
+ BindingResult bindingResult = new BeanPropertyBindingResult(ugcPostCommentForm, "Test");
+
+ //when
+ ugcPostCommentFormValidator.validate(ugcPostCommentForm, bindingResult);
+ //then
+ Assert.assertEquals(bindingResult.getErrorCount(), 3);
+ }
+
+ @Test
+ public void shouldSucceedOnValidation() {
+ //given
+ ugcPostCommentFormValidator = new UgcPostCommentFormValidator();
+ UgcPostCommentForm ugcPostCommentForm = mock(UgcPostCommentForm.class);
+ BindingResult bindingResult = new BeanPropertyBindingResult(ugcPostCommentForm, "Test");
+ when(ugcPostCommentForm.getUserName()).thenReturn("userName");
+ when(ugcPostCommentForm.getEmailAddress()).thenReturn("test@test.com");
+ when(ugcPostCommentForm.getContent()).thenReturn("message");
+
+ //when
+ ugcPostCommentFormValidator.validate(ugcPostCommentForm, bindingResult);
+
+ //then
+ Assert.assertEquals(bindingResult.getErrorCount(), 0);
+ }
+
+}
\ No newline at end of file
diff --git a/webapp-java/gradle.properties b/webapp-java/gradle.properties
index e997a9af9..431ee3726 100644
--- a/webapp-java/gradle.properties
+++ b/webapp-java/gradle.properties
@@ -1 +1 @@
-version=2.0.0
\ No newline at end of file
+version=2.1.0-SNAPSHOT
\ No newline at end of file
diff --git a/webapp-net/51Degrees/Degrees51ContextClaimsProvider.cs b/webapp-net/51Degrees/Degrees51ContextClaimsProvider.cs
index f3918fd15..74c48ab98 100644
--- a/webapp-net/51Degrees/Degrees51ContextClaimsProvider.cs
+++ b/webapp-net/51Degrees/Degrees51ContextClaimsProvider.cs
@@ -4,7 +4,6 @@
using System.Threading;
using System.Web;
using FiftyOne.Foundation.Mobile.Detection;
-using Sdl.Web.Common.Configuration;
using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Logging;
using Sdl.Web.Context.Api.Types;
@@ -131,7 +130,7 @@ protected bool PerformUpdateRequest(string key)
return false;
}
- public IDictionary GetContextClaims(string aspectName, Localization localization)
+ public IDictionary GetContextClaims(string aspectName, ILocalization localization)
{
try
{
diff --git a/webapp-net/51Degrees/Sdl.Web.Modules.Degrees51.csproj b/webapp-net/51Degrees/Sdl.Web.Modules.Degrees51.csproj
index b0aebe8d2..e9cb46579 100644
--- a/webapp-net/51Degrees/Sdl.Web.Modules.Degrees51.csproj
+++ b/webapp-net/51Degrees/Sdl.Web.Modules.Degrees51.csproj
@@ -1,6 +1,9 @@
+
+
+
Debug
AnyCPU
@@ -13,8 +16,6 @@
512
- 2.0.0
- 10.1.0
true
diff --git a/webapp-net/51Degrees/packages.config b/webapp-net/51Degrees/packages.config
index 282195bcd..5652a8fd7 100644
--- a/webapp-net/51Degrees/packages.config
+++ b/webapp-net/51Degrees/packages.config
@@ -3,5 +3,5 @@
-
+
\ No newline at end of file
diff --git a/webapp-net/AudienceManager/Sdl.Web.Modules.AudienceManager.csproj b/webapp-net/AudienceManager/Sdl.Web.Modules.AudienceManager.csproj
index 697b08522..fbd09f1a3 100644
--- a/webapp-net/AudienceManager/Sdl.Web.Modules.AudienceManager.csproj
+++ b/webapp-net/AudienceManager/Sdl.Web.Modules.AudienceManager.csproj
@@ -1,6 +1,7 @@
+
Debug
AnyCPU
@@ -18,9 +19,6 @@
12.0
- 2.0.0
- 10.1.0
- 8.5.0
true
@@ -84,7 +82,7 @@
..\packages\Sdl.Web.Delivery.$(SdlDeliveryVersion)\lib\net452\Tridion.ContentDelivery.AmbientData.dll
- false
+ True
..\packages\Sdl.AudienceManager.ContentDelivery.$(AudienceManagerVersion)\lib\net452\Tridion.OutboundEmail.ContentDelivery.dll
diff --git a/webapp-net/AudienceManager/UserProfileFactory.cs b/webapp-net/AudienceManager/UserProfileFactory.cs
index 4e30d818b..6683d225c 100644
--- a/webapp-net/AudienceManager/UserProfileFactory.cs
+++ b/webapp-net/AudienceManager/UserProfileFactory.cs
@@ -1,6 +1,7 @@
using System;
using System.Web;
using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Logging;
using Sdl.Web.Mvc.Configuration;
using Tridion.ContentDelivery.AmbientData;
@@ -57,7 +58,7 @@ public static UserProfile GetUserProfile(string identificationKey)
return null;
}
- Localization localization = WebRequestContext.Localization;
+ ILocalization localization = WebRequestContext.Localization;
// Audience Manager reads the context Publication ID from ADF:
AmbientDataContext.CurrentClaimStore.Put(new Uri("taf:claim:publication:id"), localization.Id);
diff --git a/webapp-net/AudienceManager/packages.config b/webapp-net/AudienceManager/packages.config
index aa2944298..c313affea 100644
--- a/webapp-net/AudienceManager/packages.config
+++ b/webapp-net/AudienceManager/packages.config
@@ -5,7 +5,7 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/webapp-net/AzureWebApp/AzureUnknownLocalizationHandler.cs b/webapp-net/AzureWebApp/AzureUnknownLocalizationHandler.cs
index 0b17b966d..5690c35bd 100644
--- a/webapp-net/AzureWebApp/AzureUnknownLocalizationHandler.cs
+++ b/webapp-net/AzureWebApp/AzureUnknownLocalizationHandler.cs
@@ -24,7 +24,7 @@ public class AzureUnknownLocalizationHandler : IUnknownLocalizationHandler
/// the response headers and body should be set and should be called to terminate the HTTP processing pipeline.
///
/// May return a instance if the handler manages to resolve the Localization. If null is returned, default error handling will be applied.
- public Localization HandleUnknownLocalization(DxaUnknownLocalizationException exception, HttpRequest request, HttpResponse response)
+ public ILocalization HandleUnknownLocalization(DxaUnknownLocalizationException exception, HttpRequest request, HttpResponse response)
{
using (new Tracer(exception, request, response))
{
diff --git a/webapp-net/AzureWebApp/Sdl.Web.Modules.AzureWebApp.csproj b/webapp-net/AzureWebApp/Sdl.Web.Modules.AzureWebApp.csproj
index 03b0a3ee8..6d29c20d4 100644
--- a/webapp-net/AzureWebApp/Sdl.Web.Modules.AzureWebApp.csproj
+++ b/webapp-net/AzureWebApp/Sdl.Web.Modules.AzureWebApp.csproj
@@ -1,6 +1,9 @@
+
+
+
Debug
AnyCPU
@@ -13,7 +16,6 @@
512
- 2.0.0
true
diff --git a/webapp-net/AzureWebApp/Web.config b/webapp-net/AzureWebApp/Web.config
index 38665117c..742f0ae97 100644
--- a/webapp-net/AzureWebApp/Web.config
+++ b/webapp-net/AzureWebApp/Web.config
@@ -164,48 +164,48 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/webapp-net/AzureWebApp/packages.config b/webapp-net/AzureWebApp/packages.config
index fd5cb3cfd..0a739b6de 100644
--- a/webapp-net/AzureWebApp/packages.config
+++ b/webapp-net/AzureWebApp/packages.config
@@ -15,5 +15,5 @@
-
+
\ No newline at end of file
diff --git a/webapp-net/ContextExpressions/ContextExpressionEvaluator.cs b/webapp-net/ContextExpressions/ContextExpressionEvaluator.cs
index 05883d9fc..7b392ac7c 100644
--- a/webapp-net/ContextExpressions/ContextExpressionEvaluator.cs
+++ b/webapp-net/ContextExpressions/ContextExpressionEvaluator.cs
@@ -20,7 +20,7 @@ public class ContextExpressionEvaluator : IConditionalEntityEvaluator
/// The Entity Model to be evaluated.
/// The context Localization
/// true if the Entity should be included.
- public bool IncludeEntity(EntityModel entity, Localization localization)
+ public bool IncludeEntity(EntityModel entity, ILocalization localization)
{
using (new Tracer(entity))
{
@@ -51,7 +51,7 @@ public bool IncludeEntity(EntityModel entity, Localization localization)
}
#endregion
- private static IDictionary GetCachedContextClaims(Localization localization)
+ private static IDictionary GetCachedContextClaims(ILocalization localization)
{
// TODO TSI-110: This is a temporary measure to cache the Context Claims per request
IDictionary result = null;
diff --git a/webapp-net/ContextExpressions/ContextExpressionModelBuilder.cs b/webapp-net/ContextExpressions/ContextExpressionModelBuilder.cs
index c5801b0b4..d627816f9 100644
--- a/webapp-net/ContextExpressions/ContextExpressionModelBuilder.cs
+++ b/webapp-net/ContextExpressions/ContextExpressionModelBuilder.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using DD4T.ContentModel;
-using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Logging;
using Sdl.Web.Common.Models;
using Sdl.Web.DataModel;
@@ -21,12 +21,12 @@ namespace Sdl.Web.Modules.ContextExpressions
public class ContextExpressionModelBuilder : IModelBuilder, IEntityModelBuilder
{
#region IModelBuilder members
- public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable includes, Localization localization)
+ public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable includes, ILocalization localization)
{
// Nothing to do here
}
- public void BuildEntityModel(ref EntityModel entityModel, IComponentPresentation cp, Localization localization)
+ public void BuildEntityModel(ref EntityModel entityModel, IComponentPresentation cp, ILocalization localization)
{
using (new Tracer(entityModel, cp, localization))
{
@@ -53,7 +53,7 @@ public void BuildEntityModel(ref EntityModel entityModel, IComponentPresentation
}
}
- public void BuildEntityModel(ref EntityModel entityModel, IComponent component, Type baseModelType, Localization localization)
+ public void BuildEntityModel(ref EntityModel entityModel, IComponent component, Type baseModelType, ILocalization localization)
{
// Nothing to do here
}
@@ -66,8 +66,8 @@ public void BuildEntityModel(ref EntityModel entityModel, IComponent component,
/// The strongly typed Entity Model to build. Is null for the first Entity Model Builder in the pipeline.
/// The DXA R2 Data Model.
/// The base type for the Entity Model to build.
- /// The context .
- public void BuildEntityModel(ref EntityModel entityModel, EntityModelData entityModelData, Type baseModelType, Localization localization)
+ /// The context .
+ public void BuildEntityModel(ref EntityModel entityModel, EntityModelData entityModelData, Type baseModelType, ILocalization localization)
{
using (new Tracer(entityModel, entityModelData, baseModelType, localization))
{
diff --git a/webapp-net/ContextExpressions/Sdl.Web.Modules.ContextExpressions.csproj b/webapp-net/ContextExpressions/Sdl.Web.Modules.ContextExpressions.csproj
index 19afedb5b..c4bf826f2 100644
--- a/webapp-net/ContextExpressions/Sdl.Web.Modules.ContextExpressions.csproj
+++ b/webapp-net/ContextExpressions/Sdl.Web.Modules.ContextExpressions.csproj
@@ -1,6 +1,9 @@
+
+
+
Debug
AnyCPU
@@ -13,8 +16,6 @@
512
- 2.0.0
- 2.0.0
true
@@ -39,7 +40,7 @@
False
- ..\packages\Sdl.Dxa.DataModel.$(SdlDxaDataModelPackageVersion)\lib\net452\Sdl.Web.DataModel.dll
+ ..\packages\Sdl.Dxa.DataModel.$(DxaDataModelVersion)\lib\net452\Sdl.Web.DataModel.dll
False
diff --git a/webapp-net/ContextExpressions/packages.config b/webapp-net/ContextExpressions/packages.config
index 82a2a8389..1abcd5f29 100644
--- a/webapp-net/ContextExpressions/packages.config
+++ b/webapp-net/ContextExpressions/packages.config
@@ -1,6 +1,6 @@
-
-
+
+
\ No newline at end of file
diff --git a/webapp-net/Core/Areas/Core/Views/Entity/LanguageSelector.cshtml b/webapp-net/Core/Areas/Core/Views/Entity/LanguageSelector.cshtml
index 27c285db6..fc5079098 100644
--- a/webapp-net/Core/Areas/Core/Views/Entity/LanguageSelector.cshtml
+++ b/webapp-net/Core/Areas/Core/Views/Entity/LanguageSelector.cshtml
@@ -1,7 +1,9 @@
-@model Configuration
+@using System.Collections.Generic
+@using Sdl.Web.Common.Interfaces
+@model Configuration
@{
var siteLocalizations = WebRequestContext.Localization.SiteLocalizations;
- var filteredLocalizations = new List();
+ var filteredLocalizations = new List();
if (siteLocalizations.Count > 1)
{
var excludedLocalizations = new List();
diff --git a/webapp-net/Core/Models/Entity/Article.cs b/webapp-net/Core/Models/Entity/Article.cs
index 1d78c0d17..d7d7820a3 100644
--- a/webapp-net/Core/Models/Entity/Article.cs
+++ b/webapp-net/Core/Models/Entity/Article.cs
@@ -2,7 +2,7 @@
using System;
using System.Collections.Generic;
using System.ServiceModel.Syndication;
-using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
namespace Sdl.Web.Modules.Core.Models
{
@@ -27,7 +27,7 @@ public class Article : EntityModel, ISyndicationFeedItemProvider
///
/// The context .
/// A single syndication feed item containing information extracted from this .
- public IEnumerable ExtractSyndicationFeedItems(Localization localization)
+ public IEnumerable ExtractSyndicationFeedItems(ILocalization localization)
{
return new[] { CreateSyndicationItem(Headline, Description, null, Date, localization) };
}
diff --git a/webapp-net/Core/Models/Entity/ContentList.cs b/webapp-net/Core/Models/Entity/ContentList.cs
index e7b8e1a0e..0358ca8e7 100644
--- a/webapp-net/Core/Models/Entity/ContentList.cs
+++ b/webapp-net/Core/Models/Entity/ContentList.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Logging;
namespace Sdl.Web.Modules.Core.Models
@@ -40,7 +41,7 @@ public int CurrentPage
}
}
- public override Query GetQuery(Localization localization)
+ public override Query GetQuery(ILocalization localization)
{
return new SimpleBrokerQuery
{
@@ -53,7 +54,7 @@ public override Query GetQuery(Localization localization)
};
}
- protected int MapSchema(Localization localization)
+ protected int MapSchema(ILocalization localization)
{
if (ContentType == null)
{
diff --git a/webapp-net/Core/Models/Entity/Download.cs b/webapp-net/Core/Models/Entity/Download.cs
index 582f84a24..88e0f88ed 100644
--- a/webapp-net/Core/Models/Entity/Download.cs
+++ b/webapp-net/Core/Models/Entity/Download.cs
@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.ServiceModel.Syndication;
-using Sdl.Web.Common.Configuration;
using Sdl.Web.Common.Models;
using System;
+using Sdl.Web.Common.Interfaces;
namespace Sdl.Web.Modules.Core.Models
{
@@ -50,7 +50,7 @@ public override string ToHtml(string widthFactor, double aspect = 0, string cssC
///
/// This makes it possible possible to render "embedded" Download Models using the Html.DxaEntity method.
///
- public override MvcData GetDefaultView(Localization localization)
+ public override MvcData GetDefaultView(ILocalization localization)
{
return new MvcData("Core:Download");
}
@@ -61,7 +61,7 @@ public override MvcData GetDefaultView(Localization localization)
///
/// The context .
/// A single syndication feed item containing information extracted from this .
- public IEnumerable ExtractSyndicationFeedItems(Localization localization)
+ public IEnumerable ExtractSyndicationFeedItems(ILocalization localization)
{
Link downloadLink = new Link {Url = Url};
return new[] { CreateSyndicationItem(FileName, Description, downloadLink, null, localization) };
diff --git a/webapp-net/Core/Models/Entity/Image.cs b/webapp-net/Core/Models/Entity/Image.cs
index 94ec1efbe..afa082ca3 100644
--- a/webapp-net/Core/Models/Entity/Image.cs
+++ b/webapp-net/Core/Models/Entity/Image.cs
@@ -2,6 +2,7 @@
using System.Globalization;
using System.Xml;
using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Models;
namespace Sdl.Web.Modules.Core.Models
@@ -65,7 +66,7 @@ public override void ReadFromXhtmlElement(XmlElement xhtmlElement)
///
/// This makes it possible possible to render "embedded" Image Models using the Html.DxaEntity method.
///
- public override MvcData GetDefaultView(Localization localization)
+ public override MvcData GetDefaultView(ILocalization localization)
{
return new MvcData("Core:Image");
}
diff --git a/webapp-net/Core/Models/Entity/ItemList.cs b/webapp-net/Core/Models/Entity/ItemList.cs
index fedb9e80c..3f83b7cb7 100644
--- a/webapp-net/Core/Models/Entity/ItemList.cs
+++ b/webapp-net/Core/Models/Entity/ItemList.cs
@@ -1,8 +1,8 @@
using Sdl.Web.Common.Models;
using System.Collections.Generic;
using System.ServiceModel.Syndication;
-using Sdl.Web.Common.Configuration;
using System;
+using Sdl.Web.Common.Interfaces;
namespace Sdl.Web.Modules.Core.Models
{
@@ -21,7 +21,7 @@ public class ItemList : EntityModel, ISyndicationFeedItemProvider
///
/// The context .
/// The extracted syndication feed items; a concatentation of syndication feed items provided by (if any).
- public virtual IEnumerable ExtractSyndicationFeedItems(Localization localization)
+ public virtual IEnumerable ExtractSyndicationFeedItems(ILocalization localization)
{
return ConcatenateSyndicationFeedItems(ItemListElements, localization);
}
diff --git a/webapp-net/Core/Models/Entity/Teaser.cs b/webapp-net/Core/Models/Entity/Teaser.cs
index 846c579e3..6d8023961 100644
--- a/webapp-net/Core/Models/Entity/Teaser.cs
+++ b/webapp-net/Core/Models/Entity/Teaser.cs
@@ -2,7 +2,7 @@
using System;
using System.Collections.Generic;
using System.ServiceModel.Syndication;
-using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
namespace Sdl.Web.Modules.Core.Models
{
@@ -94,7 +94,7 @@ public void SetFormatOption(string key, string value)
///
/// The context .
/// A single syndication feed item containing information extracted from this .
- public IEnumerable ExtractSyndicationFeedItems(Localization localization)
+ public IEnumerable ExtractSyndicationFeedItems(ILocalization localization)
{
Link link = Link;
if (link == null && Media != null)
diff --git a/webapp-net/Core/Models/Entity/YouTubeVideo.cs b/webapp-net/Core/Models/Entity/YouTubeVideo.cs
index b34e1b565..3b53b462c 100644
--- a/webapp-net/Core/Models/Entity/YouTubeVideo.cs
+++ b/webapp-net/Core/Models/Entity/YouTubeVideo.cs
@@ -2,6 +2,7 @@
using System.Xml;
using Newtonsoft.Json;
using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Models;
namespace Sdl.Web.Modules.Core.Models
@@ -72,7 +73,7 @@ public override void ReadFromXhtmlElement(XmlElement xhtmlElement)
///
/// This makes it possible possible to render "embedded" YouTubeVideo Models using the Html.DxaEntity method.
///
- public override MvcData GetDefaultView(Localization localization)
+ public override MvcData GetDefaultView(ILocalization localization)
{
return new MvcData("Core:YouTubeVideo");
}
diff --git a/webapp-net/Core/Sdl.Web.Modules.Core.csproj b/webapp-net/Core/Sdl.Web.Modules.Core.csproj
index 467468878..5db9f9719 100644
--- a/webapp-net/Core/Sdl.Web.Modules.Core.csproj
+++ b/webapp-net/Core/Sdl.Web.Modules.Core.csproj
@@ -1,6 +1,9 @@
+
+
+
Debug
AnyCPU
@@ -27,7 +30,6 @@
true
- 2.0.0
true
diff --git a/webapp-net/Core/packages.config b/webapp-net/Core/packages.config
index 4d08071a9..6015e2506 100644
--- a/webapp-net/Core/packages.config
+++ b/webapp-net/Core/packages.config
@@ -5,5 +5,5 @@
-
+
\ No newline at end of file
diff --git a/webapp-net/DxaModules.sln b/webapp-net/DxaModules.sln
index fe81fc341..2a0d03705 100644
--- a/webapp-net/DxaModules.sln
+++ b/webapp-net/DxaModules.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
+VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sdl.Web.Modules.Search", "Search\Sdl.Web.Modules.Search.csproj", "{8FEFFF4C-0AE2-452F-89B1-36F7D2944032}"
EndProject
@@ -32,6 +32,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sdl.Web.Modules.Core", "Cor
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sdl.Web.Modules.AudienceManager", "AudienceManager\Sdl.Web.Modules.AudienceManager.csproj", "{83EC18A4-831F-4939-9912-D8A93FA3998D}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sdl.Web.Modules.Ugc", "Ugc\Sdl.Web.Modules.Ugc.csproj", "{54DDEEE6-1F97-4E53-8018-5918F7D90A8B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sdl.Web.Modules.TridionDocs", "TridionDocs\Sdl.Web.Modules.TridionDocs.csproj", "{EE523D3E-4A5D-4E01-9FB2-7BF4352F6CE5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -86,6 +90,14 @@ Global
{83EC18A4-831F-4939-9912-D8A93FA3998D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{83EC18A4-831F-4939-9912-D8A93FA3998D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{83EC18A4-831F-4939-9912-D8A93FA3998D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {54DDEEE6-1F97-4E53-8018-5918F7D90A8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {54DDEEE6-1F97-4E53-8018-5918F7D90A8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {54DDEEE6-1F97-4E53-8018-5918F7D90A8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {54DDEEE6-1F97-4E53-8018-5918F7D90A8B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EE523D3E-4A5D-4E01-9FB2-7BF4352F6CE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EE523D3E-4A5D-4E01-9FB2-7BF4352F6CE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EE523D3E-4A5D-4E01-9FB2-7BF4352F6CE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EE523D3E-4A5D-4E01-9FB2-7BF4352F6CE5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/webapp-net/DxaModulesCommon.Props b/webapp-net/DxaModulesCommon.Props
new file mode 100644
index 000000000..3075e41be
--- /dev/null
+++ b/webapp-net/DxaModulesCommon.Props
@@ -0,0 +1,20 @@
+
+
+
+
+
+ 2.1.0-beta-201804241437
+ 2.1.0-beta-201805111302
+ 11.0.0-beta-201805161005
+ 9.0.0-beta-201804031026
+ 8.5.0
+ 10.1.0
+
+
+
\ No newline at end of file
diff --git a/webapp-net/ExperienceOptimization/Mapping/SmartTargetModelBuilder.cs b/webapp-net/ExperienceOptimization/Mapping/SmartTargetModelBuilder.cs
index 2580b563a..050a75050 100644
--- a/webapp-net/ExperienceOptimization/Mapping/SmartTargetModelBuilder.cs
+++ b/webapp-net/ExperienceOptimization/Mapping/SmartTargetModelBuilder.cs
@@ -20,6 +20,7 @@
using MvcData = Sdl.Web.Common.Models.MvcData;
using TcmUri = Tridion.SmartTarget.Utils.TcmUri;
using System.Globalization;
+using Sdl.Web.Common.Interfaces;
namespace Sdl.Web.Modules.SmartTarget.Mapping
{
@@ -44,7 +45,7 @@ public class SmartTargetModelBuilder : IModelBuilder, IPageModelBuilder
/// This implementation relies on the already having constructed Region Models of type .
/// We "upgrade" the Page Model to type and populate the ST Regions Entities.
///
- public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable includes, Localization localization)
+ public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable includes, ILocalization localization)
{
using (new Tracer(pageModel, page, includes, localization))
{
@@ -99,12 +100,12 @@ public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerableThe DXA R2 Data Model.
/// Indicates whether Include Page Regions should be included.
/// The context .
- public void BuildPageModel(ref PageModel pageModel, PageModelData pageModelData, bool includePageRegions, Localization localization)
+ public void BuildPageModel(ref PageModel pageModel, PageModelData pageModelData, bool includePageRegions, ILocalization localization)
{
using (new Tracer(pageModel, pageModelData, includePageRegions, localization))
{
@@ -155,7 +156,7 @@ public void BuildPageModel(ref PageModel pageModel, PageModelData pageModelData,
}
#endregion
- private void PopulateSmartTargetRegions(SmartTargetPageModel smartTargetPageModel, Localization localization)
+ private void PopulateSmartTargetRegions(SmartTargetPageModel smartTargetPageModel, ILocalization localization)
{
// Execute a ST Query for all SmartTargetRegions on the Page.
ResultSet resultSet = ExecuteSmartTargetQuery(smartTargetPageModel, localization);
@@ -232,7 +233,7 @@ private void PopulateSmartTargetRegions(SmartTargetPageModel smartTargetPageMode
}
}
- protected virtual SmartTargetPromotion CreatePromotionEntity(Promotion promotion, string viewName, string regionName, Localization localization, ExperimentDimensions experimentDimensions)
+ protected virtual SmartTargetPromotion CreatePromotionEntity(Promotion promotion, string viewName, string regionName, ILocalization localization, ExperimentDimensions experimentDimensions)
{
// In ST 2014 SP1 the ResultSet.FilterPromotions API represents Experiments as type Promotion, so we're not testing on type Experiment here.
SmartTargetPromotion result = (experimentDimensions != null) ? new SmartTargetExperiment(experimentDimensions) : new SmartTargetPromotion();
@@ -251,13 +252,13 @@ protected virtual SmartTargetPromotion CreatePromotionEntity(Promotion promotion
return result;
}
- protected virtual SmartTargetItem CreateSmartTargetItem(Item item, Localization localization)
+ protected virtual SmartTargetItem CreateSmartTargetItem(Item item, ILocalization localization)
{
string entityId = $"{item.ComponentUri.ItemId}-{item.TemplateUri.ItemId}";
return new SmartTargetItem(entityId, localization);
}
- private static ResultSet ExecuteSmartTargetQuery(SmartTargetPageModel smartTargetPageModel, Localization localization)
+ private static ResultSet ExecuteSmartTargetQuery(SmartTargetPageModel smartTargetPageModel, ILocalization localization)
{
using (new Tracer(smartTargetPageModel, localization))
{
@@ -282,7 +283,7 @@ private static ResultSet ExecuteSmartTargetQuery(SmartTargetPageModel smartTarge
}
}
- private static bool GetAllowDuplicatesFromConfig(Localization localization)
+ private static bool GetAllowDuplicatesFromConfig(ILocalization localization)
{
string configValue = localization.GetConfigValue("smarttarget.allowDuplicationOnSamePageConfig");
return Convert.ToBoolean(configValue);
@@ -294,7 +295,7 @@ private static bool GetAllowDuplicatesFromConfig(Localization localization)
///
/// Used in R2 Model Builder Pipeline.
///
- private static bool GetAllowDuplicatesOnSamePage(PageModelData pageModelData, Localization localization)
+ private static bool GetAllowDuplicatesOnSamePage(PageModelData pageModelData, ILocalization localization)
{
object allowDups;
if ((pageModelData.Metadata == null) || !pageModelData.Metadata.TryGetValue(AllowDuplicatesFieldName, out allowDups))
@@ -318,7 +319,7 @@ private static bool GetAllowDuplicatesOnSamePage(PageModelData pageModelData, Lo
///
/// Used in Legacy Model Builder Pipeline (DD4T-based)
///
- private static bool GetAllowDuplicatesOnSamePage(IPageTemplate pageTemplate, Localization localization)
+ private static bool GetAllowDuplicatesOnSamePage(IPageTemplate pageTemplate, ILocalization localization)
{
string allowDuplicates = null;
if ((pageTemplate?.MetadataFields != null) && pageTemplate.MetadataFields.ContainsKey(AllowDuplicatesFieldName))
diff --git a/webapp-net/ExperienceOptimization/Models/SmartTargetItem.cs b/webapp-net/ExperienceOptimization/Models/SmartTargetItem.cs
index 79f9e1a4f..bb22553fc 100644
--- a/webapp-net/ExperienceOptimization/Models/SmartTargetItem.cs
+++ b/webapp-net/ExperienceOptimization/Models/SmartTargetItem.cs
@@ -1,13 +1,14 @@
using Sdl.Web.Common.Configuration;
using Sdl.Web.Common.Models;
using System;
+using Sdl.Web.Common.Interfaces;
namespace Sdl.Web.Modules.SmartTarget.Models
{
[Serializable]
public class SmartTargetItem
{
- private readonly Localization _localization;
+ private readonly ILocalization _localization;
private EntityModel _entity;
public string EntityId { get; private set; }
@@ -20,7 +21,7 @@ public class SmartTargetItem
///
public EntityModel Entity => _entity ?? (_entity = SiteConfiguration.ContentProvider.GetEntityModel(EntityId, _localization));
- public SmartTargetItem(string entityId, Localization localization)
+ public SmartTargetItem(string entityId, ILocalization localization)
{
EntityId = entityId;
_localization = localization;
diff --git a/webapp-net/ExperienceOptimization/Models/SmartTargetPromotion.cs b/webapp-net/ExperienceOptimization/Models/SmartTargetPromotion.cs
index 7d5eed7a1..b74da737e 100644
--- a/webapp-net/ExperienceOptimization/Models/SmartTargetPromotion.cs
+++ b/webapp-net/ExperienceOptimization/Models/SmartTargetPromotion.cs
@@ -1,8 +1,8 @@
using System;
-using Sdl.Web.Common.Configuration;
using Sdl.Web.Common.Models;
using System.Collections.Generic;
using Sdl.Web.Common;
+using Sdl.Web.Common.Interfaces;
namespace Sdl.Web.Modules.SmartTarget.Models
{
@@ -19,7 +19,7 @@ public class SmartTargetPromotion : EntityModel
public List Items { get; set; }
- public override string GetXpmMarkup(Localization localization)
+ public override string GetXpmMarkup(ILocalization localization)
{
return (XpmMetadata == null) ? string.Empty : string.Format(XpmMarkupFormat, XpmMetadata["PromotionID"], XpmMetadata["RegionID"]);
}
diff --git a/webapp-net/ExperienceOptimization/Models/SmartTargetRegion.cs b/webapp-net/ExperienceOptimization/Models/SmartTargetRegion.cs
index 16dc10baf..19a8ef044 100644
--- a/webapp-net/ExperienceOptimization/Models/SmartTargetRegion.cs
+++ b/webapp-net/ExperienceOptimization/Models/SmartTargetRegion.cs
@@ -1,5 +1,5 @@
using System;
-using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Models;
namespace Sdl.Web.Modules.SmartTarget.Models
@@ -41,7 +41,7 @@ public SmartTargetRegion(string name, string qualifiedViewName)
///
public string GetStartQueryXpmMarkup()
{
- return (XpmMetadata == null) ? String.Empty : (string) XpmMetadata["Query"];
+ return (XpmMetadata == null) ? string.Empty : (string) XpmMetadata["Query"];
}
///
@@ -49,7 +49,7 @@ public string GetStartQueryXpmMarkup()
///
/// The context Localization.
/// The XPM markup.
- public override string GetXpmMarkup(Localization localization)
+ public override string GetXpmMarkup(ILocalization localization)
{
return $"";
}
diff --git a/webapp-net/ExperienceOptimization/SDL.Web.Modules.SmartTarget.csproj b/webapp-net/ExperienceOptimization/SDL.Web.Modules.SmartTarget.csproj
index 75438fedb..cba404461 100644
--- a/webapp-net/ExperienceOptimization/SDL.Web.Modules.SmartTarget.csproj
+++ b/webapp-net/ExperienceOptimization/SDL.Web.Modules.SmartTarget.csproj
@@ -1,6 +1,9 @@
+
+
+
Debug
AnyCPU
@@ -14,10 +17,6 @@
- 10.1.0
- 2.0.0
- 2.0.0
- 8.5.0
true
@@ -46,7 +45,7 @@
False
- ..\packages\Sdl.Dxa.DataModel.$(SdlDxaDataModelPackageVersion)\lib\net452\Sdl.Web.DataModel.dll
+ ..\packages\Sdl.Dxa.DataModel.$(DxaDataModelVersion)\lib\net452\Sdl.Web.DataModel.dll
False
diff --git a/webapp-net/ExperienceOptimization/packages.config b/webapp-net/ExperienceOptimization/packages.config
index 882483bf7..663cead72 100644
--- a/webapp-net/ExperienceOptimization/packages.config
+++ b/webapp-net/ExperienceOptimization/packages.config
@@ -6,8 +6,8 @@
-
-
+
+
-
+
\ No newline at end of file
diff --git a/webapp-net/GoogleAnalytics/Sdl.Web.Modules.GoogleAnalytics.csproj b/webapp-net/GoogleAnalytics/Sdl.Web.Modules.GoogleAnalytics.csproj
index c2dee1dad..296ac14a9 100644
--- a/webapp-net/GoogleAnalytics/Sdl.Web.Modules.GoogleAnalytics.csproj
+++ b/webapp-net/GoogleAnalytics/Sdl.Web.Modules.GoogleAnalytics.csproj
@@ -1,6 +1,9 @@
+
+
+
Debug
AnyCPU
@@ -14,7 +17,6 @@
- 2.0.0
true
diff --git a/webapp-net/GoogleAnalytics/packages.config b/webapp-net/GoogleAnalytics/packages.config
index 2a6363325..8cefb486f 100644
--- a/webapp-net/GoogleAnalytics/packages.config
+++ b/webapp-net/GoogleAnalytics/packages.config
@@ -5,5 +5,5 @@
-
+
\ No newline at end of file
diff --git a/webapp-net/Impress/Sdl.Web.Modules.Impress.csproj b/webapp-net/Impress/Sdl.Web.Modules.Impress.csproj
index 86aa2aa7e..5e5fe76f9 100644
--- a/webapp-net/Impress/Sdl.Web.Modules.Impress.csproj
+++ b/webapp-net/Impress/Sdl.Web.Modules.Impress.csproj
@@ -1,6 +1,9 @@
+
+
+
Debug
AnyCPU
@@ -14,7 +17,6 @@
- 2.0.0
true
diff --git a/webapp-net/Impress/packages.config b/webapp-net/Impress/packages.config
index 2a6363325..8cefb486f 100644
--- a/webapp-net/Impress/packages.config
+++ b/webapp-net/Impress/packages.config
@@ -5,5 +5,5 @@
-
+
\ No newline at end of file
diff --git a/webapp-net/MediaManager/Models/MediaManagerDistribution.cs b/webapp-net/MediaManager/Models/MediaManagerDistribution.cs
index adb0d83e2..3a25c0e35 100644
--- a/webapp-net/MediaManager/Models/MediaManagerDistribution.cs
+++ b/webapp-net/MediaManager/Models/MediaManagerDistribution.cs
@@ -2,7 +2,7 @@
using System.Globalization;
using System.Xml;
using Newtonsoft.Json;
-using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Models;
namespace Sdl.Web.Modules.MediaManager.Models
@@ -134,7 +134,7 @@ public bool IsCustomVideoControls
///
/// This makes it possible possible to render "embedded" MediaManagerDistribution Models using the Html.DxaEntity method.
///
- public override MvcData GetDefaultView(Localization localization)
+ public override MvcData GetDefaultView(ILocalization localization)
{
return new MvcData("MediaManager:" + EclDisplayTypeId);
}
diff --git a/webapp-net/MediaManager/Sdl.Web.Modules.MediaManager.csproj b/webapp-net/MediaManager/Sdl.Web.Modules.MediaManager.csproj
index ec7ea85ef..05866c5fb 100644
--- a/webapp-net/MediaManager/Sdl.Web.Modules.MediaManager.csproj
+++ b/webapp-net/MediaManager/Sdl.Web.Modules.MediaManager.csproj
@@ -1,6 +1,9 @@
+
+
+
Debug
AnyCPU
@@ -13,8 +16,7 @@
512
-
- 2.0.0
+
true
diff --git a/webapp-net/MediaManager/packages.config b/webapp-net/MediaManager/packages.config
index 2a6363325..8cefb486f 100644
--- a/webapp-net/MediaManager/packages.config
+++ b/webapp-net/MediaManager/packages.config
@@ -5,5 +5,5 @@
-
+
\ No newline at end of file
diff --git a/webapp-net/Search/Controllers/TridionDocsSearchController.cs b/webapp-net/Search/Controllers/TridionDocsSearchController.cs
new file mode 100644
index 000000000..801b6bcab
--- /dev/null
+++ b/webapp-net/Search/Controllers/TridionDocsSearchController.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Web.Mvc;
+using Newtonsoft.Json;
+using Sdl.Web.Delivery.IQQuery.API;
+using Sdl.Web.Delivery.IQQuery.Client;
+using Sdl.Web.Delivery.IQQuery.Model.Field;
+using Sdl.Web.Delivery.IQQuery.Model.Result;
+using Sdl.Web.Modules.Search.Data;
+using Sdl.Web.Mvc.Controllers;
+using System.Text.RegularExpressions;
+
+namespace Sdl.Web.Modules.Search.Controllers
+{
+ public class TridionDocsSearchController : BaseController
+ {
+ private static readonly Regex RegexpDoubleQuotes = new Regex("^\"(.*)\"$", RegexOptions.Compiled);
+
+ [Route("~/api/search")]
+ [HttpPost]
+ public virtual ActionResult Search()
+ {
+ try
+ {
+ Stream req = Request.InputStream;
+ req.Seek(0, System.IO.SeekOrigin.Begin);
+ string json = new StreamReader(req).ReadToEnd();
+
+ SearchParameters searchParams = JsonConvert.DeserializeObject(json);
+
+ IQSearchClient search =
+ new IQSearchClient();
+ search.WithResultFilter(new SearchResultFilter
+ {
+ StartOfRange = searchParams.StartIndex,
+ EndOfRange = searchParams.StartIndex + searchParams.Count,
+ IsHighlightingEnabled = true
+ });
+ var fields = new List();
+ var values = new List();
+ if (searchParams.PublicationId != null)
+ {
+ fields.Add("publicationId");
+ values.Add(new DefaultTermValue(searchParams.PublicationId.Value.ToString()));
+ }
+ fields.Add("dynamic.FISHDITADLVRREMOTESTATUS.lng.element");
+ fields.Add($"content.{CultureInfo.GetCultureInfo(searchParams.Language).EnglishName.ToLower()}");
+ values.Add(new DefaultTermValue("VDITADLVRREMOTESTATUSONLINE"));
+
+ string searchQuery = searchParams.SearchQuery;
+ Match match = RegexpDoubleQuotes.Match(searchQuery);
+ if (match.Success)
+ {
+ searchQuery = match.Groups[1].Value;
+ }
+
+ values.Add(new DefaultTermValue(searchQuery, TermTypes.Exact));
+ var results = search.WithResultFilter(new SearchResultFilter
+ {
+ StartOfRange = searchParams.StartIndex,
+ EndOfRange = searchParams.StartIndex + searchParams.Count,
+ IsHighlightingEnabled = true
+ }).Search(new Delivery.IQQuery.Model.Search.SearchQuery().GroupedAnd(fields, values).Compile());
+ var resultSet = new SearchResultSetWrapped(results)
+ {
+ Hits = results.Hits,
+ Count = searchParams.Count.Value,
+ StartIndex = searchParams.StartIndex.Value
+ };
+ return Json(resultSet);
+ }
+ catch (Exception)
+ {
+ Response.StatusCode = 405;
+ return new EmptyResult();
+ }
+ }
+ }
+}
diff --git a/webapp-net/Search/Data/SearchParameters.cs b/webapp-net/Search/Data/SearchParameters.cs
new file mode 100644
index 000000000..816b466a7
--- /dev/null
+++ b/webapp-net/Search/Data/SearchParameters.cs
@@ -0,0 +1,20 @@
+namespace Sdl.Web.Modules.Search.Data
+{
+ public class SearchParameters
+ {
+ private static readonly string DefaultLanguage = "en";
+ private static readonly int DefaultStartIndex = 0;
+ private static readonly int DefaultResultCount = 10;
+ private static readonly string DefaultSearchQuery = "";
+
+ public int? PublicationId { get; set; }
+
+ public string Language { get; set; } = DefaultLanguage;
+
+ public string SearchQuery { get; set; } = DefaultSearchQuery;
+
+ public int? StartIndex { get; set; } = DefaultStartIndex;
+
+ public int? Count { get; set; } = DefaultResultCount;
+ }
+}
diff --git a/webapp-net/Search/Data/SearchResult.cs b/webapp-net/Search/Data/SearchResult.cs
new file mode 100644
index 000000000..4c84a0a7f
--- /dev/null
+++ b/webapp-net/Search/Data/SearchResult.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using Sdl.Web.Delivery.IQQuery.API;
+
+namespace Sdl.Web.Modules.Search.Data
+{
+ public class SearchResult : IQueryResult
+ {
+ public string Id { get; set; }
+
+ public Dictionary Fields { get; set; }
+
+ public string Locale { get; set; } = "en";
+
+ public Dictionary> Highlighted { get; set; }
+
+ public string Content { get; set; }
+
+ public string CreatedDate { get; set; }
+
+ public string ModifiedDate { get; set; }
+
+ public int PublicationId { get; set; }
+
+ public string PublicationTitle { get; set; }
+
+ public string ProductFamilyName { get; set; }
+
+ public string ProductReleaseName { get; set; }
+ }
+
+ public class SearchResultWrapped : IQueryResult
+ {
+ private readonly SearchResult _wrapped;
+
+ public SearchResultWrapped(SearchResult searchResult)
+ {
+ _wrapped = searchResult;
+ }
+
+ public string Id => _wrapped.Id;
+
+ public Dictionary Meta => _wrapped.Fields;
+
+ public string Locale => _wrapped.Locale;
+
+ public Dictionary> Highlighted => _wrapped.Highlighted;
+
+ public string Content => _wrapped.Content;
+
+ public string CreatedDate => _wrapped.CreatedDate;
+
+ public string ModifiedDate => _wrapped.ModifiedDate;
+
+ public int PublicationId => _wrapped.PublicationId;
+
+ public string PublicationTitle => _wrapped.PublicationTitle;
+
+ public string ProductFamilyName => _wrapped.ProductFamilyName;
+
+ public string ProductReleaseName => _wrapped.ProductFamilyName;
+ }
+}
diff --git a/webapp-net/Search/Data/SearchResultSet.cs b/webapp-net/Search/Data/SearchResultSet.cs
new file mode 100644
index 000000000..105961c82
--- /dev/null
+++ b/webapp-net/Search/Data/SearchResultSet.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using Sdl.Web.Delivery.IQQuery.API;
+
+namespace Sdl.Web.Modules.Search.Data
+{
+ public class SearchResultSet : IQueryResultData
+ {
+ public int Hits { get; set; }
+
+ public int Count { get; set; }
+
+ public int StartIndex { get; set; }
+
+ public IList QueryResults { get; set; }
+ }
+
+ public class SearchResultSetWrapped : IQueryResultData
+ {
+ private readonly List _results;
+ public SearchResultSetWrapped(SearchResultSet searchResultSet)
+ {
+ _results = new List();
+ foreach (var x in searchResultSet.QueryResults)
+ {
+ _results.Add(new SearchResultWrapped(x));
+ }
+ }
+
+ public int Hits { get; set; }
+ public int Count { get; set; }
+ public int StartIndex { get; set; }
+
+ public IList QueryResults
+ {
+ get { return _results; }
+ set { }
+ }
+ }
+}
diff --git a/webapp-net/Search/Providers/AwsCloudSearchProvider.cs b/webapp-net/Search/Providers/AwsCloudSearchProvider.cs
index cf6805371..0fce2c5ee 100644
--- a/webapp-net/Search/Providers/AwsCloudSearchProvider.cs
+++ b/webapp-net/Search/Providers/AwsCloudSearchProvider.cs
@@ -1,6 +1,6 @@
using System.Collections.Specialized;
using System.Linq;
-using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Logging;
using Sdl.Web.Modules.Search.Models;
using SI4T.Query.Models;
@@ -9,7 +9,7 @@ namespace Sdl.Web.Modules.Search.Providers
{
public class AwsCloudSearchProvider : SI4TSearchProvider
{
- protected override NameValueCollection SetupParameters(SearchQuery searchQuery, Localization localization)
+ protected override NameValueCollection SetupParameters(SearchQuery searchQuery, ILocalization localization)
{
NameValueCollection result = base.SetupParameters(searchQuery, localization);
if (!result.AllKeys.Contains("q.options"))
diff --git a/webapp-net/Search/Providers/IQSearchProvider.cs b/webapp-net/Search/Providers/IQSearchProvider.cs
new file mode 100644
index 000000000..5fd1d3851
--- /dev/null
+++ b/webapp-net/Search/Providers/IQSearchProvider.cs
@@ -0,0 +1,14 @@
+using System;
+using Sdl.Web.Common.Interfaces;
+using Sdl.Web.Modules.Search.Models;
+
+namespace Sdl.Web.Modules.Search.Providers
+{
+ public class IQSearchProvider : ISearchProvider
+ {
+ public void ExecuteQuery(SearchQuery searchQuery, Type resultType, ILocalization localization)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/webapp-net/Search/Providers/ISearchProvider.cs b/webapp-net/Search/Providers/ISearchProvider.cs
index 0a1ba5405..d7b60c847 100644
--- a/webapp-net/Search/Providers/ISearchProvider.cs
+++ b/webapp-net/Search/Providers/ISearchProvider.cs
@@ -1,11 +1,11 @@
using System;
-using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Modules.Search.Models;
namespace Sdl.Web.Modules.Search.Providers
{
public interface ISearchProvider
{
- void ExecuteQuery(SearchQuery searchQuery, Type resultType, Localization localization);
+ void ExecuteQuery(SearchQuery searchQuery, Type resultType, ILocalization localization);
}
}
diff --git a/webapp-net/Search/Providers/SI4TSearchProvider.cs b/webapp-net/Search/Providers/SI4TSearchProvider.cs
index 2c08e2637..b848e6f1a 100644
--- a/webapp-net/Search/Providers/SI4TSearchProvider.cs
+++ b/webapp-net/Search/Providers/SI4TSearchProvider.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Specialized;
using System.Globalization;
-using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Logging;
using Sdl.Web.Common.Models;
using Sdl.Web.Modules.Search.Models;
@@ -15,7 +15,7 @@ namespace Sdl.Web.Modules.Search.Providers
public abstract class SI4TSearchProvider : ISearchProvider
{
#region ISearchProvider members
- public void ExecuteQuery(SearchQuery searchQuery, Type resultType, Localization localization)
+ public void ExecuteQuery(SearchQuery searchQuery, Type resultType, ILocalization localization)
{
using (new Tracer(searchQuery, resultType, localization))
{
@@ -40,7 +40,7 @@ public void ExecuteQuery(SearchQuery searchQuery, Type resultType, Localization
}
#endregion
- protected virtual string GetSearchIndexUrl(Localization localization)
+ protected virtual string GetSearchIndexUrl(ILocalization localization)
{
// First try the new search.queryURL setting provided by DXA 1.3 TBBs if the Search Query URL can be obtained from Topology Manager.
string result = localization.GetConfigValue("search.queryURL");
@@ -66,7 +66,7 @@ protected virtual SearchItem MapResult(SearchResult result, Type modelType, stri
return searchItem;
}
- protected virtual NameValueCollection SetupParameters(SearchQuery searchQuery, Localization localization)
+ protected virtual NameValueCollection SetupParameters(SearchQuery searchQuery, ILocalization localization)
{
NameValueCollection result = new NameValueCollection(searchQuery.QueryStringParameters);
result["fq"] = "publicationid:" + localization.Id; // TODO: What about CM URI scheme?
diff --git a/webapp-net/Search/Providers/SolrProvider.cs b/webapp-net/Search/Providers/SolrProvider.cs
index a43a5b176..5d35bd80c 100644
--- a/webapp-net/Search/Providers/SolrProvider.cs
+++ b/webapp-net/Search/Providers/SolrProvider.cs
@@ -1,5 +1,5 @@
using System.Collections.Specialized;
-using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Logging;
using Sdl.Web.Modules.Search.Models;
using SI4T.Query.Models;
@@ -8,7 +8,7 @@ namespace Sdl.Web.Modules.Search.Providers
{
public class SolrProvider : SI4TSearchProvider
{
- protected override NameValueCollection SetupParameters(SearchQuery searchQuery, Localization localization)
+ protected override NameValueCollection SetupParameters(SearchQuery searchQuery, ILocalization localization)
{
NameValueCollection parameters = base.SetupParameters(searchQuery, localization);
// We use the highlighting feature to autogenerate a Summary if no Summary is present in the search index.
diff --git a/webapp-net/Search/SI4T.Query.CloudSearch/packages.config b/webapp-net/Search/SI4T.Query.CloudSearch/packages.config
index ba5a8f787..9a8bfc3c8 100644
--- a/webapp-net/Search/SI4T.Query.CloudSearch/packages.config
+++ b/webapp-net/Search/SI4T.Query.CloudSearch/packages.config
@@ -2,5 +2,5 @@
-
+
\ No newline at end of file
diff --git a/webapp-net/Search/Sdl.Web.Modules.Search.csproj b/webapp-net/Search/Sdl.Web.Modules.Search.csproj
index a960b8013..e74f2d8ce 100644
--- a/webapp-net/Search/Sdl.Web.Modules.Search.csproj
+++ b/webapp-net/Search/Sdl.Web.Modules.Search.csproj
@@ -1,6 +1,7 @@
+
Debug
AnyCPU
@@ -14,7 +15,6 @@
- 2.0.0
true
@@ -42,6 +42,9 @@
..\packages\Sdl.Dxa.Framework.Web8.$(DxaFrameworkVersion)\lib\net452\Sdl.Web.Common.dll
False
+
+ ..\packages\Sdl.Web.Delivery.$(SdlDeliveryVersion)\lib\net452\Sdl.Web.Delivery.IQQuery.dll
+
..\packages\Sdl.Dxa.Framework.Web8.$(DxaFrameworkVersion)\lib\net452\Sdl.Web.Mvc.dll
False
@@ -59,8 +62,13 @@
+
+
+
+
+
diff --git a/webapp-net/Search/packages.config b/webapp-net/Search/packages.config
index 58f450302..9d2df9523 100644
--- a/webapp-net/Search/packages.config
+++ b/webapp-net/Search/packages.config
@@ -6,5 +6,5 @@
-
+
\ No newline at end of file
diff --git a/webapp-net/Test/App.config b/webapp-net/Test/App.config
index f780a1d05..c7c9fd1fc 100644
--- a/webapp-net/Test/App.config
+++ b/webapp-net/Test/App.config
@@ -46,48 +46,48 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/webapp-net/Test/Models/TestFlickrImageModel.cs b/webapp-net/Test/Models/TestFlickrImageModel.cs
index 8ec95a96d..4015f91d7 100644
--- a/webapp-net/Test/Models/TestFlickrImageModel.cs
+++ b/webapp-net/Test/Models/TestFlickrImageModel.cs
@@ -1,7 +1,7 @@
using System;
using Sdl.Web.Common.Models;
using System.Globalization;
-using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
namespace Sdl.Web.Modules.Test.Models
{
@@ -36,7 +36,7 @@ public string TestProperty4
set;
}
- public override MvcData GetDefaultView(Localization localization)
+ public override MvcData GetDefaultView(ILocalization localization)
{
return new MvcData("Test:TestFlickrImage");
}
diff --git a/webapp-net/Test/Models/Tsi1758TestEntity.cs b/webapp-net/Test/Models/Tsi1758TestEntity.cs
index 3bbf937a7..757c67d06 100644
--- a/webapp-net/Test/Models/Tsi1758TestEntity.cs
+++ b/webapp-net/Test/Models/Tsi1758TestEntity.cs
@@ -1,5 +1,5 @@
using System.Collections.Generic;
-using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Models;
namespace Sdl.Web.Modules.Test.Models
@@ -18,7 +18,7 @@ public class Tsi1758TestEmbeddedEntity : EntityModel
public Link EmbedField1 { get; set; }
public Tsi1758TestEmbedded2Entity EmbedField2 { get; set; }
- public override MvcData GetDefaultView(Localization localization)
+ public override MvcData GetDefaultView(ILocalization localization)
{
return new MvcData("Test:TSI1758TestEmbedded");
}
@@ -30,7 +30,7 @@ public class Tsi1758TestEmbedded2Entity : EntityModel
public string TextField { get; set; }
public Link EmbedField2 { get; set; }
- public override MvcData GetDefaultView(Localization localization)
+ public override MvcData GetDefaultView(ILocalization localization)
{
return new MvcData("Test:TSI1758TestEmbedded2");
}
diff --git a/webapp-net/Test/Sdl.Web.Modules.Test.csproj b/webapp-net/Test/Sdl.Web.Modules.Test.csproj
index 473e8e3ff..2afa70997 100644
--- a/webapp-net/Test/Sdl.Web.Modules.Test.csproj
+++ b/webapp-net/Test/Sdl.Web.Modules.Test.csproj
@@ -1,6 +1,9 @@
+
+
+
Debug
AnyCPU
@@ -15,7 +18,6 @@
- 2.0.0
true
diff --git a/webapp-net/Test/packages.config b/webapp-net/Test/packages.config
index 2a6363325..8cefb486f 100644
--- a/webapp-net/Test/packages.config
+++ b/webapp-net/Test/packages.config
@@ -5,5 +5,5 @@
-
+
\ No newline at end of file
diff --git a/webapp-net/TridionDocs/Areas/TridionDocs/Views/Page/ErrorPage.cshtml b/webapp-net/TridionDocs/Areas/TridionDocs/Views/Page/ErrorPage.cshtml
new file mode 100644
index 000000000..64887740d
--- /dev/null
+++ b/webapp-net/TridionDocs/Areas/TridionDocs/Views/Page/ErrorPage.cshtml
@@ -0,0 +1,29 @@
+
+
+
+
+
+ SDL Documentation Error
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsApi/ErrorPage.cshtml b/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsApi/ErrorPage.cshtml
new file mode 100644
index 000000000..8274961ab
--- /dev/null
+++ b/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsApi/ErrorPage.cshtml
@@ -0,0 +1,29 @@
+
+
+
+
+
+ SDL Documentation Error
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsApi/GeneralPage.cshtml b/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsApi/GeneralPage.cshtml
new file mode 100644
index 000000000..7309a6233
--- /dev/null
+++ b/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsApi/GeneralPage.cshtml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ SDL Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsPage/ErrorPage.cshtml b/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsPage/ErrorPage.cshtml
new file mode 100644
index 000000000..8274961ab
--- /dev/null
+++ b/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsPage/ErrorPage.cshtml
@@ -0,0 +1,29 @@
+
+
+
+
+
+ SDL Documentation Error
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsPage/GeneralPage.cshtml b/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsPage/GeneralPage.cshtml
new file mode 100644
index 000000000..7309a6233
--- /dev/null
+++ b/webapp-net/TridionDocs/Areas/TridionDocs/Views/TridionDocsPage/GeneralPage.cshtml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ SDL Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webapp-net/TridionDocs/Areas/TridionDocs/Views/web.config b/webapp-net/TridionDocs/Areas/TridionDocs/Views/web.config
new file mode 100644
index 000000000..c17eb43b6
--- /dev/null
+++ b/webapp-net/TridionDocs/Areas/TridionDocs/Views/web.config
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp-net/TridionDocs/Controllers/BaseController.cs b/webapp-net/TridionDocs/Controllers/BaseController.cs
new file mode 100644
index 000000000..876813411
--- /dev/null
+++ b/webapp-net/TridionDocs/Controllers/BaseController.cs
@@ -0,0 +1,100 @@
+using System.Collections.Generic;
+using System.Web.Mvc;
+using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
+using Sdl.Web.Common.Models;
+using Sdl.Web.Modules.TridionDocs.Exceptions;
+using Sdl.Web.Modules.TridionDocs.Navigation;
+using Sdl.Web.Modules.TridionDocs.Providers;
+using Sdl.Web.Mvc.Configuration;
+using Tridion.ContentDelivery.Meta;
+
+namespace Sdl.Web.Modules.TridionDocs.Controllers
+{
+ [RouteArea("TridionDocs")]
+ public class BaseController : Mvc.Controllers.PageController
+ {
+ private static readonly string TocNaventriesMeta = "tocnaventries.generated.value";
+ private static readonly string PageConditionsUsedMeta = "conditionsused.generated.value";
+ private static readonly string PageLogicalRefObjectId = "ishlogicalref.object.id";
+
+ private TridionDocsContentProvider _contentProvider;
+
+ protected ILocalization SetupLocalization(int publicationId)
+ {
+ PublicationProvider provider = new PublicationProvider();
+ provider.CheckPublicationOnline(publicationId);
+ ILocalization localization = WebRequestContext.Localization;
+ localization.Id = publicationId.ToString();
+ return localization;
+ }
+
+ protected override ViewModel EnrichModel(ViewModel model)
+ {
+ PageModel pageModel = model as PageModel;
+ if (pageModel == null)
+ {
+ return model;
+ }
+ ILocalization localization = WebRequestContext.Localization;
+ PageMetaFactory pageMetaFactory = new PageMetaFactory(localization.Id);
+ var pageMeta = pageMetaFactory.GetMeta(pageModel.Id);
+ if (pageMeta != null)
+ {
+ var customMeta = pageMeta.CustomMeta;
+
+ if (customMeta.GetFirstValue(TocNaventriesMeta) != null)
+ {
+ // Take the generated product family name from the metadata
+ NameValuePair tocNavEntries = (NameValuePair)customMeta.NameValues[TocNaventriesMeta];
+ List values = (List) tocNavEntries?.MultipleValues;
+ if (values != null)
+ {
+ pageModel.Meta.Add(TocNaventriesMeta, string.Join(", ", values));
+ }
+ }
+
+ // Put the information about used conditions form page metadata
+ if (customMeta.GetFirstValue(PageConditionsUsedMeta) != null)
+ {
+ pageModel.Meta.Add(PageConditionsUsedMeta, (string)customMeta.GetFirstValue(PageConditionsUsedMeta));
+ }
+
+ // Add logical Ref ID information
+ if (customMeta.GetFirstValue(PageLogicalRefObjectId) != null)
+ {
+ pageModel.Meta.Add(PageLogicalRefObjectId, (string)customMeta.GetFirstValue(PageLogicalRefObjectId));
+ }
+ }
+ return model;
+ }
+
+ protected TridionDocsContentProvider TridionDocsContentProvider
+ {
+ get
+ {
+ if (!(ContentProvider is TridionDocsContentProvider))
+ {
+ throw new TridionDocsApiException(
+ "TridionDocsContentProvider not configured. Please make sure you have the TridionDocsContentProvider specified in your Unity.config");
+ }
+ return ContentProvider as TridionDocsContentProvider;
+ }
+ }
+
+ protected TridionDocsNavigationProvider TridionDocsNavigationProvider
+ {
+ get
+ {
+ INavigationProvider navProvider = SiteConfiguration.NavigationProvider;
+ if (!(navProvider is TridionDocsNavigationProvider))
+ {
+ throw new TridionDocsApiException(
+ "TridionDocsNavigationProvider not configured. Please make sure you have the TridionDocsNavigationProvider specified in your Unity.config");
+
+ }
+ return navProvider as TridionDocsNavigationProvider;
+ }
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/Controllers/TridionDocsApiController.cs b/webapp-net/TridionDocs/Controllers/TridionDocsApiController.cs
new file mode 100644
index 000000000..fc0f6adac
--- /dev/null
+++ b/webapp-net/TridionDocs/Controllers/TridionDocsApiController.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Web.Mvc;
+using Sdl.Web.Common;
+using Sdl.Web.Common.Models;
+using Sdl.Web.Delivery.ServicesCore.ClaimStore;
+using Sdl.Web.Modules.TridionDocs.Providers;
+using Sdl.Web.Mvc.Configuration;
+using Sdl.Web.Mvc.Formats;
+using System.Text;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using Sdl.Web.Common.Logging;
+using Sdl.Web.Modules.TridionDocs.Exceptions;
+
+namespace Sdl.Web.Modules.TridionDocs.Controllers
+{
+ ///
+ /// TridionDocs Api Controller
+ ///
+ public class TridionDocsApiController : BaseController
+ {
+ private static readonly Uri UserConditionsUri = new Uri("taf:ish:userconditions");
+
+ [Route("~/api/page/{publicationId:int}/{pageId:int}")]
+ [HttpGet]
+ public virtual ActionResult Page(int publicationId, int pageId)
+ {
+ try
+ {
+ PageModel model = TridionDocsContentProvider.GetPageModel(pageId, SetupLocalization(publicationId));
+ WebRequestContext.PageModel = model;
+ return Json(model);
+ }
+ catch(Exception ex)
+ {
+ Log.Error(ex);
+ return ServerError(new TridionDocsApiException($"Page not found: [{publicationId}] {pageId}/index.html"));
+ }
+ }
+
+ [Route("~/api/page/{publicationId}/{pageId}")]
+ [HttpGet]
+ public virtual ActionResult Page(string publicationId, string pageId)
+ {
+ return ServerError(new TridionDocsApiException($"Page not found: [{publicationId}] {pageId}/index.html"), 400);
+ }
+
+ [Route("~/api/page/{publicationId:int}/{pageId:int}/{*content}")]
+ [HttpPost]
+ public virtual ActionResult Page(int publicationId, int pageId, string content)
+ {
+ try
+ {
+ string conditions = Request.QueryString["conditions"];
+ if (!string.IsNullOrEmpty(conditions))
+ {
+ AmbientDataContext.CurrentClaimStore.Put(UserConditionsUri, conditions);
+ }
+ PageModel model = TridionDocsContentProvider.GetPageModel(pageId, SetupLocalization(publicationId));
+ WebRequestContext.PageModel = model;
+ return Json(model);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex);
+ return ServerError(ex);
+ }
+ }
+
+ [Route("~/binary/{publicationId:int}/{binaryId:int}/{*content}")]
+ [Route("~/api/binary/{publicationId:int}/{binaryId:int}/{*content}")]
+ [HttpGet]
+ [FormatData]
+ public virtual ActionResult Binary(int publicationId, int binaryId)
+ {
+ try
+ {
+ StaticContentItem content = TridionDocsContentProvider.GetStaticContentItem(binaryId,
+ SetupLocalization(publicationId));
+ return new FileStreamResult(content.GetContentStream(), content.ContentType);
+ }
+ catch (Exception ex)
+ {
+ return ServerError(ex);
+ }
+ }
+
+ [Route("~/binary/{publicationId}/{binaryId}")]
+ [Route("~/api/binary/{publicationId}/{binaryId}")]
+ [HttpGet]
+ [FormatData]
+ public virtual ActionResult Binary(string publicationId, string binaryId)
+ {
+ return ServerError(null, 400);
+ }
+
+ [Route("~/api/publications")]
+ [HttpGet]
+ public virtual ActionResult Publications()
+ {
+ try
+ {
+ PublicationProvider provider = new PublicationProvider();
+ return JsonResult(provider.PublicationList);
+ }
+ catch (Exception ex)
+ {
+ return ServerError(ex);
+ }
+ }
+
+ [Route("~/api/conditions/{publicationId:int}")]
+ public virtual ActionResult Conditions(int publicationId)
+ {
+ try
+ {
+ return new ContentResult
+ {
+ ContentType = "application/json",
+ Content = new ConditionProvider().GetConditions(publicationId),
+ ContentEncoding = Encoding.UTF8
+ };
+ }
+ catch (Exception ex)
+ {
+ return ServerError(ex);
+ }
+ }
+
+ [Route("~/api/sitemap.xml")]
+ public virtual ActionResult SitemapXml()
+ {
+ // Use the common SiteMapXml view for rendering out the xml of all the sitemap items.
+ return View("SiteMapXml", TridionDocsNavigationProvider.SiteMap);
+ }
+
+ [Route("~/api/toc/{publicationId:int}")]
+ public virtual ActionResult RootToc(int publicationId, string conditions = "")
+ {
+ try
+ {
+ SetupLocalization(publicationId);
+ if (!string.IsNullOrEmpty(conditions))
+ {
+ AmbientDataContext.CurrentClaimStore.Put(UserConditionsUri, conditions);
+ }
+ TocProvider tocProvider = new TocProvider();
+ return Json(tocProvider.GetToc(publicationId));
+ }
+ catch (Exception ex)
+ {
+ return ServerError(ex);
+ }
+ }
+
+ [Route("~/api/toc/{publicationId:int}/{sitemapItemId}")]
+ public virtual ActionResult Toc(int publicationId, string sitemapItemId, string conditions = "",
+ bool includeAncestors = false)
+ {
+ try
+ {
+ SetupLocalization(publicationId);
+ if (!string.IsNullOrEmpty(conditions))
+ {
+ AmbientDataContext.CurrentClaimStore.Put(UserConditionsUri, conditions);
+ }
+ TocProvider tocProvider = new TocProvider();
+ var sitemapItems = tocProvider.GetToc(publicationId, sitemapItemId, includeAncestors);
+ return Json(sitemapItems);
+ }
+ catch (Exception ex)
+ {
+ return ServerError(ex);
+ }
+ }
+
+ [Route("~/api/toc/{publicationId}/{sitemapItemId}")]
+ public virtual ActionResult Toc(string publicationId, string sitemapItemId)
+ {
+ return ServerError(null, 400);
+ }
+
+ [Route("~/api/pageIdByReference/{publicationId:int}/{ishFieldValue}")]
+ public virtual ActionResult TopicIdInTargetPublication(int publicationId, string ishFieldValue)
+ {
+ try
+ {
+ SetupLocalization(publicationId);
+ if (!string.IsNullOrEmpty(ishFieldValue))
+ {
+ throw new DxaItemNotFoundException(
+ "Unable to use empty 'ishlogicalref.object.id' value as a search criteria.");
+ }
+ return Json(TridionDocsContentProvider.GetPageIdByIshLogicalReference(publicationId, ishFieldValue));
+ }
+ catch (Exception ex)
+ {
+ return ServerError(ex);
+ }
+ }
+
+ public ActionResult ServerError(Exception ex, int statusCode = 404)
+ {
+ Response.StatusCode = statusCode;
+ if(ex == null) return new EmptyResult();
+ if (ex.InnerException != null) ex = ex.InnerException;
+ return Content("{ \"Message\": \"" + ex.Message + "\" }", "application/json");
+ }
+
+ private ContentResult JsonResult(object result)
+ {
+ return new ContentResult
+ {
+ ContentType = "application/json",
+ Content = JsonConvert.SerializeObject(result, new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-ddThh:mm:ssZ" }),
+ ContentEncoding = Encoding.UTF8
+ };
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/Controllers/TridionDocsPageController.cs b/webapp-net/TridionDocs/Controllers/TridionDocsPageController.cs
new file mode 100644
index 000000000..ac73e1ace
--- /dev/null
+++ b/webapp-net/TridionDocs/Controllers/TridionDocsPageController.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Web.Mvc;
+using Sdl.Web.Common;
+using Sdl.Web.Common.Interfaces;
+using Sdl.Web.Common.Logging;
+using Sdl.Web.Common.Models;
+using Sdl.Web.Mvc.Configuration;
+
+namespace Sdl.Web.Modules.TridionDocs.Controllers
+{
+ ///
+ /// TridionDocs Page Controller
+ ///
+ public class TridionDocsPageController : BaseController
+ {
+ [Route("~/")]
+ [Route("~/home")]
+ [Route("~/publications/{*content}")]
+ public ActionResult Home()
+ {
+ return View("GeneralPage");
+ }
+
+ [Route("~/{publicationId:int}")]
+ public virtual ActionResult Page(int publicationId)
+ {
+ return GetPage(publicationId);
+ }
+
+ [Route("~/{publicationId:int}/{pageId:int}")]
+ [Route("~/{publicationId:int}/{pageId:int}/{*path}")]
+ public virtual ActionResult Page(int publicationId, int pageId, string path = "")
+ {
+ return GetPage(publicationId, pageId);
+ }
+
+ protected ActionResult GetPage(int publicationId)
+ {
+ SetupLocalization(publicationId);
+ return View("GeneralPage");
+ }
+
+ protected ActionResult GetPage(int publicationId, int pageId)
+ {
+ using (new Tracer(publicationId, pageId))
+ {
+ try
+ {
+ ILocalization localization = SetupLocalization(publicationId);
+
+ PageModel pageModel;
+ try
+ {
+ pageModel = TridionDocsContentProvider.GetPageModel(pageId, localization);
+ }
+ catch (DxaItemNotFoundException ex)
+ {
+ Log.Info(ex.Message);
+ return NotFound();
+ }
+
+ PageModelWithHttpResponseData pageModelWithHttpResponseData =
+ pageModel as PageModelWithHttpResponseData;
+ pageModelWithHttpResponseData?.SetHttpResponseData(System.Web.HttpContext.Current.Response);
+ SetupViewData(pageModel);
+ PageModel model = (EnrichModel(pageModel) as PageModel) ?? pageModel;
+ WebRequestContext.PageModel = model;
+ return View(model.MvcData.ViewName, model);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex);
+ return ServerError();
+ }
+ }
+ }
+
+ public ActionResult ServerError()
+ {
+ using (new Tracer())
+ {
+ Response.StatusCode = 404;
+ ViewResult r = View("ErrorPage");
+ r.ViewData.Add("statusCode", Response.StatusCode);
+ return r;
+ }
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/Exceptions/TridionDocsApiException.cs b/webapp-net/TridionDocs/Exceptions/TridionDocsApiException.cs
new file mode 100644
index 000000000..f79a2cbd3
--- /dev/null
+++ b/webapp-net/TridionDocs/Exceptions/TridionDocsApiException.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Sdl.Web.Modules.TridionDocs.Exceptions
+{
+ ///
+ /// TridionDocs Api Exception
+ ///
+ public class TridionDocsApiException : Exception
+ {
+ public TridionDocsApiException()
+ {
+ }
+
+ public TridionDocsApiException(string msg) : base(msg)
+ {
+ }
+
+ public TridionDocsApiException(string msg, Exception innerException) : base(msg, innerException)
+ {
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/Localization/TridionDocsLocalization.cs b/webapp-net/TridionDocs/Localization/TridionDocsLocalization.cs
new file mode 100644
index 000000000..8993e6e74
--- /dev/null
+++ b/webapp-net/TridionDocs/Localization/TridionDocsLocalization.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Logging;
+
+namespace Sdl.Web.Modules.TridionDocs.Localization
+{
+ ///
+ /// TridionDocs Localization Implementation
+ ///
+ public class TridionDocsLocalization : Common.Configuration.Localization
+ {
+ public override string Path { get; set; } = ""; // content path
+
+ public override string CmUriScheme { get; } = "ish";
+
+ public override bool IsXpmEnabled { get; set; } = false; // no xpm on dd-webapp
+
+ public override string BinaryCacheFolder => $"{SiteConfiguration.StaticsFolder}\\TridionDocs";
+
+ protected override void Load()
+ {
+ using (new Tracer(this))
+ {
+ LastRefresh = DateTime.Now;
+ }
+ }
+
+ public override IDictionary GetResources(string sectionName = null)
+ => new Hashtable(); // no resources so return empty hash to avoid default impl
+
+ public override bool IsStaticContentUrl(string urlPath)
+ {
+ List mediaPatterns = new List();
+ mediaPatterns.Add("^/favicon.ico");
+ mediaPatterns.Add($"^{Path}/{SiteConfiguration.SystemFolder}/assets/.*");
+ mediaPatterns.Add($"^{Path}/{SiteConfiguration.SystemFolder}/.*\\.json$");
+ StaticContentUrlPattern = string.Join("|", mediaPatterns);
+ Regex staticContentUrlRegex = new Regex(StaticContentUrlPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
+ return staticContentUrlRegex.IsMatch(urlPath);
+ }
+ }
+}
\ No newline at end of file
diff --git a/webapp-net/TridionDocs/Localization/TridionDocsLocalizationResolver.cs b/webapp-net/TridionDocs/Localization/TridionDocsLocalizationResolver.cs
new file mode 100644
index 000000000..e9681ef45
--- /dev/null
+++ b/webapp-net/TridionDocs/Localization/TridionDocsLocalizationResolver.cs
@@ -0,0 +1,25 @@
+using System;
+using Sdl.Web.Common.Interfaces;
+using Sdl.Web.Tridion;
+
+namespace Sdl.Web.Modules.TridionDocs.Localization
+{
+ ///
+ /// TridionDocs Localization Resolver
+ ///
+ public class TridionDocsLocalizationResolver : LocalizationResolver
+ {
+ private readonly ILocalization _localization;
+
+ public TridionDocsLocalizationResolver()
+ {
+ _localization = new TridionDocsLocalization();
+ _localization.EnsureInitialized();
+ }
+
+ public override ILocalization ResolveLocalization(Uri url)
+ {
+ return _localization;
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/Mapping/TridionDocsModelBuilder.cs b/webapp-net/TridionDocs/Mapping/TridionDocsModelBuilder.cs
new file mode 100644
index 000000000..0cd547608
--- /dev/null
+++ b/webapp-net/TridionDocs/Mapping/TridionDocsModelBuilder.cs
@@ -0,0 +1,46 @@
+using System;
+using Sdl.Web.Common.Interfaces;
+using Sdl.Web.Common.Models;
+using Sdl.Web.DataModel;
+using Sdl.Web.Tridion.Mapping;
+
+namespace Sdl.Web.Modules.TridionDocs.Mapping
+{
+ ///
+ /// Tridion Docs Model Builder
+ ///
+ /// Remaps 'Ish' mvc area to TridionDocs
+ ///
+ /// Add to Web.Config:
+ ///
+ ///
+ ///
+ /// ...
+ ///
+ ///
+ public class TridionDocsModelBuilder : IPageModelBuilder, IEntityModelBuilder
+ {
+ public void BuildPageModel(ref PageModel pageModel, PageModelData pageModelData, bool includePageRegions,
+ ILocalization localization)
+ {
+ DataModel.MvcData mvcData = pageModelData.MvcData;
+ RemapMvcAreaName(ref mvcData);
+ }
+
+ public void BuildEntityModel(ref EntityModel entityModel, EntityModelData entityModelData, Type baseModelType,
+ ILocalization localization)
+ {
+ DataModel.MvcData mvcData = entityModelData.MvcData;
+ RemapMvcAreaName(ref mvcData);
+ }
+
+ private void RemapMvcAreaName(ref DataModel.MvcData mvcData)
+ {
+ if (mvcData != null && mvcData.AreaName != null &&
+ mvcData.AreaName.Equals("Ish"))
+ {
+ mvcData.AreaName = TridionDocsModuleAreaRegistration.AREA_NAME;
+ }
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/Models/Publication.cs b/webapp-net/TridionDocs/Models/Publication.cs
new file mode 100644
index 000000000..6b09b8a3a
--- /dev/null
+++ b/webapp-net/TridionDocs/Models/Publication.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+
+namespace Sdl.Web.Modules.TridionDocs.Models
+{
+ ///
+ /// Publication
+ ///
+ public class Publication
+ {
+ public string Id { get; set; }
+ public string Title { get; set; }
+ public List ProductFamily { get; set; }
+ public List ProductReleaseVersion { get; set; }
+ public string VersionRef { get; set; }
+ public string Language { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public string Version { get; set; }
+ public string LogicalId { get; set; }
+ }
+}
diff --git a/webapp-net/TridionDocs/Models/PublicationSiteMap.cs b/webapp-net/TridionDocs/Models/PublicationSiteMap.cs
new file mode 100644
index 000000000..aa58b8655
--- /dev/null
+++ b/webapp-net/TridionDocs/Models/PublicationSiteMap.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using Sdl.Web.Common.Models;
+
+namespace Sdl.Web.Modules.TridionDocs.Models
+{
+ public class PublicationSiteMap : EntityModel
+ {
+ public int PublicationId { get; set; }
+ public int NamespaceId { get; set; }
+ public List Urls { get; set; } = new List();
+ }
+}
diff --git a/webapp-net/TridionDocs/Models/SiteMapUrlEntry.cs b/webapp-net/TridionDocs/Models/SiteMapUrlEntry.cs
new file mode 100644
index 000000000..1de08fe71
--- /dev/null
+++ b/webapp-net/TridionDocs/Models/SiteMapUrlEntry.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Sdl.Web.Modules.TridionDocs.Models
+{
+ public class SiteMapUrlEntry
+ {
+ public string Url { get; set; }
+ public DateTime LastModifiedDate { get; set; }
+ }
+}
diff --git a/webapp-net/TridionDocs/Models/Topic.cs b/webapp-net/TridionDocs/Models/Topic.cs
new file mode 100644
index 000000000..55868dab2
--- /dev/null
+++ b/webapp-net/TridionDocs/Models/Topic.cs
@@ -0,0 +1,22 @@
+using System;
+using Newtonsoft.Json;
+using Sdl.Web.Common.Models;
+
+namespace Sdl.Web.Modules.TridionDocs.Models
+{
+ ///
+ /// Topic Entity
+ ///
+ [SemanticEntity(Vocab = SchemaOrgVocabulary, EntityName = "Topic", Prefix = "s", Public = true)]
+ [Serializable]
+ public class Topic : EntityModel
+ {
+ [SemanticProperty("topicBody")]
+ [JsonProperty(PropertyName = "topicBody")]
+ public RichText TopicBody { get; set; }
+
+ [SemanticProperty("topicTitle")]
+ [JsonProperty(PropertyName = "topicTitle")]
+ public string TopicTitle { get; set; }
+ }
+}
diff --git a/webapp-net/TridionDocs/Navigation/TridionDocsNavigationProvider.cs b/webapp-net/TridionDocs/Navigation/TridionDocsNavigationProvider.cs
new file mode 100644
index 000000000..692481f30
--- /dev/null
+++ b/webapp-net/TridionDocs/Navigation/TridionDocsNavigationProvider.cs
@@ -0,0 +1,91 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using Newtonsoft.Json;
+using Sdl.Web.Common.Interfaces;
+using Sdl.Web.Common.Models;
+using Sdl.Web.Common.Models.Navigation;
+using Sdl.Web.Modules.TridionDocs.Models;
+using Sdl.Web.Tridion.ContentManager;
+using Tridion.ContentDelivery.Meta;
+using Tridion.ContentDelivery.Taxonomies;
+using System.Text.RegularExpressions;
+using Sdl.Web.Modules.TridionDocs.Exceptions;
+
+namespace Sdl.Web.Modules.TridionDocs.Navigation
+{
+ ///
+ /// Tridion Docs Navigation Provider
+ ///
+ public class TridionDocsNavigationProvider : Tridion.Navigation.CILImpl.DynamicNavigationProvider
+ {
+ private static readonly Regex RegEx = new Regex("^(?:\\w)(\\d+)(?:-\\w)(\\d+)", RegexOptions.Compiled);
+
+ public string GetBaseUrl()
+ {
+ var request = HttpContext.Current.Request;
+ var appUrl = HttpRuntime.AppDomainAppVirtualPath;
+
+ if (appUrl != "/")
+ appUrl = "/" + appUrl;
+
+ var baseUrl = $"{request.Url.Scheme}://{request.Url.Authority}{appUrl}";
+
+ return baseUrl;
+ }
+
+ protected override List SortTaxonomyNodes(IList taxonomyNodes)
+ // Sort by topic id since the base impl sorts alphabetically using the title
+ => taxonomyNodes.OrderBy(x => int.Parse(RegEx.Match(x.Id).Groups[1].Value)).
+ ThenBy(x => int.Parse(RegEx.Match(x.Id).Groups[2].Value)).ToList();
+
+ protected override TaxonomyNode CreateTaxonomyNode(Keyword keyword, int expandLevels, NavigationFilter filter, ILocalization localization)
+ {
+ TaxonomyNode node = base.CreateTaxonomyNode(keyword, expandLevels, filter, localization);
+ string ishRefUri = (string)keyword.KeywordMeta.GetFirstValue("ish.ref.uri");
+ if (ishRefUri != null)
+ {
+ var ish = CmUri.FromString(ishRefUri);
+ node.Url = $"/{ish.PublicationId}/{ish.ItemId}";
+ }
+ node.Visible = true;
+ return node;
+ }
+
+ protected override IEnumerable ExpandDescendants(string keywordUri, string taxonomyUri,
+ NavigationFilter filter, ILocalization localization)
+ {
+ TaxonomyFactory taxonomyFactory = new TaxonomyFactory();
+ TaxonomyFilter taxonomyFilter = new DepthFilter(filter.DescendantLevels, DepthFilter.FilterDown);
+ Keyword contextKeyword = taxonomyFactory.GetTaxonomyKeywords(taxonomyUri, taxonomyFilter, keywordUri);
+ if (contextKeyword == null)
+ {
+ throw new TridionDocsApiException();
+ }
+
+ TaxonomyNode contextTaxonomyNode = CreateTaxonomyNode(contextKeyword, filter.DescendantLevels, filter,
+ localization);
+ return contextTaxonomyNode.Items;
+ }
+
+ protected override SitemapItem[] ExpandClassifiedPages(Keyword keyword, string taxonomyId,
+ ILocalization localization)
+ => new SitemapItem[] {};
+
+ public SitemapItem SiteMap
+ {
+ get
+ {
+ SitemapItem root = new SitemapItem();
+ PublicationMetaFactory factory = new PublicationMetaFactory();
+ string json = factory.GetSiteMapForPublication(-1);
+ List siteMap = JsonConvert.DeserializeObject>(json);
+ foreach (var url in siteMap.SelectMany(x => x.Urls))
+ {
+ root.Items.Add(new SitemapItem {Type = "Page", Url = $"/{url.Url.TrimStart('/')}", PublishedDate = url.LastModifiedDate});
+ }
+ return root;
+ }
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/Properties/AssemblyInfo.cs b/webapp-net/TridionDocs/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..8d767c18b
--- /dev/null
+++ b/webapp-net/TridionDocs/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+
+[assembly: AssemblyTitle("SDL DXA DD Web App Module")]
+
+// NOTE: Common Assembly Info is generated by the build in CommonAssemblyInfo.cs
diff --git a/webapp-net/TridionDocs/Providers/ConditionProvider.cs b/webapp-net/TridionDocs/Providers/ConditionProvider.cs
new file mode 100644
index 000000000..64d366fa4
--- /dev/null
+++ b/webapp-net/TridionDocs/Providers/ConditionProvider.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Sdl.Web.Modules.TridionDocs.Exceptions;
+using Tridion.ContentDelivery.Meta;
+
+namespace Sdl.Web.Modules.TridionDocs.Providers
+{
+ ///
+ /// Condition Provider
+ ///
+ public class ConditionProvider
+ {
+ private static readonly string ConditionUsed = "conditionsused.generated.value";
+ private static readonly string ConditionMetadata = "conditionmetadata.generated.value";
+ private static readonly string ConditionValues = "values";
+
+ private class Condition
+ {
+ [JsonProperty("datatype")]
+ public string Datatype { get; set; }
+ [JsonProperty("range")]
+ public bool Range { get; set; }
+ [JsonProperty("values")]
+ public string[] Values { get; set; }
+ }
+
+ public string GetConditions(int publicationId)
+ {
+ var conditionUsed = GetMetadata(publicationId, ConditionUsed);
+ var conditionMetadata = GetMetadata(publicationId, ConditionMetadata);
+ Dictionary d1 =
+ JsonConvert.DeserializeObject>(conditionUsed);
+ Dictionary d2 =
+ JsonConvert.DeserializeObject>(conditionMetadata);
+ foreach (var v in d1)
+ {
+ d2[v.Key].Values = v.Value;
+ }
+ return JsonConvert.SerializeObject(d2);
+ }
+
+ private string GetMetadata(int publicationId, string metadataName)
+ {
+ try
+ {
+ PublicationMetaFactory factory = new PublicationMetaFactory();
+ PublicationMeta meta = factory.GetMeta(publicationId);
+ if (meta?.CustomMeta == null)
+ {
+ throw new TridionDocsApiException(
+ $"Metadata '{metadataName}' is not found for publication {publicationId}.");
+ }
+
+ object metadata = meta.CustomMeta.GetFirstValue(metadataName);
+ string metadataString = metadata != null ? (string) metadata : "{}";
+ return metadataString;
+ }
+ catch (Exception)
+ {
+ throw new TridionDocsApiException(
+ $"Metadata '{metadataName}' is not found for publication {publicationId}.");
+ }
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/Providers/PublicationProvider.cs b/webapp-net/TridionDocs/Providers/PublicationProvider.cs
new file mode 100644
index 000000000..45ab44b77
--- /dev/null
+++ b/webapp-net/TridionDocs/Providers/PublicationProvider.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Sdl.Web.Common.Logging;
+using Sdl.Web.Modules.TridionDocs.Models;
+using Tridion.ContentDelivery.Meta;
+using Sdl.Web.Modules.TridionDocs.Exceptions;
+
+namespace Sdl.Web.Modules.TridionDocs.Providers
+{
+ ///
+ /// Publication Provider
+ ///
+ public class PublicationProvider
+ {
+ private static readonly string PublicationTitleMeta = "publicationtitle.generated.value";
+ private static readonly string PublicationProductfamilynameMeta = "FISHPRODUCTFAMILYNAME.logical.value";
+ private static readonly string PublicationProductreleasenameMeta = "FISHPRODUCTRELEASENAME.version.value";
+ private static readonly string PublicationVersionrefMeta = "ishversionref.object.id";
+ private static readonly string PublicationLangMeta = "FISHPUBLNGCOMBINATION.lng.value";
+ private static readonly string PublicationOnlineStatusMeta = "FISHDITADLVRREMOTESTATUS.lng.element";
+ private static readonly string PublicationOnlineValue = "VDITADLVRREMOTESTATUSONLINE";
+ private static readonly string PublicationCratedonMeta = "CREATED-ON.version.value";
+ private static readonly string PublicationVersionMeta = "VERSION.version.value";
+ private static readonly string PublicationLogicalId = "ishref.object.value";
+
+ public List PublicationList
+ {
+ get
+ {
+ PublicationMetaFactory factory = new PublicationMetaFactory();
+ List result = new List();
+ try
+ {
+ PublicationMeta[] publicationMetas = factory.GetAllMeta(new List
+ {
+ PublicationTitleMeta,
+ PublicationProductfamilynameMeta,
+ PublicationProductreleasenameMeta,
+ PublicationVersionrefMeta,
+ PublicationLangMeta,
+ PublicationOnlineStatusMeta,
+ PublicationCratedonMeta,
+ PublicationVersionMeta,
+ PublicationLogicalId
+ });
+
+ result.AddRange(from meta in publicationMetas where IsPublicationOnline(meta) select BuildPublicationFrom(meta));
+ return result;
+ }
+ catch (Exception e)
+ {
+ throw new TridionDocsApiException("Unable to fetch list of publications.", e);
+ }
+ }
+ }
+
+ public void CheckPublicationOnline(int publicationId)
+ {
+ PublicationMeta meta = null;
+ PublicationMetaFactory factory = new PublicationMetaFactory();
+ try
+ {
+ meta = factory.GetMeta(publicationId);
+ }
+ catch (Exception)
+ {
+ Log.Error("Couldn't find publication metadata for id: " + publicationId);
+ }
+ if (meta == null || !IsPublicationOnline(meta))
+ {
+ throw new TridionDocsApiException($"Unable to find publication {publicationId}");
+ }
+ }
+
+ public bool IsPublicationOnline(PublicationMeta publicationMeta)
+ {
+ var customMeta = publicationMeta.CustomMeta;
+ if (customMeta == null) return false;
+ try
+ {
+ var status = customMeta.GetFirstValue(PublicationOnlineStatusMeta);
+ return status != null && PublicationOnlineValue.Equals(status.ToString());
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ private Publication BuildPublicationFrom(PublicationMeta publicationMeta)
+ {
+ Publication publication = new Publication();
+ publication.Id = publicationMeta.Id.ToString();
+ var customMeta = publicationMeta.CustomMeta;
+ if (customMeta == null) return publication;
+ if (customMeta.GetFirstValue(PublicationTitleMeta) != null)
+ {
+ publication.Title = (string)customMeta.GetFirstValue(PublicationTitleMeta);
+ }
+ else
+ {
+ publication.Title = publicationMeta.Title;
+ }
+
+ if (customMeta.GetFirstValue(PublicationProductfamilynameMeta) != null)
+ {
+ // Take the generated product family name from the metadata
+ NameValuePair pair = (NameValuePair)customMeta.NameValues[PublicationProductfamilynameMeta];
+ publication.ProductFamily = new List();
+ foreach (var value in pair.MultipleValues)
+ {
+ publication.ProductFamily.Add(value?.ToString());
+ }
+ }
+
+ if (customMeta.GetFirstValue(PublicationProductreleasenameMeta) != null)
+ {
+ // Take the generated product release name from the metadata
+ NameValuePair pair = (NameValuePair)customMeta.NameValues[PublicationProductreleasenameMeta];
+ publication.ProductReleaseVersion = new List();
+ foreach (var value in pair.MultipleValues)
+ {
+ publication.ProductReleaseVersion.Add(value?.ToString());
+ }
+ }
+
+ if (customMeta.GetFirstValue(PublicationVersionrefMeta) != null)
+ {
+ string versionRef = (string)customMeta.GetFirstValue(PublicationVersionrefMeta);
+ // The value is stored as float on Content Service, so we need to get rid of fractional part
+ publication.VersionRef = versionRef.Split('\"', '[', '.', ']')[0];
+ }
+
+ if (customMeta.GetFirstValue(PublicationLangMeta) != null)
+ {
+ publication.Language = (string)customMeta.GetFirstValue(PublicationLangMeta);
+ }
+
+ if (customMeta.GetFirstValue(PublicationCratedonMeta) != null)
+ {
+ publication.CreatedOn = DateTime.Parse((string)customMeta.GetFirstValue(PublicationCratedonMeta));
+ }
+
+ if (customMeta.GetFirstValue(PublicationVersionMeta) != null)
+ {
+ publication.Version = (string)customMeta.GetFirstValue(PublicationVersionMeta);
+ }
+
+ if (customMeta.GetFirstValue(PublicationLogicalId) != null)
+ {
+ publication.LogicalId = (string)customMeta.GetFirstValue(PublicationLogicalId);
+ }
+ return publication;
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/Providers/TocProvider.cs b/webapp-net/TridionDocs/Providers/TocProvider.cs
new file mode 100644
index 000000000..10579f2e3
--- /dev/null
+++ b/webapp-net/TridionDocs/Providers/TocProvider.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using Sdl.Web.Common.Configuration;
+using Sdl.Web.Common.Interfaces;
+using Sdl.Web.Common.Models;
+using Sdl.Web.Common.Models.Navigation;
+using Sdl.Web.Delivery.Service;
+using Sdl.Web.Mvc.Configuration;
+
+namespace Sdl.Web.Modules.TridionDocs.Providers
+{
+ ///
+ /// Table of Contents (TOC) Provider
+ ///
+ public class TocProvider
+ {
+ public IEnumerable GetToc(int publicationId)
+ => GetToc(publicationId, null, false, 1);
+
+ public IEnumerable GetToc(int publicationId, string sitemapItemId)
+ => GetToc(publicationId, sitemapItemId, false, 1);
+
+ public IEnumerable GetToc(int publicationId, string sitemapItemId, bool includeAncestors)
+ => GetToc(publicationId, sitemapItemId, includeAncestors, 1);
+
+ public IEnumerable GetToc(int publicationId, string sitemapItemId, bool includeAncestors,
+ int descendantLevels)
+ {
+ bool caching = ServiceCacheProvider.Instance.DisableCaching;
+ ServiceCacheProvider.Instance.DisableCaching = true;
+ IOnDemandNavigationProvider onDemandNavigationProvider = SiteConfiguration.NavigationProvider as IOnDemandNavigationProvider;
+ NavigationFilter navigationFilter = new NavigationFilter
+ {
+ DescendantLevels = descendantLevels,
+ IncludeAncestors = includeAncestors
+ };
+
+ ILocalization localization = WebRequestContext.Localization;
+ localization.Id = publicationId.ToString();
+
+ var result = onDemandNavigationProvider.GetNavigationSubtree(sitemapItemId, navigationFilter, localization);
+ ServiceCacheProvider.Instance.DisableCaching = caching;
+ return result;
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/Providers/TridionDocsContentProvider.cs b/webapp-net/TridionDocs/Providers/TridionDocsContentProvider.cs
new file mode 100644
index 000000000..0101b94c4
--- /dev/null
+++ b/webapp-net/TridionDocs/Providers/TridionDocsContentProvider.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections;
+using System.Web;
+using Sdl.Web.Common;
+using Sdl.Web.Common.Interfaces;
+using Sdl.Web.Common.Models;
+using Sdl.Web.Delivery.Service;
+using Sdl.Web.Modules.TridionDocs.Exceptions;
+using Sdl.Web.Tridion.Mapping;
+using Tridion.ContentDelivery.DynamicContent.Query;
+using Tridion.ContentDelivery.Meta;
+using Sdl.Web.Tridion.ContentManager;
+using Query = Tridion.ContentDelivery.DynamicContent.Query.Query;
+
+namespace Sdl.Web.Modules.TridionDocs.Providers
+{
+ ///
+ /// TridionDocs Content Provider
+ ///
+ public class TridionDocsContentProvider : DefaultContentProvider
+ {
+ private static readonly string RefFieldName = "ishlogicalref.object.id";
+ private static readonly string DefaultPublishData = "1900-01-01 00:00:00.000";
+ private static readonly string TocNaventriesMeta = "tocnaventries.generated.value";
+ private static readonly string PageConditionsUsedMeta = "conditionsused.generated.value";
+
+ public class ItemImpl : IItem
+ {
+ public void Dispose()
+ {
+ }
+
+ public Category[] GetCategories()
+ {
+ return null;
+ }
+
+ public int Id { get; }
+ public string Title { get; }
+ public int MinorVersion { get; }
+ public int MajorVersion { get; }
+ public DateTime ModificationDate { get; }
+ public DateTime InitialPublicationDate { get; }
+ public DateTime LastPublicationDate { get; }
+ public DateTime CreationDate { get; }
+ public int PublicationId { get; }
+ public int OwningPublicationId { get; }
+ }
+
+ public override PageModel GetPageModel(int pageId, ILocalization localization, bool addIncludes = true)
+ {
+ bool caching = ServiceCacheProvider.Instance.DisableCaching;
+ ServiceCacheProvider.Instance.DisableCaching = true;
+
+ PageModel pageModel = base.GetPageModel(pageId, localization, addIncludes);
+ if (pageModel != null)
+ {
+ // Enhance the page model with custom metadata
+ PageMetaFactory pageMetaFactory = new PageMetaFactory(int.Parse(localization.Id));
+ string cmId = $"ish:{localization.Id}-{pageId}-16";
+ var pageMeta = pageMetaFactory.GetMeta(cmId);
+ if (pageMeta != null)
+ {
+ var customMeta = pageMeta.CustomMeta;
+
+ // Put the information about the toc entries on the metadata
+ // This is required by the UI so it knows the location of the page in the Toc
+ if (customMeta.GetFirstValue(TocNaventriesMeta) != null)
+ {
+ NameValuePair pair = (NameValuePair)customMeta.NameValues[TocNaventriesMeta];
+ var values = (ArrayList) pair.MultipleValues;
+ if (values != null)
+ {
+ pageModel.Meta.Add(TocNaventriesMeta, string.Join(", ", values.ToArray()));
+ }
+ }
+
+ // Put the information about used conditions form page metadata
+ if (customMeta.GetFirstValue(PageConditionsUsedMeta) != null)
+ {
+ pageModel.Meta.Add(PageConditionsUsedMeta, (string)customMeta.GetFirstValue(PageConditionsUsedMeta));
+ }
+
+ // Add logical Ref ID information
+ if (customMeta.GetFirstValue(RefFieldName) != null)
+ {
+ pageModel.Meta.Add(RefFieldName, (string)customMeta.GetFirstValue(RefFieldName));
+ }
+ }
+ }
+
+ ServiceCacheProvider.Instance.DisableCaching = caching;
+ return pageModel;
+ }
+
+ public override PageModel GetPageModel(string urlPath, ILocalization localization, bool addIncludes = true)
+ {
+ HttpContext.Current.Response.StatusCode = 404;
+ return new PageModel
+ {
+ MvcData = new MvcData { ViewName = "ErrorPage", AreaName = "TridionDocs"}
+ };
+ }
+
+ public IItem GetPageIdByIshLogicalReference(int publicationId, string ishLogicalRefValue)
+ {
+ try
+ {
+ Criteria dateCriteria = new ItemLastPublishedDateCriteria(DefaultPublishData, Criteria.GreaterThanOrEqual);
+ CustomMetaKeyCriteria metaKeyCriteria = new CustomMetaKeyCriteria(RefFieldName);
+ Criteria refCriteria = new CustomMetaValueCriteria(metaKeyCriteria, ishLogicalRefValue);
+ Criteria pubCriteria = new PublicationCriteria(publicationId);
+ Criteria itemType = new ItemTypeCriteria((int)ItemType.Page);
+ Criteria composite = new AndCriteria(new[] { dateCriteria, refCriteria, itemType, pubCriteria});
+
+ Query query = new Query(composite);
+ IItem[] items = query.ExecuteEntityQuery();
+ if (items == null || items.Length == 0)
+ {
+ return new ItemImpl();
+ }
+
+ if (items.Length > 1)
+ {
+ throw new TridionDocsApiException($"Too many page Ids found in publication with logical ref value {ishLogicalRefValue}");
+ }
+
+ return items[0];
+ }
+ catch (Exception)
+ {
+ throw new DxaItemNotFoundException($"Page reference by ishlogicalref.object.id = {ishLogicalRefValue} not found in publication {publicationId}.");
+ }
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/Sdl.Web.Modules.TridionDocs.csproj b/webapp-net/TridionDocs/Sdl.Web.Modules.TridionDocs.csproj
new file mode 100644
index 000000000..8bd3207e0
--- /dev/null
+++ b/webapp-net/TridionDocs/Sdl.Web.Modules.TridionDocs.csproj
@@ -0,0 +1,133 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {EE523D3E-4A5D-4E01-9FB2-7BF4352F6CE5}
+ Library
+ Properties
+ Sdl.Web.Modules.TridionDocs
+ Sdl.Web.Modules.TridionDocs
+ v4.5.2
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll
+ True
+
+
+ ..\packages\Newtonsoft.Json.10.0.1\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+ False
+ ..\packages\Sdl.Dxa.Framework.Web8.$(DxaFrameworkVersion)\lib\net452\Sdl.Web.Common.dll
+
+
+ False
+ ..\packages\Sdl.Dxa.Framework.Web8.$(DxaFrameworkVersion)\\lib\net452\Sdl.Web.DataModel.dll
+
+
+ False
+ ..\packages\Sdl.Web.Delivery.$(SdlDeliveryVersion)\lib\net452\Sdl.Web.Delivery.Service.dll
+
+
+ False
+ ..\packages\Sdl.Web.Delivery.$(SdlDeliveryVersion)\lib\net452\Sdl.Web.Delivery.ServicesCore.dll
+
+
+ False
+ ..\packages\Sdl.Dxa.Framework.Web8.$(DxaFrameworkVersion)\lib\net452\Sdl.Web.Mvc.dll
+
+
+ False
+ ..\packages\Sdl.Dxa.Framework.Web8.$(DxaFrameworkVersion)\lib\net452\Sdl.Web.Tridion.dll
+
+
+
+
+
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll
+ True
+
+
+ ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll
+ True
+
+
+ ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll
+ True
+
+
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll
+ True
+
+
+
+
+
+
+
+
+ ..\packages\Sdl.Web.Delivery.$(SdlDeliveryVersion)\lib\net452\Tridion.ContentDelivery.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp-net/TridionDocs/TridionDocsModuleAreaRegistration.cs b/webapp-net/TridionDocs/TridionDocsModuleAreaRegistration.cs
new file mode 100644
index 000000000..7afa386fc
--- /dev/null
+++ b/webapp-net/TridionDocs/TridionDocsModuleAreaRegistration.cs
@@ -0,0 +1,24 @@
+using Sdl.Web.Common.Models;
+using Sdl.Web.Modules.TridionDocs.Models;
+using Sdl.Web.Mvc.Configuration;
+
+namespace Sdl.Web.Modules.TridionDocs
+{
+ ///
+ /// Tridion Docs module area registration
+ ///
+ public class TridionDocsModuleAreaRegistration : BaseAreaRegistration
+ {
+ public static string AREA_NAME = "TridionDocs";
+ public override string AreaName => AREA_NAME;
+ protected override void RegisterAllViewModels()
+ {
+ // Entity Views
+ RegisterViewModel("Topic", typeof(Topic));
+
+ // Page Views
+ RegisterViewModel("GeneralPage", typeof(PageModel));
+ RegisterViewModel("ErrorPage", typeof(PageModel));
+ }
+ }
+}
diff --git a/webapp-net/TridionDocs/packages.config b/webapp-net/TridionDocs/packages.config
new file mode 100644
index 000000000..da2616e98
--- /dev/null
+++ b/webapp-net/TridionDocs/packages.config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp-net/Ugc/Areas/Ugc/Views/Page/GeneralPage.cshtml b/webapp-net/Ugc/Areas/Ugc/Views/Page/GeneralPage.cshtml
new file mode 100644
index 000000000..69360da37
--- /dev/null
+++ b/webapp-net/Ugc/Areas/Ugc/Views/Page/GeneralPage.cshtml
@@ -0,0 +1,29 @@
+@model PageModel
+@{
+ bool hasLeftBar = Model.Regions.ContainsKey("Left Navigation") || Model.Regions.ContainsKey("Left");
+ int mainContainerSize = hasLeftBar ? 9 : 12;
+}
+@Html.DxaRegion("Header")
+
+
+
+ @Html.DxaRegion("Hero")
+ @Html.DxaRegion("Content Tools")
+
+ @if (hasLeftBar)
+ {
+
+ @Html.DxaRegion("Left Navigation", containerSize: 3)
+ @Html.DxaRegion("Left", containerSize: 3)
+
+ }
+
+ @Html.DxaRegions(exclude: "Header,Footer,Hero,Content Tools,Left Navigation,Left,Comments", containerSize: mainContainerSize)
+
+
+ @Html.DxaRegion("Comments", containerSize: mainContainerSize)
+
+
+
+
+@Html.DxaRegion("Footer")
diff --git a/webapp-net/Ugc/Areas/Ugc/Views/Page/_ViewStart.cshtml b/webapp-net/Ugc/Areas/Ugc/Views/Page/_ViewStart.cshtml
new file mode 100644
index 000000000..efda124b1
--- /dev/null
+++ b/webapp-net/Ugc/Areas/Ugc/Views/Page/_ViewStart.cshtml
@@ -0,0 +1,3 @@
+@{
+ Layout = "~/Views/Shared/_Layout.cshtml";
+}
\ No newline at end of file
diff --git a/webapp-net/Ugc/Areas/Ugc/Views/Region/Comments.cshtml b/webapp-net/Ugc/Areas/Ugc/Views/Region/Comments.cshtml
new file mode 100644
index 000000000..20fd0f0e9
--- /dev/null
+++ b/webapp-net/Ugc/Areas/Ugc/Views/Region/Comments.cshtml
@@ -0,0 +1,6 @@
+@model UgcRegion
+
+
+ @Html.DxaEntities()
+
+
\ No newline at end of file
diff --git a/webapp-net/Ugc/Areas/Ugc/Views/Ugc/Partials/UgcComment.cshtml b/webapp-net/Ugc/Areas/Ugc/Views/Ugc/Partials/UgcComment.cshtml
new file mode 100644
index 000000000..56401e279
--- /dev/null
+++ b/webapp-net/Ugc/Areas/Ugc/Views/Ugc/Partials/UgcComment.cshtml
@@ -0,0 +1,39 @@
+@model UgcComment
+
+
+
+
+
+
+
+
+
+ @Model.CommentData.User.Name
+ -
+ @Html.DateDiff(@Model.CommentData.LastModifiedDate.DateTime)
+
+
+
@Model.CommentData.Content
+
+
+
+ @Model.CommentData.Rating
+
+
+
+
+
+
+
+ @foreach (var comment in Model.Comments)
+ {
+
+
+ @Html.Partial("Partials/UgcComment", comment)
+
+
+ }
+
+
+
+
diff --git a/webapp-net/Ugc/Areas/Ugc/Views/Ugc/UgcComments.cshtml b/webapp-net/Ugc/Areas/Ugc/Views/Ugc/UgcComments.cshtml
new file mode 100644
index 000000000..6cf611202
--- /dev/null
+++ b/webapp-net/Ugc/Areas/Ugc/Views/Ugc/UgcComments.cshtml
@@ -0,0 +1,15 @@
+@model UgcComments
+
+
Comments (@Model.Comments.Count)
+
+
+ @foreach (var comment in Model.Comments)
+ {
+
+ @Html.Partial("Partials/UgcComment", comment)
+
+ }
+
+
+
+
diff --git a/webapp-net/Ugc/Areas/Ugc/Views/Ugc/UgcPostCommentForm.cshtml b/webapp-net/Ugc/Areas/Ugc/Views/Ugc/UgcPostCommentForm.cshtml
new file mode 100644
index 000000000..d50ad3d5f
--- /dev/null
+++ b/webapp-net/Ugc/Areas/Ugc/Views/Ugc/UgcPostCommentForm.cshtml
@@ -0,0 +1,32 @@
+@model UgcPostCommentForm
+
+
+
+ @using (Html.BeginForm())
+ {
+ if (!WebRequestContext.IsPreview)
+ {
+ @* If we put in an Anti Forgery Token, the Page can't be edited in XPM anymore *@
+ @Html.AntiForgeryToken()
+ }
+ if (!Html.ViewData.ModelState.IsValid)
+ {
+
+ @Html.ValidationSummary(excludePropertyErrors: false)
+
+ }
+
+ @Html.TextBoxFor(m => m.UserName, new {@class = "form-control", placeholder = @Model.UserNameLabel})
+
+
+ @Html.TextBoxFor(m => m.EmailAddress, new {@class = "form-control", placeholder = @Model.EmailAddressLabel})
+
+
+ @Html.TextAreaFor(m => m.Content, new {@class = "form-control", placeholder = @Model.ContentLabel})
+
+
+ Cancel
+ @Model.SubmitButtonLabel
+
+ }
+
\ No newline at end of file
diff --git a/webapp-net/Ugc/Areas/Ugc/Views/web.config b/webapp-net/Ugc/Areas/Ugc/Views/web.config
new file mode 100644
index 000000000..5c5324b79
--- /dev/null
+++ b/webapp-net/Ugc/Areas/Ugc/Views/web.config
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp-net/Ugc/Controllers/UgcApiController.cs b/webapp-net/Ugc/Controllers/UgcApiController.cs
new file mode 100644
index 000000000..bc4b3c52a
--- /dev/null
+++ b/webapp-net/Ugc/Controllers/UgcApiController.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Collections.Generic;
+using Sdl.Web.Mvc.Controllers;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Web.Mvc;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using Sdl.Web.Modules.Ugc.Data;
+using Sdl.Web.Mvc.Configuration;
+
+namespace Sdl.Web.Modules.Ugc.Controllers
+{
+ ///
+ /// Ugc Api Controller
+ ///
+ public class UgcApiController : BaseController
+ {
+ [Route("{localization}/api/comments/{pageId:int}")]
+ [Route("~/api/comments/{publicationId:int}/{pageId:int}")]
+ [HttpGet]
+ public ActionResult GetComments(int? publicationId, int pageId, bool descending = false, int[] status = null,
+ int top = 0, int skip = 0)
+ {
+ UgcService ugc = new UgcService();
+ var comments = ugc.GetComments(
+ publicationId ?? int.Parse(WebRequestContext.Localization.Id),
+ pageId, descending, status ?? new int[] {}, top, skip);
+
+ if(comments == null)
+ return ServerError(null);
+ return new ContentResult
+ {
+ ContentType = "application/json",
+ Content =
+ JsonConvert.SerializeObject(comments,
+ new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}),
+ ContentEncoding = Encoding.UTF8
+ };
+ }
+
+ [Route("~/api/comments/add")]
+ [Route("{localization}/api/comments/add")]
+ [HttpPost]
+ public ActionResult PostComment(int? publicationId, int? pageId, bool descending = false, int[] status = null,
+ int top = 0, int skip = 0)
+ {
+ try
+ {
+ if (pageId == null || publicationId == null)
+ {
+ Response.StatusCode = 200;
+ return new EmptyResult();
+ }
+ UgcService ugc = new UgcService();
+ Stream req = Request.InputStream;
+ req.Seek(0, SeekOrigin.Begin);
+ string json = new StreamReader(req).ReadToEnd();
+ PostedComment posted = JsonConvert.DeserializeObject(json);
+ Dictionary metadata = CreateMetadata(posted, true);
+
+ string userId = posted.Username;
+ if (string.IsNullOrEmpty(userId))
+ {
+ userId = "Anonymous";
+ }
+
+ Comment result = ugc.PostComment(posted.PublicationId,
+ posted.PageId.Value,
+ userId,
+ posted.Email,
+ posted.Content,
+ posted.ParentId ?? 0,
+ metadata);
+
+ if(result == null)
+ return ServerError(null);
+ result.Metadata = CreateMetadata(posted, false);
+ return new ContentResult
+ {
+ ContentType = "application/json",
+ Content =
+ JsonConvert.SerializeObject(result,
+ new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}),
+ ContentEncoding = Encoding.UTF8
+ };
+ }
+ catch (Exception ex)
+ {
+ return ServerError(ex);
+ }
+ }
+
+ [Route("~/api/comments/upvote")]
+ [Route("{localization}/api/comments/upvote")]
+ public async Task UpVoteComment(int commentId)
+ {
+ UgcService ugc = new UgcService();
+ await ugc.UpVoteComment(commentId);
+ return Redirect(Request.UrlReferrer?.AbsolutePath);
+ }
+
+ [Route("~/api/comments/downvote")]
+ [Route("{localization}/api/comments/downvote")]
+ public async Task DownVoteComment(int commentId)
+ {
+ UgcService ugc = new UgcService();
+ await ugc.DownVoteComment(commentId);
+ return Redirect(Request.UrlReferrer?.AbsolutePath);
+ }
+
+ [Route("~/api/comments/remove")]
+ [Route("{localization}/api/comments/remove")]
+ public async Task RemoveComment(int commentId)
+ {
+ UgcService ugc = new UgcService();
+ await ugc.RemoveComment(commentId);
+ return Redirect(Request.UrlReferrer?.AbsolutePath);
+ }
+
+ private static Dictionary CreateMetadata(PostedComment posted, bool escape)
+ {
+ string pubTitle = posted.PublicationTitle;
+ string pubUrl = posted.PublicationUrl;
+ string itemTitle = posted.PageTitle;
+ string pageUrl = posted.PageUrl;
+ string lang = posted.Language;
+
+ if (escape)
+ {
+ pubTitle = $"\"{Regex.Escape(pubTitle)}\"";
+ pubUrl = $"\"{pubUrl}\"";
+ pageUrl = $"\"{pageUrl}\"";
+ itemTitle = $"\"{itemTitle}\"";
+ lang = $"\"{lang}\"";
+ }
+
+ var metadata = new Dictionary
+ {
+ {"publicationTitle", pubTitle},
+ {"publicationUrl", pubUrl},
+ {"itemTitle", itemTitle},
+ {"itemUrl", pageUrl},
+ {"language", lang},
+ {"status", "0"}
+ };
+ metadata.Add("pubIdTitleLang", $"{{\"id\":{posted.PublicationId},\"title\":\"{posted.PublicationTitle}\",\"lang\":\"{posted.Language}\"}}");
+ return metadata;
+ }
+
+ public ActionResult ServerError(Exception ex)
+ {
+ Response.StatusCode = 405;
+ if (ex == null)
+ {
+ return new EmptyResult();
+ }
+ if (ex.InnerException != null) ex = ex.InnerException;
+ return Content("{ \"Message\": \"" + ex.Message + "\" }", "application/json");
+ }
+ }
+}
diff --git a/webapp-net/Ugc/Controllers/UgcController.cs b/webapp-net/Ugc/Controllers/UgcController.cs
new file mode 100644
index 000000000..dabb8e859
--- /dev/null
+++ b/webapp-net/Ugc/Controllers/UgcController.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.Linq;
+using Sdl.Web.Mvc.Controllers;
+using Sdl.Web.Common.Models;
+using Sdl.Web.Modules.Ugc.Data;
+using Sdl.Web.Modules.Ugc.Models;
+using Sdl.Web.Mvc.Configuration;
+
+namespace Sdl.Web.Modules.Ugc.Controllers
+{
+ ///
+ /// Ugc Controller
+ ///
+ public class UgcController : EntityController
+ {
+ protected override ViewModel EnrichModel(ViewModel sourceModel)
+ {
+ UgcComments model = base.EnrichModel(sourceModel) as UgcComments;
+ if (model != null)
+ {
+ var ugcService = new UgcService();
+ var comments = ugcService.GetComments(model.Target.PublicationId, model.Target.ItemId, false,
+ new int[] {}, 0, 0);
+ model.Comments = CreateEntities(comments);
+ return model;
+ }
+
+ UgcPostCommentForm postForm = base.EnrichModel(sourceModel) as UgcPostCommentForm;
+ if (postForm != null && MapRequestFormData(postForm) && ModelState.IsValid)
+ {
+ var ugcService = new UgcService();
+ ugcService.PostComment(postForm.Target.PublicationId, postForm.Target.ItemId, postForm.UserName, postForm.EmailAddress, postForm.Content, postForm.ParentId, postForm.Metadata);
+ return new RedirectModel(WebRequestContext.RequestUrl);
+ }
+
+ return sourceModel;
+ }
+
+ private static List CreateEntities(List comments) => comments.Select(CreateEntity).ToList();
+
+ private static UgcComment CreateEntity(Comment comment) => new UgcComment {Comments = CreateEntities(comment.Children), CommentData = comment};
+ }
+}
diff --git a/webapp-net/Ugc/Data/Comment.cs b/webapp-net/Ugc/Data/Comment.cs
new file mode 100644
index 000000000..54c6b7367
--- /dev/null
+++ b/webapp-net/Ugc/Data/Comment.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Sdl.Web.Modules.Ugc.Data
+{
+ public class Comment
+ {
+ public long Id { get; set; }
+
+ [JsonIgnore]
+ public long ParentId { get; set; }
+
+ public int ItemPublicationId { get; set; }
+ public int ItemId { get; set; }
+ public int ItemType { get; set; }
+ public CommentDate CreationDate { get; set; }
+ public CommentDate LastModifiedDate { get; set; }
+ public string Content { get; set; }
+ public User User { get; set; }
+ public List Children { get; set; }
+
+ [JsonIgnore]
+ public int Rating { get; set; } = 0;
+
+ public Dictionary Metadata { get; set; } = new Dictionary();
+ }
+
+ public class CommentDate
+ {
+ [JsonIgnore]
+ public DateTime DateTime { get; set; }
+
+ public int DayOfMonth { get; set; }
+ public string DayOfWeek { get; set; }
+ public int DayOfYear { get; set; }
+ public int Hour { get; set; }
+ public int Minute { get; set; }
+ public string Month { get; set; }
+ public int MonthValue { get; set; }
+ public int Nano { get; set; }
+ public int Second { get; set; }
+ public int Year { get; set; }
+ }
+}
diff --git a/webapp-net/Ugc/Data/PostedComment.cs b/webapp-net/Ugc/Data/PostedComment.cs
new file mode 100644
index 000000000..c1fcf4a51
--- /dev/null
+++ b/webapp-net/Ugc/Data/PostedComment.cs
@@ -0,0 +1,30 @@
+namespace Sdl.Web.Modules.Ugc.Data
+{
+ ///
+ /// Posted Comment
+ ///
+ public class PostedComment
+ {
+ public int PublicationId { get; set; }
+
+ public int? PageId { get; set; }
+
+ public string Username { get; set; }
+
+ public string Email { get; set; }
+
+ public string Content { get; set; }
+
+ public int? ParentId { get; set; } = 0;
+
+ public string PublicationTitle { get; set; }
+
+ public string PublicationUrl { get; set; }
+
+ public string PageTitle { get; set; }
+
+ public string PageUrl { get; set; }
+
+ public string Language { get; set; }
+ }
+}
diff --git a/webapp-net/Ugc/Data/PubIdTitleLang.cs b/webapp-net/Ugc/Data/PubIdTitleLang.cs
new file mode 100644
index 000000000..77c35d2c1
--- /dev/null
+++ b/webapp-net/Ugc/Data/PubIdTitleLang.cs
@@ -0,0 +1,9 @@
+namespace Sdl.Web.Modules.Ugc.Data
+{
+ public class PubIdTitleLang
+ {
+ public string Id { get; set; }
+ public string Title { get; set; }
+ public string Lang { get; set; }
+ }
+}
diff --git a/webapp-net/Ugc/Data/User.cs b/webapp-net/Ugc/Data/User.cs
new file mode 100644
index 000000000..c5bbdc193
--- /dev/null
+++ b/webapp-net/Ugc/Data/User.cs
@@ -0,0 +1,10 @@
+namespace Sdl.Web.Modules.Ugc.Data
+{
+ public class User
+ {
+ public string Id { get; set; }
+ public string Name { get; set; }
+ public string EmailAddress { get; set; }
+ public string ExternalId { get; set; }
+ }
+}
diff --git a/webapp-net/Ugc/Mapping/UgcModelBuilder.cs b/webapp-net/Ugc/Mapping/UgcModelBuilder.cs
new file mode 100644
index 000000000..d018f0d4a
--- /dev/null
+++ b/webapp-net/Ugc/Mapping/UgcModelBuilder.cs
@@ -0,0 +1,215 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Sdl.Web.Common.Interfaces;
+using Sdl.Web.Common.Logging;
+using Sdl.Web.Common.Models;
+using Sdl.Web.DataModel;
+using Sdl.Web.Modules.Ugc.Models;
+using Sdl.Web.Tridion.ContentManager;
+using Sdl.Web.Tridion.Mapping;
+
+namespace Sdl.Web.Modules.Ugc.Mapping
+{
+ ///
+ /// Ugc Model Builder
+ ///
+ public class UgcModelBuilder : IPageModelBuilder, IEntityModelBuilder
+ {
+ private static readonly string ShowCommentsExtData = "UgcShowComments";
+ private static readonly string PostCommentsExtData = "UgcPostComments";
+ private static readonly string CommentsEntityRegionExt = "CommentsEntityRegion";
+
+ public void BuildPageModel(ref PageModel pageModel, PageModelData pageModelData, bool includePageRegions,
+ ILocalization localization)
+ {
+ using (new Tracer(pageModel, pageModelData, localization))
+ {
+ var ugcMetadata = UgcMetadata(pageModelData.PageTemplate?.Metadata);
+
+ string regionName = GetValue(ugcMetadata, "commentsRegion");
+ string areaName = pageModel.MvcData.AreaName;
+ RegionModel ugcRegion;
+ if (string.IsNullOrEmpty(regionName))
+ {
+ areaName = "Ugc";
+ regionName = "Comments";
+ ugcRegion = FindRegion(pageModel.Regions, "Comments") ??
+ CreateRegion(pageModel, areaName, regionName);
+ }
+ else
+ {
+ ugcRegion = FindRegion(pageModel.Regions, regionName);
+ if (ugcRegion == null)
+ {
+ Log.Error("Unable to locate region for comments '" + regionName + "'.");
+ }
+ }
+
+ // Entity Comments
+ foreach (var region in pageModel.Regions)
+ {
+ AddCommentsViews(pageModel, region, localization, ugcRegion);
+ }
+
+ if (ugcRegion != null)
+ {
+ // Page Comments
+ if (ShowComments(ugcMetadata))
+ {
+ ugcRegion.Entities.Add(CreateUgcCommentsEntity(localization, pageModel.Id, ItemType.Page));
+ }
+ if (PostComments(ugcMetadata))
+ {
+ ugcRegion.Entities.Add(CreateUgcPostCommentEntity(localization, pageModel.Id, ItemType.Page,
+ UgcPostFormMetadata(ugcMetadata)));
+ }
+ }
+ }
+ }
+
+ private static void AddCommentsViews(PageModel pageModel, RegionModel region, ILocalization localization, RegionModel ugcRegion)
+ {
+ List regionEntities = new List();
+
+ foreach (var entity in region.Entities.Where(e => e.ExtensionData != null))
+ {
+ if (entity.ExtensionData == null) continue;
+
+ // comments get added to the ugcRegion if it exists else we place in same region as entity
+ IList entities = ugcRegion != null ? ugcRegion.Entities : regionEntities;
+ if (entity.ExtensionData.ContainsKey(CommentsEntityRegionExt))
+ {
+ // comment region specified for this entity so lets find it and use that
+ var targetRegion = FindRegion(pageModel.Regions, (string) entity.ExtensionData[CommentsEntityRegionExt]);
+ if (targetRegion != null && targetRegion != region)
+ {
+ entities = targetRegion.Entities;
+ }
+ else if (targetRegion == null || targetRegion == region)
+ {
+ entities = regionEntities;
+ }
+ }
+
+ if (entity.ExtensionData.ContainsKey(ShowCommentsExtData) &&
+ (bool) entity.ExtensionData[ShowCommentsExtData])
+ {
+ entities.Add(CreateUgcCommentsEntity(localization, entity.Id, ItemType.Component));
+ }
+ if (entity.ExtensionData.ContainsKey(PostCommentsExtData) && entity.ExtensionData[PostCommentsExtData] != null)
+ {
+ entities.Add(CreateUgcPostCommentEntity(localization, entity.Id, ItemType.Component, (ContentModelData)entity.ExtensionData[PostCommentsExtData]));
+ }
+ }
+
+ // Add our ugc views to either the same region as the entity we have comments enabled for or the ugc "Comments" region if available
+ foreach (var x in regionEntities)
+ {
+ region.Entities.Add(x);
+ }
+
+ foreach (var childRegion in region.Regions)
+ {
+ AddCommentsViews(pageModel, childRegion, localization, ugcRegion);
+ }
+ }
+
+ public void BuildEntityModel(ref EntityModel entityModel, EntityModelData entityModelData, Type baseModelType,
+ ILocalization localization)
+ {
+ var ugcMetadata = UgcMetadata(entityModelData.ComponentTemplate?.Metadata);
+ entityModel.SetExtensionData(ShowCommentsExtData, ShowComments(ugcMetadata));
+ entityModel.SetExtensionData(PostCommentsExtData, PostComments(ugcMetadata) ? UgcPostFormMetadata(ugcMetadata) : null);
+ entityModel.SetExtensionData(CommentsEntityRegionExt, GetCommentsRegion(ugcMetadata));
+ }
+
+ private static RegionModel FindRegion(RegionModelSet regionModelSet, string regionName)
+ {
+ foreach (var region in regionModelSet)
+ {
+ if (region.Name.Equals(regionName)) return region;
+ RegionModel childRegion = FindRegion(region.Regions, regionName);
+ if (childRegion != null) return childRegion;
+ }
+
+ return null;
+ }
+
+ private static UgcRegion CreateRegion(PageModel pageModel, string areaName, string regionName)
+ {
+ UgcRegion ugcRegion;
+ if (!pageModel.Regions.OfType().Any())
+ {
+ ugcRegion = new UgcRegion(regionName);
+ ugcRegion.MvcData = new Common.Models.MvcData($"{areaName}:{regionName}");
+ pageModel.Regions.Add(ugcRegion);
+ }
+ else
+ {
+ ugcRegion = pageModel.Regions.OfType().First();
+ }
+ return ugcRegion;
+ }
+
+ private static UgcComments CreateUgcCommentsEntity(ILocalization localization, string modelId, ItemType itemType)
+ {
+ var mvcData = new Common.Models.MvcData("Ugc:Ugc:UgcComments");
+ mvcData.ControllerAreaName = "Ugc";
+ return new UgcComments
+ {
+ Target =
+ CmUri.FromString(
+ $"{localization.CmUriScheme}:{localization.Id}-{modelId}-{(int)itemType}"),
+ MvcData = mvcData,
+ IsVolatile = true
+ };
+ }
+
+ private static UgcPostCommentForm CreateUgcPostCommentEntity(ILocalization localization, string modelId, ItemType itemType, ContentModelData postFormConfig)
+ {
+ var mvcData = new Common.Models.MvcData("Ugc:Ugc:UgcPostCommentForm");
+ mvcData.ControllerAreaName = "Ugc";
+ return new UgcPostCommentForm
+ {
+ Target =
+ CmUri.FromString(
+ $"{localization.CmUriScheme}:{localization.Id}-{modelId}-{(int)itemType}"),
+ MvcData = mvcData,
+ UserNameLabel = GetValue(postFormConfig, "userNameLabel"),
+ EmailAddressLabel = GetValue(postFormConfig, "emailAddressLabel"),
+ ContentLabel = GetValue(postFormConfig, "contentLabel"),
+ SubmitButtonLabel = GetValue(postFormConfig, "submitButtonLabel"),
+ NoContentMessage = GetValue(postFormConfig, "noContentMessage"),
+ NoEmailAddressMessage = GetValue(postFormConfig, "noEmailAddressMessage"),
+ NoUserNameMessage = GetValue(postFormConfig, "noUserNameMessage"),
+ IsVolatile = true
+ };
+ }
+
+ private static string GetCommentsRegion(ContentModelData metadata) => GetValue(metadata, "commentsRegion");
+
+ private static ContentModelData UgcMetadata(ContentModelData metadata) => metadata != null && metadata.ContainsKey("ugcConfig")
+ ? (ContentModelData) metadata["ugcConfig"]
+ : null;
+
+ private static ContentModelData UgcPostFormMetadata(ContentModelData metadata) => metadata != null && metadata.ContainsKey("postFormConfig")
+ ? (ContentModelData)metadata["postFormConfig"]
+ : null;
+
+ private static T GetValue(ContentModelData metadata, string name)
+ {
+ if (metadata == null || !metadata.ContainsKey(name)) return default(T);
+ var v = metadata[name];
+ if (v == null) return default(T);
+ if (typeof(T) == typeof(bool))
+ {
+ return (T) Convert.ChangeType(v.Equals("Yes"), typeof (T));
+ }
+ return (T) Convert.ChangeType(v, typeof (T));
+ }
+
+ private static bool ShowComments(ContentModelData metadata) => GetValue(metadata, "showComments");
+ private static bool PostComments(ContentModelData metadata) => GetValue(metadata, "allowPost");
+ }
+}
diff --git a/webapp-net/Ugc/Models/UgcComment.cs b/webapp-net/Ugc/Models/UgcComment.cs
new file mode 100644
index 000000000..1184dc33f
--- /dev/null
+++ b/webapp-net/Ugc/Models/UgcComment.cs
@@ -0,0 +1,13 @@
+using Sdl.Web.Common.Models;
+using Sdl.Web.Modules.Ugc.Data;
+using System.Collections.Generic;
+
+namespace Sdl.Web.Modules.Ugc.Models
+{
+ public class UgcComment : EntityModel
+ {
+ public Comment CommentData { get; set; }
+
+ public List Comments { get; set; }
+ }
+}
diff --git a/webapp-net/Ugc/Models/UgcComments.cs b/webapp-net/Ugc/Models/UgcComments.cs
new file mode 100644
index 000000000..0a4921bb9
--- /dev/null
+++ b/webapp-net/Ugc/Models/UgcComments.cs
@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+using Sdl.Web.Common.Models;
+using Sdl.Web.Modules.Ugc.Data;
+using Sdl.Web.Tridion.ContentManager;
+
+namespace Sdl.Web.Modules.Ugc.Models
+{
+ public class UgcComments : EntityModel
+ {
+ [SemanticProperty(IgnoreMapping = true)]
+ public CmUri Target { get; set; }
+
+ [SemanticProperty(IgnoreMapping = true)]
+ public List Comments { get; set; }
+ }
+}
diff --git a/webapp-net/Ugc/Models/UgcPostCommentForm.cs b/webapp-net/Ugc/Models/UgcPostCommentForm.cs
new file mode 100644
index 000000000..a71fcb723
--- /dev/null
+++ b/webapp-net/Ugc/Models/UgcPostCommentForm.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using Sdl.Web.Common;
+using Sdl.Web.Common.Models;
+using Sdl.Web.Tridion.ContentManager;
+
+namespace Sdl.Web.Modules.Ugc.Models
+{
+ [Serializable]
+ [DxaNoOutputCache]
+ public class UgcPostCommentForm : EntityModel
+ {
+ ///
+ /// Holds the form control value for username
+ ///
+ [Required(ErrorMessage = "@Model.NoUserNameMessage")]
+ [StringLength(80)]
+ [SemanticProperty(IgnoreMapping = true)]
+ public string UserName { get; set; }
+
+ ///
+ /// Holds the form control value for email address
+ ///
+ [Required(ErrorMessage = "@Model.NoEmailAddressMessage")]
+ [StringLength(255)]
+ [SemanticProperty(IgnoreMapping = true)]
+ public string EmailAddress { get; set; }
+
+ ///
+ /// Holds the form control value for email address
+ ///
+ [Required(ErrorMessage = "@Model.NoContentMessage")]
+ [SemanticProperty(IgnoreMapping = true)]
+ public string Content { get; set; }
+
+ ///
+ /// Metadata of comment to post
+ ///
+ [SemanticProperty(IgnoreMapping = true)]
+ public Dictionary Metadata { get; set; }
+
+ ///
+ /// Parent id of comment to post
+ ///
+ [SemanticProperty(IgnoreMapping = true)]
+ public int ParentId { get; set; } = 0;
+
+ ///
+ /// Label text for username input control on view
+ ///
+ public string UserNameLabel { get; set; }
+
+ ///
+ /// Label text for email address input control on view
+ ///
+ public string EmailAddressLabel { get; set; }
+
+ ///
+ /// Label text for content input control on view
+ ///
+ public string ContentLabel { get; set; }
+
+ ///
+ /// Label text for submit botton on view
+ ///
+ public string SubmitButtonLabel { get; set; }
+
+ ///
+ /// User name not specified message
+ ///
+ public string NoUserNameMessage { get; set; }
+
+ ///
+ /// Email not specified message
+ ///
+ public string NoEmailAddressMessage { get; set; }
+
+ ///
+ /// Content not specified message
+ ///
+ public string NoContentMessage { get; set; }
+
+ ///
+ /// Target CmUri for comments
+ ///
+ [SemanticProperty(IgnoreMapping = true)]
+ public CmUri Target { get; set; }
+ }
+}
diff --git a/webapp-net/Ugc/Models/UgcRegion.cs b/webapp-net/Ugc/Models/UgcRegion.cs
new file mode 100644
index 000000000..b0db27e46
--- /dev/null
+++ b/webapp-net/Ugc/Models/UgcRegion.cs
@@ -0,0 +1,19 @@
+using System;
+using Sdl.Web.Common.Models;
+
+namespace Sdl.Web.Modules.Ugc.Models
+{
+ [Serializable]
+ public class UgcRegion : RegionModel
+ {
+ public UgcRegion(string name)
+ : base(name)
+ {
+ }
+
+ public UgcRegion(string name, string qualifiedViewName)
+ : base(name, qualifiedViewName)
+ {
+ }
+ }
+}
diff --git a/webapp-net/Ugc/Properties/AssemblyInfo.cs b/webapp-net/Ugc/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..aa7156741
--- /dev/null
+++ b/webapp-net/Ugc/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+
+[assembly: AssemblyTitle("SDL DXA Ugc Module")]
+
+// NOTE: Common Assembly Info is generated by the build in CommonAssemblyInfo.cs
diff --git a/webapp-net/Ugc/Sdl.Web.Modules.Ugc.csproj b/webapp-net/Ugc/Sdl.Web.Modules.Ugc.csproj
new file mode 100644
index 000000000..6ba7d8d5a
--- /dev/null
+++ b/webapp-net/Ugc/Sdl.Web.Modules.Ugc.csproj
@@ -0,0 +1,131 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {54DDEEE6-1F97-4E53-8018-5918F7D90A8B}
+ Library
+ Properties
+ Sdl.Web.Modules.Ugc
+ Sdl.Web.Modules.Ugc
+ v4.5.2
+ true
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+ ..\packages\Newtonsoft.Json.10.0.1\lib\net45\Newtonsoft.Json.dll
+
+
+ False
+ ..\packages\Sdl.Dxa.Framework.Web8.$(DxaFrameworkVersion)\lib\net452\Sdl.Web.Common.dll
+
+
+ False
+ ..\packages\Sdl.Dxa.Framework.Web8.$(DxaFrameworkVersion)\lib\net452\Sdl.Web.DataModel.dll
+
+
+ False
+ ..\packages\Sdl.Web.Delivery.$(SdlDeliveryVersion)\lib\net452\Sdl.Web.Delivery.UGC.dll
+
+
+ ..\packages\Sdl.Dxa.Framework.Web8.$(DxaFrameworkVersion)\lib\net452\Sdl.Web.Mvc.dll
+
+
+ ..\packages\Sdl.Dxa.Framework.Web8.$(DxaFrameworkVersion)\lib\net452\Sdl.Web.Tridion.dll
+
+
+ False
+ ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll
+ $(CopyLocal)
+
+
+ False
+ ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll
+ $(CopyLocal)
+
+
+ False
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll
+ $(CopyLocal)
+
+
+ ..\packages\Sdl.Web.Delivery.$(SdlDeliveryVersion)\lib\net452\Tridion.ContentDelivery.AmbientData.dll
+
+
+ False
+ ..\packages\Microsoft.AspNet.WebPages.Data.3.2.3\lib\net45\WebMatrix.Data.dll
+
+
+ False
+ ..\packages\Microsoft.AspNet.WebPages.WebData.3.2.3\lib\net45\WebMatrix.WebData.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp-net/Ugc/UgcAreaRegistration.cs b/webapp-net/Ugc/UgcAreaRegistration.cs
new file mode 100644
index 000000000..a7992d380
--- /dev/null
+++ b/webapp-net/Ugc/UgcAreaRegistration.cs
@@ -0,0 +1,24 @@
+using Sdl.Web.Common.Models;
+using Sdl.Web.Modules.Ugc.Models;
+using Sdl.Web.Mvc.Configuration;
+
+namespace Sdl.Web.Modules.Ugc
+{
+ public class UgcAreaRegistration : BaseAreaRegistration
+ {
+ public override string AreaName => "Ugc";
+
+ protected override void RegisterAllViewModels()
+ {
+ // Region
+ RegisterViewModel("Comments", typeof(UgcRegion));
+
+ // Entity
+ RegisterViewModel("UgcComments", typeof(UgcComments), "Ugc");
+ RegisterViewModel("UgcPostCommentForm", typeof(UgcPostCommentForm), "Ugc");
+
+ // Page
+ RegisterViewModel("GeneralPage", typeof(PageModel));
+ }
+ }
+}
diff --git a/webapp-net/Ugc/UgcService.cs b/webapp-net/Ugc/UgcService.cs
new file mode 100644
index 000000000..2816c75b1
--- /dev/null
+++ b/webapp-net/Ugc/UgcService.cs
@@ -0,0 +1,148 @@
+using System;
+using Sdl.Web.Tridion.ContentManager;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Threading.Tasks;
+using Sdl.Web.Common.Interfaces;
+using Sdl.Web.Delivery.UGC;
+using Sdl.Web.Delivery.UGC.Model;
+using Sdl.Web.Modules.Ugc.Data;
+using Sdl.Web.Mvc.Configuration;
+using Tridion.ContentDelivery.AmbientData;
+
+namespace Sdl.Web.Modules.Ugc
+{
+ ///
+ /// Ugc Service
+ ///
+ public class UgcService
+ {
+ private static readonly int MaximumThreadsDepth = -1;
+ private readonly IUgcCommentsApi _api;
+ private readonly IUgcVoteCommentApi _votingApi;
+
+ public UgcService()
+ {
+ _api = UgcInstanceProvider.Instance.UgcCommunityClient();
+ _votingApi = (IUgcVoteCommentApi) _api;
+ }
+
+ public List GetComments(int publicationId, int pageId, bool descending, int[] status, int top, int skip)
+ {
+ SimpleCommentsFilter filter = new SimpleCommentsFilter
+ {
+ Top = top,
+ Skip = skip,
+ Depth = MaximumThreadsDepth,
+ Statuses = new List(new List(status).Select(x => (Status) x))
+ };
+ return Convert(_api.RetrieveThreadedComments(CreateUri(publicationId, pageId), filter, descending, true));
+ }
+
+ public Comment PostComment(int publicationId, int pageId, string username, string email, string content,
+ int parentId, Dictionary metadata)
+ {
+ var claimStore = AmbientDataContext.CurrentClaimStore;
+ if (claimStore != null)
+ {
+ claimStore.Put(new Uri("taf:claim:contentdelivery:webservice:user"), username);
+ claimStore.Put(new Uri("taf:claim:contentdelivery:webservice:post:allowed"), true);
+ }
+ return Convert(
+ _api.PostComment(CreateUri(publicationId, pageId), username, email, content, parentId, metadata).Result);
+ }
+
+ public async Task UpVoteComment(long commentId)
+ {
+ await _votingApi.VoteCommentUp(commentId);
+ }
+
+ public async Task DownVoteComment(long commentId)
+ {
+ await _votingApi.VoteCommentDown(commentId);
+ }
+
+ public async Task RemoveComment(long commentId)
+ {
+ return await _api.RemoveComment(commentId);
+ }
+
+ private static CmUri CreateUri(int publicationId, int pageId)
+ {
+ ILocalization localization = WebRequestContext.Localization;
+ return CmUri.Create(localization.CmUriScheme, publicationId, pageId, ItemType.Page);
+ }
+
+ private static List Convert(IEnumerable comments)
+ => comments?.Select(Convert).ToList();
+
+ private static Comment Convert(IComment comment)
+ {
+ if (comment == null) return null;
+ Comment c = new Comment
+ {
+ Id = comment.Id,
+ ParentId = comment.ParentId,
+ ItemId = comment.ItemId,
+ ItemType = comment.ItemType,
+ ItemPublicationId = comment.ItemPublicationId,
+ Content = comment.Content,
+ Rating = comment.Score,
+ Metadata = Convert(comment.Metadata)
+ };
+
+ if (comment.User != null)
+ {
+ c.User = Convert(comment.User);
+ }
+
+ if (comment.CreationDate.HasValue)
+ {
+ c.CreationDate = Convert(comment.CreationDate.Value);
+ }
+
+ if (comment.LastModifiedDate.HasValue)
+ {
+ c.LastModifiedDate = Convert(comment.LastModifiedDate.Value);
+ }
+
+ c.Children = Convert(comment.Children);
+ return c;
+ }
+
+ private static Dictionary Convert(List meta)
+ {
+ var metadata = new Dictionary();
+ if (meta == null) return metadata;
+ foreach (var m in meta.Where(m => !metadata.ContainsKey(m.KeyName)))
+ {
+ metadata.Add(m.KeyName, m.KeyValue);
+ }
+ return metadata;
+ }
+
+ private static User Convert(IUser user) => new User
+ {
+ Id = user.Id,
+ ExternalId = user.ExternalId,
+ Name = user.Name,
+ EmailAddress = user.EmailAddress,
+ };
+
+ private static CommentDate Convert(DateTime dt) => new CommentDate
+ {
+ DateTime = dt,
+ DayOfMonth = dt.Month,
+ DayOfWeek = dt.DayOfWeek.ToString(),
+ DayOfYear = dt.DayOfYear,
+ Month = new DateTimeFormatInfo().GetMonthName(dt.Month),
+ MonthValue = dt.Month,
+ Year = dt.Year,
+ Hour = dt.Hour,
+ Minute = dt.Minute,
+ Second = dt.Second,
+ Nano = dt.Millisecond
+ };
+ }
+}
diff --git a/webapp-net/Ugc/packages.config b/webapp-net/Ugc/packages.config
new file mode 100644
index 000000000..d2496af24
--- /dev/null
+++ b/webapp-net/Ugc/packages.config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp-net/_tools/NuGet.exe b/webapp-net/_tools/NuGet.exe
deleted file mode 100644
index be85ec2c4..000000000
Binary files a/webapp-net/_tools/NuGet.exe and /dev/null differ
diff --git a/webapp-net/ciBuild.proj b/webapp-net/ciBuild.proj
index 521e5baf8..92bd06ec4 100644
--- a/webapp-net/ciBuild.proj
+++ b/webapp-net/ciBuild.proj
@@ -27,7 +27,7 @@
$(JenkinsUrl)/job/DXA%20$(DxaBuildType)%20CI%20Web%20App%20.NET/lastSuccessfulBuild/artifact/Site/bin
- "$(ProjectDirectory)\_tools\NuGet.exe"
+ NuGet.exe
C:\Program Files (x86)\MSBuild\12.0\Bin\MSBuild.exe
C:\Windows\Sysnative\WindowsPowerShell\v1.0\powershell.exe
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe