<commit_before><?php

abstract class PhabricatorProfileMenuEngine extends Phobject {

  private $viewer;
  private $profileObject;
  private $customPHID;
  private $items;
  private $defaultItem;
  private $controller;
  private $navigation;
  private $showNavigation = true;
  private $editMode;
  private $pageClasses = array();
  private $showContentCrumbs = true;

  const ITEM_CUSTOM_DIVIDER = 'engine.divider';
  const ITEM_MANAGE = 'item.configure';

  const MODE_COMBINED = 'combined';
  const MODE_GLOBAL = 'global';
  const MODE_CUSTOM = 'custom';

  public function setViewer(PhabricatorUser $viewer) {
    $this->viewer = $viewer;
    return $this;
  }

  public function getViewer() {
    return $this->viewer;
  }

  public function setProfileObject($profile_object) {
    $this->profileObject = $profile_object;
    return $this;
  }

  public function getProfileObject() {
    return $this->profileObject;
  }

  public function setCustomPHID($custom_phid) {
    $this->customPHID = $custom_phid;
    return $this;
  }

  public function getCustomPHID() {
    return $this->customPHID;
  }

  private function getEditModeCustomPHID() {
    $mode = $this->getEditMode();

    switch ($mode) {
      case self::MODE_CUSTOM:
        $custom_phid = $this->getCustomPHID();
        break;
      case self::MODE_GLOBAL:
        $custom_phid = null;
        break;
    }

    return $custom_phid;
  }

  public function setController(PhabricatorController $controller) {
    $this->controller = $controller;
    return $this;
  }

  public function getController() {
    return $this->controller;
  }

  private function setDefaultItem(
    PhabricatorProfileMenuItemConfiguration $default_item) {
    $this->defaultItem = $default_item;
    return $this;
  }

  public function getDefaultItem() {
    $this->getItems();
    return $this->defaultItem;
  }

  public function setShowNavigation($show) {
    $this->showNavigation = $show;
    return $this;
  }

  public function getShowNavigation() {
    return $this->showNavigation;
  }

  public function addContentPageClass($class) {
    $this->pageClasses[] = $class;
    return $this;
  }

  public function setShowContentCrumbs($show_content_crumbs) {
    $this->showContentCrumbs = $show_content_crumbs;
    return $this;
  }

  public function getShowContentCrumbs() {
    return $this->showContentCrumbs;
  }

  abstract public function getItemURI($path);
  abstract protected function isMenuEngineConfigurable();

  abstract protected function getBuiltinProfileItems($object);

  protected function getBuiltinCustomProfileItems(
    $object,
    $custom_phid) {
    return array();
  }

  protected function getEditMode() {
    return $this->editMode;
  }

  public function buildResponse() {
    $controller = $this->getController();

    $viewer = $controller->getViewer();
    $this->setViewer($viewer);

    $request = $controller->getRequest();

    $item_action = $request->getURIData('itemAction');
    if (!$item_action) {
      $item_action = 'view';
    }

    $is_view = ($item_action == 'view');

    // If the engine is not configurable, don't respond to any of the editing
    // or configuration routes.
    if (!$this->isMenuEngineConfigurable()) {
      if (!$is_view) {
        return new Aphront404Response();
      }
    }

    $item_id = $request->getURIData('itemID');

    // If we miss on the MenuEngine route, try the EditEngine route. This will
    // be populated while editing items.
    if (!$item_id) {
      $item_id = $request->getURIData('id');
    }

    $item_list = $this->getItems();

    $selected_item = null;
    if (strlen($item_id)) {
      $item_id_int = (int)$item_id;
      foreach ($item_list as $item) {
        if ($item_id_int) {
          if ((int)$item->getID() === $item_id_int) {
            $selected_item = $item;
            break;
          }
        }

        $builtin_key = $item->getBuiltinKey();
        if ($builtin_key === (string)$item_id) {
          $selected_item = $item;
          break;
        }
      }
    }

    if (!$selected_item) {
      if ($is_view) {
        $selected_item = $this->getDefaultItem();
      }
    }

    switch ($item_action) {
      case 'view':
      case 'info':
      case 'hide':
      case 'default':
      case 'builtin':
        if (!$selected_item) {
          return new Aphront404Response();
        }
        break;
      case 'edit':
        if (!$request->getURIData('id')) {
          // If we continue along the "edit" pathway without an ID, we hit an
          // unrelated exception because we can not build a new menu item out
          // of thin air. For menus, new items are created via the "new"
          // action. Just catch this case and 404 early since there's currently
          // no clean way to make EditEngine aware of this.
          return new Aphront404Response();
        }
        break;
    }

    $navigation = $this->buildNavigation();

    $crumbs = $controller->buildApplicationCrumbsForEditEngine();

    if (!$is_view) {
      $navigation->selectFilter(self::ITEM_MANAGE);

      if ($selected_item) {
        if ($selected_item->getCustomPHID()) {
          $edit_mode = 'custom';
        } else {
          $edit_mode = 'global';
        }
      } else {
        $edit_mode = $request->getURIData('itemEditMode');
      }

      $available_modes = $this->getViewerEditModes();
      if ($available_modes) {
        $available_modes = array_fuse($available_modes);
        if (isset($available_modes[$edit_mode])) {
          $this->editMode = $edit_mode;
        } else {
          if ($item_action != 'configure') {
            return new Aphront404Response();
          }
        }
      }
      $page_title = pht('Configure Menu');
    } else {
      $page_title = $selected_item->getDisplayName();
    }

    switch ($item_action) {
      case 'view':
        $navigation->selectFilter($selected_item->getDefaultMenuItemKey());

        try {
          $content = $this->buildItemViewContent($selected_item);
        } catch (Exception $ex) {
          $content = id(new PHUIInfoView())
            ->setTitle(pht('Unable to Render Dashboard'))
            ->setErrors(array($ex->getMessage()));
        }

        $crumbs->addTextCrumb($selected_item->getDisplayName());
        if (!$content) {
          return new Aphront404Response();
        }
        break;
      case 'configure':
        $mode = $this->getEditMode();
        if (!$mode) {
          $crumbs->addTextCrumb(pht('Configure Menu'));
          $content = $this->buildMenuEditModeContent();
        } else {
          if (count($available_modes) > 1) {
            $crumbs->addTextCrumb(
              pht('Configure Menu'),
              $this->getItemURI('configure/'));

            switch ($mode) {
              case self::MODE_CUSTOM:
                $crumbs->addTextCrumb(pht('Personal'));
                break;
              case self::MODE_GLOBAL:
                $crumbs->addTextCrumb(pht('Global'));
                break;
            }
          } else {
            $crumbs->addTextCrumb(pht('Configure Menu'));
          }
          $edit_list = $this->loadItems($mode);
          $content = $this->buildItemConfigureContent($edit_list);
        }
        break;
      case 'reorder':
        $mode = $this->getEditMode();
        $edit_list = $this->loadItems($mode);
        $content = $this->buildItemReorderContent($edit_list);
        break;
      case 'new':
        $item_key = $request->getURIData('itemKey');
        $mode = $this->getEditMode();
        $content = $this->buildItemNewContent($item_key, $mode);
        break;
      case 'builtin':
        $content = $this->buildItemBuiltinContent($selected_item);
        break;
      case 'hide':
        $content = $this->buildItemHideContent($selected_item);
        break;
      case 'default':
        if (!$this->isMenuEnginePinnable()) {
          return new Aphront404Response();
        }
        $content = $this->buildItemDefaultContent(
          $selected_item,
          $item_list);
        break;
      case 'edit':
        $content = $this->buildItemEditContent();
        break;
      default:
        throw new Exception(
          pht(
            'Unsupported item action "%s".',
            $item_action));
    }

    if ($content instanceof AphrontResponse) {
      return $content;
    }

    if ($content instanceof AphrontResponseProducerInterface) {
      return $content;
    }

    $crumbs->setBorder(true);

    $page = $controller->newPage()
      ->setTitle($page_title)
      ->appendChild($content);

    if (!$is_view || $this->getShowContentCrumbs()) {
      $page->setCrumbs($crumbs);
    }

    if ($this->getShowNavigation()) {
      $page->setNavigation($navigation);
    }

    if ($is_view) {
      foreach ($this->pageClasses as $class) {
        $page->addClass($class);
      }
    }

    return $page;
  }

  public function buildNavigation() {
    if ($this->navigation) {
      return $this->navigation;
    }
    $nav = id(new AphrontSideNavFilterView())
      ->setIsProfileMenu(true)
      ->setBaseURI(new PhutilURI($this->getItemURI('')));

    $menu_items = $this->getItems();

    $filtered_items = array();
    foreach ($menu_items as $menu_item) {
      if ($menu_item->isDisabled()) {
        continue;
      }
      $filtered_items[] = $menu_item;
    }
    $filtered_groups = mgroup($filtered_items, 'getMenuItemKey');
    foreach ($filtered_groups as $group) {
      $first_item = head($group);
      $first_item->willBuildNavigationItems($group);
    }

    foreach ($menu_items as $menu_item) {
      if ($menu_item->isDisabled()) {
        continue;
      }

      $items = $menu_item->buildNavigationMenuItems();
      foreach ($items as $item) {
        $this->validateNavigationMenuItem($item);
      }

      // If the item produced only a single item which does not otherwise
      // have a key, try to automatically assign it a reasonable key. This
      // makes selecting the correct item simpler.

      if (count($items) == 1) {
        $item = head($items);
        if ($item->getKey() === null) {
          $default_key = $menu_item->getDefaultMenuItemKey();
          $item->setKey($default_key);
        }
      }

      foreach ($items as $item) {
        $nav->addMenuItem($item);
      }
    }

    $nav->selectFilter(null);

    $this->navigation = $nav;
    return $this->navigation;
  }

  private function getItems() {
    if ($this->items === null) {
      $this->items = $this->loadItems(self::MODE_COMBINED);
    }

    return $this->items;
  }

  private function loadItems($mode) {
    $viewer = $this->getViewer();
    $object = $this->getProfileObject();

    $items = $this->loadBuiltinProfileItems($mode);

    $query = id(new PhabricatorProfileMenuItemConfigurationQuery())
      ->setViewer($viewer)
      ->withProfilePHIDs(array($object->getPHID()));

    switch ($mode) {
      case self::MODE_GLOBAL:
        $query->withCustomPHIDs(array(), true);
        break;
      case self::MODE_CUSTOM:
        $query->withCustomPHIDs(array($this->getCustomPHID()), false);
        break;
      case self::MODE_COMBINED:
        $query->withCustomPHIDs(array($this->getCustomPHID()), true);
        break;
    }

    $stored_items = $query->execute();

    foreach ($stored_items as $stored_item) {
      $impl = $stored_item->getMenuItem();
      $impl->setViewer($viewer);
      $impl->setEngine($this);
    }

    // Merge the stored items into the builtin items. If a builtin item has
    // a stored version, replace the defaults with the stored changes.
    foreach ($stored_items as $stored_item) {
      if (!$stored_item->shouldEnableForObject($object)) {
        continue;
      }

      $builtin_key = $stored_item->getBuiltinKey();
      if ($builtin_key !== null) {
        // If this builtin actually exists, replace the builtin with the
        // stored configuration. Otherwise, we're just going to drop the
        // stored config: it corresponds to an out-of-date or uninstalled
        // item.
        if (isset($items[$builtin_key])) {
          $items[$builtin_key] = $stored_item;
        } else {
          continue;
        }
      } else {
        $items[] = $stored_item;
      }
    }

    $items = $this->arrangeItems($items, $mode);

    // Make sure exactly one valid item is marked as default.
    $default = null;
    $first = null;
    foreach ($items as $item) {
      if (!$item->canMakeDefault()) {
        continue;
      }

      // If this engine doesn't support pinning items, don't respect any
      // setting which might be present in the database.
      if ($this->isMenuEnginePinnable()) {
        if ($item->isDefault()) {
          $default = $item;
          break;
        }
      }

      if ($first === null) {
        $first = $item;
      }
    }

    if (!$default) {
      $default = $first;
    }

    if ($default) {
      $this->setDefaultItem($default);
    }

    return $items;<commit_msg>Don't select disabled menu items as default<commit_after><?php

abstract class PhabricatorProfileMenuEngine extends Phobject {

  private $viewer;
  private $profileObject;
  private $customPHID;
  private $items;
  private $defaultItem;
  private $controller;
  private $navigation;
  private $showNavigation = true;
  private $editMode;
  private $pageClasses = array();
  private $showContentCrumbs = true;

  const ITEM_CUSTOM_DIVIDER = 'engine.divider';
  const ITEM_MANAGE = 'item.configure';

  const MODE_COMBINED = 'combined';
  const MODE_GLOBAL = 'global';
  const MODE_CUSTOM = 'custom';

  public function setViewer(PhabricatorUser $viewer) {
    $this->viewer = $viewer;
    return $this;
  }

  public function getViewer() {
    return $this->viewer;
  }

  public function setProfileObject($profile_object) {
    $this->profileObject = $profile_object;
    return $this;
  }

  public function getProfileObject() {
    return $this->profileObject;
  }

  public function setCustomPHID($custom_phid) {
    $this->customPHID = $custom_phid;
    return $this;
  }

  public function getCustomPHID() {
    return $this->customPHID;
  }

  private function getEditModeCustomPHID() {
    $mode = $this->getEditMode();

    switch ($mode) {
      case self::MODE_CUSTOM:
        $custom_phid = $this->getCustomPHID();
        break;
      case self::MODE_GLOBAL:
        $custom_phid = null;
        break;
    }

    return $custom_phid;
  }

  public function setController(PhabricatorController $controller) {
    $this->controller = $controller;
    return $this;
  }

  public function getController() {
    return $this->controller;
  }

  private function setDefaultItem(
    PhabricatorProfileMenuItemConfiguration $default_item) {
    $this->defaultItem = $default_item;
    return $this;
  }

  public function getDefaultItem() {
    $this->getItems();
    return $this->defaultItem;
  }

  public function setShowNavigation($show) {
    $this->showNavigation = $show;
    return $this;
  }

  public function getShowNavigation() {
    return $this->showNavigation;
  }

  public function addContentPageClass($class) {
    $this->pageClasses[] = $class;
    return $this;
  }

  public function setShowContentCrumbs($show_content_crumbs) {
    $this->showContentCrumbs = $show_content_crumbs;
    return $this;
  }

  public function getShowContentCrumbs() {
    return $this->showContentCrumbs;
  }

  abstract public function getItemURI($path);
  abstract protected function isMenuEngineConfigurable();

  abstract protected function getBuiltinProfileItems($object);

  protected function getBuiltinCustomProfileItems(
    $object,
    $custom_phid) {
    return array();
  }

  protected function getEditMode() {
    return $this->editMode;
  }

  public function buildResponse() {
    $controller = $this->getController();

    $viewer = $controller->getViewer();
    $this->setViewer($viewer);

    $request = $controller->getRequest();

    $item_action = $request->getURIData('itemAction');
    if (!$item_action) {
      $item_action = 'view';
    }

    $is_view = ($item_action == 'view');

    // If the engine is not configurable, don't respond to any of the editing
    // or configuration routes.
    if (!$this->isMenuEngineConfigurable()) {
      if (!$is_view) {
        return new Aphront404Response();
      }
    }

    $item_id = $request->getURIData('itemID');

    // If we miss on the MenuEngine route, try the EditEngine route. This will
    // be populated while editing items.
    if (!$item_id) {
      $item_id = $request->getURIData('id');
    }

    $item_list = $this->getItems();

    $selected_item = null;
    if (strlen($item_id)) {
      $item_id_int = (int)$item_id;
      foreach ($item_list as $item) {
        if ($item_id_int) {
          if ((int)$item->getID() === $item_id_int) {
            $selected_item = $item;
            break;
          }
        }

        $builtin_key = $item->getBuiltinKey();
        if ($builtin_key === (string)$item_id) {
          $selected_item = $item;
          break;
        }
      }
    }

    if (!$selected_item) {
      if ($is_view) {
        $selected_item = $this->getDefaultItem();
      }
    }

    switch ($item_action) {
      case 'view':
      case 'info':
      case 'hide':
      case 'default':
      case 'builtin':
        if (!$selected_item) {
          return new Aphront404Response();
        }
        break;
      case 'edit':
        if (!$request->getURIData('id')) {
          // If we continue along the "edit" pathway without an ID, we hit an
          // unrelated exception because we can not build a new menu item out
          // of thin air. For menus, new items are created via the "new"
          // action. Just catch this case and 404 early since there's currently
          // no clean way to make EditEngine aware of this.
          return new Aphront404Response();
        }
        break;
    }

    $navigation = $this->buildNavigation();

    $crumbs = $controller->buildApplicationCrumbsForEditEngine();

    if (!$is_view) {
      $navigation->selectFilter(self::ITEM_MANAGE);

      if ($selected_item) {
        if ($selected_item->getCustomPHID()) {
          $edit_mode = 'custom';
        } else {
          $edit_mode = 'global';
        }
      } else {
        $edit_mode = $request->getURIData('itemEditMode');
      }

      $available_modes = $this->getViewerEditModes();
      if ($available_modes) {
        $available_modes = array_fuse($available_modes);
        if (isset($available_modes[$edit_mode])) {
          $this->editMode = $edit_mode;
        } else {
          if ($item_action != 'configure') {
            return new Aphront404Response();
          }
        }
      }
      $page_title = pht('Configure Menu');
    } else {
      $page_title = $selected_item->getDisplayName();
    }

    switch ($item_action) {
      case 'view':
        $navigation->selectFilter($selected_item->getDefaultMenuItemKey());

        try {
          $content = $this->buildItemViewContent($selected_item);
        } catch (Exception $ex) {
          $content = id(new PHUIInfoView())
            ->setTitle(pht('Unable to Render Dashboard'))
            ->setErrors(array($ex->getMessage()));
        }

        $crumbs->addTextCrumb($selected_item->getDisplayName());
        if (!$content) {
          return new Aphront404Response();
        }
        break;
      case 'configure':
        $mode = $this->getEditMode();
        if (!$mode) {
          $crumbs->addTextCrumb(pht('Configure Menu'));
          $content = $this->buildMenuEditModeContent();
        } else {
          if (count($available_modes) > 1) {
            $crumbs->addTextCrumb(
              pht('Configure Menu'),
              $this->getItemURI('configure/'));

            switch ($mode) {
              case self::MODE_CUSTOM:
                $crumbs->addTextCrumb(pht('Personal'));
                break;
              case self::MODE_GLOBAL:
                $crumbs->addTextCrumb(pht('Global'));
                break;
            }
          } else {
            $crumbs->addTextCrumb(pht('Configure Menu'));
          }
          $edit_list = $this->loadItems($mode);
          $content = $this->buildItemConfigureContent($edit_list);
        }
        break;
      case 'reorder':
        $mode = $this->getEditMode();
        $edit_list = $this->loadItems($mode);
        $content = $this->buildItemReorderContent($edit_list);
        break;
      case 'new':
        $item_key = $request->getURIData('itemKey');
        $mode = $this->getEditMode();
        $content = $this->buildItemNewContent($item_key, $mode);
        break;
      case 'builtin':
        $content = $this->buildItemBuiltinContent($selected_item);
        break;
      case 'hide':
        $content = $this->buildItemHideContent($selected_item);
        break;
      case 'default':
        if (!$this->isMenuEnginePinnable()) {
          return new Aphront404Response();
        }
        $content = $this->buildItemDefaultContent(
          $selected_item,
          $item_list);
        break;
      case 'edit':
        $content = $this->buildItemEditContent();
        break;
      default:
        throw new Exception(
          pht(
            'Unsupported item action "%s".',
            $item_action));
    }

    if ($content instanceof AphrontResponse) {
      return $content;
    }

    if ($content instanceof AphrontResponseProducerInterface) {
      return $content;
    }

    $crumbs->setBorder(true);

    $page = $controller->newPage()
      ->setTitle($page_title)
      ->appendChild($content);

    if (!$is_view || $this->getShowContentCrumbs()) {
      $page->setCrumbs($crumbs);
    }

    if ($this->getShowNavigation()) {
      $page->setNavigation($navigation);
    }

    if ($is_view) {
      foreach ($this->pageClasses as $class) {
        $page->addClass($class);
      }
    }

    return $page;
  }

  public function buildNavigation() {
    if ($this->navigation) {
      return $this->navigation;
    }
    $nav = id(new AphrontSideNavFilterView())
      ->setIsProfileMenu(true)
      ->setBaseURI(new PhutilURI($this->getItemURI('')));

    $menu_items = $this->getItems();

    $filtered_items = array();
    foreach ($menu_items as $menu_item) {
      if ($menu_item->isDisabled()) {
        continue;
      }
      $filtered_items[] = $menu_item;
    }
    $filtered_groups = mgroup($filtered_items, 'getMenuItemKey');
    foreach ($filtered_groups as $group) {
      $first_item = head($group);
      $first_item->willBuildNavigationItems($group);
    }

    foreach ($menu_items as $menu_item) {
      if ($menu_item->isDisabled()) {
        continue;
      }

      $items = $menu_item->buildNavigationMenuItems();
      foreach ($items as $item) {
        $this->validateNavigationMenuItem($item);
      }

      // If the item produced only a single item which does not otherwise
      // have a key, try to automatically assign it a reasonable key. This
      // makes selecting the correct item simpler.

      if (count($items) == 1) {
        $item = head($items);
        if ($item->getKey() === null) {
          $default_key = $menu_item->getDefaultMenuItemKey();
          $item->setKey($default_key);
        }
      }

      foreach ($items as $item) {
        $nav->addMenuItem($item);
      }
    }

    $nav->selectFilter(null);

    $this->navigation = $nav;
    return $this->navigation;
  }

  private function getItems() {
    if ($this->items === null) {
      $this->items = $this->loadItems(self::MODE_COMBINED);
    }

    return $this->items;
  }

  private function loadItems($mode) {
    $viewer = $this->getViewer();
    $object = $this->getProfileObject();

    $items = $this->loadBuiltinProfileItems($mode);

    $query = id(new PhabricatorProfileMenuItemConfigurationQuery())
      ->setViewer($viewer)
      ->withProfilePHIDs(array($object->getPHID()));

    switch ($mode) {
      case self::MODE_GLOBAL:
        $query->withCustomPHIDs(array(), true);
        break;
      case self::MODE_CUSTOM:
        $query->withCustomPHIDs(array($this->getCustomPHID()), false);
        break;
      case self::MODE_COMBINED:
        $query->withCustomPHIDs(array($this->getCustomPHID()), true);
        break;
    }

    $stored_items = $query->execute();

    foreach ($stored_items as $stored_item) {
      $impl = $stored_item->getMenuItem();
      $impl->setViewer($viewer);
      $impl->setEngine($this);
    }

    // Merge the stored items into the builtin items. If a builtin item has
    // a stored version, replace the defaults with the stored changes.
    foreach ($stored_items as $stored_item) {
      if (!$stored_item->shouldEnableForObject($object)) {
        continue;
      }

      $builtin_key = $stored_item->getBuiltinKey();
      if ($builtin_key !== null) {
        // If this builtin actually exists, replace the builtin with the
        // stored configuration. Otherwise, we're just going to drop the
        // stored config: it corresponds to an out-of-date or uninstalled
        // item.
        if (isset($items[$builtin_key])) {
          $items[$builtin_key] = $stored_item;
        } else {
          continue;
        }
      } else {
        $items[] = $stored_item;
      }
    }

    $items = $this->arrangeItems($items, $mode);

    // Make sure exactly one valid item is marked as default.
    $default = null;
    $first = null;
    foreach ($items as $item) {
      if (!$item->canMakeDefault() || $item->isDisabled()) {
        continue;
      }

      // If this engine doesn't support pinning items, don't respect any
      // setting which might be present in the database.
      if ($this->isMenuEnginePinnable()) {
        if ($item->isDefault()) {
          $default = $item;
          break;
        }
      }

      if ($first === null) {
        $first = $item;
      }
    }

    if (!$default) {
      $default = $first;
    }

    if ($default) {
      $this->setDefaultItem($default);
    }

    return $items;
{'fraction_non_alphanumeric': 0.1341720685819493, 'fraction_numerical': 0.0017644125656860112, 'mean_word_length': 2.4001043296817945, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 252, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '3834168', 'n_tokens_mistral': 7942, 'n_tokens_neox': 7763, 'n_words': 1704}