private function buildPropertyView(PhameBlog $blog)
 {
     $viewer = $this->getViewer();
     require_celerity_resource('aphront-tooltip-css');
     Javelin::initBehavior('phabricator-tooltips');
     $properties = id(new PHUIPropertyListView())->setUser($viewer)->setObject($blog);
     $domain = $blog->getDomain();
     if (!$domain) {
         $domain = phutil_tag('em', array(), pht('No external domain'));
     }
     $properties->addProperty(pht('Domain'), $domain);
     $feed_uri = PhabricatorEnv::getProductionURI($this->getApplicationURI('blog/feed/' . $blog->getID() . '/'));
     $properties->addProperty(pht('Atom URI'), javelin_tag('a', array('href' => $feed_uri, 'sigil' => 'has-tooltip', 'meta' => array('tip' => pht('Atom URI does not support custom domains.'), 'size' => 320)), $feed_uri));
     $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions($viewer, $blog);
     $properties->addProperty(pht('Editable By'), $descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
     $engine = id(new PhabricatorMarkupEngine())->setViewer($viewer)->addObject($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION)->process();
     $properties->invokeWillRenderEvent();
     $description = $blog->getDescription();
     if (strlen($description)) {
         $description = new PHUIRemarkupView($viewer, $description);
         $properties->addSectionHeader(pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
         $properties->addTextContent($description);
     }
     return $properties;
 }
 public static function loadOneSkinSpecification($name)
 {
     // Only allow skins which we know to exist to load. This prevents loading
     // skins like "../../secrets/evil/".
     $all = self::loadAllSkinSpecifications();
     if (empty($all[$name])) {
         throw new Exception(pht('Blog skin "%s" is not a valid skin!', $name));
     }
     $paths = PhabricatorEnv::getEnvConfig('phame.skins');
     $base = dirname(phutil_get_library_root('phabricator'));
     foreach ($paths as $path) {
         $path = Filesystem::resolvePath($path, $base);
         $skin_path = $path . DIRECTORY_SEPARATOR . $name;
         if (is_dir($skin_path)) {
             // Double check that the skin really lives in the skin directory.
             if (!Filesystem::isDescendant($skin_path, $path)) {
                 throw new Exception(pht('Blog skin "%s" is not located in path "%s"!', $name, $path));
             }
             $spec = self::loadSkinSpecification($skin_path);
             if ($spec) {
                 $spec->setName($name);
                 return $spec;
             }
         }
     }
     return null;
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
     $paste = id(new PhabricatorPasteQuery())->setViewer($viewer)->withIDs(array($id))->needContent(true)->executeOne();
     if (!$paste) {
         return new Aphront404Response();
     }
     $file = id(new PhabricatorFileQuery())->setViewer($viewer)->withPHIDs(array($paste->getFilePHID()))->executeOne();
     if (!$file) {
         return new Aphront400Response();
     }
     $forks = id(new PhabricatorPasteQuery())->setViewer($viewer)->withParentPHIDs(array($paste->getPHID()))->execute();
     $fork_phids = mpull($forks, 'getPHID');
     $header = $this->buildHeaderView($paste);
     $actions = $this->buildActionView($viewer, $paste, $file);
     $properties = $this->buildPropertyView($paste, $fork_phids, $actions);
     $object_box = id(new PHUIObjectBoxView())->setHeader($header)->addPropertyList($properties);
     $source_code = $this->buildSourceCodeView($paste, null, $this->highlightMap);
     $source_code = id(new PHUIBoxView())->appendChild($source_code)->addMargin(PHUI::MARGIN_LARGE_LEFT)->addMargin(PHUI::MARGIN_LARGE_RIGHT)->addMargin(PHUI::MARGIN_LARGE_TOP);
     $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView())->addTextCrumb('P' . $paste->getID(), '/P' . $paste->getID());
     $timeline = $this->buildTransactionTimeline($paste, new PhabricatorPasteTransactionQuery());
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
     $add_comment_header = $is_serious ? pht('Add Comment') : pht('Eat Paste');
     $draft = PhabricatorDraft::newFromUserAndKey($viewer, $paste->getPHID());
     $add_comment_form = id(new PhabricatorApplicationTransactionCommentView())->setUser($viewer)->setObjectPHID($paste->getPHID())->setDraft($draft)->setHeaderText($add_comment_header)->setAction($this->getApplicationURI('/comment/' . $paste->getID() . '/'))->setSubmitButtonName(pht('Add Comment'));
     return $this->buildApplicationPage(array($crumbs, $object_box, $source_code, $timeline, $add_comment_form), array('title' => $paste->getFullName(), 'pageObjects' => array($paste->getPHID())));
 }
 protected function execute(ConduitAPIRequest $request)
 {
     $viewer = $request->getUser();
     $query = id(new ReleephBranchQuery())->setViewer($viewer);
     $ids = $request->getValue('ids');
     if ($ids !== null) {
         $query->withIDs($ids);
     }
     $phids = $request->getValue('phids');
     if ($phids !== null) {
         $query->withPHIDs($phids);
     }
     $product_phids = $request->getValue('productPHIDs');
     if ($product_phids !== null) {
         $query->withProductPHIDs($product_phids);
     }
     $pager = $this->newPager($request);
     $branches = $query->executeWithCursorPager($pager);
     $data = array();
     foreach ($branches as $branch) {
         $id = $branch->getID();
         $uri = '/releeph/branch/' . $id . '/';
         $uri = PhabricatorEnv::getProductionURI($uri);
         $data[] = array('id' => $id, 'phid' => $branch->getPHID(), 'uri' => $uri, 'name' => $branch->getName(), 'productPHID' => $branch->getProduct()->getPHID());
     }
     return $this->addPagerResults(array('data' => $data), $pager);
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $visible = $request->getStr('visible');
     if (strlen($visible)) {
         $user->setConsoleVisible((int) $visible);
         $user->save();
         return new AphrontAjaxResponse();
     }
     $tab = $request->getStr('tab');
     if (strlen($tab)) {
         $user->setConsoleTab($tab);
         $user->save();
         return new AphrontAjaxResponse();
     }
     if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) {
         $user->setConsoleEnabled(!$user->getConsoleEnabled());
         if ($user->getConsoleEnabled()) {
             $user->setConsoleVisible(true);
         }
         $user->save();
         if ($request->isAjax()) {
             return new AphrontRedirectResponse();
         } else {
             return id(new AphrontRedirectResponse())->setURI('/');
         }
     }
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $chrono_key = $request->getStr('chronoKey');
     $user = $request->getUser();
     if ($request->isDialogFormPost()) {
         $table = new PhabricatorFeedStoryNotification();
         queryfx($table->establishConnection('w'), 'UPDATE %T SET hasViewed = 1 ' . 'WHERE userPHID = %s AND hasViewed = 0 and chronologicalKey <= %s', $table->getTableName(), $user->getPHID(), $chrono_key);
         return id(new AphrontReloadResponse())->setURI('/notification/');
     }
     $dialog = new AphrontDialogView();
     $dialog->setUser($user);
     $dialog->addCancelButton('/notification/');
     if ($chrono_key) {
         $dialog->setTitle(pht('Really mark all notifications as read?'));
         $dialog->addHiddenInput('chronoKey', $chrono_key);
         $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
         if ($is_serious) {
             $dialog->appendChild(pht('All unread notifications will be marked as read. You can not ' . 'undo this action.'));
         } else {
             $dialog->appendChild(pht("You can't ignore your problems forever, you know."));
         }
         $dialog->addSubmitButton(pht('Mark All Read'));
     } else {
         $dialog->setTitle(pht('No notifications to mark as read.'));
         $dialog->appendChild(pht('You have no unread notifications.'));
     }
     return id(new AphrontDialogResponse())->setDialog($dialog);
 }
 protected function execute(ConduitAPIRequest $request)
 {
     $diff = id(new DifferentialDiff())->load($request->getValue('diffid'));
     if (!$diff) {
         throw new ConduitException('ERR_BAD_DIFF');
     }
     $revision = id(new DifferentialRevision())->load($request->getValue('id'));
     if (!$revision) {
         throw new ConduitException('ERR_BAD_REVISION');
     }
     if ($request->getUser()->getPHID() !== $revision->getAuthorPHID()) {
         throw new ConduitException('ERR_WRONG_USER');
     }
     if ($revision->getStatus() == ArcanistDifferentialRevisionStatus::CLOSED) {
         throw new ConduitException('ERR_CLOSED');
     }
     $content_source = PhabricatorContentSource::newForSource(PhabricatorContentSource::SOURCE_CONDUIT, array());
     $editor = new DifferentialRevisionEditor($revision, $revision->getAuthorPHID());
     $editor->setContentSource($content_source);
     $fields = $request->getValue('fields');
     $editor->copyFieldsFromConduit($fields);
     $editor->addDiff($diff, $request->getValue('message'));
     $editor->save();
     return array('revisionid' => $revision->getID(), 'uri' => PhabricatorEnv::getURI('/D' . $revision->getID()));
 }
 protected function buildMailBody(PhabricatorLiskDAO $object, array $xactions)
 {
     $body = parent::buildMailBody($object, $xactions);
     $detail_uri = PhabricatorEnv::getProductionURI($object->getURI());
     $body->addLinkSection(pht('PACKAGE DETAIL'), $detail_uri);
     return $body;
 }
 public function handleException(Exception $ex)
 {
     // Always log the unhandled exception.
     phlog($ex);
     $class = phutil_escape_html(get_class($ex));
     $message = phutil_escape_html($ex->getMessage());
     if (PhabricatorEnv::getEnvConfig('phabricator.show-stack-traces')) {
         $trace = $this->renderStackTrace($ex->getTrace());
     } else {
         $trace = null;
     }
     $content = '<div class="aphront-unhandled-exception">' . '<div class="exception-message">' . $message . '</div>' . $trace . '</div>';
     $user = $this->getRequest()->getUser();
     if (!$user) {
         // If we hit an exception very early, we won't have a user.
         $user = new PhabricatorUser();
     }
     $dialog = new AphrontDialogView();
     $dialog->setTitle('Unhandled Exception ("' . $class . '")')->setClass('aphront-exception-dialog')->setUser($user)->appendChild($content);
     if ($this->getRequest()->isAjax()) {
         $dialog->addCancelButton('/', 'Close');
     }
     $response = new AphrontDialogResponse();
     $response->setDialog($dialog);
     return $response;
 }
 public function execute(PhutilArgumentParser $args)
 {
     $viewer = $this->getViewer();
     $names = $args->getArg('buildable');
     if (count($names) != 1) {
         throw new PhutilArgumentUsageException(pht('Specify exactly one buildable object, by object name.'));
     }
     $name = head($names);
     $buildable = id(new PhabricatorObjectQuery())->setViewer($viewer)->withNames($names)->executeOne();
     if (!$buildable) {
         throw new PhutilArgumentUsageException(pht('No such buildable "%s"!', $name));
     }
     if (!$buildable instanceof HarbormasterBuildableInterface) {
         throw new PhutilArgumentUsageException(pht('Object "%s" is not a buildable!', $name));
     }
     $plan_id = $args->getArg('plan');
     if (!$plan_id) {
         throw new PhutilArgumentUsageException(pht('Use --plan to specify a build plan to run.'));
     }
     $plan = id(new HarbormasterBuildPlanQuery())->setViewer($viewer)->withIDs(array($plan_id))->executeOne();
     if (!$plan) {
         throw new PhutilArgumentUsageException(pht('Build plan "%s" does not exist.', $plan_id));
     }
     $console = PhutilConsole::getConsole();
     $buildable = HarbormasterBuildable::initializeNewBuildable($viewer)->setIsManualBuildable(true)->setBuildablePHID($buildable->getHarbormasterBuildablePHID())->setContainerPHID($buildable->getHarbormasterContainerPHID())->save();
     $console->writeOut("%s\n", pht('Applying plan %s to new buildable %s...', $plan->getID(), 'B' . $buildable->getID()));
     $console->writeOut("\n    %s\n\n", PhabricatorEnv::getProductionURI('/B' . $buildable->getID()));
     PhabricatorWorker::setRunAllTasksInProcess(true);
     $buildable->applyPlan($plan);
     $console->writeOut("%s\n", pht('Done.'));
     return 0;
 }
 protected function getHead()
 {
     $framebust = null;
     if (!$this->getFrameable()) {
         $framebust = '(top == self) || top.location.replace(self.location.href);';
     }
     $viewport_tag = null;
     if ($this->getDeviceReady()) {
         $viewport_tag = phutil_tag('meta', array('name' => 'viewport', 'content' => 'width=device-width, ' . 'initial-scale=1, ' . 'maximum-scale=1'));
     }
     $icon_tag_76 = phutil_tag('link', array('rel' => 'apple-touch-icon', 'href' => celerity_get_resource_uri('/rsrc/favicons/apple-touch-icon-76x76.png')));
     $icon_tag_120 = phutil_tag('link', array('rel' => 'apple-touch-icon', 'sizes' => '120x120', 'href' => celerity_get_resource_uri('/rsrc/favicons/apple-touch-icon-120x120.png')));
     $icon_tag_152 = phutil_tag('link', array('rel' => 'apple-touch-icon', 'sizes' => '152x152', 'href' => celerity_get_resource_uri('/rsrc/favicons/apple-touch-icon-152x152.png')));
     $apple_tag = phutil_tag('meta', array('name' => 'apple-mobile-web-app-status-bar-style', 'content' => 'black-translucent'));
     $referrer_tag = phutil_tag('meta', array('name' => 'referrer', 'content' => 'never'));
     $response = CelerityAPI::getStaticResourceResponse();
     if ($this->getRequest()) {
         $viewer = $this->getRequest()->getViewer();
         if ($viewer) {
             $postprocessor_key = $viewer->getPreference(PhabricatorUserPreferences::PREFERENCE_RESOURCE_POSTPROCESSOR);
             if (strlen($postprocessor_key)) {
                 $response->setPostProcessorKey($postprocessor_key);
             }
         }
     }
     $developer = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
     return hsprintf('%s%s%s%s%s%s%s%s', $viewport_tag, $icon_tag_76, $icon_tag_120, $icon_tag_152, $apple_tag, $referrer_tag, CelerityStaticResourceResponse::renderInlineScript($framebust . jsprintf('window.__DEV__=%d;', $developer ? 1 : 0)), $response->renderResourcesOfType('css'));
 }
 /**
  * @phutil-external-symbol class PhabricatorStartup
  */
 public function buildRequest()
 {
     $parser = new PhutilQueryStringParser();
     $data = array();
     // If the request has "multipart/form-data" content, we can't use
     // PhutilQueryStringParser to parse it, and the raw data supposedly is not
     // available anyway (according to the PHP documentation, "php://input" is
     // not available for "multipart/form-data" requests). However, it is
     // available at least some of the time (see T3673), so double check that
     // we aren't trying to parse data we won't be able to parse correctly by
     // examining the Content-Type header.
     $content_type = idx($_SERVER, 'CONTENT_TYPE');
     $is_form_data = preg_match('@^multipart/form-data@i', $content_type);
     $raw_input = PhabricatorStartup::getRawInput();
     if (strlen($raw_input) && !$is_form_data) {
         $data += $parser->parseQueryString($raw_input);
     } else {
         if ($_POST) {
             $data += $_POST;
         }
     }
     $data += $parser->parseQueryString(idx($_SERVER, 'QUERY_STRING', ''));
     $cookie_prefix = PhabricatorEnv::getEnvConfig('phabricator.cookie-prefix');
     $request = new AphrontRequest($this->getHost(), $this->getPath());
     $request->setRequestData($data);
     $request->setApplicationConfiguration($this);
     $request->setCookiePrefix($cookie_prefix);
     return $request;
 }
 protected function buildResourceTransformer()
 {
     $minify_on = PhabricatorEnv::getEnvConfig('celerity.minify');
     $developer_on = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
     $should_minify = $minify_on && !$developer_on;
     return id(new CelerityResourceTransformer())->setMinify($should_minify)->setCelerityMap($this->getCelerityResourceMap());
 }
 public function handleRequest(AphrontRequest $request)
 {
     $admin = $request->getUser();
     id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession($admin, $request, $this->getApplicationURI());
     $v_type = 'standard';
     if ($request->isFormPost()) {
         $v_type = $request->getStr('type');
         if ($v_type == 'standard' || $v_type == 'bot' || $v_type == 'list') {
             return id(new AphrontRedirectResponse())->setURI($this->getApplicationURI('new/' . $v_type . '/'));
         }
     }
     $title = pht('Create New User');
     $standard_caption = pht('Create a standard user account. These users can log in to Phabricator, ' . 'use the web interface and API, and receive email.');
     $standard_admin = pht('Administrators are limited in their ability to access or edit these ' . 'accounts after account creation.');
     $bot_caption = pht('Create a bot/script user account, to automate interactions with other ' . 'systems. These users can not use the web interface, but can use the ' . 'API.');
     $bot_admin = pht('Administrators have greater access to edit these accounts.');
     $types = array();
     $can_create = $this->hasApplicationCapability(PeopleCreateUsersCapability::CAPABILITY);
     if ($can_create) {
         $types[] = array('type' => 'standard', 'name' => pht('Create Standard User'), 'help' => pht('Create a standard user account.'));
     }
     $types[] = array('type' => 'bot', 'name' => pht('Create Bot User'), 'help' => pht('Create a new user for use with automated scripts.'));
     $types[] = array('type' => 'list', 'name' => pht('Create Mailing List User'), 'help' => pht('Create a mailing list user to represent an existing, external ' . 'mailing list like a Google Group or a Mailman list.'));
     $buttons = id(new AphrontFormRadioButtonControl())->setLabel(pht('Account Type'))->setName('type')->setValue($v_type);
     foreach ($types as $type) {
         $buttons->addButton($type['type'], $type['name'], $type['help']);
     }
     $form = id(new AphrontFormView())->setUser($admin)->appendRemarkupInstructions(pht('Choose the type of user account to create. For a detailed ' . 'explanation of user account types, see [[ %s | User Guide: ' . 'Account Roles ]].', PhabricatorEnv::getDoclink('User Guide: Account Roles')))->appendChild($buttons)->appendChild(id(new AphrontFormSubmitControl())->addCancelButton($this->getApplicationURI())->setValue(pht('Continue')));
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title);
     $box = id(new PHUIObjectBoxView())->setHeaderText($title)->appendChild($form);
     return $this->buildApplicationPage(array($crumbs, $box), array('title' => $title));
 }
 protected function renderResultList(array $pastes, PhabricatorSavedQuery $query, array $handles)
 {
     assert_instances_of($pastes, 'PhabricatorPaste');
     $viewer = $this->requireViewer();
     $lang_map = PhabricatorEnv::getEnvConfig('pygments.dropdown-choices');
     $list = new PHUIObjectItemListView();
     $list->setUser($viewer);
     foreach ($pastes as $paste) {
         $created = phabricator_date($paste->getDateCreated(), $viewer);
         $author = $handles[$paste->getAuthorPHID()]->renderLink();
         $snippet_type = $paste->getSnippet()->getType();
         $lines = phutil_split_lines($paste->getSnippet()->getContent());
         $preview = id(new PhabricatorSourceCodeView())->setLines($lines)->setTruncatedFirstBytes($snippet_type == PhabricatorPasteSnippet::FIRST_BYTES)->setTruncatedFirstLines($snippet_type == PhabricatorPasteSnippet::FIRST_LINES)->setURI(new PhutilURI($paste->getURI()));
         $source_code = phutil_tag('div', array('class' => 'phabricator-source-code-summary'), $preview);
         $created = phabricator_datetime($paste->getDateCreated(), $viewer);
         $line_count = count($lines);
         $line_count = pht('%s Line(s)', new PhutilNumber($line_count));
         $title = nonempty($paste->getTitle(), pht('(An Untitled Masterwork)'));
         $item = id(new PHUIObjectItemView())->setObjectName('P' . $paste->getID())->setHeader($title)->setHref('/P' . $paste->getID())->setObject($paste)->addByline(pht('Author: %s', $author))->addIcon('none', $created)->addIcon('none', $line_count)->appendChild($source_code);
         if ($paste->isArchived()) {
             $item->setDisabled(true);
         }
         $lang_name = $paste->getLanguage();
         if ($lang_name) {
             $lang_name = idx($lang_map, $lang_name, $lang_name);
             $item->addIcon('none', $lang_name);
         }
         $list->addItem($item);
     }
     $result = new PhabricatorApplicationSearchResultView();
     $result->setObjectList($list);
     $result->setNoDataString(pht('No pastes found.'));
     return $result;
 }
 protected function executeChecks()
 {
     $adapter = PhabricatorEnv::getEnvConfig('metamta.mail-adapter');
     switch ($adapter) {
         case 'PhabricatorMailImplementationPHPMailerLiteAdapter':
             if (!Filesystem::pathExists('/usr/bin/sendmail') && !Filesystem::pathExists('/usr/sbin/sendmail')) {
                 $message = pht('Mail is configured to send via sendmail, but this system has ' . 'no sendmail binary. Install sendmail or choose a different ' . 'mail adapter.');
                 $this->newIssue('config.metamta.mail-adapter')->setShortName(pht('Missing Sendmail'))->setName(pht('No Sendmail Binary Found'))->setMessage($message)->addRelatedPhabricatorConfig('metamta.mail-adapter');
             }
             break;
         case 'PhabricatorMailImplementationAmazonSESAdapter':
             if (PhabricatorEnv::getEnvConfig('metamta.can-send-as-user')) {
                 $message = pht('Amazon SES does not support sending email as users. Disable ' . 'send as user, or choose a different mail adapter.');
                 $this->newIssue('config.can-send-as-user')->setName(pht("SES Can't Send As User"))->setMessage($message)->addRelatedPhabricatorConfig('metamta.mail-adapter')->addPhabricatorConfig('metamta.can-send-as-user');
             }
             if (!PhabricatorEnv::getEnvConfig('amazon-ses.access-key')) {
                 $message = pht('Amazon SES is selected as the mail adapter, but no SES access ' . 'key is configured. Provide an SES access key, or choose a ' . 'different mail adapter.');
                 $this->newIssue('config.amazon-ses.access-key')->setName(pht('Amazon SES Access Key Not Set'))->setMessage($message)->addRelatedPhabricatorConfig('metamta.mail-adapter')->addPhabricatorConfig('amazon-ses.access-key');
             }
             if (!PhabricatorEnv::getEnvConfig('amazon-ses.secret-key')) {
                 $message = pht('Amazon SES is selected as the mail adapter, but no SES secret ' . 'key is configured. Provide an SES secret key, or choose a ' . 'different mail adapter.');
                 $this->newIssue('config.amazon-ses.secret-key')->setName(pht('Amazon SES Secret Key Not Set'))->setMessage($message)->addRelatedPhabricatorConfig('metamta.mail-adapter')->addPhabricatorConfig('amazon-ses.secret-key');
             }
             $address_key = 'metamta.default-address';
             $options = PhabricatorApplicationConfigOptions::loadAllOptions();
             $default = $options[$address_key]->getDefault();
             $value = PhabricatorEnv::getEnvConfig($address_key);
             if ($default === $value) {
                 $message = pht('Amazon SES requires verification of the "From" address, but ' . 'you have not configured a "From" address. Configure and verify ' . 'a "From" address, or choose a different mail adapter.');
                 $this->newIssue('config.metamta.default-address')->setName(pht('No SES From Address Configured'))->setMessage($message)->addRelatedPhabricatorConfig('metamta.mail-adapter')->addPhabricatorConfig('metamta.default-address');
             }
             break;
     }
 }
 protected function processDiffusionRequest(AphrontRequest $request)
 {
     $viewer = $request->getUser();
     $this->requireApplicationCapability(DiffusionCreateRepositoriesCapability::CAPABILITY);
     if ($request->isFormPost()) {
         if ($request->getStr('type')) {
             switch ($request->getStr('type')) {
                 case 'create':
                     $uri = $this->getApplicationURI('create/');
                     break;
                 case 'import':
                 default:
                     $uri = $this->getApplicationURI('import/');
                     break;
             }
             return id(new AphrontRedirectResponse())->setURI($uri);
         }
     }
     $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: Repository Hosting');
     $doc_link = phutil_tag('a', array('href' => $doc_href, 'target' => '_blank'), pht('Diffusion User Guide: Repository Hosting'));
     $form = id(new AphrontFormView())->setUser($viewer)->appendChild(id(new AphrontFormRadioButtonControl())->setName('type')->addButton('create', pht('Create a New Hosted Repository'), array(pht('Create a new, empty repository which Phabricator will host. ' . 'For instructions on configuring repository hosting, see %s.', $doc_link)))->addButton('import', pht('Import an Existing External Repository'), pht("Import a repository hosted somewhere else, like GitHub, " . "Bitbucket, or your organization's existing servers. " . "Phabricator will read changes from the repository but will " . "not host or manage it. The authoritative master version of " . "the repository will stay where it is now.")))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Continue'))->addCancelButton($this->getApplicationURI()));
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('New Repository'));
     $form_box = id(new PHUIObjectBoxView())->setHeaderText(pht('Create or Import Repository'))->setForm($form);
     return $this->buildApplicationPage(array($crumbs, $form_box), array('title' => pht('New Repository')));
 }
Example #18
0
 /**
  * Makes sure a given custom blog uri is properly configured in DNS
  * to point at this Phabricator instance. If there is an error in
  * the configuration, return a string describing the error and how
  * to fix it. If there is no error, return an empty string.
  *
  * @return string
  */
 public function validateCustomDomain($custom_domain)
 {
     $example_domain = 'blog.example.com';
     $label = pht('Invalid');
     // note this "uri" should be pretty busted given the desired input
     // so just use it to test if there's a protocol specified
     $uri = new PhutilURI($custom_domain);
     if ($uri->getProtocol()) {
         return array($label, pht('The custom domain should not include a protocol. Just provide ' . 'the bare domain name (for example, "%s").', $example_domain));
     }
     if ($uri->getPort()) {
         return array($label, pht('The custom domain should not include a port number. Just provide ' . 'the bare domain name (for example, "%s").', $example_domain));
     }
     if (strpos($custom_domain, '/') !== false) {
         return array($label, pht('The custom domain should not specify a path (hosting a Phame ' . 'blog at a path is currently not supported). Instead, just provide ' . 'the bare domain name (for example, "%s").', $example_domain));
     }
     if (strpos($custom_domain, '.') === false) {
         return array($label, pht('The custom domain should contain at least one dot (.) because ' . 'some browsers fail to set cookies on domains without a dot. ' . 'Instead, use a normal looking domain name like "%s".', $example_domain));
     }
     if (!PhabricatorEnv::getEnvConfig('policy.allow-public')) {
         $href = PhabricatorEnv::getProductionURI('/config/edit/policy.allow-public/');
         return array(pht('Fix Configuration'), pht('For custom domains to work, this Phabricator instance must be ' . 'configured to allow the public access policy. Configure this ' . 'setting %s, or ask an administrator to configure this setting. ' . 'The domain can be specified later once this setting has been ' . 'changed.', phutil_tag('a', array('href' => $href), pht('here'))));
     }
     return null;
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $editable = $this->getAccountEditable();
     // There's no sense in showing a change password panel if the user
     // can't change their password
     if (!$editable || !PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) {
         return new Aphront400Response();
     }
     $errors = array();
     if ($request->isFormPost()) {
         if ($user->comparePassword($request->getStr('old_pw'))) {
             $pass = $request->getStr('new_pw');
             $conf = $request->getStr('conf_pw');
             if ($pass === $conf) {
                 if (strlen($pass)) {
                     $user->setPassword($pass);
                     // This write is unguarded because the CSRF token has already
                     // been checked in the call to $request->isFormPost() and
                     // the CSRF token depends on the password hash, so when it
                     // is changed here the CSRF token check will fail.
                     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
                     $user->save();
                     unset($unguarded);
                     return id(new AphrontRedirectResponse())->setURI('/settings/page/password/?saved=true');
                 } else {
                     $errors[] = 'Your new password is too short.';
                 }
             } else {
                 $errors[] = 'New password and confirmation do not match.';
             }
         } else {
             $errors[] = 'The old password you entered is incorrect.';
         }
     }
     $notice = null;
     if (!$errors) {
         if ($request->getStr('saved')) {
             $notice = new AphrontErrorView();
             $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
             $notice->setTitle('Changes Saved');
             $notice->appendChild('<p>Your password has been updated.</p>');
         }
     } else {
         $notice = new AphrontErrorView();
         $notice->setTitle('Error Changing Password');
         $notice->setErrors($errors);
     }
     $form = new AphrontFormView();
     $form->setUser($user)->appendChild(id(new AphrontFormPasswordControl())->setLabel('Old Password')->setName('old_pw'));
     $form->appendChild(id(new AphrontFormPasswordControl())->setLabel('New Password')->setName('new_pw'));
     $form->appendChild(id(new AphrontFormPasswordControl())->setLabel('Confirm Password')->setName('conf_pw'));
     $form->appendChild(id(new AphrontFormSubmitControl())->setValue('Save'));
     $panel = new AphrontPanelView();
     $panel->setHeader('Change Password');
     $panel->setWidth(AphrontPanelView::WIDTH_FORM);
     $panel->appendChild($form);
     return id(new AphrontNullView())->appendChild(array($notice, $panel));
 }
 /**
  * Release the scoped environment.
  *
  * @return void
  * @task internal
  */
 public function __destruct()
 {
     if (!$this->isPopped) {
         PhabricatorEnv::popTestEnvironment($this->key);
         $this->isPopped = true;
     }
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     // If the user already has a full session, just kick them out of here.
     $has_partial_session = $viewer->hasSession() && $viewer->getSession()->getIsPartial();
     if (!$has_partial_session) {
         return id(new AphrontRedirectResponse())->setURI('/');
     }
     $engine = new PhabricatorAuthSessionEngine();
     // If this cookie is set, the user is headed into a high security area
     // after login (normally because of a password reset) so if they are
     // able to pass the checkpoint we just want to put their account directly
     // into high security mode, rather than prompt them again for the same
     // set of credentials.
     $jump_into_hisec = $request->getCookie(PhabricatorCookies::COOKIE_HISEC);
     try {
         $token = $engine->requireHighSecuritySession($viewer, $request, '/logout/', $jump_into_hisec);
     } catch (PhabricatorAuthHighSecurityRequiredException $ex) {
         $form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm($ex->getFactors(), $ex->getFactorValidationResults(), $viewer, $request);
         return $this->newDialog()->setTitle(pht('Provide Multi-Factor Credentials'))->setShortTitle(pht('Multi-Factor Login'))->setWidth(AphrontDialogView::WIDTH_FORM)->addHiddenInput(AphrontRequest::TYPE_HISEC, true)->appendParagraph(pht('Welcome, %s. To complete the login process, provide your ' . 'multi-factor credentials.', phutil_tag('strong', array(), $viewer->getUsername())))->appendChild($form->buildLayoutView())->setSubmitURI($request->getPath())->addCancelButton($ex->getCancelURI())->addSubmitButton(pht('Continue'));
     }
     // Upgrade the partial session to a full session.
     $engine->upgradePartialSession($viewer);
     // TODO: It might be nice to add options like "bind this session to my IP"
     // here, even for accounts without multi-factor auth attached to them.
     $next = PhabricatorCookies::getNextURICookie($request);
     $request->clearCookie(PhabricatorCookies::COOKIE_NEXTURI);
     $request->clearCookie(PhabricatorCookies::COOKIE_HISEC);
     if (!PhabricatorEnv::isValidLocalURIForLink($next)) {
         $next = '/';
     }
     return id(new AphrontRedirectResponse())->setURI($next);
 }
 /**
  * Select viable default storage engines according to configuration. We'll
  * select the MySQL and Local Disk storage engines if they are configured
  * to allow a given file.
  */
 public function selectStorageEngines($data, array $params)
 {
     $length = strlen($data);
     $mysql_key = 'storage.mysql-engine.max-size';
     $mysql_limit = PhabricatorEnv::getEnvConfig($mysql_key);
     $engines = array();
     if ($mysql_limit && $length <= $mysql_limit) {
         $engines[] = new PhabricatorMySQLFileStorageEngine();
     }
     $local_key = 'storage.local-disk.path';
     $local_path = PhabricatorEnv::getEnvConfig($local_key);
     if ($local_path) {
         $engines[] = new PhabricatorLocalDiskFileStorageEngine();
     }
     $s3_key = 'storage.s3.bucket';
     if (PhabricatorEnv::getEnvConfig($s3_key)) {
         $engines[] = new PhabricatorS3FileStorageEngine();
     }
     if ($mysql_limit && empty($engines)) {
         // If we return no engines, an exception will be thrown but it will be
         // a little vague ("No valid storage engines"). Since this is a default
         // case, throw a more specific exception.
         throw new Exception("This file exceeds the configured MySQL storage engine filesize " . "limit, but no other storage engines are configured. Increase the " . "MySQL storage engine limit or configure a storage engine suitable " . "for larger files.");
     }
     return $engines;
 }
 protected function doWork()
 {
     $data = $this->getTaskData();
     $viewer = PhabricatorUser::getOmnipotentUser();
     $address = idx($data, 'address');
     $author_phid = idx($data, 'authorPHID');
     $author = id(new PhabricatorPeopleQuery())->setViewer($viewer)->withPHIDs(array($author_phid))->executeOne();
     if (!$author) {
         throw new PhabricatorWorkerPermanentFailureException(pht('Invite has invalid author PHID ("%s").', $author_phid));
     }
     $invite = id(new PhabricatorAuthInviteQuery())->setViewer($viewer)->withEmailAddresses(array($address))->executeOne();
     if ($invite) {
         // If we're inviting a user who has already been invited, we just
         // regenerate their invite code.
         $invite->regenerateVerificationCode();
     } else {
         // Otherwise, we're creating a new invite.
         $invite = id(new PhabricatorAuthInvite())->setEmailAddress($address);
     }
     // Whether this is a new invite or not, tag this most recent author as
     // the invite author.
     $invite->setAuthorPHID($author_phid);
     $code = $invite->getVerificationCode();
     $invite_uri = '/auth/invite/' . $code . '/';
     $invite_uri = PhabricatorEnv::getProductionURI($invite_uri);
     $template = idx($data, 'template');
     $template = str_replace('{$INVITE_URI}', $invite_uri, $template);
     $invite->save();
     $mail = id(new PhabricatorMetaMTAMail())->addRawTos(array($invite->getEmailAddress()))->setForceDelivery(true)->setSubject(pht('[Phabricator] %s has invited you to join Phabricator', $author->getFullName()))->setBody($template)->saveAndSend();
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $query = id(new PhabricatorNotificationQuery())->setViewer($user)->withUserPHIDs(array($user->getPHID()))->setLimit(15);
     $stories = $query->execute();
     $clear_ui_class = 'phabricator-notification-clear-all';
     $clear_uri = id(new PhutilURI('/notification/clear/'));
     if ($stories) {
         $builder = new PhabricatorNotificationBuilder($stories);
         $notifications_view = $builder->buildView();
         $content = $notifications_view->render();
         $clear_uri->setQueryParam('chronoKey', head($stories)->getChronologicalKey());
     } else {
         $content = phutil_tag_div('phabricator-notification no-notifications', pht('You have no notifications.'));
         $clear_ui_class .= ' disabled';
     }
     $clear_ui = javelin_tag('a', array('sigil' => 'workflow', 'href' => (string) $clear_uri, 'class' => $clear_ui_class), pht('Mark All Read'));
     $notifications_link = phutil_tag('a', array('href' => '/notification/'), pht('Notifications'));
     if (PhabricatorEnv::getEnvConfig('notification.enabled')) {
         $connection_status = new PhabricatorNotificationStatusView();
     } else {
         $connection_status = phutil_tag('a', array('href' => PhabricatorEnv::getDoclink('Notifications User Guide: Setup and Configuration')), pht('Notification Server not enabled.'));
     }
     $connection_ui = phutil_tag('div', array('class' => 'phabricator-notification-footer'), $connection_status);
     $header = phutil_tag('div', array('class' => 'phabricator-notification-header'), array($notifications_link, $clear_ui));
     $content = hsprintf('%s%s%s', $header, $content, $connection_ui);
     $unread_count = id(new PhabricatorFeedStoryNotification())->countUnread($user);
     $json = array('content' => $content, 'number' => (int) $unread_count);
     return id(new AphrontAjaxResponse())->setContent($json);
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
     $timeline = null;
     $url = id(new PhabricatorPhurlURLQuery())->setViewer($viewer)->withIDs(array($id))->executeOne();
     if (!$url) {
         return new Aphront404Response();
     }
     $title = $url->getMonogram();
     $page_title = $title . ' ' . $url->getName();
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title, $url->getURI());
     $timeline = $this->buildTransactionTimeline($url, new PhabricatorPhurlURLTransactionQuery());
     $header = $this->buildHeaderView($url);
     $actions = $this->buildActionView($url);
     $properties = $this->buildPropertyView($url);
     $properties->setActionList($actions);
     $url_error = id(new PHUIInfoView())->setErrors(array(pht('This URL is invalid due to a bad protocol.')))->setIsHidden($url->isValid());
     $box = id(new PHUIObjectBoxView())->setHeader($header)->addPropertyList($properties)->setInfoView($url_error);
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
     $add_comment_header = $is_serious ? pht('Add Comment') : pht('More Cowbell');
     $draft = PhabricatorDraft::newFromUserAndKey($viewer, $url->getPHID());
     $comment_uri = $this->getApplicationURI('/phurl/comment/' . $url->getID() . '/');
     $add_comment_form = id(new PhabricatorApplicationTransactionCommentView())->setUser($viewer)->setObjectPHID($url->getPHID())->setDraft($draft)->setHeaderText($add_comment_header)->setAction($comment_uri)->setSubmitButtonName(pht('Add Comment'));
     return $this->buildApplicationPage(array($crumbs, $box, $timeline, $add_comment_form), array('title' => $page_title, 'pageObjects' => array($url->getPHID())));
 }
 public function processRequest()
 {
     $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain');
     if (!$alt) {
         return new Aphront400Response();
     }
     $request = $this->getRequest();
     $alt_domain = id(new PhutilURI($alt))->getDomain();
     if ($alt_domain != $request->getHost()) {
         return new Aphront400Response();
     }
     $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $this->phid);
     if (!$file) {
         return new Aphront404Response();
     }
     if (!$file->validateSecretKey($this->key)) {
         return new Aphront404Response();
     }
     // It's safe to bypass view restrictions because we know we are being served
     // off an alternate domain which we will not set cookies on.
     $data = $file->loadFileData();
     $response = new AphrontFileResponse();
     $response->setContent($data);
     $response->setMimeType($file->getMimeType());
     $response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
     return $response;
 }
 protected function execute(ConduitAPIRequest $request)
 {
     $viewer = $request->getUser();
     $revision = id(new DifferentialRevisionQuery())->setViewer($viewer)->withIDs(array($request->getValue('revision_id')))->needReviewerStatus(true)->needReviewerAuthority(true)->executeOne();
     if (!$revision) {
         throw new ConduitException('ERR_BAD_REVISION');
     }
     $xactions = array();
     $action = $request->getValue('action');
     if ($action && $action != 'comment' && $action != 'none') {
         $xactions[] = id(new DifferentialTransaction())->setTransactionType(DifferentialTransaction::TYPE_ACTION)->setNewValue($action);
     }
     $content = $request->getValue('message');
     if (strlen($content)) {
         $xactions[] = id(new DifferentialTransaction())->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)->attachComment(id(new DifferentialTransactionComment())->setContent($content));
     }
     if ($request->getValue('attach_inlines')) {
         $type_inline = DifferentialTransaction::TYPE_INLINE;
         $inlines = DifferentialTransactionQuery::loadUnsubmittedInlineComments($viewer, $revision);
         foreach ($inlines as $inline) {
             $xactions[] = id(new DifferentialTransaction())->setTransactionType($type_inline)->attachComment($inline);
         }
     }
     $editor = id(new DifferentialTransactionEditor())->setActor($viewer)->setDisableEmail($request->getValue('silent'))->setContentSource($request->newContentSource())->setContinueOnNoEffect(true)->setContinueOnMissingFields(true);
     $editor->applyTransactions($revision, $xactions);
     return array('revisionid' => $revision->getID(), 'uri' => PhabricatorEnv::getURI('/D' . $revision->getID()));
 }
Example #28
0
 public static function getLog()
 {
     if (!self::$log) {
         $path = PhabricatorEnv::getEnvConfig('log.ssh.path');
         $format = PhabricatorEnv::getEnvConfig('log.ssh.format');
         $format = nonempty($format, "[%D]\t%p\t%h\t%r\t%s\t%S\t%u\t%C\t%U\t%c\t%T\t%i\t%o");
         // NOTE: Path may be null. We still create the log, it just won't write
         // anywhere.
         $data = array('D' => date('r'), 'h' => php_uname('n'), 'p' => getmypid(), 'e' => time());
         $sudo_user = PhabricatorEnv::getEnvConfig('phd.user');
         if (strlen($sudo_user)) {
             $data['S'] = $sudo_user;
         }
         if (function_exists('posix_geteuid')) {
             $system_uid = posix_geteuid();
             $system_info = posix_getpwuid($system_uid);
             $data['s'] = idx($system_info, 'name');
         }
         $client = getenv('SSH_CLIENT');
         if (strlen($client)) {
             $remote_address = head(explode(' ', $client));
             $data['r'] = $remote_address;
         }
         $log = id(new PhutilDeferredLog($path, $format))->setFailQuietly(true)->setData($data);
         self::$log = $log;
     }
     return self::$log;
 }
 protected function execute(ConduitAPIRequest $request)
 {
     $diff = null;
     $revision_id = $request->getValue('revision_id');
     $revision = id(new DifferentialRevision())->load($revision_id);
     if (!$revision) {
         throw new ConduitException('ERR_BAD_REVISION');
     }
     $revision->loadRelationships();
     $reviewer_phids = array_values($revision->getReviewers());
     $diffs = $revision->loadDiffs();
     $diff_dicts = array();
     foreach ($diffs as $diff) {
         $diff->attachChangesets($diff->loadChangesets());
         // TODO: We could batch this to improve performance.
         foreach ($diff->getChangesets() as $changeset) {
             $changeset->attachHunks($changeset->loadHunks());
         }
         $diff_dicts[] = $diff->getDiffDict();
     }
     $commit_dicts = array();
     $commit_phids = $revision->loadCommitPHIDs();
     $handles = id(new PhabricatorObjectHandleData($commit_phids))->loadHandles();
     foreach ($commit_phids as $commit_phid) {
         $commit_dicts[] = array('fullname' => $handles[$commit_phid]->getFullName(), 'dateCommitted' => $handles[$commit_phid]->getTimestamp());
     }
     $auxiliary_fields = $this->loadAuxiliaryFields($revision);
     $dict = array('id' => $revision->getID(), 'phid' => $revision->getPHID(), 'authorPHID' => $revision->getAuthorPHID(), 'uri' => PhabricatorEnv::getURI('/D' . $revision->getID()), 'title' => $revision->getTitle(), 'status' => $revision->getStatus(), 'statusName' => ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($revision->getStatus()), 'summary' => $revision->getSummary(), 'testPlan' => $revision->getTestPlan(), 'lineCount' => $revision->getLineCount(), 'reviewerPHIDs' => $reviewer_phids, 'diffs' => $diff_dicts, 'commits' => $commit_dicts, 'auxiliary' => $auxiliary_fields);
     return $dict;
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     $class = $request->getURIData('class');
     $sources = id(new PhutilClassMapQuery())->setAncestorClass('PhabricatorTypeaheadDatasource')->execute();
     if (!isset($sources[$class])) {
         return new Aphront404Response();
     }
     $source = $sources[$class];
     $application_class = $source->getDatasourceApplicationClass();
     if ($application_class) {
         $result = id(new PhabricatorApplicationQuery())->setViewer($this->getViewer())->withClasses(array($application_class))->execute();
         if (!$result) {
             return new Aphront404Response();
         }
     }
     $source->setViewer($viewer);
     $title = pht('Typeahead Function Help');
     $functions = $source->getAllDatasourceFunctions();
     ksort($functions);
     $content = array();
     $content[] = '= ' . pht('Overview');
     $content[] = pht('Typeahead functions are an advanced feature which allow you to build ' . 'more powerful queries. This document explains functions available ' . 'for the selected control.' . "\n\n" . 'For general help with search, see the [[ %s | Search User Guide ]] in ' . 'the documentation.' . "\n\n" . 'Note that different controls support //different// functions ' . '(depending on what the control is doing), so these specific functions ' . 'may not work everywhere. You can always check the help for a control ' . 'to review which functions are available for that control.', PhabricatorEnv::getDoclink('Search User Guide'));
     $table = array();
     $table_header = array(pht('Function'), pht('Token Name'), pht('Summary'));
     $table[] = '| ' . implode(' | ', $table_header) . ' |';
     $table[] = '|---|---|---|';
     foreach ($functions as $function => $spec) {
         $spec = $spec + array('summary' => null, 'arguments' => null);
         if (idx($spec, 'arguments')) {
             $signature = '**' . $function . '(**//' . $spec['arguments'] . '//**)**';
         } else {
             $signature = '**' . $function . '()**';
         }
         $name = idx($spec, 'name', '');
         $summary = idx($spec, 'summary', '');
         $table[] = '| ' . $signature . ' | ' . $name . ' | ' . $summary . ' |';
     }
     $table = implode("\n", $table);
     $content[] = '= ' . pht('Function Quick Reference');
     $content[] = pht('This table briefly describes available functions for this control. ' . 'For details on a particular function, see the corresponding section ' . 'below.');
     $content[] = $table;
     $content[] = '= ' . pht('Using Typeahead Functions');
     $content[] = pht("In addition to typing user and project names to build queries, you can " . "also type the names of special functions which give you more options " . "and the ability to express more complex queries.\n\n" . "Functions have an internal name (like `%s`) and a human-readable name, " . "like `Current Viewer`. In general, you can type either one to select " . "the function. You can also click the {nav icon=search} button on any " . "typeahead control to browse available functions and find this " . "documentation.\n\n" . "This documentation uses the internal names to make it clear where " . "tokens begin and end. Specifically, you will find queries written " . "out like this in the documentation:\n\n%s\n\n" . "When this query is actually shown in the control, it will look more " . "like this:\n\n%s", 'viewer()', '> viewer(), alincoln', '> {nav Current Viewer} {nav alincoln (Abraham Lincoln)}');
     $middot = "·";
     foreach ($functions as $function => $spec) {
         $arguments = idx($spec, 'arguments', '');
         $name = idx($spec, 'name');
         $content[] = '= ' . $function . '(' . $arguments . ') ' . $middot . ' ' . $name;
         $content[] = $spec['description'];
     }
     $content = implode("\n\n", $content);
     $content_box = new PHUIRemarkupView($viewer, $content);
     $header = id(new PHUIHeaderView())->setHeader($title);
     $document = id(new PHUIDocumentViewPro())->setHeader($header)->appendChild($content_box);
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Function Help'));
     $crumbs->setBorder(true);
     return $this->newPage()->setTitle($title)->setCrumbs($crumbs)->appendChild($document);
 }