Commit 50bc2ad6 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '170-article-template' into 'master'

added template to Article

Closes #170

See merge request genesys-pgr/genesys-server!82
parents decc3ef4 0d26b7a2
......@@ -603,7 +603,7 @@
<configuration>
<source>${jdk.source}</source>
<target>${jdk.target}</target>
<optimize>false</optimize>
<optimize>true</optimize>
<showDeprecation>${show.deprecations}</showDeprecation>
<showWarnings>true</showWarnings>
<failOnError>true</failOnError>
......
/**
* Copyright 2014 Global Crop Diversity Trust
/*
* Copyright 2017 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -12,7 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
*/
package org.genesys2.server.listener.sample;
......@@ -22,9 +22,14 @@ import java.util.Iterator;
import java.util.Locale;
import java.util.Map.Entry;
import org.apache.commons.io.IOUtils;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.genesys2.server.listener.RunAsAdminListener;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.service.CRMException;
import org.genesys2.server.service.ContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
......@@ -32,10 +37,6 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* This startup listener enumerates the resources in "default-content" directory
* of this package. Each resource name represents a content slug (URL) of a
......@@ -59,7 +60,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
public class CreateContentListener extends RunAsAdminListener {
@Value("${auto.createContent}")
private final boolean createContent = false;
private boolean createContent = false;
@Autowired
private ContentService contentService;
......@@ -72,38 +73,43 @@ public class CreateContentListener extends RunAsAdminListener {
private void createArticles() throws IOException, JsonProcessingException {
if (!createContent) {
_logger.info("Skipping menu creation on startup.");
_logger.warn("Skipping content creation on startup.");
return;
}
_logger.info("Checking if default content exists");
_logger.debug("Checking if default content exists");
final ClassLoader classLoader = CreateContentListener.class.getClassLoader();
final PathMatchingResourcePatternResolver rpr = new PathMatchingResourcePatternResolver(classLoader);
final String resourcePath = "/default-content/*";
final Resource[] rs = rpr.getResources(resourcePath);
for (final Resource r : rs) {
_logger.info(r.getFilename());
final String slug = r.getFilename();
final String slug = r.getFilename().substring(0, r.getFilename().lastIndexOf(".json"));
_logger.info("Using " + r.getFilename() + " for article slug=" + slug);
final ObjectMapper mapper = new ObjectMapper();
final InputStream stream = r.getInputStream();
final JsonNode json = mapper.readTree(stream);
IOUtils.closeQuietly(stream);
final Iterator<Entry<String, JsonNode>> it = json.fields();
while (it.hasNext()) {
final Entry<String, JsonNode> entry = it.next();
final Locale locale = new Locale(entry.getKey());
// Load from default locale if exists
final Article article = contentService.getGlobalArticle(slug, locale, false);
// If nothing is found, parse the resource and create content
if (article == null) {
contentService.updateGlobalArticle(slug, locale, entry.getValue().get("title").asText(), entry.getValue().get("body").asText(), null);
_logger.info("Created article for slug: " + slug + " lang=" + locale.getLanguage());
try (InputStream stream = r.getInputStream()) {
final JsonNode json = mapper.readTree(stream);
final Iterator<Entry<String, JsonNode>> it = json.fields();
while (it.hasNext()) {
final Entry<String, JsonNode> entry = it.next();
final Locale locale = new Locale(entry.getKey());
// Load from default locale if exists
final Article article = contentService.getGlobalArticle(slug, locale, false);
// If nothing is found, parse the resource and create content
if (article == null) {
try {
contentService.createGlobalArticle(slug, locale, entry.getValue().get("title").asText(), entry.getValue().get("body").asText(), null, false);
_logger.info("Created article for slug: " + slug + " lang=" + locale.getLanguage());
} catch (CRMException e) {
_logger.warn("Failed to create global article slug=" + slug + ".", e);
}
}
}
} catch (JsonParseException e) {
_logger.error("Could not create global article " + slug + " from " + r.getFilename() + ". JSON is not valid.");
}
}
}
......@@ -128,13 +134,15 @@ public class CreateContentListener extends RunAsAdminListener {
contentService.ensureMenuItem("about", "/content/about/newsletter", "menu.newsletter");
contentService.ensureMenuItem("about", "/content/about/frequently-asked-questions", "menu.faq");
contentService.ensureMenuItem("about", "/content/help/how-to-use-genesys", "menu.how-to-use-genesys");
// legal
contentService.ensureMenuItem("help", "/content/help/how-to-use-genesys", "menu.how-to-use-genesys");
// contentService.ensureMenuItem("help", "/content/help/about-genesys-data", "menu.about-genesys-data");
// contentService.ensureMenuItem("help", "/content/help/who-uses-genesys", "menu.who-uses-genesys");
// contentService.ensureMenuItem("help", "/content/help/how-to-use-genesys", "menu.how-to-use-genesys");
// contentService.ensureMenuItem("help", "/content/help/about-genesys-data",
// "menu.about-genesys-data");
// contentService.ensureMenuItem("help", "/content/help/who-uses-genesys",
// "menu.who-uses-genesys");
// contentService.ensureMenuItem("help", "/content/help/how-to-use-genesys",
// "menu.how-to-use-genesys");
contentService.ensureMenuItem("help", "/content/about/about", "menu.about");
}
......
......@@ -69,6 +69,17 @@ public class Article extends AuditedModel {
@Temporal(TemporalType.TIMESTAMP)
private Calendar postDate;
@Column(nullable = false, columnDefinition = "boolean default false")
private boolean template;
public boolean isTemplate() {
return template;
}
public void setTemplate(boolean template) {
this.template = template;
}
public String getSlug() {
return slug;
}
......
......@@ -24,6 +24,8 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface ArticleRepository extends JpaRepository<Article, Long> {
Article findByClassPkAndTargetIdAndSlugAndLang(ClassPK classPk, Long id, String slug, String lang);
......@@ -37,4 +39,6 @@ public interface ArticleRepository extends JpaRepository<Article, Long> {
Article findBySlugAndLangAndTargetIdAndClassPk(String slug, String lang, Long targetId, ClassPK classPK);
@Query("select a from Article a where a.template = :isTemplate")
List<Article> findAllByTemplate(@Param("isTemplate") Boolean isTemplate);
}
/**
* Copyright 2014 Global Crop Diversity Trust
/*
* Copyright 2017 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -12,47 +12,18 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package org.genesys2.server.service.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.HtmlSanitizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
@Component
public class ContentSanitizer {
public static final Log LOG = LogFactory.getLog(ContentSanitizer.class);
*/
@Autowired
private ContentService contentService;
package org.genesys2.server.service;
@Autowired
private HtmlSanitizer htmlSanitizer;
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CONTENTMANAGER')")
public void sanitizeAll() {
LOG.info("Sanitizing articles");
Page<Article> articles;
int page = 0;
do {
articles = contentService.listArticles(new PageRequest(page++, 10));
for (final Article a : articles) {
a.setBody(htmlSanitizer.sanitize(a.getBody()));
}
/**
* Content Service related exceptions
*/
public class CRMException extends Exception {
contentService.save(articles.getContent());
private static final long serialVersionUID = 1L;
} while (articles.hasNext());
public CRMException(String message) {
super(message);
}
}
......@@ -37,16 +37,15 @@ public interface ContentService {
Page<ActivityPost> allNews(int page);
ClassPK ensureClassPK(Class<?> clazz);
ClassPK getClassPk(String shortName);
ClassPK getClassPk(Class<?> clazz);
/**
* Load article with {@link ClassPK} of clazz with specified id and the
* "slug" for the locale
* Load article with {@link ClassPK} of clazz with specified id and the "slug"
* for the locale
*
* @param target
* @param slug
* @param locale
* @return
......@@ -55,7 +54,7 @@ public interface ContentService {
Article getArticle(EntityId entity, String slug, Locale locale);
Article getArticleBySlugAndLang(String slug, String lang);
Article getArticleBySlugAndLang(String slug, String lang);
Article getArticleBySlugLangTargetIdClassPk(String slug, String lang, Long targetId, String classPkShortName);
......@@ -64,15 +63,14 @@ public interface ContentService {
*
* @param slug
* @param locale
* @param useDefault
* Load article from default language
* @param useDefault Load article from default language
* @return
*/
Article getGlobalArticle(String slug, Locale locale, boolean useDefault);
/**
* Loads a global article in the specified locale, or when not found the
* default locale
* Loads a global article in the specified locale, or when not found the default
* locale
*
* @param string
* @param locale
......@@ -82,9 +80,9 @@ public interface ContentService {
Page<Article> listArticles(Pageable pageable);
Page<Article> listArticlesByLang(String lang, Pageable pageable);
Page<Article> listArticlesByLang(String lang, Pageable pageable);
void save(Iterable<Article> articles);
// void save(Iterable<Article> articles);
/**
* Create new activity post
......@@ -95,14 +93,16 @@ public interface ContentService {
*/
ActivityPost createActivityPost(String title, String body);
Article updateArticle(EntityId entity, String slug, String title, String body, String summary, Locale locale);
Article updateArticle(EntityId entity, String slug, String title, String body, String summary, Locale locale) throws CRMException;
Article updateArticle(Class<?> clazz, Long id, String slug, String title, String body, String summary, Locale locale);
Article updateArticle(Class<?> clazz, Long id, String slug, String title, String body, String summary, Locale locale) throws CRMException;
Article updateArticle(long id, String slug, String title, String body, String summary);
Article updateGlobalArticle(String slug, Locale locale, String title, String body, String summary);
Article createGlobalArticle(String slug, Locale locale, String title, String body, String summary, boolean isTemplate) throws CRMException;
Article updateGlobalArticle(String slug, Locale locale, String title, String body, String summary) throws CRMException;
ActivityPost getActivityPost(long id);
ActivityPost updateActivityPost(long id, String title, String body);
......@@ -125,4 +125,6 @@ public interface ContentService {
Menu updateMenu(String key, List<MenuItem> menuItems);
MenuItem ensureMenuItem(String menuKey, String url, String text);
void sanitizeAll();
}
......@@ -38,7 +38,7 @@ public interface CropService {
List<Crop> getCrops(Taxonomy2 taxonomy2);
void updateBlurp(Crop crop, String textBody, String summary, Locale locale);
void updateBlurp(Crop crop, String textBody, String summary, Locale locale) throws CRMException;
void rebuildTaxonomies();
......
......@@ -61,7 +61,7 @@ public interface GeoService {
List<Country> listAll();
void updateBlurp(Country country, String blurp, Locale locale);
void updateBlurp(Country country, String blurp, Locale locale) throws CRMException;
List<Country> listAll(Locale locale);
......
......@@ -47,7 +47,7 @@ public interface InstituteService {
List<FaoInstitute> update(Collection<FaoInstitute> institutes);
void updateAbout(FaoInstitute faoInstitute, String body, String summary, Locale locale);
void updateAbout(FaoInstitute faoInstitute, String body, String summary, Locale locale) throws CRMException;
void updateCountryRefs();
......
......@@ -31,7 +31,7 @@ public interface OrganizationService {
Organization getOrganization(String slug);
Article updateAbout(Organization organization, String body, String summary, Locale locale);
Article updateAbout(Organization organization, String body, String summary, Locale locale) throws CRMException;
Organization update(long id, String newSlug, String title);
......
......@@ -39,5 +39,5 @@ public interface ProjectService {
void deleteProject(Project project);
void updateBlurp(Project project, String textBody, String summary, Locale locale);
void updateBlurp(Project project, String textBody, String summary, Locale locale) throws CRMException;
}
......@@ -32,14 +32,6 @@ import org.springframework.security.core.userdetails.UserDetails;
public interface UserService extends BasicUserService<UserRole, User> {
List<UserRole> listAvailableRoles();
@PreAuthorize("hasRole('ADMINISTRATOR')")
User addUser(User user) throws UserException, PasswordPolicyException;
@PreAuthorize("hasRole('ADMINISTRATOR')")
void updateUser(User user) throws UserException;
@PreAuthorize("hasRole('ADMINISTRATOR')")
void removeUserById(long userId) throws UserException;
......
/**
* Copyright 2014 Global Crop Diversity Trust
/*
* Copyright 2017 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -12,7 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
*/
package org.genesys2.server.service.impl;
......@@ -41,6 +41,7 @@ import org.genesys2.server.persistence.domain.ActivityPostRepository;
import org.genesys2.server.persistence.domain.ArticleRepository;
import org.genesys2.server.persistence.domain.MenuItemRepository;
import org.genesys2.server.persistence.domain.MenuRepository;
import org.genesys2.server.service.CRMException;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.HtmlSanitizer;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -111,14 +112,6 @@ public class ContentServiceImpl implements ContentService {
return articleRepository.getAllByLang(lang, pageable);
}
@Override
@Transactional(readOnly = false)
// @PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CONTENTMANAGER')")
@CacheEvict(value = "contentcache", allEntries = true)
public void save(Iterable<Article> articles) {
articleRepository.save(articles);
}
@Override
@Cacheable(value = "contentcache", key = "'globalarticle-' + #slug + '-' + #locale.language + '-' + #useDefault")
public Article getGlobalArticle(String slug, Locale locale, boolean useDefault) {
......@@ -168,39 +161,43 @@ public class ContentServiceImpl implements ContentService {
public Article updateArticle(long id, String slug, String title, String body, String summary) {
final Article article = articleRepository.findOne(id);
updateArticleContent(article, slug, title, body, summary);
articleRepository.save(article);
return article;
}
private void updateArticleContent(final Article article, String slug, String title, String body, String summary) {
article.setPostDate(Calendar.getInstance());
article.setSlug(slug);
article.setTitle(htmlSanitizer.sanitize(title));
article.setSummary(htmlSanitizer.sanitize(summary));
// FIXME TODO Find a better way to distinguish between Velocity
// templates and general articles
if (StringUtils.contains(body, "VELOCITY")) {
if (article.isTemplate()) {
article.setBody(body);
} else {
article.setBody(htmlSanitizer.sanitize(body));
}
articleRepository.save(article);
return article;
}
@Override
@Transactional(readOnly = false)
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CONTENTMANAGER')")
@CacheEvict(value = "contentcache", allEntries = true)
public Article updateGlobalArticle(String slug, Locale locale, String title, String body, String summary) {
public Article updateGlobalArticle(String slug, Locale locale, String title, String body, String summary) throws CRMException {
Article article = getGlobalArticle(slug, locale, false);
if (article == null) {
article = new Article();
Article sourceArticle = getGlobalArticle(slug, locale, true);
if (sourceArticle == null) {
throw new CRMException("Article " + slug + " must exist in original language!");
}
article.setTemplate(sourceArticle.isTemplate());
article.setClassPk(sourceArticle.getClassPk());
}
article.setClassPk(ensureClassPK(Article.class));
article.setLang(locale.getLanguage());
article.setSlug(slug);
article.setTitle(htmlSanitizer.sanitize(title));
article.setSummary(htmlSanitizer.sanitize(summary));
article.setBody(htmlSanitizer.sanitize(body));
article.setPostDate(Calendar.getInstance());
updateArticleContent(article, slug, title, body, summary);
articleRepository.save(article);
return article;
......@@ -213,15 +210,13 @@ public class ContentServiceImpl implements ContentService {
* @param body
* @param locale
* @return
* @throws CRMException
*/
@Override
@Transactional(readOnly = false)
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CONTENTMANAGER') or hasPermission(#entity, 'ADMINISTRATION')")
@CacheEvict(value = "contentcache", allEntries = true)
public Article updateArticle(EntityId entity, String slug, String title, String body, String summary, Locale locale) {
// return
// articleRepository.findByClassPkAndTargetIdAndSlugAndLang(getClassPk(clazz),
// id, slug, locale.getLanguage());
public Article updateArticle(EntityId entity, String slug, String title, String body, String summary, Locale locale) throws CRMException {
return updateArticle(entity.getClass(), entity.getId(), slug, title, body, summary, locale);
}
......@@ -229,22 +224,19 @@ public class ContentServiceImpl implements ContentService {
@Transactional(readOnly = false)
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CONTENTMANAGER')")
@CacheEvict(value = "contentcache", allEntries = true)
public Article updateArticle(Class<?> clazz, Long id, String slug, String title, String body, String summary, Locale locale) {
Article article = getArticle(clazz, id, slug, locale, false);
if (article == null || !article.getLang().equals(locale.getLanguage())) {
public Article updateArticle(Class<?> clazz, Long id, String slug, String title, String body, String summary, Locale locale) throws CRMException {
Article article = getArticle(clazz, id, slug, locale, true);
if (article == null) {
article = new Article();
article.setClassPk(ensureClassPK(clazz));
article.setTargetId(id);
article.setLang(locale.getLanguage());
article.setPostDate(Calendar.getInstance());
article.setSlug(slug);
article.setTemplate(false);
}
article.setBody(htmlSanitizer.sanitize(body));
article.setSummary(htmlSanitizer.sanitize(summary));
article.setTitle(htmlSanitizer.sanitize(title));
articleRepository.save(article);
return article;
updateArticleContent(article, slug, title, body, summary);
return articleRepository.save(article);
}
@Override
......@@ -258,7 +250,7 @@ public class ContentServiceImpl implements ContentService {
}
@Override
@Transactional(readOnly = false)
@Transactional
public ClassPK ensureClassPK(Class<?> clazz) {
ClassPK classPk = classPkRepository.getByClassname(clazz.getName());
if (classPk == null) {
......@@ -473,4 +465,50 @@ public class ContentServiceImpl implements ContentService {
return menu;
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CONTENTMANAGER')")
@Transactional
public void sanitizeAll() {
LOG.info("Sanitizing articles");
Page<Article> articles;
int page = 0;
do {
articles = listArticles(new PageRequest(page++, 10));
for (final Article a : articles) {
a.setBody(htmlSanitizer.sanitize(a.getBody()));
}
articleRepository.save(articles.getContent());
} while (articles.hasNext());
}
@Override
@Transactional
public Article createGlobalArticle(String slug, Locale locale, String title, String body, String summary, boolean isTemplate) throws CRMException {
Article primaryArticle = getGlobalArticle(slug, locale, true);
if (primaryArticle == null && !getDefaultLocale().getLanguage().equals(locale.getLanguage())) {
throw new CRMException("Global articles must first be created in primary language=" + getDefaultLocale().getLanguage());
}