<commit_before>/**
 * Copyright (c) 2012 <NAME> (born 1979)
 */

package controllers

import com.debiki.v0._
import debiki._
import debiki.DebikiHttp._
import java.{util => ju}
import play.api._
import play.api.mvc.{Action => _, _}
import play.api.Play.current
import ApiActions._
import SafeActions._
import Prelude._
import Utils.ValidationImplicits._


object AppCreateWebsite extends mvc.Controller {

  val log = play.api.Logger("app.create-website")

  def newWebsiteAddr(websiteName: String,
        tpi: InternalTemplateProgrammingInterface): String = {
    websiteName +"."+ tpi.websiteConfigValue("new-website-domain",
      or = throwForbidden(
        "DwE903IKW3", "You may not create a new website from this website"))
  }


  def showWebsiteNameForm() = GetAction { request =>
    _throwIfMayNotCreateWebsite(request)

    val tpi = InternalTemplateProgrammingInterface(request.dao)
    Ok(views.html.createWebsiteChooseName(tpi,
      xsrfToken = request.xsrfToken.value))
  }


  def handleWebsiteNameForm() = JsonOrFormDataPostAction(maxBytes = 100) {
      request =>

    val newWebsiteName =
      request.body.getEmptyAsNone("websiteNameInp") getOrElse
        throwBadReq("DwE01kI72", "Please specify a name for your new website")

    if (!isOkayWebsiteName(newWebsiteName))
      throwForbidden("DwE390IR3", "Bad website name. (The name must be "+
        "at least 6 characters long, not be too long, contain only "+
        "lowercase a-z, 0-9 and hyphens ('-').)")

    if (request.body.getFirst("acceptTermsInp") != Some("yes"))
      throwForbidden(
        "DwE9fZ31", "To create a new website, you need to accept the "+
         "Terms of Use and the Privacy Policy.")

    val websiteAddr = newWebsiteAddr(newWebsiteName,
       InternalTemplateProgrammingInterface(request.dao))

    // *Preliminarily* test if it's possible & allowed to create the website.
    _throwIfMayNotCreateWebsite(request, newWebsiteAddr = Some(websiteAddr))

    Redirect(routes.AppCreateWebsite.showWebsiteOwnerForm.url)
       .withSession(
          request.session + ("website-name" -> newWebsiteName))
  }


  def showWebsiteOwnerForm() = CheckSidActionNoBody {
        (sidOk, xsrfOk, request) =>
    Ok(views.html.login(xsrfToken = xsrfOk.value,
      returnToUrl = routes.AppCreateWebsite.tryCreateWebsite.url,
      title = "Choose Website Owner Account",
      providerLoginMessage = "It will become the owner of the new website."))
  }


  def tryCreateWebsite() = GetAction { request =>

    // Check permissions — and load authentication details, so OpenID/OAuth
    // info can be replicated to a new identity + user in the new website.
    val loginId = request.loginId_!
    val (identity, user) = {
      request.dao.loadIdtyDetailsAndUser(forLoginId = loginId) match {
        case Some((identity, user)) => (identity, user)
        case None =>
          runErr("DwE01j920", "Cannot create website: Bad login ID: "+ loginId)
      }
    }

    if (!user.isAuthenticated) _showLoginPageAgain(
      "DwE01B7", "Cannot create website: User not authenticated. "+
         "Please login again, but not as guest")

    if (user.email isEmpty) _showLoginPageAgain(
      "DwE56Yr5", "Cannot create website: User's email address unknown. " +
         "Please use an account that has an email address")

    def _showLoginPageAgain(errorCode: String, errorMessage: String)
          : PlainResult = {
      // For now:
      throwForbidden(errorCode, errorMessage)
      // Could instead show this page, + helpful info on why failed:
      //Ok(views.html.createWebsite(doWhat = "showClaimWebsiteLoginForm",
      //  xsrfToken = xsrfOk.value))
    }

    val newWebsiteName = request.session.get("website-name") getOrElse {
      throwForbidden("DwE091EQ7", "No website-name cookie")
    }

    val tpi = InternalTemplateProgrammingInterface(request.dao)
    val websiteAddr = newWebsiteAddr(newWebsiteName, tpi)

    _throwIfMayNotCreateWebsite(request, newWebsiteAddr = Some(websiteAddr))

    log.info("Creating website, name: "+ newWebsiteName +
       ", address: "+ websiteAddr +", on behalf of: "+ user)

    // SECURITY should whitelist allowed OpenID and OAuth providers.

    // Require OpenID or OAuth (todo) or password (todo) login.
    val idtyOpenId = identity.asInstanceOf[IdentityOpenId]

    // CreateWebsite throws error if one creates too many websites
    // from the same IP.
    val newWebsite = request.dao.createWebsite(
       name = newWebsiteName, address = websiteAddr,
       ownerIp = request.ip, ownerLoginId = loginId,
       ownerIdentity = idtyOpenId, ownerRole = user)

    val result = newWebsite match {
      case Some(website) =>
        // COULD do this in the same transaction as `createWebsite`?
        val email = _makeNewWebsiteEmail(website, user)
        request.dao.saveUnsentEmail(email)

        Debiki.sendEmail(email, website.id)

        Redirect("http://"+ websiteAddr +
           routes.AppCreateWebsite.welcomeOwner.url)
      case None =>
        Ok(views.html.createWebsiteFailNotFirst())
    }

    result.withSession(request.session - "website-name")
  }


  def welcomeOwner() = CheckSidActionNoBody { (sidOk, xsrfOk, request) =>
    // SHOULD log in user, so s/he can create pages or choose a template.
    // Like so? Pass a magic token in the URL, which is valid for 1 minute,
    // and then, here, check if DW1_LOGINS has *no logins* for the new websitew
    // Then, if the URL token is valid, auto-login the user
    // because s/he is the owner and this'll work *once* only. (Assuming
    // we're using HTTPS (which we aren't), i.e. no man in the middle attack.)
    Ok(views.html.createWebsiteWelcomeOwner())
  }


  /**
   * Must be a valid host namme, not too long or too short (less than 6 chars),
   * no '.' and no leading or trailing '-'. See test suite in
   * AppCreateWebsiteSpec.
   */
  def isOkayWebsiteName(name: String): Boolean = {
    _OkWebsiteNameRegex matches name
  }


  private val _OkWebsiteNameRegex = """[a-z][a-z0-9\-]{4,38}[a-z0-9]""".r


  private def _makeNewWebsiteEmail(website: Tenant, owner: User): Email = {
    val address = website.chost_!.address
    val message = views.html.createWebsiteWelcomeEmail(address).body
    Email(sendTo = owner.email,
      subject = s"New Debiki website created, here: http://$address",
      bodyHtmlText = message)
  }


  private def _throwIfMayNotCreateWebsite(request: ApiRequest[_],
        newWebsiteAddr: Option[String] = None) {
    if (request.host != "www.debiki.com" &&
        !request.host.contains("localhost:"))
      throwForbidden(
        "DwE093AQ2", "You cannot create a new website via that website")

    COULD // check if website already exists, then deny

    // Tenantdao.createWebsite already throws error if too many websites
    // created from the same IP.
  }

}<commit_msg>Create empty homepage when initializing new website.<commit_after>/**
 * Copyright (c) 2012 <NAME> (born 1979)
 */

package controllers

import com.debiki.v0._
import debiki._
import debiki.DebikiHttp._
import java.{util => ju}
import play.api._
import play.api.mvc.{Action => _, _}
import play.api.Play.current
import ApiActions._
import SafeActions._
import Prelude._
import Utils.ValidationImplicits._


object AppCreateWebsite extends mvc.Controller {

  val log = play.api.Logger("app.create-website")

  def newWebsiteAddr(websiteName: String,
        tpi: InternalTemplateProgrammingInterface): String = {
    websiteName +"."+ tpi.websiteConfigValue("new-website-domain",
      or = throwForbidden(
        "DwE903IKW3", "You may not create a new website from this website"))
  }


  def showWebsiteNameForm() = GetAction { request =>
    _throwIfMayNotCreateWebsite(request)

    val tpi = InternalTemplateProgrammingInterface(request.dao)
    Ok(views.html.createWebsiteChooseName(tpi,
      xsrfToken = request.xsrfToken.value))
  }


  def handleWebsiteNameForm() = JsonOrFormDataPostAction(maxBytes = 100) {
      request =>

    val newWebsiteName =
      request.body.getEmptyAsNone("websiteNameInp") getOrElse
        throwBadReq("DwE01kI72", "Please specify a name for your new website")

    if (!isOkayWebsiteName(newWebsiteName))
      throwForbidden("DwE390IR3", "Bad website name. (The name must be "+
        "at least 6 characters long, not be too long, contain only "+
        "lowercase a-z, 0-9 and hyphens ('-').)")

    if (request.body.getFirst("acceptTermsInp") != Some("yes"))
      throwForbidden(
        "DwE9fZ31", "To create a new website, you need to accept the "+
         "Terms of Use and the Privacy Policy.")

    val websiteAddr = newWebsiteAddr(newWebsiteName,
       InternalTemplateProgrammingInterface(request.dao))

    // *Preliminarily* test if it's possible & allowed to create the website.
    _throwIfMayNotCreateWebsite(request, newWebsiteAddr = Some(websiteAddr))

    Redirect(routes.AppCreateWebsite.showWebsiteOwnerForm.url)
       .withSession(
          request.session + ("website-name" -> newWebsiteName))
  }


  def showWebsiteOwnerForm() = CheckSidActionNoBody {
        (sidOk, xsrfOk, request) =>
    Ok(views.html.login(xsrfToken = xsrfOk.value,
      returnToUrl = routes.AppCreateWebsite.tryCreateWebsite.url,
      title = "Choose Website Owner Account",
      providerLoginMessage = "It will become the owner of the new website."))
  }


  def tryCreateWebsite() = GetAction { request =>

    // Check permissions — and load authentication details, so OpenID/OAuth
    // info can be replicated to a new identity + user in the new website.
    val loginId = request.loginId_!
    val (identity, user) = {
      request.dao.loadIdtyDetailsAndUser(forLoginId = loginId) match {
        case Some((identity, user)) => (identity, user)
        case None =>
          runErr("DwE01j920", "Cannot create website: Bad login ID: "+ loginId)
      }
    }

    if (!user.isAuthenticated) _showLoginPageAgain(
      "DwE01B7", "Cannot create website: User not authenticated. "+
         "Please login again, but not as guest")

    if (user.email isEmpty) _showLoginPageAgain(
      "DwE56Yr5", "Cannot create website: User's email address unknown. " +
         "Please use an account that has an email address")

    def _showLoginPageAgain(errorCode: String, errorMessage: String)
          : PlainResult = {
      // For now:
      throwForbidden(errorCode, errorMessage)
      // Could instead show this page, + helpful info on why failed:
      //Ok(views.html.createWebsite(doWhat = "showClaimWebsiteLoginForm",
      //  xsrfToken = xsrfOk.value))
    }

    val newWebsiteName = request.session.get("website-name") getOrElse {
      throwForbidden("DwE091EQ7", "No website-name cookie")
    }

    val tpi = InternalTemplateProgrammingInterface(request.dao)
    val websiteAddr = newWebsiteAddr(newWebsiteName, tpi)

    _throwIfMayNotCreateWebsite(request, newWebsiteAddr = Some(websiteAddr))

    log.info("Creating website, name: "+ newWebsiteName +
       ", address: "+ websiteAddr +", on behalf of: "+ user)

    // SECURITY should whitelist allowed OpenID and OAuth providers.

    // Require OpenID or OAuth (todo) or password (todo) login.
    val idtyOpenId = identity.asInstanceOf[IdentityOpenId]

    // CreateWebsite throws error if one creates too many websites
    // from the same IP.
    val newWebsite = request.dao.createWebsite(
       name = newWebsiteName, address = websiteAddr,
       ownerIp = request.ip, ownerLoginId = loginId,
       ownerIdentity = idtyOpenId, ownerRole = user)

    val result = newWebsite match {
      case Some(website) =>
        // COULD do this in the same transaction as `createWebsite`?
        val email = _makeNewWebsiteEmail(website, user)
        request.dao.saveUnsentEmail(email)

        Debiki.sendEmail(email, website.id)

        val newWebsiteDao = Debiki.tenantDao(
          tenantId = website.id, ip = request.ip, roleId = None)
        createHomepage(newWebsiteDao, creationDati = request.ctime)

        Redirect("http://"+ websiteAddr +
           routes.AppCreateWebsite.welcomeOwner.url)
      case None =>
        Ok(views.html.createWebsiteFailNotFirst())
    }

    result.withSession(request.session - "website-name")
  }


  def welcomeOwner() = CheckSidActionNoBody { (sidOk, xsrfOk, request) =>
    // SHOULD log in user, so s/he can create pages or choose a template.
    // Like so? Pass a magic token in the URL, which is valid for 1 minute,
    // and then, here, check if DW1_LOGINS has *no logins* for the new websitew
    // Then, if the URL token is valid, auto-login the user
    // because s/he is the owner and this'll work *once* only. (Assuming
    // we're using HTTPS (which we aren't), i.e. no man in the middle attack.)
    Ok(views.html.createWebsiteWelcomeOwner())
  }


  /**
   * Must be a valid host namme, not too long or too short (less than 6 chars),
   * no '.' and no leading or trailing '-'. See test suite in
   * AppCreateWebsiteSpec.
   */
  def isOkayWebsiteName(name: String): Boolean = {
    _OkWebsiteNameRegex matches name
  }


  private val _OkWebsiteNameRegex = """[a-z][a-z0-9\-]{4,38}[a-z0-9]""".r


  private def _makeNewWebsiteEmail(website: Tenant, owner: User): Email = {
    val address = website.chost_!.address
    val message = views.html.createWebsiteWelcomeEmail(address).body
    Email(sendTo = owner.email,
      subject = s"New Debiki website created, here: http://$address",
      bodyHtmlText = message)
  }


  private def _throwIfMayNotCreateWebsite(request: ApiRequest[_],
        newWebsiteAddr: Option[String] = None) {
    if (request.host != "www.debiki.com" &&
        !request.host.contains("localhost:"))
      throwForbidden(
        "DwE093AQ2", "You cannot create a new website via that website")

    COULD // check if website already exists, then deny

    // Tenantdao.createWebsite already throws error if too many websites
    // created from the same IP.
  }


  /**
   * Creates an empty page at /, with PageRole.Homepage, so I don't need
   * to tell users how to create a homepage. Instead, I create a default
   * empty homepage, and add a "Use this page as homepage" button,
   * so they can easily switch homepage.
   * If they make another page to the homepage, the default homepage
   * is automatically moved from / to /_old/default-homepage.
   */
  private def createHomepage(newWebsiteDao: TenantDao, creationDati: ju.Date) {
    val pageId = AppCreatePage.generateNewPageId()
    val emptyPage = Debate(pageId)
    val pageMeta = PageMeta.forNewPage(pageId, creationDati, PageRole.Homepage)

    val oldPath = PagePath(newWebsiteDao.tenantId, folder = "/_old/",
      pageId = Some(pageId), showId = false, pageSlug = "default-homepage")

    val path = oldPath.copy(folder = "/", pageSlug = "")

    // First create the homepage at '/_old/default-homepage'.
    // Then change its location to '/'. — When '/' is overwritten by a
    // path to another page (if the admin lets another page be the homepage),
    // the old path will be activated again (since it's the one that was
    // in use before we changed to '/'). ...
    //  ... But! Not implemented: reactivation of old paths. So:

    // For now:
    newWebsiteDao.createPage(PageStuff(pageMeta, path, emptyPage))
    // Because the code below would *overwrite* oldPath, instead of adding
    // a new PagePath entry.
    //// COULD do like this: (when old path reactivation implemented, see above)
    //newWebsiteDao.createPage(PageStuff(pageMeta, oldPath, emptyPage))
    //newWebsiteDao.moveRenamePage(Seq(pageId), newFolder = Some("/"),
    //  newSlug = Some(""))

    // Set homepage title. (First, allow loginId to be None.)
    /*
    val title = Post(Page.TitleId, Page.TitleId, creationDati,
      loginId = unimplemented("Null loginId"), newIp = None,
      text = DefaultHomepageTitle,
      markup = Markup.DefaultForPageTitle.id, approval = None,
      tyype = PostType.Text)
    newWebsiteDao.savePageActionsImpl(emptyPage, List(title), pageMeta)
    */
  }


  val DefaultHomepageTitle = "Default Homepage (click to edit)"

}
{'fraction_non_alphanumeric': 0.10445630585898709, 'fraction_numerical': 0.007323733862959285, 'mean_word_length': 3.209247648902821, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7304979', 'n_tokens_mistral': 5064, 'n_tokens_neox': 4720, 'n_words': 1426}