Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/project/projectadminadvanced.py b/project/projectadminadvanced.py
new file mode 100644
index 0000000..9c5fc1b
--- /dev/null
+++ b/project/projectadminadvanced.py
@@ -0,0 +1,213 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Page and form handlers for project administration "advanced" subtab.
+
+The advanced subtab allows the project to be archived, unarchived, deleted, or
+marked as moved.  Site admins can use this page to "doom" a project, which is
+basically archiving it in a way that cannot be reversed by the project owners.
+
+The page also shows project data storage quota and usage values, and
+site admins can edit those quotas.
+"""
+
+from __future__ import division
+from __future__ import print_function
+from __future__ import absolute_import
+
+import logging
+import time
+
+import ezt
+
+from businesslogic import work_env
+from framework import framework_constants
+from framework import framework_helpers
+from framework import permissions
+from framework import servlet
+from framework import template_helpers
+from framework import urls
+from proto import project_pb2
+from tracker import tracker_constants
+
+
+class ProjectAdminAdvanced(servlet.Servlet):
+  """A page with project state options for the Project Owner(s)."""
+
+  _PAGE_TEMPLATE = 'project/project-admin-advanced-page.ezt'
+  _MAIN_TAB_MODE = servlet.Servlet.MAIN_TAB_ADMIN
+
+  def AssertBasePermission(self, mr):
+    """Make sure that the logged in user has permission to view this page.
+
+    Args:
+      mr: commonly used info parsed from the request.
+    """
+    super(ProjectAdminAdvanced, self).AssertBasePermission(mr)
+    if not self.CheckPerm(mr, permissions.EDIT_PROJECT):
+      raise permissions.PermissionException(
+          'User is not allowed to administer this project')
+
+  def GatherPageData(self, mr):
+    """Build up a dictionary of data values to use when rendering the page.
+
+    Args:
+      mr: commonly used info parsed from the request.
+
+    Returns:
+      Dict of values used by EZT for rendering the "Advanced" subtab.
+    """
+    page_data = {
+        'admin_tab_mode': self.ADMIN_TAB_ADVANCED,
+        }
+    page_data.update(self._GatherPublishingOptions(mr))
+    page_data.update(self._GatherQuotaData(mr))
+
+    return page_data
+
+  def _GatherPublishingOptions(self, mr):
+    """Gather booleans to control the publishing buttons to show in EZT."""
+    state = mr.project.state
+    offer_archive = state != project_pb2.ProjectState.ARCHIVED
+    offer_delete = state == project_pb2.ProjectState.ARCHIVED
+    offer_publish = (
+        state == project_pb2.ProjectState.ARCHIVED and
+        (self.CheckPerm(mr, permissions.PUBLISH_PROJECT) or
+         not mr.project.state_reason))
+    offer_move = state == project_pb2.ProjectState.LIVE
+    offer_doom = self.CheckPerm(mr, permissions.ADMINISTER_SITE)
+    moved_to = mr.project.moved_to or 'http://'
+
+    publishing_data = {
+        'offer_archive': ezt.boolean(offer_archive),
+        'offer_publish': ezt.boolean(offer_publish),
+        'offer_delete': ezt.boolean(offer_delete),
+        'offer_move': ezt.boolean(offer_move),
+        'moved_to': moved_to,
+        'offer_doom': ezt.boolean(offer_doom),
+        'default_doom_reason': framework_constants.DEFAULT_DOOM_REASON,
+        }
+
+    return publishing_data
+
+  def _GatherQuotaData(self, mr):
+    """Gather quota info from backends so that it can be passed to EZT."""
+    offer_quota_editing = self.CheckPerm(mr, permissions.EDIT_QUOTA)
+
+    quota_data = {
+        'offer_quota_editing': ezt.boolean(offer_quota_editing),
+        'attachment_quota': self._BuildAttachmentQuotaData(mr.project),
+        }
+
+    return quota_data
+
+  def _BuildComponentQuota(self, used_bytes, quota_bytes, field_name):
+    """Return an object to easily display quota info in EZT."""
+    if quota_bytes:
+      used_percent = 100 * used_bytes // quota_bytes
+    else:
+      used_percent = 0
+
+    quota_mb = quota_bytes // 1024 // 1024
+
+    return template_helpers.EZTItem(
+        used=template_helpers.BytesKbOrMb(used_bytes),
+        quota_mb=quota_mb,
+        used_percent=used_percent,
+        avail_percent=100 - used_percent,
+        field_name=field_name)
+
+  def _BuildAttachmentQuotaData(self, project):
+    return self._BuildComponentQuota(
+      project.attachment_bytes_used,
+      project.attachment_quota or
+      tracker_constants.ISSUE_ATTACHMENTS_QUOTA_HARD,
+      'attachment_quota_mb')
+
+  def ProcessFormData(self, mr, post_data):
+    """Process the posted form.
+
+    Args:
+      mr: commonly used info parsed from the request.
+      post_data: dictionary of HTML form data.
+
+    Returns:
+      String URL to redirect to after processing is completed.
+    """
+    if 'savechanges' in post_data:
+      self._ProcessQuota(mr, post_data)
+    else:
+      self._ProcessPublishingOptions(mr, post_data)
+
+    if 'deletebtn' in post_data:
+      url = framework_helpers.FormatAbsoluteURL(
+          mr, urls.HOSTING_HOME, include_project=False)
+    else:
+      url = framework_helpers.FormatAbsoluteURL(
+          mr, urls.ADMIN_ADVANCED, saved=1, ts=int(time.time()))
+
+    return url
+
+  def _ProcessQuota(self, mr, post_data):
+    """Process form data to update project quotas."""
+    if not self.CheckPerm(mr, permissions.EDIT_QUOTA):
+      raise permissions.PermissionException(
+          'User is not allowed to change project quotas')
+
+    try:
+      new_attachment_quota = int(post_data['attachment_quota_mb'])
+      new_attachment_quota *= 1024 * 1024
+    except ValueError:
+      mr.errors.attachment_quota = 'Invalid value'
+      self.PleaseCorrect(mr)  # Don't echo back the bad input, just start over.
+      return
+
+    with work_env.WorkEnv(mr, self.services) as we:
+      we.UpdateProject(
+          mr.project.project_id, attachment_quota=new_attachment_quota)
+
+  def _ProcessPublishingOptions(self, mr, post_data):
+    """Process form data to update project state."""
+    # Note that EDIT_PROJECT is the base permission for this servlet, but
+    # dooming and undooming projects also requires PUBLISH_PROJECT.
+
+    state = mr.project.state
+
+    with work_env.WorkEnv(mr, self.services) as we:
+      if 'archivebtn' in post_data and not mr.project.delete_time:
+        we.UpdateProject(
+            mr.project.project_id, state=project_pb2.ProjectState.ARCHIVED)
+
+      elif 'deletebtn' in post_data:  # Mark the project for immediate deletion.
+        if state != project_pb2.ProjectState.ARCHIVED:
+          raise permissions.PermissionException(
+              'Projects must be archived before being deleted')
+        we.DeleteProject(mr.project_id)
+
+      elif 'doombtn' in post_data:  # Go from any state to forced ARCHIVED.
+        if not self.CheckPerm(mr, permissions.PUBLISH_PROJECT):
+          raise permissions.PermissionException(
+              'User is not allowed to doom projects')
+        reason = post_data.get('reason')
+        delete_time = time.time() + framework_constants.DEFAULT_DOOM_PERIOD
+        we.UpdateProject(
+            mr.project.project_id, state=project_pb2.ProjectState.ARCHIVED,
+            state_reason=reason, delete_time=delete_time)
+
+      elif 'publishbtn' in post_data:  # Go from any state to LIVE
+        if (mr.project.delete_time and
+            not self.CheckPerm(mr, permissions.PUBLISH_PROJECT)):
+          raise permissions.PermissionException(
+              'User is not allowed to unarchive doomed projects')
+        we.UpdateProject(
+            mr.project.project_id, state=project_pb2.ProjectState.LIVE,
+            state_reason='', delete_time=0, read_only_reason='')
+
+      elif 'movedbtn' in post_data:  # Record the moved_to location.
+        if state != project_pb2.ProjectState.LIVE:
+          raise permissions.PermissionException(
+              'This project is not live, no user can move it')
+        moved_to = post_data.get('moved_to', '')
+        we.UpdateProject(mr.project.project_id, moved_to=moved_to)