From 12ef2a5ce54d3e532aff728cdbc8f2f8c563860a Mon Sep 17 00:00:00 2001 From: Jadon Fowler Date: Sat, 6 Jan 2018 11:49:00 -0800 Subject: [PATCH 01/77] Sanatize Page & Version Names Page Names weren't being sanatized before they were displayed. An error message will occur if you attempt to create a page that is illegal. Version Names are now sanitized. Signed-off-by: Jadon Fowler --- app/assets/stylesheets/_user.scss | 6 ++++++ app/views/projects/pages/modalPageCreate.scala.html | 5 +++++ conf/messages | 1 + public/javascripts/iconUpload.js | 2 +- public/javascripts/main.js | 4 ++++ public/javascripts/pageCollapse.js | 6 ++++-- public/javascripts/pageEdit.js | 4 ++++ public/javascripts/versionList.js | 6 +++--- 8 files changed, 28 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/_user.scss b/app/assets/stylesheets/_user.scss index 582e019cf..44541c4b1 100644 --- a/app/assets/stylesheets/_user.scss +++ b/app/assets/stylesheets/_user.scss @@ -355,3 +355,9 @@ textarea[name="pgp-pub-key"] { top: -5px; left: 275px; } + +#new-page { + #new-page-label { + color: $sponge_grey; + } +} diff --git a/app/views/projects/pages/modalPageCreate.scala.html b/app/views/projects/pages/modalPageCreate.scala.html index aff4a6091..a76e892d3 100644 --- a/app/views/projects/pages/modalPageCreate.scala.html +++ b/app/views/projects/pages/modalPageCreate.scala.html @@ -14,10 +14,15 @@ + + + + + +} diff --git a/conf/application.conf.template b/conf/application.conf.template index 469c99438..a1d532742 100755 --- a/conf/application.conf.template +++ b/conf/application.conf.template @@ -67,6 +67,7 @@ security { # Ore configuration ore { date-format = "MMM dd, yyyy" + date-and-time-format = "MMM dd, yyyy HH:mm" debug = true debug-level = 3 # Used in /admin/seed route. Run "gradle build" in OreTestPlugin before using that route @@ -116,6 +117,10 @@ ore { groupId = 64 createLimit = 5 } + + queue { + max-review-time = 86400000 // 1 day (millis) + } } mail { diff --git a/conf/evolutions/default/82.sql b/conf/evolutions/default/82.sql new file mode 100644 index 000000000..66fc68b21 --- /dev/null +++ b/conf/evolutions/default/82.sql @@ -0,0 +1,14 @@ +# --- !Ups + +CREATE TABLE project_version_reviews ( + id SERIAL PRIMARY KEY, + version_id INT NOT NULL REFERENCES project_versions ON DELETE CASCADE, + user_id INT NOT NULL REFERENCES users ON DELETE CASCADE, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + ended_at TIMESTAMP, + comment TEXT +); + +# --- !Downs + +DROP TABLE project_version_reviews; diff --git a/conf/messages b/conf/messages index 5aa8ba8b7..93d1bb691 100755 --- a/conf/messages +++ b/conf/messages @@ -272,6 +272,8 @@ user.flags.messageOwner = Message project owner user.queue = Approval queue user.queue.none = There are no versions to review. user.queue.approve = Approve +user.queue.open = Approval queue +user.queue.progress = In Review user.pgp.edit = PGP Public Key user.pgp.pubKey = PGP Public Key user.pgp.pubKey.info = Paste your PGP public key in it's entirety to the text box below. The submitted key must be associated with the email that is associated with your Sponge account. @@ -330,3 +332,13 @@ ph.comment = Comment api.deploy.versionExists = A version of that name already exists. api.deploy.invalidKey = Invalid API key. api.deploy.channelNotFound = Channel not found. + +review.title = Reviews +review.start = Start review +review.stop = Stop review +review.takeover = Takeover review +review.log = Review logs +review.whystop = Please explain why you are stopping the review +review.whytakeover = Please explain why you are takingover +review.addmessage = Add message +queue.review.none = There are no reviews in progress diff --git a/conf/routes b/conf/routes index c88df8e70..c8c441a0e 100755 --- a/conf/routes +++ b/conf/routes @@ -171,6 +171,16 @@ POST /:author/:slug/versions/:version/save @controllers POST /:author/:slug/versions/:version/recommended @controllers.project.Versions.setRecommended(author, slug, version) +# ---------- Reviews ---------- +GET /:author/:slug/versions/:version/reviews @controllers.Reviews.showReviews(author, slug, version) +POST /:author/:slug/versions/:version/reviews/init @controllers.Reviews.createReview(author, slug, version) +POST /:author/:slug/versions/:version/reviews/stop @controllers.Reviews.stopReview(author, slug, version) +POST /:author/:slug/versions/:version/reviews/approve @controllers.Reviews.approveReview(author, slug, version) +POST /:author/:slug/versions/:version/reviews/takeover @controllers.Reviews.takeoverReview(author, slug, version) +POST /:author/:slug/versions/:version/reviews/addmessage @controllers.Reviews.addMessage(author, slug, version) +POST /:author/:slug/versions/:version/reviews/edit/:review @controllers.Reviews.editReview(author, slug, version, review: Int) + + # ---------- Other ---------- GET /assets/*file controllers.Assets.at(path="/public", file) diff --git a/public/javascripts/review.js b/public/javascripts/review.js new file mode 100644 index 000000000..12f3dc402 --- /dev/null +++ b/public/javascripts/review.js @@ -0,0 +1,133 @@ +/* + * ================================================== + * _____ _ + * | |___ ___ |_|___ + * | | | _| -_|_ | |_ -| + * |_____|_| |___|_|_| |___| + * |___| + * + * (C) SpongePowered 2018 MIT License + * https://github.com/SpongePowered/Ore + * + * Powers the admin reviews. + * + * ================================================== + */ + + +/* + * ================================================== + * = Doc ready = + * ================================================== + */ + +$(function() { + $('.btn-review-start').click(function() { + var icon = $(this).find('i').removeClass('fa-terminal').addClass('fa-spinner fa-spin'); + $(this).attr("disabled", "disabled"); + $.ajax({ + type: 'post', + url: '/' + versionPath + '/reviews/init', + data: { csrfToken: csrf }, + complete: function() { icon.removeClass('fa-spinner fa-spin').addClass('fa-terminal'); }, + success: function() { + location.reload(); + } + }); + }); + + $('.btn-review-stop').click(function () { + var modal = $('#modal-review-stop'); + modal.modal().show(); + modal.on('shown.bs.modal', function() { $(this).find('textarea').focus(); }); + }); + + $('.btn-review-stop-submit').click(function() { + var icon = $(this).find('i').removeClass('fa-times-circle-o').addClass('fa-spinner fa-spin'); + $.ajax({ + type: 'post', + url: '/' + versionPath + '/reviews/stop', + + data: { csrfToken: csrf, content: $('.textarea-stop').val() }, + complete: function() { icon.removeClass('fa-spinner fa-spin').addClass('fa-times-circle-o'); }, + success: function() { + location.reload(); + } + }); + }); + + $('.btn-review-approve').click(function() { + var icon = $(this).find('i').removeClass('fa-thumbs-up').addClass('fa-spinner fa-spin'); + $.ajax({ + type: 'post', + url: '/' + versionPath + '/reviews/approve', + data: { csrfToken: csrf } + }); + $.ajax({ + type: 'post', + url: '/' + versionPath + '/approve', + data: { csrfToken: csrf }, + success: function() { + location.reload(); + } + }); + }); + + $('.btn-review-takeover').click(function () { + var modal = $('#modal-review-takeover'); + modal.modal().show(); + modal.on('shown.bs.modal', function() { $(this).find('textarea').focus(); }); + }); + + $('.btn-review-takeover-submit').click(function() { + var icon = $(this).find('i').removeClass('fa-clipboard').addClass('fa-spinner fa-spin'); + $.ajax({ + type: 'post', + url: '/' + versionPath + '/reviews/takeover', + data: { csrfToken: csrf, content: $('.textarea-takeover').val() }, + success: function() { + location.reload(); + } + }); + }); + + $('.btn-edit-message').click(function () { + var panel = $(this).parent().parent().parent(); + var text = panel.find('textarea'); + text.attr('disabled', null); + $(this).hide(); + panel.find('.btn-cancel-message').show(); + panel.find('.btn-save-message').show(); + }); + + $('.btn-cancel-message').click(function(){ + location.reload(); + }); + + $('.btn-save-message').click(function() { + var panel = $(this).parent().parent().parent(); + var textarea = panel.find('textarea'); + textarea.attr('disabled', 'disabled'); + $(this).find('i').removeClass('fa-save').addClass('fa-spinner fa-spin'); + $.ajax({ + type: 'post', + url: '/' + versionPath + '/reviews/edit/' + panel.data('review'), + data: { csrfToken: csrf, content: textarea.val() }, + success: function() { + location.reload(); + } + }); + }); + + $('.btn-review-addmessage-submit').click(function() { + var icon = $(this).find('i').removeClass('fa-clipboard').addClass('fa-spinner fa-spin'); + $.ajax({ + type: 'post', + url: '/' + versionPath + '/reviews/addmessage', + data: { csrfToken: csrf, content: $('.textarea-addmessage').val() }, + success: function() { + location.reload(); + } + }); + }); +}); From 46782fcbf1e2c5bc05e5aff3395340f726884722 Mon Sep 17 00:00:00 2001 From: Jadon Fowler Date: Tue, 16 Jan 2018 11:50:46 -0800 Subject: [PATCH 14/77] Fix version & channel deletion Files.deleteIfExists will throw a DirectoryNotEmptyException if the directory it is trying to delete is not empty. FileUtils has a function to delete directories that we can use. Closes #361 Signed-off-by: Jadon Fowler --- app/db/impl/access/ProjectBase.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/db/impl/access/ProjectBase.scala b/app/db/impl/access/ProjectBase.scala index 66ea3b0b2..72d4f7c29 100644 --- a/app/db/impl/access/ProjectBase.scala +++ b/app/db/impl/access/ProjectBase.scala @@ -151,7 +151,7 @@ class ProjectBase(override val service: ModelService, channel.versions.all.foreach { version: Version => val versionFolder = this.fileManager.getVersionDir(project.ownerName, project.name, version.name) - Files.deleteIfExists(versionFolder) + FileUtils.deleteDirectory(versionFolder) version.remove() } } @@ -181,7 +181,7 @@ class ProjectBase(override val service: ModelService, this.deleteChannel(channel) val versionDir = this.fileManager.getVersionDir(proj.ownerName, project.name, version.name) - Files.deleteIfExists(versionDir) + FileUtils.deleteDirectory(versionDir) } /** From 28a2e8bf9d241d38db43151d765bf1da496b9e3d Mon Sep 17 00:00:00 2001 From: Jadon Fowler Date: Tue, 16 Jan 2018 12:17:54 -0800 Subject: [PATCH 15/77] Give Organization Admins their own Trust Level Org Owners & Admins had the same Trust Level. Owners couldn't modify Admin positions because they had Absolute Trust. They are now ordered correctly, and Admins can modify the positions of everyone except the Owner. Closes #364 Closes #365 Signed-off-by: Jadon Fowler --- app/ore/permission/Permission.scala | 8 ++++---- app/ore/permission/role/RoleTypes.scala | 2 +- app/ore/permission/role/Trust.scala | 7 ++++++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/ore/permission/Permission.scala b/app/ore/permission/Permission.scala index 24823b6e1..b64dc3fe6 100644 --- a/app/ore/permission/Permission.scala +++ b/app/ore/permission/Permission.scala @@ -1,6 +1,6 @@ package ore.permission -import ore.permission.role.{Absolute, Limited, Standard, Trust} +import ore.permission.role._ /** * Represents a permission for a user to do something in the application. @@ -8,7 +8,7 @@ import ore.permission.role.{Absolute, Limited, Standard, Trust} sealed trait Permission { def trust: Trust } case object EditChannels extends Permission { val trust = Standard } case object EditPages extends Permission { val trust = Limited } -case object EditSettings extends Permission { val trust = Absolute } +case object EditSettings extends Permission { val trust = Lifted } case object EditVersions extends Permission { val trust = Standard } case object HideProjects extends Permission { val trust = Standard } case object ReviewFlags extends Permission { val trust = Standard } @@ -18,6 +18,6 @@ case object ViewLogs extends Permission { val trust = Standard } case object ResetOre extends Permission { val trust = Absolute } case object SeedOre extends Permission { val trust = Absolute } case object MigrateOre extends Permission { val trust = Absolute } -case object CreateProject extends Permission { val trust = Absolute } +case object CreateProject extends Permission { val trust = Lifted } case object PostAsOrganization extends Permission { val trust = Standard } -case object EditApiKeys extends Permission { val trust = Absolute } +case object EditApiKeys extends Permission { val trust = Lifted } diff --git a/app/ore/permission/role/RoleTypes.scala b/app/ore/permission/role/RoleTypes.scala index 74e5a1e22..198efac89 100644 --- a/app/ore/permission/role/RoleTypes.scala +++ b/app/ore/permission/role/RoleTypes.scala @@ -48,7 +48,7 @@ object RoleTypes extends Enumeration { isAssignable = false) val OrganizationOwner = new RoleType(22, -5, classOf[OrganizationRole], Absolute, "Owner", Purple, isAssignable = false) - val OrganizationAdmin = new RoleType(26, -9, classOf[OrganizationRole], Absolute, "Admin", Purple) + val OrganizationAdmin = new RoleType(26, -9, classOf[OrganizationRole], Lifted, "Admin", Purple) val OrganizationDev = new RoleType(23, -6, classOf[OrganizationRole], Standard, "Developer", Transparent) val OrganizationEditor = new RoleType(24, -7, classOf[OrganizationRole], Limited, "Editor", Transparent) val OrganizationSupport = new RoleType(25, -8, classOf[OrganizationRole], Default, "Support", Transparent) diff --git a/app/ore/permission/role/Trust.scala b/app/ore/permission/role/Trust.scala index 99a39eff5..fba857582 100644 --- a/app/ore/permission/role/Trust.scala +++ b/app/ore/permission/role/Trust.scala @@ -25,7 +25,12 @@ case object Limited extends Trust { override val level = 1 } */ case object Standard extends Trust { override val level = 2 } +/** + * User that can perform any action but they are not on top. + */ +case object Lifted extends Trust { override val level = 3 } + /** * User is absolutely trusted and may perform any action. */ -case object Absolute extends Trust { override val level = 3 } +case object Absolute extends Trust { override val level = 4 } From 08656acbc1a3c858501935fbed0aa0b446d2856a Mon Sep 17 00:00:00 2001 From: Ewout Van Schil Date: Wed, 17 Jan 2018 19:10:39 +0100 Subject: [PATCH 16/77] Add resolved_at & resolved_by to a flag and make comment non-optional --- app/controllers/Application.scala | 2 +- app/controllers/project/Projects.scala | 2 +- app/db/impl/schema.scala | 4 +++- app/db/impl/table/ModelKeys.scala | 2 ++ app/form/OreForms.scala | 2 +- app/form/project/FlagForm.scala | 2 +- app/models/project/Flag.scala | 14 ++++++++++++-- app/views/projects/view.scala.html | 2 +- conf/evolutions/default/83.sql | 19 +++++++++++++++++++ conf/messages | 1 + 10 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 conf/evolutions/default/83.sql diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala index a792d3835..27ad85558 100755 --- a/app/controllers/Application.scala +++ b/app/controllers/Application.scala @@ -137,7 +137,7 @@ final class Application @Inject()(data: DataHelper, case None => notFound case Some(flag) => - flag.setResolved(resolved) + flag.setResolved(resolved, users.current) Ok } } diff --git a/app/controllers/project/Projects.scala b/app/controllers/project/Projects.scala index 90c2e3095..16d733fc3 100755 --- a/app/controllers/project/Projects.scala +++ b/app/controllers/project/Projects.scala @@ -289,7 +289,7 @@ class Projects @Inject()(stats: StatTracker, hasErrors => FormError(ShowProject(project), hasErrors), formData => { - project.flagFor(user, formData.reason, formData.comment.orNull) + project.flagFor(user, formData.reason, formData.comment) Redirect(self.show(author, slug)).flashing("reported" -> "true") } ) diff --git a/app/db/impl/schema.scala b/app/db/impl/schema.scala index 4d3f277e0..393a2e973 100755 --- a/app/db/impl/schema.scala +++ b/app/db/impl/schema.scala @@ -336,8 +336,10 @@ class FlagTable(tag: RowTag) extends ModelTable[Flag](tag, "project_flags") { def reason = column[FlagReason]("reason") def comment = column[String]("comment") def isResolved = column[Boolean]("is_resolved") + def resolvedAt = column[Timestamp]("resolved_at") + def resolvedBy = column[Int]("resolved_by") - override def * = (id.?, createdAt.?, projectId, userId, reason, comment, isResolved) <> (Flag.tupled, Flag.unapply) + override def * = (id.?, createdAt.?, projectId, userId, reason, comment, isResolved, resolvedAt.?, resolvedBy.?) <> (Flag.tupled, Flag.unapply) } diff --git a/app/db/impl/table/ModelKeys.scala b/app/db/impl/table/ModelKeys.scala index 509b7ab52..ea6c8289f 100755 --- a/app/db/impl/table/ModelKeys.scala +++ b/app/db/impl/table/ModelKeys.scala @@ -92,6 +92,8 @@ object ModelKeys { // Flag val IsResolved = new BooleanKey[Flag](_.isResolved, _.isResolved) + val ResolvedAt = new TimestampKey[Flag](_.resolvedAt, _.resolvedAt.orNull) + val ResolvedBy = new IntKey[Flag](_.resolvedBy, _.resolvedBy.getOrElse(-1)) // StatEntry val UserId = new IntKey[StatEntry[_]](_.userId, _.user.flatMap(_.id).getOrElse(-1)) diff --git a/app/form/OreForms.scala b/app/form/OreForms.scala index 9b1ac73cc..4de42c749 100755 --- a/app/form/OreForms.scala +++ b/app/form/OreForms.scala @@ -61,7 +61,7 @@ class OreForms @Inject()(implicit config: OreConfig, factory: ProjectFactory, se */ lazy val ProjectFlag = Form(mapping( "flag-reason" -> number, - "comment" -> optional(nonEmptyText)) + "comment" -> nonEmptyText) (FlagForm.apply)(FlagForm.unapply)) /** diff --git a/app/form/project/FlagForm.scala b/app/form/project/FlagForm.scala index 2f727730c..1083c9a69 100644 --- a/app/form/project/FlagForm.scala +++ b/app/form/project/FlagForm.scala @@ -3,7 +3,7 @@ package form.project import ore.project.FlagReasons import ore.project.FlagReasons.FlagReason -case class FlagForm(reasonId: Int, comment: Option[String]) { +case class FlagForm(reasonId: Int, comment: String) { val reason: FlagReason = FlagReasons.values.find(_.id == reasonId).getOrElse(FlagReasons.Other) diff --git a/app/models/project/Flag.scala b/app/models/project/Flag.scala index e7e03750d..358b1e07f 100644 --- a/app/models/project/Flag.scala +++ b/app/models/project/Flag.scala @@ -1,10 +1,12 @@ package models.project import java.sql.Timestamp +import java.time.Instant import db.impl.FlagTable import db.impl.model.OreModel import db.impl.table.ModelKeys._ +import models.user.User import ore.permission.scope.ProjectScope import ore.project.FlagReasons.FlagReason import ore.user.UserOwned @@ -25,7 +27,9 @@ case class Flag(override val id: Option[Int], override val userId: Int, reason: FlagReason, comment: String, - private var _isResolved: Boolean = false) + private var _isResolved: Boolean = false, + var resolvedAt: Option[Timestamp] = None, + var resolvedBy: Option[Int] = None) extends OreModel(id, createdAt) with UserOwned with ProjectScope { @@ -50,9 +54,15 @@ case class Flag(override val id: Option[Int], * * @param resolved True if resolved */ - def setResolved(resolved: Boolean) = Defined { + def setResolved(resolved: Boolean, user: Option[User]) = Defined { this._isResolved = resolved update(IsResolved) + if (resolved) { + this.resolvedAt = Some(Timestamp.from(Instant.now)) + update(ResolvedAt) + this.resolvedBy = Some(user.flatMap(_.id).getOrElse(-1)) + update(ResolvedBy) + } } override def copyWith(id: Option[Int], theTime: Option[Timestamp]) = this.copy(id = id, createdAt = theTime) diff --git a/app/views/projects/view.scala.html b/app/views/projects/view.scala.html index 0742f19f9..4c9baec7b 100755 --- a/app/views/projects/view.scala.html +++ b/app/views/projects/view.scala.html @@ -180,7 +180,7 @@ } diff --git a/app/views/projects/view.scala.html b/app/views/projects/view.scala.html index 580740f35..82a9302f1 100755 --- a/app/views/projects/view.scala.html +++ b/app/views/projects/view.scala.html @@ -47,22 +47,27 @@ @messages("visibility.notice.author." + project.visibility.nameKey) Publish } - } else { - @if(project.visibility == VisibilityTypes.NeedsChanges) { - @if(canEditPages) { - Send for approval - } - @messages("visibility.notice." + project.visibility.nameKey) -
- @if(project.lastVisibilityChange.isDefined) { - @project.lastVisibilityChange.get.renderComment() - } else { - Unknown - } + } else { @if(project.visibility == VisibilityTypes.NeedsChanges) { + @if(canEditPages) { + Send for approval + } + @messages("visibility.notice." + project.visibility.nameKey) +
+ @if(project.lastVisibilityChange.isDefined) { + @project.lastVisibilityChange.get.renderComment() } else { - @messages("visibility.notice." + project.visibility.nameKey) + Unknown } - } + } else { @if(project.visibility == VisibilityTypes.SoftDelete) { + @if(project.lastVisibilityChange.isDefined) { + @messages("visibility.notice." + project.visibility.nameKey, users.get(project.lastVisibilityChange.get.createdBy.get).map(_.username).getOrElse("Unknown")) + @project.lastVisibilityChange.get.renderComment() + } else { + @messages("visibility.notice." + project.visibility.nameKey, "Unknown") + } + } else { + @messages("visibility.notice." + project.visibility.nameKey) + }}} diff --git a/app/views/users/admin/visibility.scala.html b/app/views/users/admin/visibility.scala.html index fa7494952..b917126d8 100644 --- a/app/views/users/admin/visibility.scala.html +++ b/app/views/users/admin/visibility.scala.html @@ -60,7 +60,11 @@

Needs Approval

- @users.get(project.lastVisibilityChange.get.createdBy.getOrElse(1)).map(_.username).getOrElse("Unknown") + @if(project.lastVisibilityChange.isDefined) { + @users.get(project.lastVisibilityChange.get.createdBy.getOrElse(1)).map(_.username).getOrElse("Unknown") + } else { + Unknown + } requested changes on @project.ownerName/@project.slug diff --git a/conf/evolutions/default/88.sql b/conf/evolutions/default/88.sql new file mode 100644 index 000000000..b75ddc192 --- /dev/null +++ b/conf/evolutions/default/88.sql @@ -0,0 +1,6 @@ +# --- !Ups +ALTER TABLE project_visibility_changes DROP CONSTRAINT project_visibility_changes_project_id_fkey; +ALTER TABLE project_visibility_changes ADD CONSTRAINT project_visibility_changes_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; + +# --- !Downs +DROP TABLE project_visibility_changes; diff --git a/conf/messages b/conf/messages index 1452f487d..8ce953cac 100755 --- a/conf/messages +++ b/conf/messages @@ -128,7 +128,7 @@ project.author = Author project.category = Category project.category.plural = Categories project.delete.title = Delete project -project.delete.info = Are you sure you want to delete your Project? This action cannot be undone. +project.delete.info = Are you sure you want to delete your Project? This action cannot be undone. Please explain why you want to delete it. project.download.recommend = Download the latest recommended version project.download.recommend.warn = This project's recommended version has not been reviewed by our moderation staff and may not be safe for download. project.download.warn = This version has not been reviewed by our moderation staff and may not be safe for download. @@ -363,4 +363,4 @@ visibility.notice.new = This project is new, and the author may not ha visibility.notice.author.new = Click ''Publish'' to publish. This project will automatically be moved from the new stage in 24 hours. visibility.notice.needsChanges = This project requires changes: visibility.notice.needsApproval = You have send the project for review -visibility.notice.softDelete = Project deleted by {0}: {1} +visibility.notice.softDelete = Project deleted by {0} diff --git a/conf/routes b/conf/routes index 469007e44..ea1112063 100755 --- a/conf/routes +++ b/conf/routes @@ -121,7 +121,8 @@ GET /:author/:slug/manage/publish @controllers GET /:author/:slug/manage/sendforapproval @controllers.project.Projects.sendForApproval(author, slug) POST /:author/:slug/manage/save @controllers.project.Projects.save(author, slug) POST /:author/:slug/manage/rename @controllers.project.Projects.rename(author, slug) -POST /:author/:slug/manage/delete @controllers.project.Projects.delete(author, slug) +POST /:author/:slug/manage/hardDelete @controllers.project.Projects.delete(author, slug) +POST /:author/:slug/manage/delete @controllers.project.Projects.softDelete(author, slug) POST /:author/:slug/manage/members/remove @controllers.project.Projects.removeMember(author, slug) GET /:author/:slug/log @controllers.project.Projects.showLog(author, slug) diff --git a/public/javascripts/hideProject.js b/public/javascripts/hideProject.js index a451977c8..506f69c45 100644 --- a/public/javascripts/hideProject.js +++ b/public/javascripts/hideProject.js @@ -51,9 +51,10 @@ $(function() { }); function sendVisibilityRequest(project, level, comment, spinner) { + var _url = '/' + project + (level == -99 ? '/manage/hardDelete' : '/visible/' + level); $.ajax({ type: 'post', - url: '/' + project + '/visible/' + level, + url: _url, data: { csrfToken: csrf, comment: comment }, fail: function () { spinner.addClass(ICON).removeClass('fa-spinner fa-spin'); From a322b2b7a3a3653583618ae1b2ce413c931449ad Mon Sep 17 00:00:00 2001 From: Ewout Van Schil Date: Wed, 21 Feb 2018 22:04:17 +0100 Subject: [PATCH 61/77] Fix evolution breaking things --- conf/evolutions/default/88.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conf/evolutions/default/88.sql b/conf/evolutions/default/88.sql index b75ddc192..95b9438ac 100644 --- a/conf/evolutions/default/88.sql +++ b/conf/evolutions/default/88.sql @@ -3,4 +3,5 @@ ALTER TABLE project_visibility_changes DROP CONSTRAINT project_visibility_change ALTER TABLE project_visibility_changes ADD CONSTRAINT project_visibility_changes_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; # --- !Downs -DROP TABLE project_visibility_changes; +ALTER TABLE project_visibility_changes DROP CONSTRAINT project_visibility_changes_project_id_fkey; +ALTER TABLE project_visibility_changes ADD CONSTRAINT project_visibility_changes_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id); From 7fd18b1de5cf7c29da00da683d40448db1a8424f Mon Sep 17 00:00:00 2001 From: Ewout Van Schil Date: Wed, 21 Feb 2018 18:28:50 +0100 Subject: [PATCH 62/77] Add WikiLinks, Fix #421 and #265 --- app/controllers/project/Pages.scala | 61 ++++++++++++++++++++---- app/db/impl/schema/PageSchema.scala | 2 +- app/models/project/Page.scala | 52 ++++++++++++++++---- app/ore/rest/OreRestfulApi.scala | 9 +++- app/views/projects/pages/view.scala.html | 5 +- build.sbt | 15 +++--- conf/evolutions/default/89.sql | 13 +++++ conf/routes | 8 ++-- public/javascripts/pageCollapse.js | 2 +- public/javascripts/pageEdit.js | 2 + 10 files changed, 136 insertions(+), 33 deletions(-) create mode 100644 conf/evolutions/default/89.sql diff --git a/app/controllers/project/Pages.scala b/app/controllers/project/Pages.scala index d250eef7a..e8ff0cb34 100755 --- a/app/controllers/project/Pages.scala +++ b/app/controllers/project/Pages.scala @@ -4,9 +4,10 @@ import javax.inject.Inject import controllers.BaseController import controllers.sugar.Bakery -import db.ModelService +import db.impl.OrePostgresDriver.api._ +import db.{ModelFilter, ModelService} import form.OreForms -import models.project.Page +import models.project.{Page, Project} import ore.permission.EditPages import ore.{OreConfig, OreEnv, StatTracker} import play.api.i18n.MessagesApi @@ -33,6 +34,29 @@ class Pages @Inject()(forms: OreForms, private def PageEditAction(author: String, slug: String) = AuthedProjectAction(author, slug, requireUnlock = true) andThen ProjectPermissionAction(EditPages) + /** + * Return the best guess of the page + * + * @param project + * @param page + * @return Tuple: Optional Page, true if using legacy fallback + */ + def withPage(project: Project, page: String): (Option[Page], Boolean) = { + val parts = page.split("/") + if (parts.size == 2) { + val parentId = project.pages.find(equalsIgnoreCase(_.name, parts(0))).map(_.id.getOrElse(-1)).getOrElse(-1) + val pages: Seq[Page] = project.pages.filter(equalsIgnoreCase(_.name, parts(1))).seq + (pages.find(_.parentId == parentId), false) + } else { + val result = project.pages.find((ModelFilter[Page](_.name === parts(0)) +&& ModelFilter[Page](_.parentId === -1)).fn) + if (!result.isDefined) { + (project.pages.find((ModelFilter[Page](_.name === parts(0))).fn), true) + } else { + (result, false) + } + } + } + /** * Displays the specified page. * @@ -43,9 +67,11 @@ class Pages @Inject()(forms: OreForms, */ def show(author: String, slug: String, page: String) = ProjectAction(author, slug) { implicit request => val project = request.project - project.pages.find(equalsIgnoreCase(_.name, page)) match { - case None => notFound - case Some(p) => this.stats.projectViewed(implicit request => Ok(views.view(project, p))) + val optionPage = withPage(project, page) + if (optionPage._1.isDefined) { + this.stats.projectViewed(implicit request => Ok(views.view(project, optionPage._1.get, optionPage._2))) + } else { + notFound } } @@ -60,7 +86,15 @@ class Pages @Inject()(forms: OreForms, */ def showEditor(author: String, slug: String, page: String) = PageEditAction(author, slug) { implicit request => val project = request.project - Ok(views.view(project, project.getOrCreatePage(page), editorOpen = true)) + val parts = page.split("/") + var pageName = parts(0) + var parentId = -1 + if (parts.size == 2) { + pageName = parts(1) + parentId = project.pages.find(equalsIgnoreCase(_.name, parts(0))).map(_.id.getOrElse(-1)).getOrElse(-1) + } + val pageModel = project.getOrCreatePage(pageName, parentId) + Ok(views.view(project, pageModel, editorOpen = true)) } /** @@ -86,7 +120,7 @@ class Pages @Inject()(forms: OreForms, Redirect(self.show(author, slug, page)).withError(hasErrors.errors.head.message), pageData => { val project = request.project - val parentId = pageData.parentId.getOrElse(-1) + var parentId = pageData.parentId.getOrElse(-1) //noinspection ComparingUnrelatedTypes if (parentId != -1 && !project.rootPages.filterNot(_.name.equals(Page.HomeName)).exists(_.id.get == parentId)) { BadRequest("Invalid parent ID.") @@ -95,7 +129,13 @@ class Pages @Inject()(forms: OreForms, if (page.equals(Page.HomeName) && (content.isEmpty || content.get.length < Page.MinLength)) { Redirect(self.show(author, slug, page)).withError("error.minLength") } else { - val pageModel = project.getOrCreatePage(page, parentId) + val parts = page.split("/") + var pageName = parts(0) + if (parts.size == 2) { + pageName = parts(1) + parentId = project.pages.find(equalsIgnoreCase(_.name, parts(0))).map(_.id.getOrElse(-1)).getOrElse(-1) + } + val pageModel = project.getOrCreatePage(pageName, parentId) pageData.content.map(pageModel.contents = _) Redirect(self.show(author, slug, page)) } @@ -114,7 +154,10 @@ class Pages @Inject()(forms: OreForms, */ def delete(author: String, slug: String, page: String) = PageEditAction(author, slug) { implicit request => val project = request.project - this.service.access[Page](classOf[Page]).remove(project.pages.find(equalsIgnoreCase(_.name, page)).get) + val optionPage = withPage(project, page) + if (optionPage._1.isDefined) + this.service.access[Page](classOf[Page]).remove(optionPage._1.get) + Redirect(routes.Projects.show(author, slug)) } diff --git a/app/db/impl/schema/PageSchema.scala b/app/db/impl/schema/PageSchema.scala index 8953ddde8..8b63a3388 100644 --- a/app/db/impl/schema/PageSchema.scala +++ b/app/db/impl/schema/PageSchema.scala @@ -15,7 +15,7 @@ class PageSchema(override val service: ModelService) override def like(page: Page): Future[Option[Page]] = { this.service.find[Page](this.modelClass, p => - p.projectId === page.projectId && p.name.toLowerCase === page.name.toLowerCase + p.projectId === page.projectId && p.name.toLowerCase === page.name.toLowerCase && p.parentId === page.parentId ) } diff --git a/app/models/project/Page.scala b/app/models/project/Page.scala index f65ac210f..4c56c682c 100644 --- a/app/models/project/Page.scala +++ b/app/models/project/Page.scala @@ -11,10 +11,11 @@ import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension import com.vladsch.flexmark.ext.tables.TablesExtension import com.vladsch.flexmark.ext.typographic.TypographicExtension -import com.vladsch.flexmark.html.renderer.{LinkStatus, LinkType, NodeRendererContext, ResolvedLink} +import com.vladsch.flexmark.ext.wikilink.{WikiLink, WikiLinkExtension} +import com.vladsch.flexmark.html.renderer._ import com.vladsch.flexmark.html.{HtmlRenderer, LinkResolver, LinkResolverFactory} import com.vladsch.flexmark.parser.Parser -import com.vladsch.flexmark.util.options.MutableDataSet +import com.vladsch.flexmark.util.options.{MutableDataHolder, MutableDataSet} import db.access.ModelAccess import db.impl.OrePostgresDriver.api._ import db.impl.PageTable @@ -22,6 +23,7 @@ import db.impl.model.OreModel import db.impl.schema.PageSchema import db.impl.table.ModelKeys._ import db.{ModelFilter, Named} +import models.project import ore.permission.scope.ProjectScope import ore.{OreConfig, Visitable} import play.twirl.api.Html @@ -99,14 +101,34 @@ case class Page(override val id: Option[Int] = None, * * @return HTML representation */ - def html: Html = Render(this.contents) + def html: Html = RenderPage(this) /** * Returns true if this is the home page. * * @return True if home page */ - def isHome: Boolean = this.name.equals(HomeName) + def isHome: Boolean = this.name.equals(HomeName) && parentId == -1 + + /** + * Get Project associated with page. + * + * @return Optional Project + */ + def parentProject: Option[Project] = this.projectBase.get(projectId) + + /** + * + * @return + */ + def parentPage: Option[Page] = if (parentProject.isDefined) { parentProject.get.pages.find(ModelFilter[Page](_.id === parentId).fn).lastOption } else { None } + + /** + * Get the /:parent/:child + * + * @return String + */ + def fullSlug: String = if (parentPage.isDefined) { s"${parentPage.get.name}/${slug}" } else { slug } /** * Returns access to this Page's children (if any). @@ -116,7 +138,7 @@ case class Page(override val id: Option[Int] = None, def children: ModelAccess[Page] = this.service.access[Page](classOf[Page], ModelFilter[Page](_.parentId === this.id.get)) - override def url: String = this.project.url + "/pages/" + this.slug + override def url: String = this.project.url + "/pages/" + this.fullSlug override def copyWith(id: Option[Int], theTime: Option[Timestamp]) = this.copy(id = id, createdAt = theTime) } @@ -132,13 +154,13 @@ object Page { override def affectsGlobalScope() = false - override def create(context: NodeRendererContext) = new ExternalLinkResolver(this.config) + override def create(context: LinkResolverContext) = new ExternalLinkResolver(this.config) } } private class ExternalLinkResolver(config: OreConfig) extends LinkResolver { - override def resolveLink(node: Node, context: NodeRendererContext, link: ResolvedLink): ResolvedLink = { + override def resolveLink(node: Node, context: LinkResolverContext, link: ResolvedLink): ResolvedLink = { if (link.getLinkType.equals(LinkType.IMAGE) || node.isInstanceOf[MailLink]) { link } else { @@ -191,7 +213,8 @@ object Page { StrikethroughExtension.create(), TaskListExtension.create(), TablesExtension.create(), - TypographicExtension.create() + TypographicExtension.create(), + WikiLinkExtension.create() )) (Parser.builder(options).build(), HtmlRenderer.builder(options) @@ -206,6 +229,19 @@ object Page { Html(htmlRenderer.render(markdownParser.parse(markdown))) } + def RenderPage(page: Page)(implicit config: OreConfig): Html = { + if (linkResolver.isEmpty) + linkResolver = Some(new ExternalLinkResolver.Factory(config)) + + val options = new MutableDataSet().set[String](WikiLinkExtension.LINK_ESCAPE_CHARS, " +<>") + val project = page.parentProject + + if (project.isDefined) + options.set[String](WikiLinkExtension.LINK_PREFIX, s"/${project.get.ownerName}/${project.get.slug}/pages/") + + Html(htmlRenderer.withOptions(options).render(markdownParser.parse(page._contents))) + } + /** * The name of each Project's homepage. */ diff --git a/app/ore/rest/OreRestfulApi.scala b/app/ore/rest/OreRestfulApi.scala index c42c2b4e8..82d3261ee 100755 --- a/app/ore/rest/OreRestfulApi.scala +++ b/app/ore/rest/OreRestfulApi.scala @@ -119,7 +119,14 @@ trait OreRestfulApi { } else { result = pages.toSeq } - result + Some(toJson(result.map(page => obj( + "createdAt" -> page.createdAt, + "id" -> page.id, + "name" -> page.name, + "parentId" -> page.parentId, + "slug" -> page.slug, + "fullSlug" -> page.fullSlug + )))) } map { toJson(_) } diff --git a/app/views/projects/pages/view.scala.html b/app/views/projects/pages/view.scala.html index 8553fe24a..89c1b13b6 100755 --- a/app/views/projects/pages/view.scala.html +++ b/app/views/projects/pages/view.scala.html @@ -15,11 +15,12 @@ @import models.project.Page @(model: Project, page: Page, + forceHideEditor: Boolean = false, editorOpen: Boolean = false)(implicit messages: Messages, request: Request[_], flash: Flash, service: ModelService, config: OreConfig, userBase: UserBase) @canEditPages = @{ - userBase.current.isDefined && (userBase.current.get can EditPages in model) + userBase.current.isDefined && (userBase.current.get can EditPages in model) && !forceHideEditor } @projects.view(model, "#docs") { @@ -98,7 +99,7 @@

@messages("page.plural")

} } - + @pg.name diff --git a/build.sbt b/build.sbt index f036c124f..6050d2f1c 100755 --- a/build.sbt +++ b/build.sbt @@ -32,13 +32,14 @@ libraryDependencies ++= Seq( "org.bouncycastle" % "bcpg-jdk15on" % "1.56", "javax.mail" % "mail" % "1.4.7", - "com.vladsch.flexmark" % "flexmark" % "0.18.2", - "com.vladsch.flexmark" % "flexmark-ext-autolink" % "0.18.2", - "com.vladsch.flexmark" % "flexmark-ext-anchorlink" % "0.18.2", - "com.vladsch.flexmark" % "flexmark-ext-gfm-strikethrough" % "0.18.2", - "com.vladsch.flexmark" % "flexmark-ext-gfm-tasklist" % "0.18.2", - "com.vladsch.flexmark" % "flexmark-ext-tables" % "0.18.2", - "com.vladsch.flexmark" % "flexmark-ext-typographic" % "0.18.2", + "com.vladsch.flexmark" % "flexmark" % "0.32.4", + "com.vladsch.flexmark" % "flexmark-ext-autolink" % "0.32.4", + "com.vladsch.flexmark" % "flexmark-ext-anchorlink" % "0.32.4", + "com.vladsch.flexmark" % "flexmark-ext-gfm-strikethrough" % "0.32.4", + "com.vladsch.flexmark" % "flexmark-ext-gfm-tasklist" % "0.32.4", + "com.vladsch.flexmark" % "flexmark-ext-tables" % "0.32.4", + "com.vladsch.flexmark" % "flexmark-ext-typographic" % "0.32.4", + "com.vladsch.flexmark" % "flexmark-ext-wikilink" % "0.32.4", "org.webjars" % "jquery" % "2.2.4", "org.webjars" % "font-awesome" % "4.7.0", diff --git a/conf/evolutions/default/89.sql b/conf/evolutions/default/89.sql new file mode 100644 index 000000000..be694ecce --- /dev/null +++ b/conf/evolutions/default/89.sql @@ -0,0 +1,13 @@ +# --- !Ups +ALTER TABlE project_pages DROP CONSTRAINT IF EXISTS pages_project_id_name_key; +ALTER TABlE project_pages DROP CONSTRAINT IF EXISTS pages_project_id_slug_key; + +# --- !Downs +DELETE FROM project_pages WHERE (project_id, slug) IN + (SELECT project_id, slug FROM project_pages GROUP BY project_id, slug HAVING COUNT(*) > 1); + +DELETE FROM project_pages WHERE (project_id, name) IN + (SELECT project_id, name FROM project_pages GROUP BY project_id, name HAVING COUNT(*) > 1); + +ALTER TABlE project_pages ADD CONSTRAINT pages_project_id_name_key UNIQUE(project_id, name); +ALTER TABlE project_pages ADD CONSTRAINT pages_project_id_slug_key UNIQUE(project_id, slug); diff --git a/conf/routes b/conf/routes index ea1112063..c4863e525 100755 --- a/conf/routes +++ b/conf/routes @@ -142,10 +142,10 @@ GET /:author/:slug/icon/pending @controllers # ---------- Pages ---------- -GET /:author/:slug/pages/:page/edit @controllers.project.Pages.showEditor(author, slug, page) -POST /:author/:slug/pages/:page/edit @controllers.project.Pages.save(author, slug, page) -POST /:author/:slug/pages/:page/delete @controllers.project.Pages.delete(author, slug, page) -GET /:author/:slug/pages/:page @controllers.project.Pages.show(author, slug, page) +GET /:author/:slug/pages/*page/edit @controllers.project.Pages.showEditor(author, slug, page) +POST /:author/:slug/pages/*page/edit @controllers.project.Pages.save(author, slug, page) +POST /:author/:slug/pages/*page/delete @controllers.project.Pages.delete(author, slug, page) +GET /:author/:slug/pages/*page @controllers.project.Pages.show(author, slug, page) # ---------- Channels ---------- diff --git a/public/javascripts/pageCollapse.js b/public/javascripts/pageCollapse.js index d3faee137..6567a5009 100644 --- a/public/javascripts/pageCollapse.js +++ b/public/javascripts/pageCollapse.js @@ -22,7 +22,7 @@ function bindExpand(e) { '' ); var link = childPage.find('a'); - link.attr("href", '/' + namespace + '/pages/' + page.name); + link.attr("href", '/' + namespace + '/pages/' + page.fullSlug); link.text(page.name); // this will sanitize the input div.append(childPage); } diff --git a/public/javascripts/pageEdit.js b/public/javascripts/pageEdit.js index c2b3c4600..b23f0ed0a 100644 --- a/public/javascripts/pageEdit.js +++ b/public/javascripts/pageEdit.js @@ -49,6 +49,8 @@ $(function() { var parentId = -1; if (parent.length) { parentId = parent.val(); + if (parentId != -1) + url = '/' + PROJECT_OWNER + '/' + PROJECT_SLUG + '/pages/' + parent.text() + '/' + pageName + '/edit'; } $.ajax({ method: 'post', From 456cceb4b899fa4c4a22d9f235718f499cab8a38 Mon Sep 17 00:00:00 2001 From: Ewout Van Schil Date: Thu, 22 Feb 2018 19:58:05 +0100 Subject: [PATCH 63/77] Fix #445 --- app/views/users/admin/reviews.scala.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/views/users/admin/reviews.scala.html b/app/views/users/admin/reviews.scala.html index e8f6e553a..001d20826 100644 --- a/app/views/users/admin/reviews.scala.html +++ b/app/views/users/admin/reviews.scala.html @@ -10,6 +10,9 @@ @(reviews: Seq[Review])(implicit messages: Messages, request: Request[_], service: ModelService, config: OreConfig, users: UserBase, project: Project, version: Version) +@projectRoutes = @{controllers.project.routes.Projects} +@versionRoutes = @{controllers.project.routes.Versions} + @bootstrap.layout(messages("review.title")) { @@ -28,6 +31,10 @@

@project.name @version.versionString

@if(!version.isReviewed) {
+ + Project Page + Download File + @if(version.mostRecentUnfinishedReview.isDefined) { @if(users.current == users.get(version.mostRecentUnfinishedReview.get.userId)) { From 2ca0d4623cee0834da1ac6ba16742c9351a586f5 Mon Sep 17 00:00:00 2001 From: Ewout Van Schil Date: Thu, 22 Feb 2018 21:38:53 +0100 Subject: [PATCH 64/77] Fix #446 --- app/views/users/admin/activity.scala.html | 4 ++-- app/views/users/admin/queue.scala.html | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/views/users/admin/activity.scala.html b/app/views/users/admin/activity.scala.html index bbae7f6f5..631d2b2a8 100644 --- a/app/views/users/admin/activity.scala.html +++ b/app/views/users/admin/activity.scala.html @@ -38,7 +38,7 @@

@messages("activity.title")

@activities.map { tuple => @if(tuple._1.isInstanceOf[Review]) { - Review approved: + Review approved @prettifyDateAndTime(tuple._1.asInstanceOf[Review].endedAt.getOrElse(Timestamp.from(Instant.EPOCH))) for: @if(tuple._2.isDefined) { @@ -52,7 +52,7 @@

@messages("activity.title")

} @if(tuple._1.isInstanceOf[Flag]) { - Flag resolved: + Flag resolved @prettifyDateAndTime(tuple._1.asInstanceOf[Flag].resolvedAt.getOrElse(Timestamp.from(Instant.EPOCH))) for: @if(tuple._2.isDefined) { diff --git a/app/views/users/admin/queue.scala.html b/app/views/users/admin/queue.scala.html index e18d5c777..ae22817ef 100755 --- a/app/views/users/admin/queue.scala.html +++ b/app/views/users/admin/queue.scala.html @@ -10,6 +10,8 @@ @(underReview: Seq[(Project, Version)], versions: Seq[(Project, Version)])(implicit messages: Messages, request: Request[_], service: ModelService, config: OreConfig, users: UserBase) +@import java.sql.Timestamp +@import java.time.Instant @versionRoutes = @{controllers.project.routes.Versions} @currentUserId = @{users.current.get.id.get} @@ -66,6 +68,8 @@

@messages("queue.review.none")

@tuple._2.author.get.name + } else { + Unknown }
@prettifyDateAndTime(tuple._2.createdAt.get) @@ -85,11 +89,11 @@

@messages("queue.review.none")

@if(currentReview.isDefined) { @users.get(currentReview.get.userId).get.name
- + } else { @users.get(tuple._2.mostRecentReviews.last.userId).get.name
- + } From 075492aa4081cde4bff63a9e3f8e5e6c6b49f925 Mon Sep 17 00:00:00 2001 From: Ewout Van Schil Date: Mon, 26 Feb 2018 20:32:09 +0100 Subject: [PATCH 65/77] Add trigger to clean old project_views --- conf/evolutions/default/90.sql | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 conf/evolutions/default/90.sql diff --git a/conf/evolutions/default/90.sql b/conf/evolutions/default/90.sql new file mode 100644 index 000000000..2715f32a0 --- /dev/null +++ b/conf/evolutions/default/90.sql @@ -0,0 +1,20 @@ +# --- !Ups + +CREATE OR REPLACE FUNCTION delete_old_project_views() + RETURNS trigger AS +$$ +BEGIN + DELETE FROM project_views WHERE created_at < current_date - interval '30' day;; + RETURN NEW;; +END +$$ LANGUAGE plpgsql; + +CREATE TRIGGER clean_old_project_views AFTER INSERT + ON project_views + FOR EACH STATEMENT +EXECUTE PROCEDURE delete_old_project_views(); + +# --- !Downs + +DROP TRIGGER clean_old_project_views ON project_views; +DROP FUNCTION delete_old_project_views(); From 7fb278fb47400e9cf1644f3ca1a98564b489f6d5 Mon Sep 17 00:00:00 2001 From: Progwml6 Date: Mon, 26 Feb 2018 15:42:16 -0500 Subject: [PATCH 66/77] Bump version for release --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6050d2f1c..39a039e5c 100755 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ name := "ore" -version := "1.5.5" +version := "1.5.6" lazy val `ore` = (project in file(".")).enablePlugins(PlayScala) From 5d11df1d3ebaee955739e30533fe5975cc5fa00c Mon Sep 17 00:00:00 2001 From: Jadon Fowler Date: Mon, 26 Feb 2018 21:42:03 -0800 Subject: [PATCH 67/77] Remove Privileges from non-Ore Roles They don't need any permissions. Closes #454 Signed-off-by: Jadon Fowler --- app/ore/permission/role/RoleTypes.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/ore/permission/role/RoleTypes.scala b/app/ore/permission/role/RoleTypes.scala index a59a7226c..d9370af9d 100644 --- a/app/ore/permission/role/RoleTypes.scala +++ b/app/ore/permission/role/RoleTypes.scala @@ -17,14 +17,14 @@ object RoleTypes extends Enumeration { val Admin = new RoleType( 0, 61, classOf[GlobalRole], Absolute, "Ore Admin", Red) val Mod = new RoleType( 1, 62, classOf[GlobalRole], Lifted, "Ore Moderator", Aqua) - val SpongeLeader = new RoleType( 2, 44, classOf[GlobalRole], Absolute, "Sponge Leader", Amber) - val TeamLeader = new RoleType( 3, 58, classOf[GlobalRole], Standard, "Team Leader", Amber) - val CommunityLeader = new RoleType( 4, 59, classOf[GlobalRole], Standard, "Community Leader", Amber) - val Staff = new RoleType( 5, 3, classOf[GlobalRole], Standard, "Sponge Staff", Amber) - val SpongeDev = new RoleType( 6, 41, classOf[GlobalRole], Standard, "Sponge Developer", Green) - val WebDev = new RoleType( 7, 45, classOf[GlobalRole], Standard, "Web Developer", Blue) - val Scribe = new RoleType( 8, 51, classOf[GlobalRole], Limited, "Sponge Documenter", Aqua) - val Support = new RoleType( 9, 43, classOf[GlobalRole], Limited, "Sponge Support", Aqua) + val SpongeLeader = new RoleType( 2, 44, classOf[GlobalRole], Default, "Sponge Leader", Amber) + val TeamLeader = new RoleType( 3, 58, classOf[GlobalRole], Default, "Team Leader", Amber) + val CommunityLeader = new RoleType( 4, 59, classOf[GlobalRole], Default, "Community Leader", Amber) + val Staff = new RoleType( 5, 3, classOf[GlobalRole], Default, "Sponge Staff", Amber) + val SpongeDev = new RoleType( 6, 41, classOf[GlobalRole], Default, "Sponge Developer", Green) + val WebDev = new RoleType( 7, 45, classOf[GlobalRole], Default, "Web Developer", Blue) + val Scribe = new RoleType( 8, 51, classOf[GlobalRole], Default, "Sponge Documenter", Aqua) + val Support = new RoleType( 9, 43, classOf[GlobalRole], Default, "Sponge Support", Aqua) val Contributor = new RoleType(10, 49, classOf[GlobalRole], Default, "Sponge Contributor", Green) val Adviser = new RoleType(11, 48, classOf[GlobalRole], Default, "Sponge Adviser", Aqua) From 64168f17785a44bd6c189ebba6190f9b75aa4460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Kleinekath=C3=B6fer?= Date: Tue, 27 Feb 2018 16:04:18 +0100 Subject: [PATCH 68/77] Fixed wiki link special characters edge case Co-authored-by: Ewout Van Schil --- app/controllers/project/Pages.scala | 21 ++++++++++--------- app/form/OreForms.scala | 1 + app/form/project/PageSaveForm.scala | 2 +- app/models/project/Page.scala | 2 +- .../projects/pages/modalPageCreate.scala.html | 2 +- app/views/projects/pages/view.scala.html | 9 ++++---- app/views/utils/editor.scala.html | 6 +++++- public/javascripts/main.js | 4 ++++ public/javascripts/pageEdit.js | 6 +++--- 9 files changed, 32 insertions(+), 21 deletions(-) diff --git a/app/controllers/project/Pages.scala b/app/controllers/project/Pages.scala index e8ff0cb34..dce447981 100755 --- a/app/controllers/project/Pages.scala +++ b/app/controllers/project/Pages.scala @@ -44,13 +44,13 @@ class Pages @Inject()(forms: OreForms, def withPage(project: Project, page: String): (Option[Page], Boolean) = { val parts = page.split("/") if (parts.size == 2) { - val parentId = project.pages.find(equalsIgnoreCase(_.name, parts(0))).map(_.id.getOrElse(-1)).getOrElse(-1) - val pages: Seq[Page] = project.pages.filter(equalsIgnoreCase(_.name, parts(1))).seq + val parentId = project.pages.find(equalsIgnoreCase(_.slug, parts(0))).map(_.id.getOrElse(-1)).getOrElse(-1) + val pages: Seq[Page] = project.pages.filter(equalsIgnoreCase(_.slug, parts(1))).seq (pages.find(_.parentId == parentId), false) } else { - val result = project.pages.find((ModelFilter[Page](_.name === parts(0)) +&& ModelFilter[Page](_.parentId === -1)).fn) - if (!result.isDefined) { - (project.pages.find((ModelFilter[Page](_.name === parts(0))).fn), true) + val result = project.pages.find((ModelFilter[Page](_.slug === parts(0)) +&& ModelFilter[Page](_.parentId === -1)).fn) + if (result.isEmpty) { + (project.pages.find(ModelFilter[Page](_.slug === parts(0)).fn), true) } else { (result, false) } @@ -91,9 +91,10 @@ class Pages @Inject()(forms: OreForms, var parentId = -1 if (parts.size == 2) { pageName = parts(1) - parentId = project.pages.find(equalsIgnoreCase(_.name, parts(0))).map(_.id.getOrElse(-1)).getOrElse(-1) + parentId = project.pages.find(equalsIgnoreCase(_.slug, parts(0))).map(_.id.getOrElse(-1)).getOrElse(-1) } - val pageModel = project.getOrCreatePage(pageName, parentId) + val optionPage = project.pages.find(equalsIgnoreCase(_.slug, pageName)) + val pageModel = optionPage.getOrElse(project.getOrCreatePage(pageName, parentId)) Ok(views.view(project, pageModel, editorOpen = true)) } @@ -130,10 +131,10 @@ class Pages @Inject()(forms: OreForms, Redirect(self.show(author, slug, page)).withError("error.minLength") } else { val parts = page.split("/") - var pageName = parts(0) + var pageName = pageData.name.getOrElse(parts(0)) if (parts.size == 2) { - pageName = parts(1) - parentId = project.pages.find(equalsIgnoreCase(_.name, parts(0))).map(_.id.getOrElse(-1)).getOrElse(-1) + pageName = pageData.name.getOrElse(parts(1)) + parentId = project.pages.find(equalsIgnoreCase(_.slug, parts(0))).map(_.id.getOrElse(-1)).getOrElse(-1) } val pageModel = project.getOrCreatePage(pageName, parentId) pageData.content.map(pageModel.contents = _) diff --git a/app/form/OreForms.scala b/app/form/OreForms.scala index da1c6369b..9d2dec28e 100755 --- a/app/form/OreForms.scala +++ b/app/form/OreForms.scala @@ -168,6 +168,7 @@ class OreForms @Inject()(implicit config: OreConfig, factory: ProjectFactory, se */ lazy val PageEdit = Form(mapping( "parent-id" -> optional(number), + "name" -> optional(text), "content" -> optional(text( maxLength = MaxLength )))(PageSaveForm.apply)(PageSaveForm.unapply)) diff --git a/app/form/project/PageSaveForm.scala b/app/form/project/PageSaveForm.scala index 31487684f..30794769f 100644 --- a/app/form/project/PageSaveForm.scala +++ b/app/form/project/PageSaveForm.scala @@ -1,3 +1,3 @@ package form.project -case class PageSaveForm(parentId: Option[Int], content: Option[String]) +case class PageSaveForm(parentId: Option[Int], name: Option[String], content: Option[String]) diff --git a/app/models/project/Page.scala b/app/models/project/Page.scala index 4c56c682c..d6becdcc1 100644 --- a/app/models/project/Page.scala +++ b/app/models/project/Page.scala @@ -128,7 +128,7 @@ case class Page(override val id: Option[Int] = None, * * @return String */ - def fullSlug: String = if (parentPage.isDefined) { s"${parentPage.get.name}/${slug}" } else { slug } + def fullSlug: String = if (parentPage.isDefined) { s"${parentPage.get.slug}/${slug}" } else { slug } /** * Returns access to this Page's children (if any). diff --git a/app/views/projects/pages/modalPageCreate.scala.html b/app/views/projects/pages/modalPageCreate.scala.html index a76e892d3..01ce75374 100644 --- a/app/views/projects/pages/modalPageCreate.scala.html +++ b/app/views/projects/pages/modalPageCreate.scala.html @@ -44,7 +44,7 @@

@messages("project.page.parent")

diff --git a/app/views/projects/pages/view.scala.html b/app/views/projects/pages/view.scala.html index 89c1b13b6..3299f0764 100755 --- a/app/views/projects/pages/view.scala.html +++ b/app/views/projects/pages/view.scala.html @@ -42,13 +42,14 @@
@utils.alert("error") @utils.editor( - saveCall = routes.Pages.save(model.ownerName, model.slug, page.name), - deleteCall = routes.Pages.delete(model.ownerName, model.slug, page.name), + saveCall = routes.Pages.save(model.ownerName, model.slug, page.fullSlug), + deleteCall = routes.Pages.delete(model.ownerName, model.slug, page.fullSlug), deletable = !page.isHome, enabled = canEditPages, raw = page.contents, cooked = page.html, - subject = "Page") + subject = "Page", + extraFormValue = page.name)
@@ -107,7 +108,7 @@

@messages("page.plural")

@pg.children.sorted(_.name).map { child =>
  • - + @child.name
  • diff --git a/app/views/utils/editor.scala.html b/app/views/utils/editor.scala.html index 74cee99be..7ce7ad5da 100644 --- a/app/views/utils/editor.scala.html +++ b/app/views/utils/editor.scala.html @@ -9,7 +9,8 @@ cooked: Html = Html(""), subject: String = null, cancellable: Boolean = true, - targetForm: String = null)(implicit messages: Messages, request: Request[_]) + targetForm: String = null, + extraFormValue: String = null)(implicit messages: Messages, request: Request[_]) @if(enabled) { @@ -91,6 +92,9 @@ @if(savable) { @form(action = saveCall, 'id -> "form-editor-save") { @CSRF.formField + @if(extraFormValue != null) { + + } } } diff --git a/public/javascripts/main.js b/public/javascripts/main.js index f5cc08e8a..35c912344 100755 --- a/public/javascripts/main.js +++ b/public/javascripts/main.js @@ -75,6 +75,10 @@ function initTooltips() { }); } +function slugify(name) { + return name.trim().replace(/ +/g, ' ').replace(/ /g, '-'); +} + /* * ================================================== * = Google Analytics = diff --git a/public/javascripts/pageEdit.js b/public/javascripts/pageEdit.js index b23f0ed0a..bcbed5ece 100644 --- a/public/javascripts/pageEdit.js +++ b/public/javascripts/pageEdit.js @@ -44,18 +44,18 @@ $(function() { $('#continue-page').click(function() { var pageName = $('#page-name').val(); - var url = '/' + PROJECT_OWNER + '/' + PROJECT_SLUG + '/pages/' + pageName + '/edit'; + var url = '/' + PROJECT_OWNER + '/' + PROJECT_SLUG + '/pages/' + slugify(pageName) + '/edit'; var parent = $('.select-parent').find(':selected'); var parentId = -1; if (parent.length) { parentId = parent.val(); if (parentId != -1) - url = '/' + PROJECT_OWNER + '/' + PROJECT_SLUG + '/pages/' + parent.text() + '/' + pageName + '/edit'; + url = '/' + PROJECT_OWNER + '/' + PROJECT_SLUG + '/pages/' + parent.data('slug') + '/' + slugify(pageName) + '/edit'; } $.ajax({ method: 'post', url: url, - data: {csrfToken: csrf, 'parent-id': parentId, 'content': '# ' + pageName + '\n'}, + data: {csrfToken: csrf, 'parent-id': parentId, 'content': '# ' + pageName + '\n', 'name': pageName}, success: function() { go(url); }, From 98ade7150fb1a377f4c074fe598f21d51caad0ba Mon Sep 17 00:00:00 2001 From: Ewout Van Schil Date: Tue, 27 Feb 2018 22:31:42 +0100 Subject: [PATCH 69/77] Fix #456 --- app/views/projects/log.scala.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/log.scala.html b/app/views/projects/log.scala.html index 99311db6e..b06e71106 100644 --- a/app/views/projects/log.scala.html +++ b/app/views/projects/log.scala.html @@ -32,7 +32,7 @@

    @messages("project.log.visibility.title")

    State Time Comment - Resolved by + Set by @@ -44,8 +44,8 @@

    @messages("project.log.visibility.title")

    @VisibilityTypes.withId(entry.visibility) @prettifyDateAndTime(entry.createdAt.getOrElse(Timestamp.from(Instant.now()))) @entry.renderComment() - @if(entry.resolvedBy.isDefined) { - @users.get(entry.resolvedBy.get).map(_.username).getOrElse("Unknown") + @if(entry.createdBy.isDefined) { + @users.get(entry.createdBy.get).map(_.username).getOrElse("Unknown") } else { Unknown } From 204db600d997d8ec19eed0cf976e2ee750de7c58 Mon Sep 17 00:00:00 2001 From: Ewout Van Schil Date: Wed, 28 Feb 2018 11:55:58 +0100 Subject: [PATCH 70/77] Redirect to project when state changes on user action --- app/controllers/project/Projects.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/project/Projects.scala b/app/controllers/project/Projects.scala index a3ffb3502..095878ad6 100755 --- a/app/controllers/project/Projects.scala +++ b/app/controllers/project/Projects.scala @@ -528,7 +528,7 @@ class Projects @Inject()(stats: StatTracker, if (project.visibility == VisibilityTypes.New) { project.setVisibility(VisibilityTypes.Public, "", request.user) } - Redirect(ShowHome) + Redirect(self.show(project.ownerName, project.slug)) } /** @@ -542,7 +542,7 @@ class Projects @Inject()(stats: StatTracker, if (project.visibility == VisibilityTypes.NeedsChanges) { project.setVisibility(VisibilityTypes.NeedsApproval, "", request.user) } - Redirect(ShowHome) + Redirect(self.show(project.ownerName, project.slug)) } def showLog(author: String, slug: String) = { From 3b5b2842ce99706935b7e4959490544838e38d87 Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 28 Feb 2018 11:24:34 -0500 Subject: [PATCH 71/77] Bump version for release --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 39a039e5c..983a27875 100755 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ name := "ore" -version := "1.5.6" +version := "1.5.7" lazy val `ore` = (project in file(".")).enablePlugins(PlayScala) From 3355dbdf6944c6f7c2230d7df48dfebff75dd072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Kleinekath=C3=B6fer?= Date: Wed, 28 Feb 2018 17:40:29 +0100 Subject: [PATCH 72/77] Trim page name and bump version --- build.sbt | 2 +- public/javascripts/pageEdit.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 983a27875..f7284d360 100755 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ name := "ore" -version := "1.5.7" +version := "1.5.8" lazy val `ore` = (project in file(".")).enablePlugins(PlayScala) diff --git a/public/javascripts/pageEdit.js b/public/javascripts/pageEdit.js index bcbed5ece..c89bf4b18 100644 --- a/public/javascripts/pageEdit.js +++ b/public/javascripts/pageEdit.js @@ -43,7 +43,7 @@ $(function() { }); $('#continue-page').click(function() { - var pageName = $('#page-name').val(); + var pageName = $('#page-name').val().trim(); var url = '/' + PROJECT_OWNER + '/' + PROJECT_SLUG + '/pages/' + slugify(pageName) + '/edit'; var parent = $('.select-parent').find(':selected'); var parentId = -1; From 7fc2e51e41e578404fe1690e3289f36c61077f64 Mon Sep 17 00:00:00 2001 From: Ewout Van Schil Date: Thu, 1 Mar 2018 12:58:35 +0100 Subject: [PATCH 73/77] Improve user stars navigation --- app/views/users/projects.scala.html | 3 +-- public/javascripts/userPage.js | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/views/users/projects.scala.html b/app/views/users/projects.scala.html index 1f024254e..6f482370a 100755 --- a/app/views/users/projects.scala.html +++ b/app/views/users/projects.scala.html @@ -42,7 +42,7 @@

    @messages("project.starred")<
    @star.recommendedVersion.versionString @defining(star.category) { category => - + }
    @@ -54,7 +54,6 @@

    @messages("project.starred")<