diff --git a/app/src/main/java/org/apache/roller/weblogger/business/MediaFileManager.java b/app/src/main/java/org/apache/roller/weblogger/business/MediaFileManager.java
index f8a361132..326b46feb 100644
--- a/app/src/main/java/org/apache/roller/weblogger/business/MediaFileManager.java
+++ b/app/src/main/java/org/apache/roller/weblogger/business/MediaFileManager.java
@@ -103,6 +103,13 @@ void removeMediaFile(Weblog weblog, MediaFile mediaFile)
List searchMediaFiles(Weblog weblog,
MediaFileFilter filter) throws WebloggerException;
+ /**
+ * Count the media files searchMediaFiles() would return for the same filter,
+ * ignoring start index, length and order. Used to size pagers.
+ */
+ long getMediaFileCount(Weblog weblog,
+ MediaFileFilter filter) throws WebloggerException;
+
/**
* Create default folder for media files in a weblog.
*/
diff --git a/app/src/main/java/org/apache/roller/weblogger/business/WeblogEntryManager.java b/app/src/main/java/org/apache/roller/weblogger/business/WeblogEntryManager.java
index 3ed56d16b..f841193e7 100644
--- a/app/src/main/java/org/apache/roller/weblogger/business/WeblogEntryManager.java
+++ b/app/src/main/java/org/apache/roller/weblogger/business/WeblogEntryManager.java
@@ -79,6 +79,16 @@ WeblogEntry getWeblogEntryByAnchor(Weblog website, String anchor)
List getWeblogEntries(WeblogEntrySearchCriteria wesc)
throws WebloggerException;
+ /**
+ * Count the rows getWeblogEntries() would return for the same criteria,
+ * ignoring offset, max results and sort order. Used to size pagers.
+ * @param wesc WeblogEntrySearchCriteria object listing desired search parameters
+ * @return number of matching rows
+ * @throws WebloggerException
+ */
+ long getWeblogEntryCount(WeblogEntrySearchCriteria wesc)
+ throws WebloggerException;
+
/**
* Get Weblog Entries grouped by day.
* @param wesc WeblogEntrySearchCriteria object listing desired search parameters
@@ -227,6 +237,14 @@ List getWeblogCategories(Weblog website)
*/
List getComments(CommentSearchCriteria csc) throws WebloggerException;
+ /**
+ * Count the comments getComments() would return for the same criteria,
+ * ignoring offset, max results and sort order. Used to size pagers.
+ * @param csc CommentSearchCriteria object with fields indicating search criteria
+ * @return number of matching comments
+ */
+ long getCommentCount(CommentSearchCriteria csc) throws WebloggerException;
+
/**
* Deletes comments that match paramters.
* @param website Website or null for all comments on site
diff --git a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAMediaFileManagerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAMediaFileManagerImpl.java
index e3c0ddcaa..a3eec1b38 100644
--- a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAMediaFileManagerImpl.java
+++ b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAMediaFileManagerImpl.java
@@ -583,11 +583,65 @@ public List searchMediaFiles(Weblog weblog,
MediaFileFilter filter) throws WebloggerException {
List params = new ArrayList<>();
- int size = 0;
- String queryString = "SELECT m FROM MediaFile m WHERE ";
- StringBuilder whereClause = new StringBuilder();
+ String queryString = "SELECT m " + mediaFilesFromWhere(weblog, filter, params);
StringBuilder orderBy = new StringBuilder();
+ if (filter.getOrder() != null) {
+ switch (filter.getOrder()) {
+ case NAME:
+ orderBy.append(" order by m.name");
+ break;
+ case DATE_UPLOADED:
+ orderBy.append(" order by m.dateUploaded");
+ break;
+ case TYPE:
+ orderBy.append(" order by m.contentType");
+ break;
+ default:
+ }
+ } else {
+ orderBy.append(" order by m.name");
+ }
+
+ TypedQuery query = strategy.getDynamicQuery(queryString
+ + orderBy.toString(), MediaFile.class);
+ for (int i = 0; i < params.size(); i++) {
+ query.setParameter(i + 1, params.get(i));
+ }
+
+ if (filter.getStartIndex() >= 0) {
+ query.setFirstResult(filter.getStartIndex());
+ query.setMaxResults(filter.getLength());
+ }
+ return query.getResultList();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getMediaFileCount(Weblog weblog,
+ MediaFileFilter filter) throws WebloggerException {
+
+ List params = new ArrayList<>();
+ TypedQuery query = strategy.getDynamicQuery(
+ "SELECT COUNT(m) " + mediaFilesFromWhere(weblog, filter, params), Long.class);
+ for (int i = 0; i < params.size(); i++) {
+ query.setParameter(i + 1, params.get(i));
+ }
+ return query.getSingleResult();
+ }
+
+ /**
+ * Build the FROM and WHERE clauses shared by searchMediaFiles() and
+ * getMediaFileCount(), adding positional parameters to params.
+ */
+ private String mediaFilesFromWhere(Weblog weblog, MediaFileFilter filter,
+ List params) {
+
+ int size = params.size();
+ StringBuilder whereClause = new StringBuilder();
+
params.add(size++, weblog);
whereClause.append("m.directory.weblog = ?").append(size);
@@ -659,34 +713,7 @@ public List searchMediaFiles(Weblog weblog,
}
}
- if (filter.getOrder() != null) {
- switch (filter.getOrder()) {
- case NAME:
- orderBy.append(" order by m.name");
- break;
- case DATE_UPLOADED:
- orderBy.append(" order by m.dateUploaded");
- break;
- case TYPE:
- orderBy.append(" order by m.contentType");
- break;
- default:
- }
- } else {
- orderBy.append(" order by m.name");
- }
-
- TypedQuery query = strategy.getDynamicQuery(queryString
- + whereClause.toString() + orderBy.toString(), MediaFile.class);
- for (int i = 0; i < params.size(); i++) {
- query.setParameter(i + 1, params.get(i));
- }
-
- if (filter.getStartIndex() >= 0) {
- query.setFirstResult(filter.getStartIndex());
- query.setMaxResults(filter.getLength());
- }
- return query.getResultList();
+ return "FROM MediaFile m WHERE " + whereClause;
}
/**
diff --git a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java
index 477179a3f..93a478a9e 100644
--- a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java
+++ b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java
@@ -381,19 +381,67 @@ public List getWeblogCategories(Weblog website)
@Override
public List getWeblogEntries(WeblogEntrySearchCriteria wesc) throws WebloggerException {
+ List params = new ArrayList<>();
+ StringBuilder queryString = new StringBuilder("SELECT e ");
+ queryString.append(weblogEntriesFromWhere(wesc, params));
+
+ if (wesc.getSortBy() != null && wesc.getSortBy().equals(WeblogEntrySearchCriteria.SortBy.UPDATE_TIME)) {
+ queryString.append(" ORDER BY e.updateTime ");
+ } else {
+ queryString.append(" ORDER BY e.pubTime ");
+ }
+
+ if (wesc.getSortOrder() != null && wesc.getSortOrder().equals(WeblogEntrySearchCriteria.SortOrder.ASCENDING)) {
+ queryString.append("ASC ");
+ } else {
+ queryString.append("DESC ");
+ }
+
+
+ TypedQuery query = strategy.getDynamicQuery(queryString.toString(), WeblogEntry.class);
+ for (int i=0; i params = new ArrayList<>();
+ // COUNT(e), not COUNT(DISTINCT e): the tag join can repeat an entry, and the
+ // count must match the rows getWeblogEntries() pages through.
+ TypedQuery query = strategy.getDynamicQuery(
+ "SELECT COUNT(e) " + weblogEntriesFromWhere(wesc, params), Long.class);
+ for (int i=0; i params)
+ throws WebloggerException {
+
WeblogCategory cat = null;
if (StringUtils.isNotEmpty(wesc.getCatName()) && wesc.getWeblog() != null) {
cat = getWeblogCategoryByName(wesc.getWeblog(), wesc.getCatName());
}
- List params = new ArrayList<>();
- int size = 0;
+ int size = params.size();
StringBuilder queryString = new StringBuilder();
if (wesc.getTags() == null || wesc.getTags().isEmpty()) {
- queryString.append("SELECT e FROM WeblogEntry e WHERE ");
+ queryString.append("FROM WeblogEntry e WHERE ");
} else {
- queryString.append("SELECT e FROM WeblogEntry e JOIN e.tags t WHERE ");
+ queryString.append("FROM WeblogEntry e JOIN e.tags t WHERE ");
queryString.append("(");
for (int i = 0; i < wesc.getTags().size(); i++) {
if (i != 0) {
@@ -453,26 +501,7 @@ public List getWeblogEntries(WeblogEntrySearchCriteria wesc) throws
queryString.append(") ");
}
- if (wesc.getSortBy() != null && wesc.getSortBy().equals(WeblogEntrySearchCriteria.SortBy.UPDATE_TIME)) {
- queryString.append(" ORDER BY e.updateTime ");
- } else {
- queryString.append(" ORDER BY e.pubTime ");
- }
-
- if (wesc.getSortOrder() != null && wesc.getSortOrder().equals(WeblogEntrySearchCriteria.SortOrder.ASCENDING)) {
- queryString.append("ASC ");
- } else {
- queryString.append("DESC ");
- }
-
-
- TypedQuery query = strategy.getDynamicQuery(queryString.toString(), WeblogEntry.class);
- for (int i=0; i getComments(CommentSearchCriteria csc) throws WebloggerException {
List params = new ArrayList<>();
- int size = 0;
- StringBuilder queryString = new StringBuilder();
- queryString.append("SELECT c FROM WeblogEntryComment c ");
+ StringBuilder queryString = new StringBuilder("SELECT c ");
+ queryString.append(commentsFromWhere(csc, params));
+ if (csc.isReverseChrono()) {
+ queryString.append(" ORDER BY c.postTime DESC");
+ } else {
+ queryString.append(" ORDER BY c.postTime ASC");
+ }
+ TypedQuery query = strategy.getDynamicQuery(queryString.toString(), WeblogEntryComment.class);
+ setFirstMax( query, csc.getOffset(), csc.getMaxResults());
+ for (int i=0; i params = new ArrayList<>();
+ TypedQuery query = strategy.getDynamicQuery(
+ "SELECT COUNT(c) " + commentsFromWhere(csc, params), Long.class);
+ for (int i=0; i params) {
+
+ int size = params.size();
+ StringBuilder queryString = new StringBuilder("FROM WeblogEntryComment c ");
+
StringBuilder whereClause = new StringBuilder();
if (csc.getEntry() != null) {
params.add(size++, csc.getEntry());
@@ -669,19 +734,8 @@ public List getComments(CommentSearchCriteria csc) throws We
if(whereClause.length() != 0) {
queryString.append(" WHERE ").append(whereClause);
}
- if (csc.isReverseChrono()) {
- queryString.append(" ORDER BY c.postTime DESC");
- } else {
- queryString.append(" ORDER BY c.postTime ASC");
- }
-
- TypedQuery query = strategy.getDynamicQuery(queryString.toString(), WeblogEntryComment.class);
- setFirstMax( query, csc.getOffset(), csc.getMaxResults());
- for (int i=0; i comments = Collections.emptyList();
- boolean hasMore = false;
+ long totalCount = 0;
try {
WeblogEntryManager wmgr = WebloggerFactory.getWeblogger().getWeblogEntryManager();
@@ -109,19 +109,13 @@ public void loadComments() {
csc.setEndDate(getBean().getEndDate());
csc.setStatus(getBean().getStatus());
csc.setReverseChrono(true);
+ totalCount = wmgr.getCommentCount(csc);
csc.setOffset(getBean().getPage() * COUNT);
- csc.setMaxResults(COUNT+1);
+ csc.setMaxResults(COUNT);
- List rawComments = wmgr.getComments(csc);
- comments = new ArrayList<>();
- comments.addAll(rawComments);
+ comments = new ArrayList<>(wmgr.getComments(csc));
if(!comments.isEmpty()) {
- if(comments.size() > COUNT) {
- comments.remove(comments.size()-1);
- hasMore = true;
- }
-
setFirstComment(comments.get(0));
setLastComment(comments.get(comments.size()-1));
}
@@ -132,7 +126,7 @@ public void loadComments() {
// build comments pager
String baseUrl = buildBaseUrl();
- setPager(new CommentsPager(baseUrl, getBean().getPage(), comments, hasMore));
+ setPager(new CommentsPager(baseUrl, getBean().getPage(), comments, totalCount, COUNT));
}
@@ -184,25 +178,9 @@ public String query() {
// load bean data using comments list
getBean().loadCheckboxes(getPager().getItems());
- try {
- WeblogEntryManager wmgr = WebloggerFactory.getWeblogger().getWeblogEntryManager();
-
- CommentSearchCriteria csc = new CommentSearchCriteria();
- csc.setSearchText(getBean().getSearchString());
- csc.setStartDate(getBean().getStartDate());
- csc.setEndDate(getBean().getEndDate());
- csc.setStatus(getBean().getStatus());
- csc.setReverseChrono(true);
-
- List allMatchingComments = wmgr.getComments(csc);
-
- if(allMatchingComments.size() > COUNT) {
- setBulkDeleteCount(allMatchingComments.size());
- }
-
- } catch (WebloggerException ex) {
- log.error("Error looking up comments", ex);
- addError("commentManagement.lookupError");
+ // offer bulk delete when the matches span more than one page
+ if (getPager().getTotalCount() > COUNT) {
+ setBulkDeleteCount((int) getPager().getTotalCount());
}
return LIST;
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Comments.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Comments.java
index 889fc9cae..a19f7f980 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Comments.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Comments.java
@@ -99,30 +99,24 @@ public List requiredWeblogPermissionActions() {
public void loadComments() {
List comments = Collections.emptyList();
- boolean hasMore = false;
+ long totalCount = 0;
try {
WeblogEntryManager wmgr = WebloggerFactory.getWeblogger()
.getWeblogEntryManager();
if (!resolveQueryEntry(wmgr)) {
setPager(new CommentsPager(buildBaseUrl(), getBean().getPage(),
- comments, false));
+ comments, 0, COUNT));
return;
}
CommentSearchCriteria csc = getCommentSearchCriteria();
+ totalCount = wmgr.getCommentCount(csc);
csc.setOffset(getBean().getPage() * COUNT);
- csc.setMaxResults(COUNT + 1);
+ csc.setMaxResults(COUNT);
- List rawComments = wmgr.getComments(csc);
- comments = new ArrayList<>();
- comments.addAll(rawComments);
+ comments = new ArrayList<>(wmgr.getComments(csc));
if (!comments.isEmpty()) {
- if (comments.size() > COUNT) {
- comments.remove(comments.size() - 1);
- hasMore = true;
- }
-
setFirstComment(comments.get(0));
setLastComment(comments.get(comments
.size() - 1));
@@ -135,7 +129,7 @@ public void loadComments() {
// build comments pager
String baseUrl = buildBaseUrl();
setPager(new CommentsPager(baseUrl, getBean().getPage(), comments,
- hasMore));
+ totalCount, COUNT));
}
// use the action data to build a url representing this action, including
@@ -191,19 +185,9 @@ public String query() {
return LIST;
}
- try {
- WeblogEntryManager wmgr = WebloggerFactory.getWeblogger().getWeblogEntryManager();
-
- CommentSearchCriteria csc = getCommentSearchCriteria();
-
- List allMatchingComments = wmgr.getComments(csc);
- if (allMatchingComments.size() > COUNT) {
- setBulkDeleteCount(allMatchingComments.size());
- }
-
- } catch (WebloggerException ex) {
- log.error("Error looking up comments", ex);
- addError("Error looking up comments");
+ // offer bulk delete when the matches span more than one page
+ if (getPager().getTotalCount() > COUNT) {
+ setBulkDeleteCount((int) getPager().getTotalCount());
}
return LIST;
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Entries.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Entries.java
index e34aa4778..942a12648 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Entries.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Entries.java
@@ -85,7 +85,7 @@ public String execute() {
}
List entries = null;
- boolean hasMore = false;
+ long totalCount = 0;
try {
String status = getBean().getStatus();
@@ -99,18 +99,12 @@ public String execute() {
wesc.setStatus("ALL".equals(status) ? null : WeblogEntry.PubStatus.valueOf(status));
wesc.setText(getBean().getText());
wesc.setSortBy(getBean().getSortBy());
+ totalCount = wmgr.getWeblogEntryCount(wesc);
wesc.setOffset(getBean().getPage() * COUNT);
- wesc.setMaxResults(COUNT + 1);
- List rawEntries = wmgr.getWeblogEntries(wesc);
- entries = new ArrayList<>();
- entries.addAll(rawEntries);
+ wesc.setMaxResults(COUNT);
+ entries = new ArrayList<>(wmgr.getWeblogEntries(wesc));
if (!entries.isEmpty()) {
- log.debug("query found "+rawEntries.size()+" results");
-
- if(rawEntries.size() > COUNT) {
- entries.remove(entries.size()-1);
- hasMore = true;
- }
+ log.debug("query found "+entries.size()+" of "+totalCount+" results");
setFirstEntry(entries.get(0));
setLastEntry(entries.get(entries.size()-1));
@@ -122,7 +116,7 @@ public String execute() {
// build entries pager
String baseUrl = buildBaseUrl();
- setPager(new EntriesPager(baseUrl, getBean().getPage(), entries, hasMore));
+ setPager(new EntriesPager(baseUrl, getBean().getPage(), entries, totalCount, COUNT));
return LIST;
}
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileSearchBean.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileSearchBean.java
index 3b96c875c..d015ae1ad 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileSearchBean.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileSearchBean.java
@@ -1,211 +1,209 @@
-/*
- * 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.ui.struts2.editor;
-
-import java.util.Arrays;
-
-import java.util.ResourceBundle;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.roller.util.RollerConstants;
-import org.apache.roller.weblogger.pojos.MediaFileFilter;
-import org.apache.roller.weblogger.pojos.MediaFileType;
-import org.apache.roller.weblogger.pojos.MediaFileFilter.MediaFileOrder;
-import org.apache.roller.weblogger.pojos.MediaFileFilter.SizeFilterType;
-
-/**
- * Bean for holding media file search criteria.
- */
-public class MediaFileSearchBean {
- private transient ResourceBundle bundle =
- ResourceBundle.getBundle("ApplicationResources");
-
- public static final int PAGE_SIZE = 10;
-
- // Media file name as search criteria
- private String name;
-
- // Media file type as search criteria
- private String type;
-
- // Type of size filter as search criteria
- private String sizeFilterType;
-
- // Size of file as search criteria
- private long size;
-
- // Size unit
- private String sizeUnit;
-
- // Tags as search criteria
- private String tags;
-
- // Page number of results
- private int pageNum = 0;
-
- // Sort option for search results
- private int sortOption;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getType() {
- return type;
- }
-
- public String getTypeLabel() {
- return this.bundle.getString(type);
- }
-
- public void setType(String type) {
- this.type = type;
- }
-
- public String getSizeFilterType() {
- return sizeFilterType;
- }
-
- public void setSizeFilterType(String sizeFilterType) {
- this.sizeFilterType = sizeFilterType;
- }
-
- public String getSizeFilterTypeLabel() {
- return this.bundle.getString(sizeFilterType);
- }
-
- public long getSize() {
- return size;
- }
-
- public void setSize(long size) {
- this.size = size;
- }
-
- public String getSizeUnit() {
- return sizeUnit;
- }
-
- public String getSizeUnitLabel() {
- return this.bundle.getString(sizeUnit);
- }
-
- public void setSizeUnit(String sizeUnit) {
- this.sizeUnit = sizeUnit;
- }
-
- public String getTags() {
- return tags;
- }
-
- public void setTags(String tags) {
- this.tags = tags;
- }
-
- public int getPageNum() {
- return pageNum;
- }
-
- public void setPageNum(int pageNum) {
- this.pageNum = pageNum;
- }
-
- public int getSortOption() {
- return sortOption;
- }
-
- public void setSortOption(int sortOption) {
- this.sortOption = sortOption;
- }
-
- /**
- * Copies data from this bean to media file filter object.
- *
- */
- public void copyTo(MediaFileFilter dataHolder) {
- dataHolder.setName(this.name);
-
- if (!StringUtils.isEmpty(this.type)) {
- MediaFileType filterType = null;
- if ("mediaFileView.audio".equals(this.type)) {
- filterType = MediaFileType.AUDIO;
- } else if ("mediaFileView.video".equals(this.type)) {
- filterType = MediaFileType.VIDEO;
- } else if ("mediaFileView.image".equals(this.type)) {
- filterType = MediaFileType.IMAGE;
- } else if ("mediaFileView.others".equals(this.type)) {
- filterType = MediaFileType.OTHERS;
- }
-
- dataHolder.setType(filterType);
- }
-
- if (this.size > 0) {
- SizeFilterType sftype = SizeFilterType.EQ;
- if ("mediaFileView.gt".equals(this.sizeFilterType)) {
- sftype = SizeFilterType.GT;
- } else if ("mediaFileView.ge".equals(this.sizeFilterType)) {
- sftype = SizeFilterType.GTE;
- } else if ("mediaFileView.eq".equals(this.sizeFilterType)) {
- sftype = SizeFilterType.EQ;
- } else if ("mediaFileView.le".equals(this.sizeFilterType)) {
- sftype = SizeFilterType.LTE;
- } else if ("mediaFileView.lt".equals(this.sizeFilterType)) {
- sftype = SizeFilterType.LT;
- }
- dataHolder.setSizeFilterType(sftype);
-
- long filterSize = this.size ;
- if ("mediaFileView.kb".equals(this.sizeUnit)) {
- filterSize = this.size * RollerConstants.ONE_KB_IN_BYTES;
- } else if ("mediaFileView.mb".equals(this.sizeUnit)) {
- filterSize = this.size * RollerConstants.ONE_MB_IN_BYTES;
- }
- dataHolder.setSize(filterSize);
- }
-
- if (!StringUtils.isEmpty(this.tags)) {
- dataHolder.setTags(Arrays.asList(this.tags.split(" ")));
- }
-
- dataHolder.setStartIndex(pageNum * PAGE_SIZE);
-
- // set length to fetch to one more than what is required.
- // this would help us determine whether there are more pages
- dataHolder.setLength(PAGE_SIZE + 1);
-
- MediaFileOrder order;
- switch (this.sortOption) {
- case 0:
- order = MediaFileOrder.NAME;
- break;
- case 1:
- order = MediaFileOrder.DATE_UPLOADED;
- break;
- case 2:
- order = MediaFileOrder.TYPE;
- break;
- default:
- order = null;
- }
- dataHolder.setOrder(order);
- }
-}
+/*
+ * 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.ui.struts2.editor;
+
+import java.util.Arrays;
+
+import java.util.ResourceBundle;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.roller.util.RollerConstants;
+import org.apache.roller.weblogger.pojos.MediaFileFilter;
+import org.apache.roller.weblogger.pojos.MediaFileType;
+import org.apache.roller.weblogger.pojos.MediaFileFilter.MediaFileOrder;
+import org.apache.roller.weblogger.pojos.MediaFileFilter.SizeFilterType;
+
+/**
+ * Bean for holding media file search criteria.
+ */
+public class MediaFileSearchBean {
+ private transient ResourceBundle bundle =
+ ResourceBundle.getBundle("ApplicationResources");
+
+ public static final int PAGE_SIZE = 10;
+
+ // Media file name as search criteria
+ private String name;
+
+ // Media file type as search criteria
+ private String type;
+
+ // Type of size filter as search criteria
+ private String sizeFilterType;
+
+ // Size of file as search criteria
+ private long size;
+
+ // Size unit
+ private String sizeUnit;
+
+ // Tags as search criteria
+ private String tags;
+
+ // Page number of results
+ private int pageNum = 0;
+
+ // Sort option for search results
+ private int sortOption;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getTypeLabel() {
+ return this.bundle.getString(type);
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getSizeFilterType() {
+ return sizeFilterType;
+ }
+
+ public void setSizeFilterType(String sizeFilterType) {
+ this.sizeFilterType = sizeFilterType;
+ }
+
+ public String getSizeFilterTypeLabel() {
+ return this.bundle.getString(sizeFilterType);
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public void setSize(long size) {
+ this.size = size;
+ }
+
+ public String getSizeUnit() {
+ return sizeUnit;
+ }
+
+ public String getSizeUnitLabel() {
+ return this.bundle.getString(sizeUnit);
+ }
+
+ public void setSizeUnit(String sizeUnit) {
+ this.sizeUnit = sizeUnit;
+ }
+
+ public String getTags() {
+ return tags;
+ }
+
+ public void setTags(String tags) {
+ this.tags = tags;
+ }
+
+ public int getPageNum() {
+ return pageNum;
+ }
+
+ public void setPageNum(int pageNum) {
+ this.pageNum = pageNum;
+ }
+
+ public int getSortOption() {
+ return sortOption;
+ }
+
+ public void setSortOption(int sortOption) {
+ this.sortOption = sortOption;
+ }
+
+ /**
+ * Copies data from this bean to media file filter object.
+ *
+ */
+ public void copyTo(MediaFileFilter dataHolder) {
+ dataHolder.setName(this.name);
+
+ if (!StringUtils.isEmpty(this.type)) {
+ MediaFileType filterType = null;
+ if ("mediaFileView.audio".equals(this.type)) {
+ filterType = MediaFileType.AUDIO;
+ } else if ("mediaFileView.video".equals(this.type)) {
+ filterType = MediaFileType.VIDEO;
+ } else if ("mediaFileView.image".equals(this.type)) {
+ filterType = MediaFileType.IMAGE;
+ } else if ("mediaFileView.others".equals(this.type)) {
+ filterType = MediaFileType.OTHERS;
+ }
+
+ dataHolder.setType(filterType);
+ }
+
+ if (this.size > 0) {
+ SizeFilterType sftype = SizeFilterType.EQ;
+ if ("mediaFileView.gt".equals(this.sizeFilterType)) {
+ sftype = SizeFilterType.GT;
+ } else if ("mediaFileView.ge".equals(this.sizeFilterType)) {
+ sftype = SizeFilterType.GTE;
+ } else if ("mediaFileView.eq".equals(this.sizeFilterType)) {
+ sftype = SizeFilterType.EQ;
+ } else if ("mediaFileView.le".equals(this.sizeFilterType)) {
+ sftype = SizeFilterType.LTE;
+ } else if ("mediaFileView.lt".equals(this.sizeFilterType)) {
+ sftype = SizeFilterType.LT;
+ }
+ dataHolder.setSizeFilterType(sftype);
+
+ long filterSize = this.size ;
+ if ("mediaFileView.kb".equals(this.sizeUnit)) {
+ filterSize = this.size * RollerConstants.ONE_KB_IN_BYTES;
+ } else if ("mediaFileView.mb".equals(this.sizeUnit)) {
+ filterSize = this.size * RollerConstants.ONE_MB_IN_BYTES;
+ }
+ dataHolder.setSize(filterSize);
+ }
+
+ if (!StringUtils.isEmpty(this.tags)) {
+ dataHolder.setTags(Arrays.asList(this.tags.split(" ")));
+ }
+
+ dataHolder.setStartIndex(pageNum * PAGE_SIZE);
+
+ dataHolder.setLength(PAGE_SIZE);
+
+ MediaFileOrder order;
+ switch (this.sortOption) {
+ case 0:
+ order = MediaFileOrder.NAME;
+ break;
+ case 1:
+ order = MediaFileOrder.DATE_UPLOADED;
+ break;
+ case 2:
+ order = MediaFileOrder.TYPE;
+ break;
+ default:
+ order = null;
+ }
+ dataHolder.setOrder(order);
+ }
+}
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java
index e52a7b243..fdf8ba498 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java
@@ -1,491 +1,487 @@
-/*
- * 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.ui.struts2.editor;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.roller.weblogger.WebloggerException;
-import org.apache.roller.weblogger.business.FileIOException;
-import org.apache.roller.weblogger.business.MediaFileManager;
-import org.apache.roller.weblogger.business.WebloggerFactory;
-import org.apache.roller.weblogger.pojos.MediaFile;
-import org.apache.roller.weblogger.pojos.MediaFileComparator;
-import org.apache.roller.weblogger.pojos.MediaFileComparator.MediaFileComparatorType;
-import org.apache.roller.weblogger.pojos.MediaFileDirectory;
-import org.apache.roller.weblogger.pojos.MediaFileFilter;
-import org.apache.roller.weblogger.ui.struts2.pagers.MediaFilePager;
-import org.apache.roller.weblogger.ui.struts2.util.KeyValueObject;
-import org.apache.roller.weblogger.util.cache.CacheManager;
-import org.apache.struts2.convention.annotation.AllowedMethods;
-import org.apache.struts2.interceptor.parameter.StrutsParameter;
-import org.apache.struts2.interceptor.validation.SkipValidation;
-
-/**
- * View media files.
- */
-@SuppressWarnings("serial")
-// TODO: make this work @AllowedMethods({"execute","view","search","delete","deleteSelected","deleteFolder","includeInGallery","moveSelected"})
-public class MediaFileView extends MediaFileBase {
-
- private static Log log = LogFactory.getLog(MediaFileView.class);
-
- private String directoryId;
- private String directoryName;
- private String sortBy;
- private String newDirectoryName;
-
- private List childFiles;
- private MediaFileDirectory currentDirectory;
-
- // Search criteria - drop-down for file type
- private static List FILE_TYPES = null;
-
- // Search criteria - drop-down for size filter
- private static List SIZE_FILTER_TYPES = null;
-
- // Search criteria - drop-down for size unit
- private static List SIZE_UNITS = null;
-
- // Sort options for search results.
- private static List SORT_OPTIONS = null;
-
- // Pager for displaying search results.
- private MediaFilePager pager;
-
- // Path of new directory to be created.
- private String newDirectoryPath;
-
- // a new directory the user wishes to view
- private String viewDirectoryId = null;
-
- private MediaFileSearchBean bean = new MediaFileSearchBean();
-
- public MediaFileView() {
- this.actionName = "mediaFileView";
- this.desiredMenu = "editor";
- this.pageTitle = "mediaFileView.title";
- }
-
- /**
- * Prepares view action
- */
- @Override
- public void myPrepare() {
-
- if (SIZE_FILTER_TYPES == null) {
-
- SIZE_FILTER_TYPES = Arrays.asList(new KeyValueObject(
- "mediaFileView.gt", getText("mediaFileView.gt")),
- new KeyValueObject("mediaFileView.ge",
- getText("mediaFileView.ge")), new KeyValueObject(
- "mediaFileView.eq", getText("mediaFileView.eq")),
- new KeyValueObject("mediaFileView.le",
- getText("mediaFileView.le")), new KeyValueObject(
- "mediaFileView.lt", getText("mediaFileView.lt")));
-
- FILE_TYPES = Arrays.asList(new KeyValueObject("mediaFileView.any",
- getText("mediaFileView.any")), new KeyValueObject(
- "mediaFileView.others", getText("mediaFileView.others")),
- new KeyValueObject("mediaFileView.image",
- getText("mediaFileView.image")),
- new KeyValueObject("mediaFileView.video",
- getText("mediaFileView.video")),
- new KeyValueObject("mediaFileView.audio",
- getText("mediaFileView.audio")));
-
- SIZE_UNITS = Arrays.asList(new KeyValueObject(
- "mediaFileView.bytes", getText("mediaFileView.bytes")),
- new KeyValueObject("mediaFileView.kb",
- getText("mediaFileView.kb")), new KeyValueObject(
- "mediaFileView.mb", getText("mediaFileView.mb")));
-
- SORT_OPTIONS = Arrays.asList(new KeyValueObject("name",
- getText("generic.name")), new KeyValueObject(
- "date_uploaded", getText("mediaFileView.date")),
- new KeyValueObject("type", getText("mediaFileView.type")));
- }
-
- refreshAllDirectories();
- }
-
- /**
- * Create a new directory by name. All folders placed at the root.
- */
- public String createNewDirectory() {
- boolean dirCreated = false;
- if (StringUtils.isEmpty(this.newDirectoryName)) {
- addError("mediaFile.error.view.dirNameEmpty");
- } else if (this.newDirectoryName.contains("/")) {
- addError("mediaFile.error.view.dirNameInvalid");
- } else {
- try {
-
- log.debug("Creating new directory - " + this.newDirectoryName);
- MediaFileManager manager = WebloggerFactory.getWeblogger()
- .getMediaFileManager();
-
- if (!getActionWeblog().hasMediaFileDirectory(
- this.newDirectoryName)) {
- // Create
- MediaFileDirectory dir = manager.createMediaFileDirectory(
- getActionWeblog(), this.newDirectoryName);
- // flush changes
- WebloggerFactory.getWeblogger().flush();
- addMessage("mediaFile.directoryCreate.success",
- this.newDirectoryName);
-
- // Switch to folder
- setDirectoryId(dir.getId());
-
- dirCreated = true;
- } else {
- // already exists
- addMessage("mediaFile.directoryCreate.error.exists",
- this.newDirectoryName);
- }
-
- } catch (WebloggerException e) {
- log.error("Error creating new directory", e);
- addError("Error creating new directory");
- }
- }
-
- if (dirCreated) {
- // Refresh list of directories so the newly created directory is
- // included.
- refreshAllDirectories();
- }
- return execute();
-
- }
-
- /**
- * Returns directory content in JSON format.
- */
- public String fetchDirectoryContentLight() {
- execute();
- return "success.json";
- }
-
- /**
- * Fetches and displays list of media file for the given directory. The
- * directory could be chosen by ID or path.
- *
- * @return String The result of the action.
- */
- @SkipValidation
- @Override
- public String execute() {
- MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager();
- try {
- MediaFileDirectory directory;
- if (StringUtils.isNotEmpty(this.directoryId)) {
- directory = manager.getMediaFileDirectory(getActionWeblog(), this.directoryId);
-
- } else if (StringUtils.isNotEmpty(this.directoryName)) {
- directory = manager.getMediaFileDirectoryByName(
- getActionWeblog(), this.directoryName);
-
- } else {
- directory = manager.getDefaultMediaFileDirectory(getActionWeblog());
- }
- this.directoryId = directory.getId();
- this.directoryName = directory.getName();
-
- this.childFiles = new ArrayList<>();
- this.childFiles.addAll(directory.getMediaFiles());
-
- if ("type".equals(sortBy)) {
- this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.TYPE));
-
- } else if ("date_uploaded".equals(sortBy)) {
- this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.DATE_UPLOADED));
-
- } else {
- // default to sort by name
- sortBy = "name";
- this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.NAME));
- }
-
- this.currentDirectory = directory;
-
- // set current directory if valid
- if (directory != null) {
- setViewDirectoryId(directory.getId());
- }
-
- return SUCCESS;
-
- } catch (FileIOException ex) {
- log.error("Error viewing media file directory ", ex);
- addError("MediaFile.error.view");
- } catch (Exception e) {
- log.error("Error viewing media file directory ", e);
- addError("MediaFile.error.view");
- }
- return SUCCESS;
- }
-
- /**
- * View the contents of another Media folder.
- */
- public String view() {
- try {
- MediaFileManager manager = WebloggerFactory.getWeblogger()
- .getMediaFileManager();
- if (!StringUtils.isEmpty(viewDirectoryId)) {
- setDirectoryId(viewDirectoryId);
- setCurrentDirectory(manager.getMediaFileDirectory(getActionWeblog(), viewDirectoryId));
- }
- } catch (WebloggerException ex) {
- log.error("Error looking up directory", ex);
- }
- return execute();
- }
-
- /**
- * Save a media file.
- *
- * @return String The result of the action.
- */
- public String search() {
-
- boolean valSuccess = myValidate();
-
- if (valSuccess) {
- MediaFileFilter filter = new MediaFileFilter();
- bean.copyTo(filter);
- MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager();
- try {
- List rawResults = manager.searchMediaFiles(getActionWeblog(), filter);
- boolean hasMore = false;
- List results = new ArrayList<>(rawResults);
- if (results.size() > MediaFileSearchBean.PAGE_SIZE) {
- results.remove(results.size() - 1);
- hasMore = true;
- }
-
- this.pager = new MediaFilePager(bean.getPageNum(), results,
- hasMore);
-
- } catch (Exception e) {
- log.error("Error applying search criteria", e);
- addError("Error applying search criteria - check Roller logs");
- }
-
- }
-
- return SUCCESS;
- }
-
- /**
- * Delete selected media files.
- *
- */
- public String deleteSelected() {
- doDeleteSelected();
- return execute();
- }
-
- /**
- * Delete selected media file
- *
- */
- public String delete() {
- doDeleteMediaFile();
- return execute();
- }
-
- /**
- * Delete folder
- */
- public String deleteFolder() {
-
- try {
- MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager();
- if (directoryId != null) {
- log.debug("Deleting media file folder - " + directoryId + " ("
- + directoryName + ")");
- MediaFileDirectory mediaFileDir = manager
- .getMediaFileDirectory(getActionWeblog(), directoryId);
- manager.removeMediaFileDirectory(mediaFileDir);
- refreshAllDirectories();
- WebloggerFactory.getWeblogger().getWeblogManager()
- .saveWeblog(this.getActionWeblog());
-
- // flush changes
- WebloggerFactory.getWeblogger().flush();
- WebloggerFactory.getWeblogger().release();
- addMessage("mediaFile.deleteFolder.success");
-
- // notify caches
- CacheManager.invalidate(getActionWeblog());
-
- // re-route to default folder
- mediaFileDir = manager
- .getDefaultMediaFileDirectory(getActionWeblog());
- setDirectoryId(mediaFileDir.getId());
- setDirectoryName(mediaFileDir.getName());
- } else {
- log.error("(System error) No directory ID provided for media file folder delete.");
- }
- } catch (WebloggerException ex) {
- log.error("Error deleting folder", ex);
- }
- return execute();
- }
-
- /**
- * Include selected media file in gallery
- *
- */
- public String includeInGallery() {
- doIncludeMediaFileInGallery();
- return execute();
- }
-
- /**
- * Move selected media files to a different directory
- *
- */
- public String moveSelected() {
- doMoveSelected();
- return execute();
- }
-
- public String getDirectoryId() {
- return directoryId;
- }
-
- @StrutsParameter
- public void setDirectoryId(String id) {
- this.directoryId = id;
- }
-
- public List getChildFiles() {
- return childFiles;
- }
-
- public void setChildFiles(List files) {
- this.childFiles = files;
- }
-
- public String getNewDirectoryName() {
- return newDirectoryName;
- }
-
- @StrutsParameter
- public void setNewDirectoryName(String newDirectoryName) {
- this.newDirectoryName = newDirectoryName;
- }
-
- public MediaFileDirectory getCurrentDirectory() {
- return currentDirectory;
- }
-
- public void setCurrentDirectory(MediaFileDirectory currentDirectory) {
- this.currentDirectory = currentDirectory;
- }
-
- public String getDirectoryName() {
- return directoryName;
- }
-
- @StrutsParameter
- public void setDirectoryName(String path) {
- this.directoryName = path;
- }
-
- public String getSortBy() {
- return sortBy;
- }
-
- @StrutsParameter
- public void setSortBy(String sortBy) {
- this.sortBy = sortBy;
- }
-
- /**
- * Validates search input
- */
- public boolean myValidate() {
- if (StringUtils.isEmpty(bean.getName())
- && StringUtils.isEmpty(bean.getTags())
- && StringUtils.isEmpty(bean.getType()) && bean.getSize() == 0) {
- addError("MediaFile.error.search.empty");
- return false;
- }
- return true;
- }
-
- @StrutsParameter(depth = 1)
- public MediaFileSearchBean getBean() {
- return bean;
- }
-
- public void setBean(MediaFileSearchBean b) {
- this.bean = b;
- }
-
- public List getFileTypes() {
- return FILE_TYPES;
- }
-
- public List getSizeFilterTypes() {
- return SIZE_FILTER_TYPES;
- }
-
- public List getSizeUnits() {
- return SIZE_UNITS;
- }
-
- public List getSortOptions() {
- return SORT_OPTIONS;
- }
-
- public MediaFilePager getPager() {
- return pager;
- }
-
- public void setPager(MediaFilePager pager) {
- this.pager = pager;
- }
-
- public String getNewDirectoryPath() {
- return newDirectoryPath;
- }
-
- public void setNewDirectoryPath(String newDirectoryPath) {
- this.newDirectoryPath = newDirectoryPath;
- }
-
- public String getViewDirectoryId() {
- return viewDirectoryId;
- }
-
- @StrutsParameter
- public void setViewDirectoryId(String viewDirectoryId) {
- this.viewDirectoryId = viewDirectoryId;
- }
-
-}
+/*
+ * 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.ui.struts2.editor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.weblogger.WebloggerException;
+import org.apache.roller.weblogger.business.FileIOException;
+import org.apache.roller.weblogger.business.MediaFileManager;
+import org.apache.roller.weblogger.business.WebloggerFactory;
+import org.apache.roller.weblogger.pojos.MediaFile;
+import org.apache.roller.weblogger.pojos.MediaFileComparator;
+import org.apache.roller.weblogger.pojos.MediaFileComparator.MediaFileComparatorType;
+import org.apache.roller.weblogger.pojos.MediaFileDirectory;
+import org.apache.roller.weblogger.pojos.MediaFileFilter;
+import org.apache.roller.weblogger.ui.struts2.pagers.MediaFilePager;
+import org.apache.roller.weblogger.ui.struts2.util.KeyValueObject;
+import org.apache.roller.weblogger.util.cache.CacheManager;
+import org.apache.struts2.convention.annotation.AllowedMethods;
+import org.apache.struts2.interceptor.parameter.StrutsParameter;
+import org.apache.struts2.interceptor.validation.SkipValidation;
+
+/**
+ * View media files.
+ */
+@SuppressWarnings("serial")
+// TODO: make this work @AllowedMethods({"execute","view","search","delete","deleteSelected","deleteFolder","includeInGallery","moveSelected"})
+public class MediaFileView extends MediaFileBase {
+
+ private static Log log = LogFactory.getLog(MediaFileView.class);
+
+ private String directoryId;
+ private String directoryName;
+ private String sortBy;
+ private String newDirectoryName;
+
+ private List childFiles;
+ private MediaFileDirectory currentDirectory;
+
+ // Search criteria - drop-down for file type
+ private static List FILE_TYPES = null;
+
+ // Search criteria - drop-down for size filter
+ private static List SIZE_FILTER_TYPES = null;
+
+ // Search criteria - drop-down for size unit
+ private static List SIZE_UNITS = null;
+
+ // Sort options for search results.
+ private static List SORT_OPTIONS = null;
+
+ // Pager for displaying search results.
+ private MediaFilePager pager;
+
+ // Path of new directory to be created.
+ private String newDirectoryPath;
+
+ // a new directory the user wishes to view
+ private String viewDirectoryId = null;
+
+ private MediaFileSearchBean bean = new MediaFileSearchBean();
+
+ public MediaFileView() {
+ this.actionName = "mediaFileView";
+ this.desiredMenu = "editor";
+ this.pageTitle = "mediaFileView.title";
+ }
+
+ /**
+ * Prepares view action
+ */
+ @Override
+ public void myPrepare() {
+
+ if (SIZE_FILTER_TYPES == null) {
+
+ SIZE_FILTER_TYPES = Arrays.asList(new KeyValueObject(
+ "mediaFileView.gt", getText("mediaFileView.gt")),
+ new KeyValueObject("mediaFileView.ge",
+ getText("mediaFileView.ge")), new KeyValueObject(
+ "mediaFileView.eq", getText("mediaFileView.eq")),
+ new KeyValueObject("mediaFileView.le",
+ getText("mediaFileView.le")), new KeyValueObject(
+ "mediaFileView.lt", getText("mediaFileView.lt")));
+
+ FILE_TYPES = Arrays.asList(new KeyValueObject("mediaFileView.any",
+ getText("mediaFileView.any")), new KeyValueObject(
+ "mediaFileView.others", getText("mediaFileView.others")),
+ new KeyValueObject("mediaFileView.image",
+ getText("mediaFileView.image")),
+ new KeyValueObject("mediaFileView.video",
+ getText("mediaFileView.video")),
+ new KeyValueObject("mediaFileView.audio",
+ getText("mediaFileView.audio")));
+
+ SIZE_UNITS = Arrays.asList(new KeyValueObject(
+ "mediaFileView.bytes", getText("mediaFileView.bytes")),
+ new KeyValueObject("mediaFileView.kb",
+ getText("mediaFileView.kb")), new KeyValueObject(
+ "mediaFileView.mb", getText("mediaFileView.mb")));
+
+ SORT_OPTIONS = Arrays.asList(new KeyValueObject("name",
+ getText("generic.name")), new KeyValueObject(
+ "date_uploaded", getText("mediaFileView.date")),
+ new KeyValueObject("type", getText("mediaFileView.type")));
+ }
+
+ refreshAllDirectories();
+ }
+
+ /**
+ * Create a new directory by name. All folders placed at the root.
+ */
+ public String createNewDirectory() {
+ boolean dirCreated = false;
+ if (StringUtils.isEmpty(this.newDirectoryName)) {
+ addError("mediaFile.error.view.dirNameEmpty");
+ } else if (this.newDirectoryName.contains("/")) {
+ addError("mediaFile.error.view.dirNameInvalid");
+ } else {
+ try {
+
+ log.debug("Creating new directory - " + this.newDirectoryName);
+ MediaFileManager manager = WebloggerFactory.getWeblogger()
+ .getMediaFileManager();
+
+ if (!getActionWeblog().hasMediaFileDirectory(
+ this.newDirectoryName)) {
+ // Create
+ MediaFileDirectory dir = manager.createMediaFileDirectory(
+ getActionWeblog(), this.newDirectoryName);
+ // flush changes
+ WebloggerFactory.getWeblogger().flush();
+ addMessage("mediaFile.directoryCreate.success",
+ this.newDirectoryName);
+
+ // Switch to folder
+ setDirectoryId(dir.getId());
+
+ dirCreated = true;
+ } else {
+ // already exists
+ addMessage("mediaFile.directoryCreate.error.exists",
+ this.newDirectoryName);
+ }
+
+ } catch (WebloggerException e) {
+ log.error("Error creating new directory", e);
+ addError("Error creating new directory");
+ }
+ }
+
+ if (dirCreated) {
+ // Refresh list of directories so the newly created directory is
+ // included.
+ refreshAllDirectories();
+ }
+ return execute();
+
+ }
+
+ /**
+ * Returns directory content in JSON format.
+ */
+ public String fetchDirectoryContentLight() {
+ execute();
+ return "success.json";
+ }
+
+ /**
+ * Fetches and displays list of media file for the given directory. The
+ * directory could be chosen by ID or path.
+ *
+ * @return String The result of the action.
+ */
+ @SkipValidation
+ @Override
+ public String execute() {
+ MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager();
+ try {
+ MediaFileDirectory directory;
+ if (StringUtils.isNotEmpty(this.directoryId)) {
+ directory = manager.getMediaFileDirectory(getActionWeblog(), this.directoryId);
+
+ } else if (StringUtils.isNotEmpty(this.directoryName)) {
+ directory = manager.getMediaFileDirectoryByName(
+ getActionWeblog(), this.directoryName);
+
+ } else {
+ directory = manager.getDefaultMediaFileDirectory(getActionWeblog());
+ }
+ this.directoryId = directory.getId();
+ this.directoryName = directory.getName();
+
+ this.childFiles = new ArrayList<>();
+ this.childFiles.addAll(directory.getMediaFiles());
+
+ if ("type".equals(sortBy)) {
+ this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.TYPE));
+
+ } else if ("date_uploaded".equals(sortBy)) {
+ this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.DATE_UPLOADED));
+
+ } else {
+ // default to sort by name
+ sortBy = "name";
+ this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.NAME));
+ }
+
+ this.currentDirectory = directory;
+
+ // set current directory if valid
+ if (directory != null) {
+ setViewDirectoryId(directory.getId());
+ }
+
+ return SUCCESS;
+
+ } catch (FileIOException ex) {
+ log.error("Error viewing media file directory ", ex);
+ addError("MediaFile.error.view");
+ } catch (Exception e) {
+ log.error("Error viewing media file directory ", e);
+ addError("MediaFile.error.view");
+ }
+ return SUCCESS;
+ }
+
+ /**
+ * View the contents of another Media folder.
+ */
+ public String view() {
+ try {
+ MediaFileManager manager = WebloggerFactory.getWeblogger()
+ .getMediaFileManager();
+ if (!StringUtils.isEmpty(viewDirectoryId)) {
+ setDirectoryId(viewDirectoryId);
+ setCurrentDirectory(manager.getMediaFileDirectory(getActionWeblog(), viewDirectoryId));
+ }
+ } catch (WebloggerException ex) {
+ log.error("Error looking up directory", ex);
+ }
+ return execute();
+ }
+
+ /**
+ * Save a media file.
+ *
+ * @return String The result of the action.
+ */
+ public String search() {
+
+ boolean valSuccess = myValidate();
+
+ if (valSuccess) {
+ MediaFileFilter filter = new MediaFileFilter();
+ bean.copyTo(filter);
+ MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager();
+ try {
+ List results = new ArrayList<>(
+ manager.searchMediaFiles(getActionWeblog(), filter));
+ long totalCount = manager.getMediaFileCount(getActionWeblog(), filter);
+
+ this.pager = new MediaFilePager(bean.getPageNum(), results,
+ totalCount, MediaFileSearchBean.PAGE_SIZE);
+
+ } catch (Exception e) {
+ log.error("Error applying search criteria", e);
+ addError("Error applying search criteria - check Roller logs");
+ }
+
+ }
+
+ return SUCCESS;
+ }
+
+ /**
+ * Delete selected media files.
+ *
+ */
+ public String deleteSelected() {
+ doDeleteSelected();
+ return execute();
+ }
+
+ /**
+ * Delete selected media file
+ *
+ */
+ public String delete() {
+ doDeleteMediaFile();
+ return execute();
+ }
+
+ /**
+ * Delete folder
+ */
+ public String deleteFolder() {
+
+ try {
+ MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager();
+ if (directoryId != null) {
+ log.debug("Deleting media file folder - " + directoryId + " ("
+ + directoryName + ")");
+ MediaFileDirectory mediaFileDir = manager
+ .getMediaFileDirectory(getActionWeblog(), directoryId);
+ manager.removeMediaFileDirectory(mediaFileDir);
+ refreshAllDirectories();
+ WebloggerFactory.getWeblogger().getWeblogManager()
+ .saveWeblog(this.getActionWeblog());
+
+ // flush changes
+ WebloggerFactory.getWeblogger().flush();
+ WebloggerFactory.getWeblogger().release();
+ addMessage("mediaFile.deleteFolder.success");
+
+ // notify caches
+ CacheManager.invalidate(getActionWeblog());
+
+ // re-route to default folder
+ mediaFileDir = manager
+ .getDefaultMediaFileDirectory(getActionWeblog());
+ setDirectoryId(mediaFileDir.getId());
+ setDirectoryName(mediaFileDir.getName());
+ } else {
+ log.error("(System error) No directory ID provided for media file folder delete.");
+ }
+ } catch (WebloggerException ex) {
+ log.error("Error deleting folder", ex);
+ }
+ return execute();
+ }
+
+ /**
+ * Include selected media file in gallery
+ *
+ */
+ public String includeInGallery() {
+ doIncludeMediaFileInGallery();
+ return execute();
+ }
+
+ /**
+ * Move selected media files to a different directory
+ *
+ */
+ public String moveSelected() {
+ doMoveSelected();
+ return execute();
+ }
+
+ public String getDirectoryId() {
+ return directoryId;
+ }
+
+ @StrutsParameter
+ public void setDirectoryId(String id) {
+ this.directoryId = id;
+ }
+
+ public List getChildFiles() {
+ return childFiles;
+ }
+
+ public void setChildFiles(List files) {
+ this.childFiles = files;
+ }
+
+ public String getNewDirectoryName() {
+ return newDirectoryName;
+ }
+
+ @StrutsParameter
+ public void setNewDirectoryName(String newDirectoryName) {
+ this.newDirectoryName = newDirectoryName;
+ }
+
+ public MediaFileDirectory getCurrentDirectory() {
+ return currentDirectory;
+ }
+
+ public void setCurrentDirectory(MediaFileDirectory currentDirectory) {
+ this.currentDirectory = currentDirectory;
+ }
+
+ public String getDirectoryName() {
+ return directoryName;
+ }
+
+ @StrutsParameter
+ public void setDirectoryName(String path) {
+ this.directoryName = path;
+ }
+
+ public String getSortBy() {
+ return sortBy;
+ }
+
+ @StrutsParameter
+ public void setSortBy(String sortBy) {
+ this.sortBy = sortBy;
+ }
+
+ /**
+ * Validates search input
+ */
+ public boolean myValidate() {
+ if (StringUtils.isEmpty(bean.getName())
+ && StringUtils.isEmpty(bean.getTags())
+ && StringUtils.isEmpty(bean.getType()) && bean.getSize() == 0) {
+ addError("MediaFile.error.search.empty");
+ return false;
+ }
+ return true;
+ }
+
+ @StrutsParameter(depth = 1)
+ public MediaFileSearchBean getBean() {
+ return bean;
+ }
+
+ public void setBean(MediaFileSearchBean b) {
+ this.bean = b;
+ }
+
+ public List getFileTypes() {
+ return FILE_TYPES;
+ }
+
+ public List getSizeFilterTypes() {
+ return SIZE_FILTER_TYPES;
+ }
+
+ public List getSizeUnits() {
+ return SIZE_UNITS;
+ }
+
+ public List getSortOptions() {
+ return SORT_OPTIONS;
+ }
+
+ public MediaFilePager getPager() {
+ return pager;
+ }
+
+ public void setPager(MediaFilePager pager) {
+ this.pager = pager;
+ }
+
+ public String getNewDirectoryPath() {
+ return newDirectoryPath;
+ }
+
+ public void setNewDirectoryPath(String newDirectoryPath) {
+ this.newDirectoryPath = newDirectoryPath;
+ }
+
+ public String getViewDirectoryId() {
+ return viewDirectoryId;
+ }
+
+ @StrutsParameter
+ public void setViewDirectoryId(String viewDirectoryId) {
+ this.viewDirectoryId = viewDirectoryId;
+ }
+
+}
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/CommentsPager.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/CommentsPager.java
index 7f1b0de42..629c3cba4 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/CommentsPager.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/CommentsPager.java
@@ -18,79 +18,17 @@
package org.apache.roller.weblogger.ui.struts2.pagers;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import org.apache.roller.weblogger.pojos.WeblogEntryComment;
-import org.apache.roller.weblogger.util.URLUtilities;
/**
* Paging through a collection of comments.
*/
-public class CommentsPager {
-
- // the collection for the pager
- private final List items;
-
- // base url for the pager
- private final String baseUrl;
-
- // what page we are on
- private final int pageNum;
-
- // are there more items?
- private final boolean moreItems;
-
-
- public CommentsPager(String url, int page, List comments, boolean hasMore) {
- this.baseUrl = url;
- this.pageNum = page;
- this.items = comments;
- this.moreItems = hasMore;
- }
-
-
- public String getNextLink() {
- if(isMoreItems()) {
- int nextPage = pageNum + 1;
- Map params = new HashMap<>();
- params.put("bean.page", ""+nextPage);
- return createURL(baseUrl, params);
- }
- return null;
- }
-
-
- public String getPrevLink() {
- if (pageNum > 0) {
- int prevPage = pageNum - 1;
- Map params = new HashMap<>();
- params.put("bean.page", ""+prevPage);
- return createURL(baseUrl, params);
- }
- return null;
- }
-
-
- private String createURL(String base, Map params) {
- String qString = URLUtilities.getQueryString(params);
-
- if(base.indexOf('?') != -1) {
- // if base url already has params them just append our query string
- return base + "&" + qString.substring(1);
- } else {
- return base + qString;
- }
- }
-
-
- public List getItems() {
- return items;
- }
+public class CommentsPager extends LinkPager {
- public boolean isMoreItems() {
- return moreItems;
+ public CommentsPager(String url, int page, List comments, long totalCount, int pageSize) {
+ super(url, page, comments, totalCount, pageSize);
}
-
+
}
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/EntriesPager.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/EntriesPager.java
index 58e1768c1..943473a9d 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/EntriesPager.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/EntriesPager.java
@@ -18,79 +18,17 @@
package org.apache.roller.weblogger.ui.struts2.pagers;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import org.apache.roller.weblogger.pojos.WeblogEntry;
-import org.apache.roller.weblogger.util.URLUtilities;
/**
* Paging through a collection of entries.
*/
-public class EntriesPager {
-
- // the collection for the pager
- private final List items;
-
- // base url for the pager
- private final String baseUrl;
-
- // what page we are on
- private final int pageNum;
-
- // are there more items?
- private final boolean moreItems;
-
-
- public EntriesPager(String url, int page, List entries, boolean hasMore) {
- this.baseUrl = url;
- this.pageNum = page;
- this.items = entries;
- this.moreItems = hasMore;
- }
-
-
- public String getNextLink() {
- if(isMoreItems()) {
- int nextPage = pageNum + 1;
- Map params = new HashMap<>();
- params.put("bean.page", ""+nextPage);
- return createURL(baseUrl, params);
- }
- return null;
- }
-
-
- public String getPrevLink() {
- if (pageNum > 0) {
- int prevPage = pageNum - 1;
- Map params = new HashMap<>();
- params.put("bean.page", ""+prevPage);
- return createURL(baseUrl, params);
- }
- return null;
- }
-
-
- private String createURL(String base, Map params) {
- String qString = URLUtilities.getQueryString(params);
-
- if(base.indexOf('?') != -1) {
- // if base url already has params them just append our query string
- return base + "&" + qString.substring(1);
- } else {
- return base + qString;
- }
- }
-
-
- public List getItems() {
- return items;
- }
+public class EntriesPager extends LinkPager {
- public boolean isMoreItems() {
- return moreItems;
+ public EntriesPager(String url, int page, List entries, long totalCount, int pageSize) {
+ super(url, page, entries, totalCount, pageSize);
}
-
+
}
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPager.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPager.java
new file mode 100644
index 000000000..d064130e5
--- /dev/null
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPager.java
@@ -0,0 +1,110 @@
+/*
+ * 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.ui.struts2.pagers;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.roller.weblogger.util.URLUtilities;
+
+
+/**
+ * Paging through query results with links: first, previous (newer), next (older)
+ * and last page. Page numbers are zero-based in links and one-based for display.
+ */
+public class LinkPager {
+
+ // the collection for the pager
+ private final List items;
+
+ // base url for the pager
+ private final String baseUrl;
+
+ // what page we are on
+ private final int pageNum;
+
+ // rows matching the query across all pages
+ private final long totalCount;
+
+ private final int pageSize;
+
+
+ public LinkPager(String url, int page, List items, long totalCount, int pageSize) {
+ this.baseUrl = url;
+ this.pageNum = page;
+ this.items = items;
+ this.totalCount = totalCount;
+ this.pageSize = pageSize;
+ }
+
+
+ public String getFirstLink() {
+ return pageNum > 0 ? pageLink(0) : null;
+ }
+
+ public String getPrevLink() {
+ return pageNum > 0 ? pageLink(pageNum - 1) : null;
+ }
+
+ public String getNextLink() {
+ return isMoreItems() ? pageLink(pageNum + 1) : null;
+ }
+
+ public String getLastLink() {
+ return isMoreItems() ? pageLink(getPageCount() - 1) : null;
+ }
+
+
+ private String pageLink(int page) {
+ Map params = new HashMap<>();
+ params.put("bean.page", ""+page);
+ String qString = URLUtilities.getQueryString(params);
+
+ if(baseUrl.indexOf('?') != -1) {
+ // if base url already has params them just append our query string
+ return baseUrl + "&" + qString.substring(1);
+ } else {
+ return baseUrl + qString;
+ }
+ }
+
+
+ public List getItems() {
+ return items;
+ }
+
+ public boolean isMoreItems() {
+ return (long) (pageNum + 1) * pageSize < totalCount;
+ }
+
+ public long getTotalCount() {
+ return totalCount;
+ }
+
+ /** One-based page number, for display. */
+ public int getPageNumber() {
+ return pageNum + 1;
+ }
+
+ /** Number of pages; at least 1, so an empty result reads "Page 1 of 1". */
+ public int getPageCount() {
+ return (int) Math.max(1, (totalCount + pageSize - 1) / pageSize);
+ }
+
+}
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/MediaFilePager.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/MediaFilePager.java
index de328eb54..ecd3aeee7 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/MediaFilePager.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/MediaFilePager.java
@@ -1,80 +1,77 @@
-/*
- * 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.ui.struts2.pagers;
-
-import java.util.List;
-
-import org.apache.roller.weblogger.pojos.MediaFile;
-
-/**
- * A pager implementation for a collection of media files.
- *
- */
-public class MediaFilePager {
-
- // the collection for the pager
- private final List items;
-
- // What page we are on
- private final int pageNum;
-
- // Are there more items?
- private final boolean moreItems;
-
- public MediaFilePager(int page, List mediaFiles, boolean hasMore) {
- this.pageNum = page;
- this.items = mediaFiles;
- this.moreItems = hasMore;
- }
-
- public List getItems() {
- return items;
- }
-
- public boolean isMoreItems() {
- return moreItems;
- }
-
- /**
- * Indicates whether there are more than one page of item.
- *
- */
- public boolean isJustOnePage() {
- return (pageNum == 0 && !moreItems);
- }
-
- /**
- * Has previous page?
- *
- */
- public boolean hasPrevious() {
- return (pageNum > 0);
- }
-
- /**
- * Has next page?
- *
- */
- public boolean hasNext() {
- return this.moreItems;
- }
-
-
-
-}
+/*
+ * 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.ui.struts2.pagers;
+
+import java.util.List;
+
+import org.apache.roller.weblogger.pojos.MediaFile;
+
+/**
+ * A pager implementation for a collection of media files. Media file search
+ * is a POSTed form, so instead of links this exposes page numbers that the
+ * page resubmits the search form with.
+ */
+public class MediaFilePager {
+
+ // the collection for the pager
+ private final List items;
+
+ // What page we are on
+ private final int pageNum;
+
+ // media files matching the search across all pages
+ private final long totalCount;
+
+ private final int pageSize;
+
+ public MediaFilePager(int page, List mediaFiles, long totalCount, int pageSize) {
+ this.pageNum = page;
+ this.items = mediaFiles;
+ this.totalCount = totalCount;
+ this.pageSize = pageSize;
+ }
+
+ public List getItems() {
+ return items;
+ }
+
+ public boolean isMoreItems() {
+ return (long) (pageNum + 1) * pageSize < totalCount;
+ }
+
+ public long getTotalCount() {
+ return totalCount;
+ }
+
+ /** Zero-based page number, as the search form submits it. */
+ public int getPageNum() {
+ return pageNum;
+ }
+
+ /** One-based page number, for display. */
+ public int getPageNumber() {
+ return pageNum + 1;
+ }
+
+ /** Number of pages; at least 1, so an empty result reads "Page 1 of 1". */
+ public int getPageCount() {
+ return (int) Math.max(1, (totalCount + pageSize - 1) / pageSize);
+ }
+
+}
diff --git a/app/src/main/resources/ApplicationResources.properties b/app/src/main/resources/ApplicationResources.properties
index 598bd4c61..38ca7a7d7 100644
--- a/app/src/main/resources/ApplicationResources.properties
+++ b/app/src/main/resources/ApplicationResources.properties
@@ -43,6 +43,14 @@ generic.toggle=Toggle
generic.success=Success
generic.looksGood=Looks good!
generic.poweredBy=Powered by Apache Roller
+generic.first=First
+generic.newer=Newer
+generic.older=Older
+generic.last=Last
+generic.pageNumber=Page {0} of {1}
+generic.pagination=Pagination
+generic.chooseDate=Choose date
+generic.clearDate=Clear date
# ------------------------------------------------------------- BookmarkForm.jsp
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp
index ad2240cc0..718c63ffb 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp
@@ -84,46 +84,32 @@
-
-
-
-
-
-
-
-
-
-
-
- ---
-
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+ ---
+
+
+
+
+
+
-
<%-- ============================================================= --%>
<%-- Next / previous links --%>
-
-
-
+
<%-- class="tablenav" --%>
@@ -143,231 +129,218 @@
-
+
+
- <%-- ======================================================== --%>
- <%-- Comment table header --%>
+ <%-- ======================================================== --%>
+ <%-- Comment table header --%>
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
- <%-- ======================================================== --%>
- <%-- Select ALL and NONE buttons --%>
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%-- ========================================================= --%>
- <%-- Loop through comments --%>
-
- <%-- it is a bit funky to use checkbox list here, but using checkbox didn't work for me :(
- we are effectively just creating a checkbox list of 1 item for each iteration of our collection. --%>
-
-
-
+ <%-- ======================================================== --%>
+ <%-- Select ALL and NONE buttons --%>
+
+
- <%-- only blog admins (not the global admin) can approve blog comments --%>
-
-
+
+
+
+
+
-
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
- <%-- ======================================================== --%>
- <%-- Display comment details and text --%>
- <%-- with style if comment is spam or pending --%>
-
-
+ <%-- end comment details table in table --%>
+
+
+
+
+
<%-- ============================================================= --%>
<%-- Next / previous links --%>
-
-
-
+
<%-- ========================================================= --%>
<%-- Save changes and cancel buttons --%>
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/CommentsSidebar.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/CommentsSidebar.jsp
index 7e8cf2f74..50a10c1a8 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/CommentsSidebar.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/CommentsSidebar.jsp
@@ -29,7 +29,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%-- with style if comment is spam or pending --%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%-- with style if comment is spam or pending --%>
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
- "
- data-post-title=" ">
-
-
-
-
+
+
+
+
+
+
+
+ "
+ data-post-title=" ">
+
+
+
+
-
-
+
+
-
+
+
<%-- ============================================================= --%>
<%-- Next / previous links --%>
-
-
-
+
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/EntriesSidebar.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/EntriesSidebar.jsp
index e58527d92..ae3b9bab6 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/EntriesSidebar.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/EntriesSidebar.jsp
@@ -22,7 +22,7 @@
-
+
@@ -49,7 +49,7 @@
<%-- filter by date --%>
-
+
@@ -57,16 +57,21 @@
-
-
-
+
+
+
+
+
+
-
+
@@ -74,16 +79,19 @@
-
-
-
+
+
+
+
+
+
-
-
<%-- ========================================================= --%>
<%-- filter by status --%>
@@ -106,12 +114,4 @@
-
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp
index 59b6cd858..e93b76ded 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp
@@ -150,7 +150,7 @@
-
+
<%-- Weblog editor --%>
@@ -160,17 +160,16 @@
-
-
-
-
+
+
+
+
@@ -182,18 +181,16 @@
<%-- Advanced settings --%>
-
-
-
-
+
+
+
+
@@ -213,9 +210,10 @@
-
-
-
+
+
+
@@ -257,43 +255,45 @@
<%-- ================================================================== --%>
<%-- The button box --%>
- <%-- save draft --%>
-
+
-
+ <%-- save draft --%>
+
- <%-- preview mode --%>
-
-
-
+
- <%-- publish --%>
-
-
-
+ <%-- preview mode --%>
+
+
+
- <%-- submit for review --%>
-
-
+ <%-- publish --%>
+
+
+
-
+ <%-- submit for review --%>
+
+
+
+
- <%-- delete --%>
-
-
+ "
data-post-title=" ">
-
-
+
+
+
@@ -363,10 +363,6 @@
+
diff --git a/app/src/main/webapp/roller-ui/scripts/date-fields.js b/app/src/main/webapp/roller-ui/scripts/date-fields.js
new file mode 100644
index 000000000..fd4e01a5e
--- /dev/null
+++ b/app/src/main/webapp/roller-ui/scripts/date-fields.js
@@ -0,0 +1,35 @@
+/*
+* 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.
+*/
+
+
+/*
+ * Date fields in the admin UI: a readonly input.date-picker inside an .input-group,
+ * optionally followed by a .date-picker-show button (opens the picker) and a
+ * .date-picker-clear button (empties the field, so a filter date can be removed).
+ */
+$(function () {
+ $(".date-picker").datepicker();
+
+ $(document).on("click", ".date-picker-show", function () {
+ $(this).closest(".input-group").find(".date-picker").datepicker("show");
+ });
+
+ $(document).on("click", ".date-picker-clear", function () {
+ $(this).closest(".input-group").find(".date-picker").val("").trigger("change");
+ });
+});
diff --git a/app/src/main/webapp/roller-ui/styles/roller.css b/app/src/main/webapp/roller-ui/styles/roller.css
index b8dea7b99..8e4bde61d 100644
--- a/app/src/main/webapp/roller-ui/styles/roller.css
+++ b/app/src/main/webapp/roller-ui/styles/roller.css
@@ -30,9 +30,31 @@ p {
margin: 1em;
}
+/* Bootstrap 5 sizes everything for a 16px root, which reads large in this
+ dense admin UI; 15px scales text, controls and spacing down together. */
+html {
+ font-size: 15px;
+}
+
.roller-page-title {
margin-top: 0px;
margin-bottom: 5px;
+ font-size: 1.75rem;
+}
+
+/* Section headings in sidebar cards */
+.roller-column-left h3 {
+ font-size: 1.25rem;
+}
+
+.roller-column-left h4 {
+ font-size: 1.1rem;
+}
+
+/* Cards stacked in a column (sidebar cards, setup steps, upload results)
+ would otherwise touch */
+.card + .card {
+ margin-top: 1rem;
}
.roller-column-right {
@@ -82,20 +104,34 @@ table.mm_table_actions td {
/* edit entry page */
-/* Only the panel collapse toggles, not links in other toolbars that use
- .panel-heading, such as the rich text editor's menus */
-.panel-heading a[data-toggle="collapse"]:after {
- font-family:'Glyphicons Halflings';
- content:"\e114";
- float: right;
- color: grey;
+/* Panel titles at the size of a section label, not a page heading */
+.accordion-button {
+ font-size: 1.1rem;
+ font-weight: 500;
}
-.panel-heading a[data-toggle="collapse"].collapsed:after {
- content:"\e080";
+
+/* entries and comments pages */
+
+/* Sidebar filter forms: the bootstrap theme adds no spacing to form-vertical groups */
+.roller-filter-form > div {
+ margin-bottom: 1rem;
}
-#panel-plugins { /* Kludge for EntryEditor.jsp */
- margin-top: 6px; /* Without this there no space between Summary and Plugins to Apply panels */
+.roller-filter-form input[type="submit"] {
+ margin-top: 0.5rem;
+}
+
+.roller-pager {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.75rem;
+ margin: 1rem 0;
+}
+
+.roller-pager-page {
+ color: var(--bs-secondary-color);
+ font-size: 0.875rem;
}
/* keep jQuery UI datepicker in front of other elements */
@@ -165,6 +201,8 @@ table.innertable tbody td {
}
table.innertable td.viewbody {
padding: 5px;
+ /* spam often holds long unbroken URLs; let them wrap so the table fits the page */
+ overflow-wrap: anywhere;
}
table.innertable tr .bot {
margin-bottom: 5px;
@@ -357,6 +395,12 @@ tr.rollertable_pending {
opacity: 1;
}
+/* Date fields are readonly only so the picker sets them; they are not locked */
+.form-control.date-picker[readonly] {
+ background-color: var(--bs-body-bg);
+ cursor: pointer;
+}
+
/* Field hint icons sit next to the label rather than in the text flow. */
.s2b_tooltip {
margin-left: .35rem;
diff --git a/app/src/test/java/org/apache/roller/weblogger/business/CommentTest.java b/app/src/test/java/org/apache/roller/weblogger/business/CommentTest.java
index 075c1f141..df55fd99b 100644
--- a/app/src/test/java/org/apache/roller/weblogger/business/CommentTest.java
+++ b/app/src/test/java/org/apache/roller/weblogger/business/CommentTest.java
@@ -30,6 +30,7 @@
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@@ -85,6 +86,7 @@ public void tearDown() throws Exception {
/**
* Test basic persistence operations ... Create, Update, Delete
*/
+ @Test
public void testCommentCRUD() throws Exception {
WeblogEntryManager mgr = WebloggerFactory.getWeblogger().getWeblogEntryManager();
@@ -132,6 +134,7 @@ public void testCommentCRUD() throws Exception {
/**
* Test lookup mechanisms ...
*/
+ @Test
public void testCommentLookups() throws Exception {
WeblogEntryManager mgr = WebloggerFactory.getWeblogger().getWeblogEntryManager();
@@ -149,6 +152,7 @@ public void testCommentLookups() throws Exception {
comments = mgr.getComments(csc);
assertNotNull(comments);
assertEquals(3, comments.size());
+ assertEquals(3, mgr.getCommentCount(csc));
// get all comments for entry
testEntry = TestUtils.getManagedWeblogEntry(testEntry);
@@ -169,6 +173,7 @@ public void testCommentLookups() throws Exception {
comments = mgr.getComments(csc);
assertNotNull(comments);
assertEquals(1, comments.size());
+ assertEquals(1, mgr.getCommentCount(csc));
// get approved comments
csc.setStatus(ApprovalStatus.APPROVED);
@@ -182,6 +187,8 @@ public void testCommentLookups() throws Exception {
comments = mgr.getComments(csc);
assertNotNull(comments);
assertEquals(2, comments.size());
+ // the count ignores offset: it sizes the pager across all pages
+ assertEquals(3, mgr.getCommentCount(csc));
// remove test comments
TestUtils.teardownComment(comment1.getId());
@@ -197,6 +204,7 @@ public void testCommentLookups() throws Exception {
* should delete all comments on that entry, and deleting a weblog should
* delete all comments, etc.
*/
+ @Test
public void testCommentParentDeletes() throws Exception {
log.info("BEGIN");
diff --git a/app/src/test/java/org/apache/roller/weblogger/business/MediaFileTest.java b/app/src/test/java/org/apache/roller/weblogger/business/MediaFileTest.java
index 7e8929574..32515d5e2 100644
--- a/app/src/test/java/org/apache/roller/weblogger/business/MediaFileTest.java
+++ b/app/src/test/java/org/apache/roller/weblogger/business/MediaFileTest.java
@@ -505,6 +505,7 @@ public void testSearchMediaFile() throws Exception {
filter1.setName("mytest.jpg");
searchResults = mfMgr.searchMediaFiles(testWeblog, filter1);
assertTrue(searchResults.isEmpty());
+ assertEquals(0, mfMgr.getMediaFileCount(testWeblog, filter1));
MediaFileFilter filter2 = new MediaFileFilter();
filter2.setName("test_home.jpg");
@@ -542,6 +543,7 @@ public void testSearchMediaFile() throws Exception {
searchResults = mfMgr.searchMediaFiles(testWeblog, filter4);
assertFalse(searchResults.isEmpty());
assertEquals(1, searchResults.size());
+ assertEquals(1, mfMgr.getMediaFileCount(testWeblog, filter4));
assertEquals("test_work.jpg", searchResults.get(0).getName());
// search by size
@@ -576,6 +578,7 @@ public void testSearchMediaFile() throws Exception {
searchResults = mfMgr.searchMediaFiles(testWeblog, filter9);
assertFalse(searchResults.isEmpty());
assertEquals(2, searchResults.size());
+ assertEquals(2, mfMgr.getMediaFileCount(testWeblog, filter9));
MediaFileFilter filter10 = new MediaFileFilter();
filter10.setSize(3000);
@@ -598,6 +601,7 @@ public void testSearchMediaFile() throws Exception {
searchResults = mfMgr.searchMediaFiles(testWeblog, filter12);
assertFalse(searchResults.isEmpty());
assertEquals(2, searchResults.size());
+ assertEquals(2, mfMgr.getMediaFileCount(testWeblog, filter12));
} finally {
TestUtils.endSession(true);
@@ -668,6 +672,8 @@ public void testSearchMediaFilePaging() throws Exception {
assertFalse(searchResults2.isEmpty());
assertEquals(3, searchResults2.size());
assertEquals("test_file13.jpg", searchResults2.get(0).getName());
+ // the count ignores start index and length: it sizes the pager across all pages
+ assertEquals(15, mfMgr.getMediaFileCount(testWeblog, filter2));
MediaFileFilter filter3 = new MediaFileFilter();
filter3.setSize(1000);
diff --git a/app/src/test/java/org/apache/roller/weblogger/business/WeblogEntryTest.java b/app/src/test/java/org/apache/roller/weblogger/business/WeblogEntryTest.java
index b9398d926..04837304a 100644
--- a/app/src/test/java/org/apache/roller/weblogger/business/WeblogEntryTest.java
+++ b/app/src/test/java/org/apache/roller/weblogger/business/WeblogEntryTest.java
@@ -209,6 +209,7 @@ public void testWeblogEntryLookups() throws Exception {
assertNotNull(entries);
assertEquals(5, entries.size());
assertEquals(entry3, entries.get(0));
+ assertEquals(5, mgr.getWeblogEntryCount(wesc));
// get all (non-future) PUBLISHED entries in category
WeblogEntrySearchCriteria wesc9 = new WeblogEntrySearchCriteria();
@@ -226,6 +227,7 @@ public void testWeblogEntryLookups() throws Exception {
entries = mgr.getWeblogEntries(wesc2);
assertNotNull(entries);
assertEquals(3, entries.size());
+ assertEquals(3, mgr.getWeblogEntryCount(wesc2));
// get all entries in date range
WeblogEntrySearchCriteria wesc3 = new WeblogEntrySearchCriteria();
@@ -244,6 +246,8 @@ public void testWeblogEntryLookups() throws Exception {
entries = mgr.getWeblogEntries(wesc4);
assertNotNull(entries);
assertEquals(2, entries.size());
+ // the count ignores maxResults: it sizes the pager across all pages
+ assertEquals(5, mgr.getWeblogEntryCount(wesc4));
// get all entries in category
WeblogEntrySearchCriteria wesc5 = new WeblogEntrySearchCriteria();
@@ -262,6 +266,7 @@ public void testWeblogEntryLookups() throws Exception {
assertNotNull(entries);
assertEquals(1, entries.size());
assertEquals(entry2, entries.get(0));
+ assertEquals(5, mgr.getWeblogEntryCount(wesc6));
// get all entries, limited by locale
WeblogEntrySearchCriteria wesc7 = new WeblogEntrySearchCriteria();
@@ -271,6 +276,7 @@ public void testWeblogEntryLookups() throws Exception {
assertNotNull(entries);
assertEquals(4, entries.size());
assertEquals(entry3, entries.get(0));
+ assertEquals(4, mgr.getWeblogEntryCount(wesc7));
// get pinned entries only
entries = mgr.getWeblogEntriesPinnedToMain(5);
@@ -575,6 +581,39 @@ public void testGetEntriesByTag() throws Exception {
}
+ /**
+ * The entry count must match the rows getWeblogEntries() returns, even when a
+ * multi-tag search joins an entry once per matching tag, or the pager's last page is wrong.
+ */
+ @Test
+ public void testWeblogEntryCountWithMultipleTags() throws Exception {
+
+ WeblogEntryManager mgr = WebloggerFactory.getWeblogger().getWeblogEntryManager();
+
+ testWeblog = TestUtils.getManagedWebsite(testWeblog);
+ testUser = TestUtils.getManagedUser(testUser);
+ WeblogEntry entry1 = TestUtils.setupWeblogEntry("entry1", testWeblog, testUser);
+ entry1.addTag("counttagone");
+ entry1.addTag("counttagtwo");
+ mgr.saveWeblogEntry(entry1);
+ WeblogEntry entry2 = TestUtils.setupWeblogEntry("entry2", testWeblog, testUser);
+ entry2.addTag("counttagone");
+ mgr.saveWeblogEntry(entry2);
+ TestUtils.endSession(true);
+
+ testWeblog = TestUtils.getManagedWebsite(testWeblog);
+ WeblogEntrySearchCriteria wesc = new WeblogEntrySearchCriteria();
+ wesc.setWeblog(testWeblog);
+ wesc.setTags(Arrays.asList("counttagone", "counttagtwo"));
+ List
results = mgr.getWeblogEntries(wesc);
+ assertEquals(results.size(), mgr.getWeblogEntryCount(wesc));
+
+ TestUtils.teardownWeblogEntry(entry1.getId());
+ TestUtils.teardownWeblogEntry(entry2.getId());
+ TestUtils.endSession(true);
+ }
+
+
@Test
public void testRemoveEntryTagCascading() throws Exception {
diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/EntryEditorAssetsTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/EntryEditorAssetsTest.java
index 02d9fed20..5528aa903 100644
--- a/app/src/test/java/org/apache/roller/weblogger/ui/EntryEditorAssetsTest.java
+++ b/app/src/test/java/org/apache/roller/weblogger/ui/EntryEditorAssetsTest.java
@@ -63,15 +63,24 @@ public void summernoteClosesItsIconElements() throws IOException {
@Test
public void panelChevronIsOnlyOnCollapseToggles() throws IOException {
+ // The entry editor's panels are Bootstrap 5 accordions, which draw their own
+ // chevron on .accordion-button. A roller.css chevron on ".panel-heading a"
+ // also lands on the rich text editor's menu items, so none may come back.
String css = read(WEBAPP.resolve("roller-ui/styles/roller.css"));
- Matcher rule = Pattern.compile("([^{}]*\\.panel-heading\\s+a[^{}]*:after)\\s*\\{").matcher(css);
- boolean found = false;
- while (rule.find()) {
- found = true;
- assertTrue(rule.group(1).contains("[data-toggle=\"collapse\"]"),
- "rule must be limited to collapse toggles: " + rule.group(1).trim());
+ assertFalse(Pattern.compile("\\.panel-heading[^{}]*:after").matcher(css).find(),
+ "roller.css must not add a chevron to .panel-heading links");
+
+ for (String jsp : new String[] {"EntryEdit.jsp", "EntryEditor.jsp"}) {
+ String page = read(WEBAPP.resolve("WEB-INF/jsps/editor/" + jsp));
+ Matcher toggle = Pattern.compile("<[^>]*data-bs-toggle=\"collapse\"[^>]*>").matcher(page);
+ boolean found = false;
+ while (toggle.find()) {
+ found = true;
+ assertTrue(toggle.group().contains("accordion-button"),
+ jsp + " collapse toggle must be an accordion button: " + toggle.group());
+ }
+ assertTrue(found, jsp + " has collapsible panels");
}
- assertTrue(found, "roller.css still styles the panel collapse toggles");
}
private static String read(Path file) throws IOException {
diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPagerTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPagerTest.java
new file mode 100644
index 000000000..01a0d2c49
--- /dev/null
+++ b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPagerTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.ui.struts2.pagers;
+
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+public class LinkPagerTest {
+
+ private static final String URL = "/roller-ui/authoring/entries.rol?weblog=roller";
+
+ private static LinkPager pager(int page, long total) {
+ return new LinkPager<>(URL, page, Collections.emptyList(), total, 30);
+ }
+
+ @Test
+ public void testEmptyResultIsOnePage() {
+ LinkPager p = pager(0, 0);
+ assertEquals(1, p.getPageNumber());
+ assertEquals(1, p.getPageCount());
+ assertFalse(p.isMoreItems());
+ assertNull(p.getFirstLink());
+ assertNull(p.getPrevLink());
+ assertNull(p.getNextLink());
+ assertNull(p.getLastLink());
+ }
+
+ @Test
+ public void testFirstPage() {
+ LinkPager p = pager(0, 65);
+ assertEquals(1, p.getPageNumber());
+ assertEquals(3, p.getPageCount());
+ assertTrue(p.isMoreItems());
+ assertNull(p.getFirstLink());
+ assertNull(p.getPrevLink());
+ assertEquals(URL + "&bean.page=1", p.getNextLink());
+ assertEquals(URL + "&bean.page=2", p.getLastLink());
+ }
+
+ @Test
+ public void testMiddlePage() {
+ LinkPager p = pager(1, 65);
+ assertEquals(2, p.getPageNumber());
+ assertEquals(URL + "&bean.page=0", p.getFirstLink());
+ assertEquals(URL + "&bean.page=0", p.getPrevLink());
+ assertEquals(URL + "&bean.page=2", p.getNextLink());
+ assertEquals(URL + "&bean.page=2", p.getLastLink());
+ }
+
+ @Test
+ public void testLastPage() {
+ LinkPager p = pager(2, 65);
+ assertEquals(3, p.getPageNumber());
+ assertFalse(p.isMoreItems());
+ assertEquals(URL + "&bean.page=0", p.getFirstLink());
+ assertEquals(URL + "&bean.page=1", p.getPrevLink());
+ assertNull(p.getNextLink());
+ assertNull(p.getLastLink());
+ }
+
+ @Test
+ public void testExactMultipleOfPageSize() {
+ // 60 rows is exactly two pages, not three
+ assertEquals(2, pager(0, 60).getPageCount());
+ assertFalse(pager(1, 60).isMoreItems());
+ assertEquals(URL + "&bean.page=1", pager(0, 60).getLastLink());
+ }
+
+ @Test
+ public void testBaseUrlWithoutQueryString() {
+ LinkPager p = new LinkPager<>("/entries.rol", 0, Collections.emptyList(), 31, 30);
+ assertEquals("/entries.rol?bean.page=1", p.getNextLink());
+ }
+
+}
diff --git a/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java b/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java
index 158f70f20..05859f275 100644
--- a/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java
+++ b/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java
@@ -349,11 +349,16 @@ protected void insertMediaFileThroughDialog(String weblogHandle) {
* stacked form rows.
*/
protected void assertPublishingTimeOnOneLine() {
- page.locator("a[href='#collapseAdvanced']").click();
- assertThat(page.locator("#collapseAdvanced")).isVisible();
- double hours = page.locator("select[name='bean.hours']").boundingBox().y;
- double minutes = page.locator("select[name='bean.minutes']").boundingBox().y;
- double seconds = page.locator("select[name='bean.seconds']").boundingBox().y;
+ page.locator("[data-bs-target='#collapseAdvanced']").click();
+ // Bootstrap adds "show" only when the collapse transition ends; read
+ // all three positions at once so the layout cannot move between reads
+ assertThat(page.locator("#collapseAdvanced.show:not(.collapsing)")).isVisible();
+ @SuppressWarnings("unchecked")
+ var ys = (java.util.List) page.evaluate("() => ['hours', 'minutes', 'seconds'].map(f =>"
+ + " document.querySelector(`select[name='bean.${f}']`).getBoundingClientRect().y)");
+ double hours = ys.get(0).doubleValue();
+ double minutes = ys.get(1).doubleValue();
+ double seconds = ys.get(2).doubleValue();
org.junit.jupiter.api.Assertions.assertTrue(
Math.abs(hours - minutes) < 2 && Math.abs(minutes - seconds) < 2,
"publishing time selects should share a line, but sit at y=" + hours + ", " + minutes + ", " + seconds);