public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     if ($this->id) {
         $question = id(new PonderQuestionQuery())->setViewer($user)->withIDs(array($this->id))->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->executeOne();
         if (!$question) {
             return new Aphront404Response();
         }
         $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs($question->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
         $v_projects = array_reverse($v_projects);
     } else {
         $question = id(new PonderQuestion())->setStatus(PonderQuestionStatus::STATUS_OPEN)->setAuthorPHID($user->getPHID())->setVoteCount(0)->setAnswerCount(0)->setHeat(0.0);
         $v_projects = array();
     }
     $v_title = $question->getTitle();
     $v_content = $question->getContent();
     $errors = array();
     $e_title = true;
     if ($request->isFormPost()) {
         $v_title = $request->getStr('title');
         $v_content = $request->getStr('content');
         $v_projects = $request->getArr('projects');
         $len = phutil_utf8_strlen($v_title);
         if ($len < 1) {
             $errors[] = pht('Title must not be empty.');
             $e_title = pht('Required');
         } else {
             if ($len > 255) {
                 $errors[] = pht('Title is too long.');
                 $e_title = pht('Too Long');
             }
         }
         if (!$errors) {
             $template = id(new PonderQuestionTransaction());
             $xactions = array();
             $xactions[] = id(clone $template)->setTransactionType(PonderQuestionTransaction::TYPE_TITLE)->setNewValue($v_title);
             $xactions[] = id(clone $template)->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT)->setNewValue($v_content);
             $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
             $xactions[] = id(new PonderQuestionTransaction())->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', $proj_edge_type)->setNewValue(array('=' => array_fuse($v_projects)));
             $editor = id(new PonderQuestionEditor())->setActor($user)->setContentSourceFromRequest($request)->setContinueOnNoEffect(true);
             $editor->applyTransactions($question, $xactions);
             return id(new AphrontRedirectResponse())->setURI('/Q' . $question->getID());
         }
     }
     $form = id(new AphrontFormView())->setUser($user)->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Question'))->setName('title')->setValue($v_title)->setError($e_title))->appendChild(id(new PhabricatorRemarkupControl())->setUser($user)->setName('content')->setID('content')->setValue($v_content)->setLabel(pht('Description'))->setUser($user));
     $form->appendControl(id(new AphrontFormTokenizerControl())->setLabel(pht('Projects'))->setName('projects')->setValue($v_projects)->setDatasource(new PhabricatorProjectDatasource()));
     $form->appendChild(id(new AphrontFormSubmitControl())->addCancelButton($this->getApplicationURI())->setValue(pht('Ask Away!')));
     $preview = id(new PHUIRemarkupPreviewPanel())->setHeader(pht('Question Preview'))->setControlID('content')->setPreviewURI($this->getApplicationURI('preview/'));
     $form_box = id(new PHUIObjectBoxView())->setHeaderText(pht('Ask New Question'))->setFormErrors($errors)->setForm($form);
     $crumbs = $this->buildApplicationCrumbs();
     $id = $question->getID();
     if ($id) {
         $crumbs->addTextCrumb("Q{$id}", "/Q{$id}");
         $crumbs->addTextCrumb(pht('Edit'));
     } else {
         $crumbs->addTextCrumb(pht('Ask Question'));
     }
     return $this->buildApplicationPage(array($crumbs, $form_box, $preview), array('title' => pht('Ask New Question')));
 }
Esempio n. 2
0
 public function testUTF8len()
 {
     $strings = array('' => 0, 'x' => 1, "�" => 1, "x東y" => 3, "xyz" => 3, 'quack' => 5);
     foreach ($strings as $str => $expect) {
         $this->assertEqual($expect, phutil_utf8_strlen($str), 'Length of ' . $str);
     }
 }
 public function markupText($text, $children)
 {
     $text = trim($text);
     $lines = phutil_split_lines($text);
     if (count($lines) > 1) {
         $level = $lines[1][0] == '=' ? 1 : 2;
         $text = trim($lines[0]);
     } else {
         $level = 0;
         for ($ii = 0; $ii < min(5, strlen($text)); $ii++) {
             if ($text[$ii] == '=' || $text[$ii] == '#') {
                 ++$level;
             } else {
                 break;
             }
         }
         $text = trim($text, ' =#');
     }
     $engine = $this->getEngine();
     if ($engine->isTextMode()) {
         $char = $level == 1 ? '=' : '-';
         return $text . "\n" . str_repeat($char, phutil_utf8_strlen($text));
     }
     $use_anchors = $engine->getConfig('header.generate-toc');
     $anchor = null;
     if ($use_anchors) {
         $anchor = $this->generateAnchor($level, $text);
     }
     $text = phutil_tag('h' . ($level + 1), array('class' => 'remarkup-header'), array($anchor, $this->applyRules($text)));
     return $text;
 }
 public static function assertValidPublisherKey($value)
 {
     $length = phutil_utf8_strlen($value);
     if (!$length) {
         throw new Exception(pht('Publisher key "%s" is not valid: publisher keys are required.', $value));
     }
     $max_length = 64;
     if ($length > $max_length) {
         throw new Exception(pht('Publisher key "%s" is not valid: publisher keys must not be ' . 'more than %s characters long.', $value, new PhutilNumber($max_length)));
     }
     if (!preg_match('/^[a-z]+\\z/', $value)) {
         throw new Exception(pht('Publisher key "%s" is not valid: publisher keys may only contain ' . 'lowercase latin letters.', $value));
     }
 }
 public static function assertValidVersionName($value)
 {
     $length = phutil_utf8_strlen($value);
     if (!$length) {
         throw new Exception(pht('Version name "%s" is not valid: version names are required.', $value));
     }
     $max_length = 64;
     if ($length > $max_length) {
         throw new Exception(pht('Version name "%s" is not valid: version names must not be ' . 'more than %s characters long.', $value, new PhutilNumber($max_length)));
     }
     if (!preg_match('/^[A-Za-z0-9.-]+\\z/', $value)) {
         throw new Exception(pht('Version name "%s" is not valid: version names may only contain ' . 'latin letters, digits, periods, and hyphens.', $value));
     }
     if (preg_match('/^[.-]|[.-]$/', $value)) {
         throw new Exception(pht('Version name "%s" is not valid: version names may not start or ' . 'end with a period or hyphen.', $value));
     }
 }
 protected function renderRemarkupTable(array $out_rows)
 {
     assert_instances_of($out_rows, 'array');
     if ($this->getEngine()->isTextMode()) {
         $lengths = array();
         foreach ($out_rows as $r => $row) {
             foreach ($row['content'] as $c => $cell) {
                 $text = $this->getEngine()->restoreText($cell['content']);
                 $lengths[$c][$r] = phutil_utf8_strlen($text);
             }
         }
         $max_lengths = array_map('max', $lengths);
         $out = array();
         foreach ($out_rows as $r => $row) {
             $headings = false;
             foreach ($row['content'] as $c => $cell) {
                 $length = $max_lengths[$c] - $lengths[$c][$r];
                 $out[] = '| ' . $cell['content'] . str_repeat(' ', $length) . ' ';
                 if ($cell['type'] == 'th') {
                     $headings = true;
                 }
             }
             $out[] = "|\n";
             if ($headings) {
                 foreach ($row['content'] as $c => $cell) {
                     $char = $cell['type'] == 'th' ? '-' : ' ';
                     $out[] = '| ' . str_repeat($char, $max_lengths[$c]) . ' ';
                 }
                 $out[] = "|\n";
             }
         }
         return rtrim(implode('', $out), "\n");
     }
     $out = array();
     $out[] = "\n";
     foreach ($out_rows as $row) {
         $cells = array();
         foreach ($row['content'] as $cell) {
             $cells[] = phutil_tag($cell['type'], array(), $cell['content']);
         }
         $out[] = phutil_tag($row['type'], array(), $cells);
         $out[] = "\n";
     }
     return phutil_tag('table', array('class' => 'remarkup-table'), $out);
 }
 private function handlePost()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $errors = array();
     $title = $request->getStr('title');
     $content = $request->getStr('content');
     // form validation
     if (phutil_utf8_strlen($title) < 1 || phutil_utf8_strlen($title) > 255) {
         $errors[] = "Please enter a title (1-255 characters)";
     }
     if ($errors) {
         return $this->showForm($errors, $title, $content);
     }
     // no validation errors -> save it
     $content_source = PhabricatorContentSource::newForSource(PhabricatorContentSource::SOURCE_WEB, array('ip' => $request->getRemoteAddr()));
     $question = id(new PonderQuestion())->setTitle($title)->setContent($content)->setAuthorPHID($user->getPHID())->setContentSource($content_source)->setVoteCount(0)->setAnswerCount(0)->setHeat(0.0)->save();
     PhabricatorSearchPonderIndexer::indexQuestion($question);
     return id(new AphrontRedirectResponse())->setURI('/Q' . $question->getID());
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $question = id(new PonderQuestion())->setAuthorPHID($user->getPHID())->setVoteCount(0)->setAnswerCount(0)->setHeat(0.0);
     $errors = array();
     $e_title = true;
     if ($request->isFormPost()) {
         $question->setTitle($request->getStr('title'));
         $question->setContent($request->getStr('content'));
         $len = phutil_utf8_strlen($question->getTitle());
         if ($len < 1) {
             $errors[] = pht('Title must not be empty.');
             $e_title = pht('Required');
         } else {
             if ($len > 255) {
                 $errors[] = pht('Title is too long.');
                 $e_title = pht('Too Long');
             }
         }
         if (!$errors) {
             $content_source = PhabricatorContentSource::newForSource(PhabricatorContentSource::SOURCE_WEB, array('ip' => $request->getRemoteAddr()));
             $question->setContentSource($content_source);
             id(new PonderQuestionEditor())->setQuestion($question)->setUser($user)->save();
             return id(new AphrontRedirectResponse())->setURI('/Q' . $question->getID());
         }
     }
     $error_view = null;
     if ($errors) {
         $error_view = id(new AphrontErrorView())->setTitle('Form Errors')->setErrors($errors);
     }
     $header = id(new PhabricatorHeaderView())->setHeader(pht('Ask Question'));
     $form = id(new AphrontFormView())->setUser($user)->setFlexible(true)->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Question'))->setName('title')->setValue($question->getTitle())->setError($e_title))->appendChild(id(new PhabricatorRemarkupControl())->setName('content')->setID('content')->setValue($question->getContent())->setLabel(pht('Description')))->appendChild(id(new AphrontFormSubmitControl())->setValue('Ask Away!'));
     $preview = '<div class="aphront-panel-flush">' . '<div id="question-preview">' . '<span class="aphront-panel-preview-loading-text">' . pht('Loading question preview...') . '</span>' . '</div>' . '</div>';
     Javelin::initBehavior('ponder-feedback-preview', array('uri' => '/ponder/question/preview/', 'content' => 'content', 'preview' => 'question-preview', 'question_id' => null));
     $nav = $this->buildSideNavView($question);
     $nav->selectFilter($question->getID() ? null : 'question/ask');
     $nav->appendChild(array($header, $error_view, $form, $preview));
     return $this->buildApplicationPage($nav, array('device' => true, 'title' => 'Ask a Question'));
 }
 protected function renderRemarkupTable(array $out_rows)
 {
     assert_instances_of($out_rows, 'array');
     if ($this->getEngine()->isTextMode()) {
         $lengths = array();
         foreach ($out_rows as $r => $row) {
             foreach ($row['content'] as $c => $cell) {
                 $text = $this->getEngine()->restoreText($cell['content']);
                 $lengths[$c][$r] = phutil_utf8_strlen($text);
             }
         }
         $max_lengths = array_map('max', $lengths);
         $out = array();
         foreach ($out_rows as $r => $row) {
             $headings = false;
             foreach ($row['content'] as $c => $cell) {
                 $length = $max_lengths[$c] - $lengths[$c][$r];
                 $out[] = '| ' . $cell['content'] . str_repeat(' ', $length) . ' ';
                 if ($cell['type'] == 'th') {
                     $headings = true;
                 }
             }
             $out[] = "|\n";
             if ($headings) {
                 foreach ($row['content'] as $c => $cell) {
                     $char = $cell['type'] == 'th' ? '-' : ' ';
                     $out[] = '| ' . str_repeat($char, $max_lengths[$c]) . ' ';
                 }
                 $out[] = "|\n";
             }
         }
         return rtrim(implode('', $out), "\n");
     }
     if ($this->getEngine()->isHTMLMailMode()) {
         $table_attributes = array('style' => 'border-collapse: separate;
       border-spacing: 1px;
       background: #d3d3d3;
       margin: 12px 0;');
         $cell_attributes = array('style' => 'background: #ffffff;
       padding: 3px 6px;');
     } else {
         $table_attributes = array('class' => 'remarkup-table');
         $cell_attributes = array();
     }
     $out = array();
     $out[] = "\n";
     foreach ($out_rows as $row) {
         $cells = array();
         foreach ($row['content'] as $cell) {
             $cells[] = phutil_tag($cell['type'], $cell_attributes, $cell['content']);
         }
         $out[] = phutil_tag($row['type'], array(), $cells);
         $out[] = "\n";
     }
     return phutil_tag('table', $table_attributes, $out);
 }
 /**
  * Check that text field input isn't longer than a specified length.
  *
  * A text field input is invalid if the length of the input is longer than a
  * specified length. This length can be determined by the space allotted in
  * the database, or given arbitrarily.
  * This method is intended to make implementing @{method:validateTransaction}
  * more convenient:
  *
  *   $overdrawn = $this->validateIsTextFieldTooLong(
  *     $object->getName(),
  *     $xactions,
  *     $field_length);
  *
  * This will return `true` if the net effect of the object and transactions
  * is a field that is too long.
  *
  * @param wild Current field value.
  * @param list<PhabricatorApplicationTransaction> Transactions editing the
  *          field.
  * @param integer for maximum field length.
  * @return bool True if the field will be too long after edits.
  */
 protected function validateIsTextFieldTooLong($field_value, array $xactions, $length)
 {
     if ($xactions) {
         $new_value_length = phutil_utf8_strlen(last($xactions)->getNewValue());
         if ($new_value_length <= $length) {
             return false;
         } else {
             return true;
         }
     }
     $old_value_length = phutil_utf8_strlen($field_value);
     if ($old_value_length <= $length) {
         return false;
     }
     return true;
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
     if ($id) {
         $question = id(new PonderQuestionQuery())->setViewer($viewer)->withIDs(array($id))->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->executeOne();
         if (!$question) {
             return new Aphront404Response();
         }
         $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs($question->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
         $v_projects = array_reverse($v_projects);
         $is_new = false;
     } else {
         $is_new = true;
         $question = PonderQuestion::initializeNewQuestion($viewer);
         $v_projects = array();
     }
     $v_title = $question->getTitle();
     $v_content = $question->getContent();
     $v_wiki = $question->getAnswerWiki();
     $v_view = $question->getViewPolicy();
     $v_space = $question->getSpacePHID();
     $v_status = $question->getStatus();
     $errors = array();
     $e_title = true;
     if ($request->isFormPost()) {
         $v_title = $request->getStr('title');
         $v_content = $request->getStr('content');
         $v_wiki = $request->getStr('answerWiki');
         $v_projects = $request->getArr('projects');
         $v_view = $request->getStr('viewPolicy');
         $v_space = $request->getStr('spacePHID');
         $v_status = $request->getStr('status');
         $len = phutil_utf8_strlen($v_title);
         if ($len < 1) {
             $errors[] = pht('Title must not be empty.');
             $e_title = pht('Required');
         } else {
             if ($len > 255) {
                 $errors[] = pht('Title is too long.');
                 $e_title = pht('Too Long');
             }
         }
         if (!$errors) {
             $template = id(new PonderQuestionTransaction());
             $xactions = array();
             $xactions[] = id(clone $template)->setTransactionType(PonderQuestionTransaction::TYPE_TITLE)->setNewValue($v_title);
             $xactions[] = id(clone $template)->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT)->setNewValue($v_content);
             $xactions[] = id(clone $template)->setTransactionType(PonderQuestionTransaction::TYPE_ANSWERWIKI)->setNewValue($v_wiki);
             if (!$is_new) {
                 $xactions[] = id(clone $template)->setTransactionType(PonderQuestionTransaction::TYPE_STATUS)->setNewValue($v_status);
             }
             $xactions[] = id(clone $template)->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)->setNewValue($v_view);
             $xactions[] = id(clone $template)->setTransactionType(PhabricatorTransactions::TYPE_SPACE)->setNewValue($v_space);
             $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
             $xactions[] = id(new PonderQuestionTransaction())->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', $proj_edge_type)->setNewValue(array('=' => array_fuse($v_projects)));
             $editor = id(new PonderQuestionEditor())->setActor($viewer)->setContentSourceFromRequest($request)->setContinueOnNoEffect(true);
             $editor->applyTransactions($question, $xactions);
             return id(new AphrontRedirectResponse())->setURI('/Q' . $question->getID());
         }
     }
     $policies = id(new PhabricatorPolicyQuery())->setViewer($viewer)->setObject($question)->execute();
     $form = id(new AphrontFormView())->setUser($viewer)->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Question'))->setName('title')->setValue($v_title)->setError($e_title))->appendChild(id(new PhabricatorRemarkupControl())->setUser($viewer)->setName('content')->setID('content')->setValue($v_content)->setLabel(pht('Question Details'))->setUser($viewer))->appendChild(id(new PhabricatorRemarkupControl())->setUser($viewer)->setName('answerWiki')->setID('answerWiki')->setValue($v_wiki)->setLabel(pht('Answer Summary'))->setUser($viewer))->appendControl(id(new AphrontFormPolicyControl())->setName('viewPolicy')->setPolicyObject($question)->setSpacePHID($v_space)->setPolicies($policies)->setValue($v_view)->setCapability(PhabricatorPolicyCapability::CAN_VIEW));
     if (!$is_new) {
         $form->appendChild(id(new AphrontFormSelectControl())->setLabel(pht('Status'))->setName('status')->setValue($v_status)->setOptions(PonderQuestionStatus::getQuestionStatusMap()));
     }
     $form->appendControl(id(new AphrontFormTokenizerControl())->setLabel(pht('Tags'))->setName('projects')->setValue($v_projects)->setDatasource(new PhabricatorProjectDatasource()));
     $form->appendChild(id(new AphrontFormSubmitControl())->addCancelButton($this->getApplicationURI())->setValue(pht('Submit')));
     $preview = id(new PHUIRemarkupPreviewPanel())->setHeader(pht('Question Preview'))->setControlID('content')->setPreviewURI($this->getApplicationURI('preview/'));
     $answer_preview = id(new PHUIRemarkupPreviewPanel())->setHeader(pht('Answer Summary Preview'))->setControlID('answerWiki')->setPreviewURI($this->getApplicationURI('preview/'));
     $crumbs = $this->buildApplicationCrumbs();
     $id = $question->getID();
     if ($id) {
         $crumbs->addTextCrumb("Q{$id}", "/Q{$id}");
         $crumbs->addTextCrumb(pht('Edit'));
         $title = pht('Edit Question');
         $header = id(new PHUIHeaderView())->setHeader($title)->setHeaderIcon('fa-pencil');
     } else {
         $crumbs->addTextCrumb(pht('Ask Question'));
         $title = pht('Ask New Question');
         $header = id(new PHUIHeaderView())->setHeader($title)->setHeaderIcon('fa-plus-square');
     }
     $crumbs->setBorder(true);
     $box = id(new PHUIObjectBoxView())->setHeaderText(pht('Question'))->setFormErrors($errors)->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)->setForm($form);
     $view = id(new PHUITwoColumnView())->setHeader($header)->setFooter(array($box, $preview, $answer_preview));
     return $this->newPage()->setTitle($title)->setCrumbs($crumbs)->appendChild($view);
 }
Esempio n. 12
0
 protected function validateTransaction(PhabricatorLiskDAO $object, $type, array $xactions)
 {
     $errors = parent::validateTransaction($object, $type, $xactions);
     switch ($type) {
         case PhameBlogTransaction::TYPE_NAME:
             $missing = $this->validateIsEmptyTextField($object->getName(), $xactions);
             if ($missing) {
                 $error = new PhabricatorApplicationTransactionValidationError($type, pht('Required'), pht('Name is required.'), nonempty(last($xactions), null));
                 $error->setIsMissingFieldError(true);
                 $errors[] = $error;
             }
             foreach ($xactions as $xaction) {
                 $new = $xaction->getNewValue();
                 if (phutil_utf8_strlen($new) > 64) {
                     $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('The selected blog title is too long. The maximum length ' . 'of a blog title is 64 characters.'), $xaction);
                 }
             }
             break;
         case PhameBlogTransaction::TYPE_SUBTITLE:
             foreach ($xactions as $xaction) {
                 $new = $xaction->getNewValue();
                 if (phutil_utf8_strlen($new) > 64) {
                     $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('The selected blog subtitle is too long. The maximum length ' . 'of a blog subtitle is 64 characters.'), $xaction);
                 }
             }
             break;
         case PhameBlogTransaction::TYPE_PARENTDOMAIN:
             if (!$xactions) {
                 continue;
             }
             $parent_domain = last($xactions)->getNewValue();
             if (empty($parent_domain)) {
                 continue;
             }
             try {
                 PhabricatorEnv::requireValidRemoteURIForLink($parent_domain);
             } catch (Exception $ex) {
                 $error = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid URI'), pht('Parent Domain must be set to a valid Remote URI.'), nonempty(last($xactions), null));
                 $errors[] = $error;
             }
             break;
         case PhameBlogTransaction::TYPE_FULLDOMAIN:
             if (!$xactions) {
                 continue;
             }
             $custom_domain = last($xactions)->getNewValue();
             if (empty($custom_domain)) {
                 continue;
             }
             list($error_label, $error_text) = $object->validateCustomDomain($custom_domain);
             if ($error_label) {
                 $error = new PhabricatorApplicationTransactionValidationError($type, $error_label, $error_text, nonempty(last($xactions), null));
                 $errors[] = $error;
             }
             if ($object->getViewPolicy() != PhabricatorPolicies::POLICY_PUBLIC) {
                 $error_text = pht('For custom domains to work, the blog must have a view policy of ' . 'public.');
                 $error = new PhabricatorApplicationTransactionValidationError(PhabricatorTransactions::TYPE_VIEW_POLICY, pht('Invalid Policy'), $error_text, nonempty(last($xactions), null));
                 $errors[] = $error;
             }
             $domain = new PhutilURI($custom_domain);
             $domain = $domain->getDomain();
             $duplicate_blog = id(new PhameBlogQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withDomain($domain)->executeOne();
             if ($duplicate_blog && $duplicate_blog->getID() != $object->getID()) {
                 $error = new PhabricatorApplicationTransactionValidationError($type, pht('Not Unique'), pht('Domain must be unique; another blog already has this domain.'), nonempty(last($xactions), null));
                 $errors[] = $error;
             }
             break;
     }
     return $errors;
 }
 public function correctSpelling($input, array $options)
 {
     $matrix = $this->getEditDistanceMatrix();
     if (!$matrix) {
         throw new PhutilInvalidStateException('setEditDistanceMatrix');
     }
     $max_distance = $this->getMaximumDistance();
     if (!$max_distance) {
         throw new PhutilInvalidStateException('setMaximumDistance');
     }
     $input = $this->normalizeString($input);
     foreach ($options as $key => $option) {
         $options[$key] = $this->normalizeString($option);
     }
     $distances = array();
     $inputv = phutil_utf8v($input);
     foreach ($options as $option) {
         $optionv = phutil_utf8v($option);
         $matrix->setSequences($optionv, $inputv);
         $distances[$option] = $matrix->getEditDistance();
     }
     asort($distances);
     $best = min($max_distance, head($distances));
     foreach ($distances as $option => $distance) {
         if ($distance > $best) {
             unset($distances[$option]);
         }
     }
     // Before filtering, check if we have multiple equidistant matches and
     // return them if we do. This prevents us from, e.g., matching "alnd" with
     // both "land" and "amend", then dropping "land" for being too short, and
     // incorrectly completing to "amend".
     if (count($distances) > 1) {
         return array_keys($distances);
     }
     foreach ($distances as $option => $distance) {
         if (phutil_utf8_strlen($option) < $distance) {
             unset($distances[$option]);
         }
     }
     return array_keys($distances);
 }
Esempio n. 14
0
/**
 * Shorten a string to provide a summary, respecting UTF-8 characters. This
 * function attempts to truncate strings at word boundaries.
 *
 * NOTE: This function makes a best effort to apply some reasonable rules but
 * will not work well for the full range of unicode languages. For instance,
 * no effort is made to deal with combining characters.
 *
 * @param   string  UTF-8 string to shorten.
 * @param   int     Maximum length of the result.
 * @param   string  If the string is shortened, add this at the end. Defaults to
 *                  horizontal ellipsis.
 * @return  string  A string with no more than the specified character length.
 *
 * @group utf8
 */
function phutil_utf8_shorten($string, $length, $terminal = "…")
{
    $terminal_len = phutil_utf8_strlen($terminal);
    if ($terminal_len >= $length) {
        // If you provide a terminal we still enforce that the result (including
        // the terminal) is no longer than $length, but we can't do that if the
        // terminal is too long.
        throw new Exception("String terminal length must be less than string length!");
    }
    $string_v = phutil_utf8v($string);
    $string_len = count($string_v);
    if ($string_len <= $length) {
        // If the string is already shorter than the requested length, simply return
        // it unmodified.
        return $string;
    }
    // NOTE: This is not complete, and there are many other word boundary
    // characters and reasonable places to break words in the UTF-8 character
    // space. For now, this gives us reasonable behavior for latin langauges. We
    // don't necessarily have access to PCRE+Unicode so there isn't a great way
    // for us to look up character attributes.
    // If we encounter these, prefer to break on them instead of cutting the
    // string off in the middle of a word.
    static $break_characters = array(' ' => true, "\n" => true, ';' => true, ':' => true, '[' => true, '(' => true, ',' => true, '-' => true);
    // If we encounter these, shorten to this character exactly without appending
    // the terminal.
    static $stop_characters = array('.' => true, '!' => true, '?' => true);
    // Search backward in the string, looking for reasonable places to break it.
    $word_boundary = null;
    $stop_boundary = null;
    // If we do a word break with a terminal, we have to look beyond at least the
    // number of characters in the terminal.
    $terminal_area = $length - $terminal_len;
    for ($ii = $length; $ii >= 0; $ii--) {
        $c = $string_v[$ii];
        if (isset($break_characters[$c]) && $ii <= $terminal_area) {
            $word_boundary = $ii;
        } else {
            if (isset($stop_characters[$c]) && $ii < $length) {
                $stop_boundary = $ii + 1;
                break;
            } else {
                if ($word_boundary !== null) {
                    break;
                }
            }
        }
    }
    if ($stop_boundary !== null) {
        // We found a character like ".". Cut the string there, without appending
        // the terminal.
        $string_part = array_slice($string_v, 0, $stop_boundary);
        return implode('', $string_part);
    }
    // If we didn't find any boundary characters or we found ONLY boundary
    // characters, just break at the maximum character length.
    if ($word_boundary === null || $word_boundary === 0) {
        $word_boundary = $length - $terminal_len;
    }
    $string_part = array_slice($string_v, 0, $word_boundary);
    $string_part = implode('', $string_part);
    return $string_part . $terminal;
}