package utils import org.apache.commons.lang3.{ArrayUtils, StringUtils} import play.mvc.{Call, Http} import org.joda.time.DateTimeConstants import org.apache.commons.io.FilenameUtils import play.i18n.Messages import controllers.{Application, UserApp, routes} import views.html._ import java.net.URI import playRepository.DiffLine import playRepository.DiffLineType import models.CodeRange.Side import views.html.partial_diff_comment_on_line import views.html.partial_diff_line import views.html.git.partial_pull_request_event import models._ import java.net.URLEncoder import java.util import java.util.Date import scala.annotation.tailrec import playRepository.FileDiff import play.api.i18n.Lang import play.twirl.api.Html import collection.convert.wrapAll._ import scala.util.control.Breaks._ object TemplateHelper { def isAllowedOAuthProvider(provider: String): Boolean = { val allowedProviders = play.Configuration.root.getString("application.social.login.support", "").replaceAll(" ", "").split(",") allowedProviders.toStream.contains(provider) } def showWatchers(posting: AbstractPosting): String = { "
" + "" + "
" } def GithubLogo: String = {""""""} def GoogleLogo: String = { val url = routes.Assets.at("images/provider-logo/btn_google_light_normal_ios.svg") s"""""" } def providerWithLogo(provider:String): String = { val googleLogo = routes.Assets.at("images/provider-logo/btn_google_light_normal_ios.svg") provider match { case "github" => s"""""" case "google" => s"""""" case _ => "" } } def buildQueryString(call: Call, queryMap: Map[String, String]): String = { val baseUrl = call.toString var prefix = "?" var query = "" if ((baseUrl indexOf "?") != -1) { prefix = "&" } queryMap.map { v => query += v._1 + "=" + v._2 + "&" } baseUrl + prefix + query.dropRight(1) } def buildAttrString(attrMap: java.util.Map[String, String]): String = { var attr = "" attrMap.map { v => attr += v._1 + "=" + v._2 + " " } attr.dropRight(1) } def agoString(duration: org.joda.time.Duration) = { if (duration != null){ val sec = duration.getMillis / DateTimeConstants.MILLIS_PER_SECOND sec match { case x if x >= 86400 => plural("common.time.day", duration.getStandardDays) case x if x >= 3600 => plural("common.time.hour", duration.getStandardHours) case x if x >= 60 => plural("common.time.minute", duration.getStandardMinutes) case x if x > 0 => plural("common.time.second", duration.getStandardSeconds) case x if x == null => "" case _ => Messages.get("common.time.just") } } else { "" } } def agoOrDateString(date: java.util.Date) = { var year = JodaDateUtil.getDateString(date, "yyyy") var thisYear = JodaDateUtil.getDateString(new Date(), "yyyy") val ago = JodaDateUtil.ago(date) if (ago.getStandardDays < 8) { agoString(ago) } else if (thisYear.equals(year)) { JodaDateUtil.getDateString(date, "MM-dd") } else { JodaDateUtil.getDateString(date, "yyyy-MM-dd") } } def plural(key: String, count: Number): String = { var _key = key if (count != 1) _key = key + "s" Messages.get(_key, count.toString) } def urlToPicture(email: String, size: Int = 64) = { GravatarUtil.getAvatar(email, size) } def simpleForm(elements: helper.FieldElements) = { elements.input } def getJSPath: String = { routes.Assets.at("javascripts/").toString } def nullOrEquals(a: String, b: String) = (a == null || b == null) || a.equals(b) def ifElse(condition:Boolean, a: String, b: String): String = { if(condition){ a } else { b } } def equalsThen(a: String, b: String, thenStr: String): String = { if(a != null && b != null && StringUtils.equals(a, b)){ thenStr } else { "" } } def getPort(uri: URI) = { val port = uri.getPort port match { case -1 => uri.toURL.getDefaultPort case _ => port } } // Whether the given uris are pointing the same resource. def resourceEquals(a: URI, b: URI) = nullOrEquals(a.getHost, b.getHost) && getPort(a) == getPort(b) && StringUtils.equals(a.getPath, b.getPath) // Get the url to return to the list page from the view page. // Return the referrer if the it is the uri for the list page, an/ return the // default uri if not. def urlToList(referrer: String, defaultURI: String) = { def fullURI(u: String) = Config.createFullURI(u).normalize referrer match { case (uri: String) if resourceEquals(fullURI(uri), fullURI(defaultURI)) => uri case (_) => defaultURI } } def getUserAvatarUrl(user: models.User, avatarSize: Int): String = { if (user.avatarUrl == UserApp.DEFAULT_AVATAR_URL) { urlToPicture(user.email, avatarSize) } else { user.avatarUrl } } def getUserAvatar(user: models.User, avatarSize:String = "small"): String = { user.refresh() var userInfoURL = routes.UserApp.userInfo(user.loginId).toString() "" } def urlToProjectBG(project: Project) = { models.Attachment.findByContainer(project.asResource) match { case files if files.size > 0 => routes.AttachmentApp.getFile(files.head.id) case _ => routes.Assets.at("images/project_default.jpg") } } def urlToProjectLogo(project: Project) = { models.Attachment.findByContainer(project.asResource) match { case files if files.size > 0 => routes.AttachmentApp.getFile(files.head.id) case _ => routes.Assets.at("images/project_default_logo.png") } } def hasProjectLogo(project: Project) = { models.Attachment.findByContainer(project.asResource) match { case files if files.size > 0 => true case _ => false } } /** * get branch item name * @param branch * @return */ def branchItemName(branch:String) = { Branches.itemName(branch) } object Branches { def itemType(branch: String): String = { val names = branch.split('/').toList names match { case "refs" :: "heads" :: _ => "branch" case "refs" :: "tags" :: _ => "tag" case "refs" :: name :: _ => name case _ => branch } } def itemName(branch: String): String = { val names = branch.split("/", 3).toList names match { case "refs" :: branchType :: branchName => branchName(0) case _ => branch } } def branchInHTML(branch: String) = { val names = branch.split('/') val branchType = itemType(branch) val branchName = itemName(branch) if(names(0).equals("refs") && names.length >= 3){ "" + branchType + "" + branchName } else { branch } } def getURL(viewType:String, project:Project, branchName:String, path:String) = viewType match { case "history" => routes.CodeHistoryApp.history(project.owner, project.name, URLEncoder.encode(branchName, "UTF-8"), null) case "code" => routes.CodeApp.codeBrowserWithBranch(project.owner, project.name, URLEncoder.encode(branchName, "UTF-8"), path) case _ => "#" } def getItemHTML(viewType:String, project:Project, branch:String, path:String, selectedBranch:String): String = { "
  • " + "" + branchInHTML(branch) + "" + "
  • " } } def urlToCompare(project: Project, compare: String) = { val commits = compare.split(PullRequest.DELIMETER) routes.CompareApp.compare(project.owner, project.name, commits(0), commits(1)).url } def getPercent(unit:Double, total:Double) = { ((unit / total) * 100).toInt } def makeIssuesLink(project:Project, param:Map[String,String]) = { buildQueryString( routes.IssueApp.issues(project.owner, project.name, "open"), param ) } def urlToOrganizationLogo(organization: Organization) = { models.Attachment.findByContainer(organization.asResource) match { case files if files.size > 0 => routes.AttachmentApp.getFile(files.head.id) case _ => routes.Assets.at("images/group_default.png") } } def hasOrganizationLogo(organization: Organization) = { models.Attachment.findByContainer(organization.asResource) match { case files if files.size > 0 => true case _ => false } } object DiffRenderer { def removedWord(word: String) = "" + word + "" def addedWord(word: String) = "" + word + "" def mergeList(a: List[String], b: List[String]) = { a.zip(b).map(v => v._1 + v._2) } /* def wordDiffLinesInHtml(diffList: List[Diff]): List[String] = diffList match { case Nil => List("", "") case head :: tail => mergeList(wordDiffLineInHtml(head), wordDiffLinesInHtml(tail)) } def wordDiffLineInHtml(diff: Diff) = diff.operation match { case DELETE => List(removedWord(diff.text), "") case INSERT => List("", addedWord(diff.text)) case _ => List(diff.text, diff.text) } def writeHtmlLine(klass: String, indicator: String, numA: Integer, numB: Integer, html: String, commentsOnLine: List[_ <: CodeCommentThread]) = { partial_diff_line_html(klass, indicator, numA, numB, html) + (if(commentsOnLine != null) partial_diff_comment_on_line(commentsOnLine).body else "") } def renderWordDiff(lineA: DiffLine, lineB: DiffLine, comments: Map[String, List[_ <: CodeCommentThread]]) = { val lines = wordDiffLinesInHtml((new DiffMatchPatch()).diffMain(lineA.content, lineB.content).toList) writeHtmlLine(lineA.kind.toString.toLowerCase, "-", null, lineA.numA + 1, lines(0), threadsOrEmpty(comments, threadKey(lineA.file.pathA, "remove", lineA.numA + 1))) + writeHtmlLine(lineB.kind.toString.toLowerCase, "+", lineB.numB + 1, null, lines(1), threadsOrEmpty(comments, threadKey(lineB.file.pathB, "add", lineB.numB + 1))) } */ /* Not implemented yet */ def renderWordDiff(lineA: DiffLine, lineB: DiffLine, comments: Map[String, List[CodeCommentThread]], isEndOfLineMissing: DiffLine => Boolean) = renderLine(lineA, comments, isEndOfLineMissing) + renderLine(lineB, comments, isEndOfLineMissing) def renderTwoLines(lineA: DiffLine, lineB: DiffLine, comments: Map[String, List[CodeCommentThread]], isEndOfLineMissing: DiffLine => Boolean) = (lineA.kind, lineB.kind) match { case (DiffLineType.REMOVE, DiffLineType.ADD) => renderWordDiff(lineA, lineB, comments, isEndOfLineMissing) case _ => renderLine(lineA, comments, isEndOfLineMissing) + renderLine(lineB, comments, isEndOfLineMissing) } def threadKey(path: String, side: Side, lineNum: Integer) = path + ":" + side + ":" + lineNum def threadsOrEmpty(threads: Map[String, List[CodeCommentThread]], key: String) = if (threads != null && threads.contains(key)) threads(key) else Nil def threadsOnAddLine(line: DiffLine, threads: Map[String, List[CodeCommentThread]]) = threadsOrEmpty(threads, threadKey(line.file.pathB, Side.B, line.numB + 1)) def threadsOnRemoveLine(line: DiffLine, threads: Map[String, List[CodeCommentThread]]) = threadsOrEmpty(threads, threadKey(line.file.pathA, Side.A, line.numA + 1)) def threadsOnContextLine(line: DiffLine, threads: Map[String, List[CodeCommentThread]]) = threadsOrEmpty(threads, threadKey(line.file.pathB, Side.B, line.numB + 1)) def indicator(line: DiffLine) = line.kind match { case DiffLineType.ADD => "+" case DiffLineType.REMOVE => "-" case _ => " " } val noNewlineAtEof = "(" + Messages.get("code.eolMissing") + ")" def eolMissingChecker(diff: FileDiff)(line: DiffLine) = line.kind match { case DiffLineType.REMOVE => (line.numA + 1) == diff.a.size && diff.a.isMissingNewlineAtEnd case _ => (line.numB + 1) == diff.b.size && diff.b.isMissingNewlineAtEnd } def renderLine(line: DiffLine, num: Integer, numA: Integer, numB: Integer, threads: List[CodeCommentThread], isEndOfLineMissing: DiffLine => Boolean) = partial_diff_line(line.kind.toString.toLowerCase, indicator(line), num, numA, numB, line.content, isEndOfLineMissing(line)) + partial_diff_comment_on_line(threads).body.trim def renderLine(line: DiffLine, threads: Map[String, List[CodeCommentThread]], isEndOfLineMissing: DiffLine => Boolean): String = line.kind match { case DiffLineType.ADD => renderLine(line, line.numB + 1, null, line.numB + 1, threadsOnAddLine(line, threads), isEndOfLineMissing) case DiffLineType.REMOVE => renderLine(line, line.numA + 1, line.numA + 1, null, threadsOnRemoveLine(line, threads), isEndOfLineMissing) case _ => renderLine(line, line.numB + 1, line.numA + 1, line.numB + 1, threadsOnContextLine(line, threads), isEndOfLineMissing) } @tailrec def _renderLines(progress: String, lines: List[DiffLine], comments: Map[String, List[CodeCommentThread]], isEndOfLineMissing: DiffLine => Boolean): String = lines match { case Nil => progress case first::Nil => progress + renderLine(first, comments, isEndOfLineMissing) case first::second::tail => _renderLines(progress + renderTwoLines(first, second, comments, isEndOfLineMissing), tail, comments, isEndOfLineMissing) } def renderLines(lines: List[DiffLine], comments: Map[String, List[CodeCommentThread]], isEndOfLineMissing: DiffLine => Boolean): String = _renderLines("", lines, comments, isEndOfLineMissing) def isAuthorComment(commentId: String) = if(commentId == UserApp.currentUser().loginId) "author" def shortId(commitId: String) = commitId.substring(0, Math.min(7, commitId.size)) @tailrec def renderNonRangedThreads(threads: List[models.CommentThread], commitId: String, html: play.twirl.api.Html): play.twirl.api.Html = threads match { case head :: tail => renderNonRangedThreads( tail, commitId, head match { case (thread: models.NonRangedCodeCommentThread) if commitId == null || commitId == thread.commitId => new Html(List(html, partial_comment_thread(thread))) case _ => html } ) case _ => html } @tailrec def _renderEventsOnPullRequest(pull: PullRequest, events: List[PullRequestEvent], html: play.twirl.api.Html): play.twirl.api.Html = events match { case head :: tail => _renderEventsOnPullRequest(pull, tail, new Html(List(html, partial_pull_request_event(pull, head)))) case _ => html } def renderEventsOnPullRequest(pull: PullRequest) = _renderEventsOnPullRequest(pull, pull.pullRequestEvents.toList, play.twirl.api.Html("")) def urlToCommentThread(thread: CommentThread) = { urlToContainer(thread) + "#thread-" + thread.id } def urlToContainer(thread: CommentThread) = { // Before access any field in thread.project, thread.pullRequest and // thread.pullRequest.project refresh() should be called because lazy // loading does not work for direct field access from Scala source files. // See http://www.playframework.com/documentation/2.2.x/JavaEbean if (thread.isOnPullRequest) { thread.pullRequest.refresh() thread.pullRequest.toProject.refresh() urlToPullRequest(thread, thread.pullRequest, thread.pullRequest.toProject) } else { thread.project.refresh() urlToCommit(thread, thread.project) } } def urlToPullRequest(thread: CommentThread, pullRequest: PullRequest, project: Project) = { thread match { case (t: CodeCommentThread) if t.isOnAllChangesOfPullRequest => routes.PullRequestApp.specificChange( project.owner, project.name, pullRequest.number, t.isOutdated match { case true => t.commitId // This link may occur 404 Not Found because the repository does not have the commit matches with the given commitId. case false => "" }) case (t: CodeCommentThread) if t.isOnChangesOfPullRequest => routes.PullRequestApp.specificChange(project.owner, project.name, pullRequest.number, t.commitId) case (t: models.NonRangedCodeCommentThread) if t.isOnChangesOfPullRequest => routes.PullRequestApp.specificChange(project.owner, project.name, pullRequest.number, t.commitId) case (t: CommentThread) => routes.PullRequestApp.pullRequestChanges(project.owner, project.name, pullRequest.number) case _ => "" } } def urlToCommit(thread: CommentThread, project: Project) = { thread match { case (t: models.NonRangedCodeCommentThread) => routes.CodeHistoryApp.show(project.owner, project.name, t.commitId) case (t: CodeCommentThread) => routes.CodeHistoryApp.show(project.owner, project.name, t.commitId) case _ => "" } } def urlToPostNewComment(thread: CommentThread) = { thread.project.refresh() if(thread.isOnPullRequest){ routes.PullRequestApp.newComment(thread.project.owner, thread.project.name, thread.pullRequest.id, _getCommitId(thread)) } else { routes.CodeHistoryApp.newComment(thread.project.owner, thread.project.name, _getCommitId(thread)) } } def _getCommitId(thread: CommentThread) = { thread match { case (t: CodeCommentThread) => t.commitId case (t: models.NonRangedCodeCommentThread) => t.commitId case _ => "" } } def getResourceType(thread: CommentThread) = { if(thread.isOnPullRequest){ models.enumeration.ResourceType.REVIEW_COMMENT } else { models.enumeration.ResourceType.COMMIT_COMMENT } } } object CodeBrowser { def fieldText(obj:com.fasterxml.jackson.databind.JsonNode, field:String):String = { if(obj.get(field) != null){ obj.get(field).textValue() } else { "" } } def getDataPath(listPath:String, fileName:String):String = { if(listPath == ""){ fileName }else{ getCorrectedPath(listPath, fileName) } } def getUserLink(userLoginId:String):String = { if(userLoginId != null && userLoginId != ""){ "/" + userLoginId } else { "javascript:void(); return false;" } } def getAvatar(file:com.fasterxml.jackson.databind.JsonNode):String = { val avatarURL = fieldText(file, "avatar") if(avatarURL != null){ "" } else { "" } } def getFileClass(file:com.fasterxml.jackson.databind.JsonNode):String = { if(fieldText(file, "name") == ".."){ "updir" } else { fieldText(file, "type") match { case "folder" => "dynatree-ico-cf" case _ => "dynatree-ico-c" } } } def getFileDate(file:com.fasterxml.jackson.databind.JsonNode, field:String)(implicit lang:Lang):String = { JodaDateUtil.momentFromNow(file.get(field).longValue, lang.language) } def getFileAgoOrDate(file:com.fasterxml.jackson.databind.JsonNode, field:String) = { agoOrDateString(new java.util.Date(file.get(field).longValue)) } def getCorrectedPath(filePath:String, fileName:String):String = { if(StringUtils.isNotEmpty(filePath) && (filePath.substring(filePath.length() - 1) == "/")){ filePath + getEncodeEachPathName(fileName) } else { filePath + "/" + getEncodeEachPathName(fileName) } } def getEncodeEachPathName(path: String): String ={ val paths = path.split("/") var encodedPaths = new Array[String](paths.length) for ( i <- 0 until paths.length ) { encodedPaths(i) = HttpUtil.encodeUrlString(paths(i)) } encodedPaths.mkString("/") } def getFileRev(vcsType:String, file:com.fasterxml.jackson.databind.JsonNode):String = { vcsType match { case "GIT" => fieldText(file,"commitId") case "Subversion" => fieldText(file, "revisionNo") case _ => "" } } } def countHtml(icon:String, link:String, count: Int, strong:String = "") = { Html(""" %d """.format(link, strong, icon, strong, count)) } def isMarkdownExtension(path: String):Boolean = { var ext = FilenameUtils.getExtension(path).toLowerCase() var markdownExtenstions = List("markdown", "mdown", "mkdn", "mkd", "md", "mdwn") markdownExtenstions.contains(ext) } def showHeaderWordsInBracketsIfExist(title: String) = { val prefixes = new StringBuilder for(prefix <- extractHeaderWordsInBrackets(title) if prefix.trim.indexOf("[") == 0 ){ if(prefix.contains("]")){ prefixes.append("" + prefix.trim + "") } } if(!madeByHeaderWordsOnly(title)){ Html(prefixes.toString) } } def removeHeaderWords(title: String):String = { if(madeByHeaderWordsOnly(title)){ return title } else { return title.replace(findHeaderWords(title).toString(),"") } } private def findHeaderWords(title: String): StringBuilder = { val prefixes = new StringBuilder for (prefix <- extractHeaderWordsInBrackets(title) if prefix.trim.indexOf("[") == 0) { if (prefix.contains("]")) { prefixes.append(prefix) } } prefixes } private def madeByHeaderWordsOnly(title: String): Boolean = { title.trim.indexOf("]") + 1 == title.trim.length || StringUtils.isEmpty(title.replace(findHeaderWords(title),"").trim) } def extractHeaderWordsInBrackets(title: String): Array[String] = { return title.split("(=\\[)|(?<=\\])") } def userInfo(loginId: String) = { Config.getContextRoot() + loginId } def containsInDefaultMenus(menuName: String) = { val menus = play.Configuration.root.getString("project.default.menus.when.create", "code, issue, pullRequest, review, milestone, board").replaceAll(" ", "").split(",") menus.toStream.contains(menuName) } }