Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

## 7.0.0 (in development)

### Improvements

- **AtomPub can manage templates, categories and comments.** New collections
in each weblog's workspace, documented in the user guide:
- Templates (standard and mobile versions) for weblog administrators,
when the weblog uses a custom theme.
- Categories, including an RFC 5023 category document at
`/{handle}/categories.atomcat`. The entries collection lists its
categories inline, as before, and also references that document.
- Comment moderation: list, change status, delete.

- **New AtomPub Guide** (`docs/roller-atompub-guide.adoc`). It documents
every AtomPub URI, permission, element and status code, with examples
that the test suite checks against the code.

### Behaviour changes worth reading before upgrading

- **AtomPub no longer uses ROME Propono.** Roller now has its own AtomPub
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ WeblogTemplate getTemplateByLink(Weblog w, String p)
*/
void saveTemplateRendition(CustomTemplateRendition templateCode) throws WebloggerException;

/**
* Remove a template's mobile rendition. The template then renders its
* standard rendition for mobile visitors too. The standard rendition
* cannot be removed.
*/
void removeTemplateRendition(CustomTemplateRendition rendition) throws WebloggerException;

/**
* Get all custom templates for a weblog
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ public boolean isDuplicateWeblogCategoryName(WeblogCategory cat)
@Override
public boolean isWeblogCategoryInUse(WeblogCategory cat)
throws WebloggerException {
if (cat.getWeblog().getBloggerCategory().equals(cat)) {
if (cat.equals(cat.getWeblog().getBloggerCategory())) {
return true;
}
TypedQuery<WeblogEntry> q = strategy.getNamedQuery("WeblogEntry.getByCategory", WeblogEntry.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.apache.roller.weblogger.pojos.WeblogEntryTag;
import org.apache.roller.weblogger.pojos.WeblogEntryTagAggregate;
import org.apache.roller.weblogger.pojos.WeblogPermission;
import org.apache.roller.weblogger.pojos.TemplateRendition.RenditionType;
import org.apache.roller.weblogger.pojos.WeblogTemplate;


Expand Down Expand Up @@ -248,6 +249,19 @@ public void saveTemplate(WeblogTemplate template) throws WebloggerException {
roller.getWeblogManager().saveWeblog(template.getWeblog());
}

@Override
public void removeTemplateRendition(CustomTemplateRendition rendition) throws WebloggerException {
if (RenditionType.STANDARD.equals(rendition.getType())) {
throw new WebloggerException("The standard rendition of a template cannot be removed");
}
WeblogTemplate template = rendition.getWeblogTemplate();
template.getTemplateRenditions().remove(rendition);
this.strategy.remove(rendition);

// update weblog last modified date. date updated by saveWeblog()
roller.getWeblogManager().saveWeblog(template.getWeblog());
}

@Override
public void saveTemplateRendition(CustomTemplateRendition rendition) throws WebloggerException {
this.strategy.store(rendition);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* 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. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*/

package org.apache.roller.weblogger.business.themes;

import java.util.List;

/**
* Thrown by {@link WeblogTemplateEditor} when a template change breaks one or
* more template rules. Each violation carries a message key from
* ApplicationResources and its arguments.
*/
public class TemplateRuleException extends Exception {

private static final long serialVersionUID = 1L;

/** One broken rule. {@code conflict} is true when another template is in the way. */
public static final class Violation {
private final String messageKey;
private final List<String> args;
private final boolean conflict;

public Violation(String messageKey, List<String> args, boolean conflict) {
this.messageKey = messageKey;
this.args = List.copyOf(args);
this.conflict = conflict;
}

public String getMessageKey() {
return messageKey;
}

public List<String> getArgs() {
return args;
}

public boolean isConflict() {
return conflict;
}
}

private final List<Violation> violations;

public TemplateRuleException(List<Violation> violations) {
super(violations.isEmpty() ? "Template rule violation"
: violations.get(0).getMessageKey() + " " + violations.get(0).getArgs());
this.violations = List.copyOf(violations);
}

public List<Violation> getViolations() {
return violations;
}

/** True when any violation is a conflict with an existing template. */
public boolean isConflict() {
return violations.stream().anyMatch(Violation::isConflict);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* 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. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*/

package org.apache.roller.weblogger.business.themes;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.roller.util.RollerConstants;
import org.apache.roller.weblogger.WebloggerException;
import org.apache.roller.weblogger.business.WeblogManager;
import org.apache.roller.weblogger.business.Weblogger;
import org.apache.roller.weblogger.pojos.CustomTemplateRendition;
import org.apache.roller.weblogger.pojos.TemplateRendition.RenditionType;
import org.apache.roller.weblogger.pojos.TemplateRendition.TemplateLanguage;
import org.apache.roller.weblogger.pojos.ThemeTemplate;
import org.apache.roller.weblogger.pojos.ThemeTemplate.ComponentType;
import org.apache.roller.weblogger.pojos.Weblog;
import org.apache.roller.weblogger.pojos.WeblogTemplate;
import org.apache.roller.weblogger.pojos.WeblogTheme;
import org.apache.roller.weblogger.util.cache.CacheManager;

/**
* The rules for adding, changing and removing a weblog's templates. The
* template editor pages and the AtomPub template collection both use this
* class, so they enforce the same rules. Callers flush the session.
*/
public class WeblogTemplateEditor {

private final Weblogger roller;

public WeblogTemplateEditor(Weblogger roller) {
this.roller = roller;
}

/**
* The template actions that may still be added to the weblog. A custom
* theme may have one template per non-custom action; a shared theme may
* add only custom templates and, if it has none yet, a Weblog template.
*/
public Set<ComponentType> availableActions(Weblog weblog) throws WebloggerException {
List<WeblogTemplate> templates = roller.getWeblogManager().getTemplates(weblog);
Set<ComponentType> actions = EnumSet.of(ComponentType.CUSTOM);
if (WeblogTheme.CUSTOM.equals(weblog.getEditorTheme())) {
actions.addAll(EnumSet.of(ComponentType.PERMALINK, ComponentType.SEARCH,
ComponentType.WEBLOG, ComponentType.TAGSINDEX));
for (WeblogTemplate template : templates) {
if (!ComponentType.CUSTOM.equals(template.getAction())) {
actions.remove(template.getAction());
}
}
} else {
actions.add(ComponentType.WEBLOG);
for (WeblogTemplate template : templates) {
if (ComponentType.WEBLOG.equals(template.getAction())) {
actions.remove(ComponentType.WEBLOG);
break;
}
}
}
return actions;
}

/** Checks the name and action of a template that is about to be added. */
public List<TemplateRuleException.Violation> validateNew(Weblog weblog,
ComponentType action, String name) throws WebloggerException {
List<TemplateRuleException.Violation> violations = new ArrayList<>();
if (StringUtils.isEmpty(name)) {
violations.add(invalid("Template.error.nameNull"));
} else if (name.length() > RollerConstants.TEXTWIDTH_255) {
violations.add(invalid("Template.error.nameSize"));
}
if (action == null) {
violations.add(invalid("Template.error.actionNull"));
} else if (!availableActions(weblog).contains(action)) {
violations.add(new TemplateRuleException.Violation(
"Template.error.actionNotAvailable",
List.of(action.getReadableName()), true));
}
if (StringUtils.isNotEmpty(name)
&& roller.getWeblogManager().getTemplateByName(weblog, name) != null) {
violations.add(conflict(name));
}
return violations;
}

/**
* Adds a template with a standard rendition and, when
* {@code mobileSource} is not null, a mobile rendition.
*/
public WeblogTemplate create(Weblog weblog, ComponentType action, String name,
String standardSource, String mobileSource)
throws TemplateRuleException, WebloggerException {

List<TemplateRuleException.Violation> violations = validateNew(weblog, action, name);
if (!violations.isEmpty()) {
throw new TemplateRuleException(violations);
}

WeblogManager mgr = roller.getWeblogManager();
WeblogTemplate template = new WeblogTemplate();
template.setWeblog(weblog);
template.setAction(action);
template.setName(name);
template.setHidden(false);
template.setNavbar(false);
template.setLastModified(new Date());
if (ComponentType.CUSTOM.equals(action)) {
template.setLink(name);
}
// Always keep a Weblog main page
if (ComponentType.WEBLOG.equals(action)) {
template.setName(WeblogTemplate.DEFAULT_PAGE);
}
mgr.saveTemplate(template);

saveRendition(template, RenditionType.STANDARD, standardSource);
if (mobileSource != null) {
saveRendition(template, RenditionType.MOBILE, mobileSource);
}

// A new Weblog template becomes the weblog's default page
if (WeblogTemplate.DEFAULT_PAGE.equals(template.getName())) {
mgr.saveWeblog(weblog);
}
return template;
}

/**
* Checks a new name and link for an existing template: neither may be
* used by another template of the same weblog.
*/
public List<TemplateRuleException.Violation> validateUpdate(Weblog weblog,
WeblogTemplate template, String newName, String newLink) throws WebloggerException {
List<TemplateRuleException.Violation> violations = new ArrayList<>();
if (newName != null && newName.length() > RollerConstants.TEXTWIDTH_255) {
violations.add(invalid("Template.error.nameSize"));
}
if (newLink != null && newLink.length() > RollerConstants.TEXTWIDTH_255) {
violations.add(invalid("Template.error.linkSize"));
}
WeblogManager mgr = roller.getWeblogManager();
if (newName != null && !newName.equals(template.getName())
&& mgr.getTemplateByName(weblog, newName) != null) {
violations.add(conflict(newName));
}
if (StringUtils.isNotEmpty(newLink) && !newLink.equals(template.getLink())
&& mgr.getTemplateByLink(weblog, newLink) != null) {
violations.add(conflict(newLink));
}
return violations;
}

/**
* Sets the source of a rendition, creating the rendition when the
* template does not have it yet.
*/
public void saveRendition(WeblogTemplate template, RenditionType type, String source)
throws WebloggerException {
CustomTemplateRendition rendition = template.getTemplateRendition(type);
if (rendition == null) {
rendition = new CustomTemplateRendition(template, type);
rendition.setTemplateLanguage(TemplateLanguage.VELOCITY);
}
rendition.setTemplate(source != null ? source : "");
roller.getWeblogManager().saveTemplateRendition(rendition);
}

/** Removes the template's mobile rendition, if it has one. */
public void removeMobileRendition(WeblogTemplate template) throws WebloggerException {
CustomTemplateRendition mobile = template.getTemplateRendition(RenditionType.MOBILE);
if (mobile != null) {
roller.getWeblogManager().removeTemplateRendition(mobile);
}
}

/**
* Removes a template. A required template of a custom theme cannot be
* removed. Removing the Weblog template also removes the theme's custom
* stylesheet.
*/
public void remove(Weblog weblog, WeblogTemplate template)
throws TemplateRuleException, WebloggerException {
if (template.isRequired() && WeblogTheme.CUSTOM.equals(weblog.getEditorTheme())) {
throw new TemplateRuleException(Collections.singletonList(
new TemplateRuleException.Violation(
"editPages.remove.requiredTemplate", List.of(), true)));
}
WeblogManager mgr = roller.getWeblogManager();
if (WeblogTemplate.DEFAULT_PAGE.equals(template.getName())) {
ThemeTemplate stylesheet = weblog.getTheme().getStylesheet();
if (stylesheet != null) {
WeblogTemplate css = mgr.getTemplateByLink(weblog, stylesheet.getLink());
if (css != null) {
mgr.removeTemplate(css);
}
}
}
CacheManager.invalidate(template);
mgr.removeTemplate(template);
}

private static TemplateRuleException.Violation invalid(String key) {
return new TemplateRuleException.Violation(key, List.of(), false);
}

private static TemplateRuleException.Violation conflict(String value) {
return new TemplateRuleException.Violation(
"pagesForm.error.alreadyExists", List.of(value), true);
}
}
Loading