/** * Prepare array of data for the answer, * then create Answer object from it and * save. It will also fire onBeforeNewAnswer * and onNewAnswer events * * @throws AnswerParserException * * @return object $this */ protected function makeAnswer() { $username = $this->SubmittedAnswer->getUserObject()->getDisplayName(); /** * Must pass array('drop-proprietary-attributes' => false) * otherwise tidy removes rel="code" */ $aEditorConfig = $this->Registry->Ini->getSection('EDITOR'); $tidyConfig = $aEditorConfig['ENABLE_CODE_EDITOR'] ? array('drop-proprietary-attributes' => false) : null; $Body = $this->SubmittedAnswer->getBody()->tidy($tidyConfig)->safeHtml()->asHtml(); $htmlBody = HTMLStringParser::factory($Body)->parseCodeTags()->linkify()->importCDATA()->setNofollow()->valueOf(); d('after HTMLStringParser: ' . $htmlBody); $username = $this->SubmittedAnswer->getUserObject()->getDisplayName(); $uid = $this->SubmittedAnswer->getUserObject()->getUid(); $qid = $this->SubmittedAnswer->getQid(); $hash = hash('md5', \mb_strtolower($htmlBody . $qid)); /** * * We need to copy the title * here too because Answer by itself does not have own * title but we need a title when displaying links * to answer on profile pages * * @todo later can also parse for smilies here * */ $this->checkForDuplicate($hash); $aData = array('_id' => $this->Registry->Resource->create('ANSWER'), 'i_qid' => $qid, 'i_uid' => $uid, 'i_quid' => $this->Question->getOwnerId(), 'title' => $this->Question->getTitle(), 'hash' => $hash, 'username' => $username, 'ulink' => '<a href="' . $this->SubmittedAnswer->getUserObject()->getProfileUrl() . '">' . $username . '</a>', 'avtr' => $this->SubmittedAnswer->getUserObject()->getAvatarSrc(), 'i_words' => $Body->asPlainText()->getWordsCount(), 'i_up' => 0, 'i_down' => 0, 'i_votes' => 0, 'i_cat' => $this->Question->getCategoryId(), 'b' => $htmlBody, 'i_ts' => time(), 'i_lm_ts' => time(), 'hts' => date('F j, Y g:i a T'), 'v_s' => 's', 'accepted' => false, 'ip' => $this->SubmittedAnswer->getIP(), 'app' => 'web'); /** * Submitted answer object may provide * extra elements to be added to aData array * This is usually useful for parsing answers that * came from external API * * as well as adding 'credit' div */ $aExtraData = $this->SubmittedAnswer->getExtraData(); d('$aExtraData: ' . print_r($aExtraData, 1)); if (!empty($aExtraData)) { $aData = array_merge($aData, $aExtraData); } d('$aData: ' . print_r($aData, 1)); $this->Answer = new Answer($this->Registry, $aData); /** * Post onBeforeNewQuestion event * and watch for filter either cancelling the event * or throwing FilterException (prefferred way because * a specific error message can be passed in FilterException * this way) * * In either case we throw QuestionParserException * Controller that handles the question form should be ready * to handle this exception and set the form error using * message from exception. This way the error will be shown to * the user right on the question form while question form's data * is preserved in form. */ try { $oNotification = $this->Registry->Dispatcher->post($this->Answer, 'onBeforeNewAnswer'); if ($oNotification->isNotificationCancelled()) { throw new AnswerParserException('Sorry, we are unable to process your answer at this time.'); } } catch (FilterException $e) { e('Got filter exteption: ' . $e->getFile() . ' ' . $e->getLine() . ' ' . $e->getMessage() . ' ' . $e->getTraceAsString()); throw new AnswerParserException($e->getMessage()); } /** * Do ensureIndexes() now and not before we are sure that we even going * to add a new question. */ $this->ensureIndexes(); $this->Answer->insert(); d('cp'); $this->Registry->Dispatcher->post($this->Answer, 'onNewAnswer', array('question' => $this->Question)); d('cp'); /** * Reuse $uid since we already resolved it here, * so no need to go through the same * $this->SubmittedAnswer->getUserObject()->getUid() again */ $this->addUserTags($uid); d('cp'); return $this; }
/** * * Set tags for this question * It will also update "a_edited" array * to record the retag action, records * user who retagged, and "Retag" as reason for edit * Will also update lastModified * * @param User $user object User who retagged this question * @param array $tags array of tags */ public function retag(User $user, array $tags) { parent::offsetSet('a_tags', $tags); parent::offsetSet('tags_html', \tplQtags::loop($tags, false)); $b = $this->offsetGet('b'); d('b: ' . $b); $oHtmlParser = \Lampcms\String\HTMLStringParser::factory(Utf8String::factory($b, 'utf-8', true)); $body = $oHtmlParser->unhilight()->hilightWords($tags)->valueOf(); $this->offsetSet('b', $body); $this->setEdited($user, 'Retagged')->touch(); return $this; }
/** * Prepares data for the question object, * creates the $this->Question object * and saves data to QUESTIONS collection * * @return object $this * * @throws QuestionParserException in case a filter (which is an observer) * either throws a FilterException (or sub-class of it) OR just cancells event * */ protected function makeQuestion() { $oTitle = $this->Submitted->getTitle()->htmlentities()->trim(); $username = $this->Submitted->getUserObject()->getDisplayName(); $aTags = $this->Submitted->getTagsArray(); /** * Must pass array('drop-proprietary-attributes' => false) * otherwise tidy removes rel="code" */ $aEditorConfig = $this->Registry->Ini->getSection('EDITOR'); $tidyConfig = $aEditorConfig['ENABLE_CODE_EDITOR'] ? array('drop-proprietary-attributes' => false) : null; $Body = $this->Submitted->getBody()->tidy($tidyConfig)->safeHtml()->asHtml(); /** * * Now body is in html but we still need to run * it through HTMLStringParser string in order * to make clickable links and to * make sure all links are nofollow * */ $htmlBody = HTMLStringParser::factory($Body)->parseCodeTags()->linkify()->importCDATA()->setNofollow()->hilightWords($aTags)->valueOf(); d('after HTMLStringParser: ' . $htmlBody); $uid = $this->Submitted->getUserObject()->getUid(); $hash = hash('md5', strtolower($htmlBody . json_encode($aTags))); /** * @todo can parse forMakrdown now but ideally * parseMarkdown() would be done inside Utf8string * as well as parseSmilies * * @todo later can also parse for smilies here * */ $this->checkForDuplicate($uid, $hash); $username = $this->Submitted->getUserObject()->getDisplayName(); $time = time(); /** * * @var array */ $aData = array('_id' => $this->Registry->Resource->create('QUESTION'), 'title' => $oTitle->valueOf(), 'b' => $htmlBody, 'hash' => $hash, 'intro' => $this->Submitted->getBody()->asPlainText()->truncate(150)->valueOf(), 'url' => $this->Submitted->getTitle()->toASCII()->makeLinkTitle()->valueOf(), 'i_words' => $this->Submitted->getBody()->asPlainText()->getWordsCount(), 'i_uid' => $uid, 'username' => $username, 'ulink' => '<a href="' . $this->Submitted->getUserObject()->getProfileUrl() . '">' . $username . '</a>', 'avtr' => $this->Submitted->getUserObject()->getAvatarSrc(), 'i_up' => 0, 'i_down' => 0, 'i_votes' => 0, 'i_favs' => 0, 'i_views' => 0, 'i_cat' => $this->Submitted->getCategoryId(), 'a_tags' => $aTags, 'a_title' => TitleTokenizer::factory($oTitle)->getArrayCopy(), 'status' => 'unans', 'tags_html' => \tplQtags::loop($aTags, false), 'credits' => '', 'i_ts' => $time, 'hts' => date('F j, Y g:i a T'), 'i_lm_ts' => $time, 'i_ans' => 0, 'ans_s' => 's', 'v_s' => 's', 'f_s' => 's', 'ip' => $this->Submitted->getIP(), 'app' => $this->Submitted->getApp(), 'app_id' => $this->Submitted->getAppId(), 'app_link' => $this->Submitted->getAppLink(), 'i_flwrs' => 1); /** * Submitted question object may provide * extra elements to be added to aData array * This is usually useful for parsing questions that * came from external API, in which case the answered/unanswred * status as well as number of answers is already known * * as well as adding 'credit' div */ $aExtraData = $this->Submitted->getExtraData(); d('$aExtraData: ' . print_r($aExtraData, 1)); if (is_array($aExtraData) && !empty($aExtraData)) { $aData = array_merge($aData, $aExtraData); } $this->Question = new Question($this->Registry, $aData); /** * Post onBeforeNewQuestion event * and watch for filter either cancelling the event * or throwing FilterException (prefferred way because * a specific error message can be passed in FilterException * this way) * * In either case we throw QuestionParserException * Controller that handles the question form should be ready * to handle this exception and set the form error using * message from exception. This way the error will be shown to * the user right on the question form while question form's data * is preserved in form. * * Filter can also modify the data in Question before * it is saved. This is convenient, we can even set different * username, i_uid if we want to 'post as alias' */ try { $oNotification = $this->Registry->Dispatcher->post($this->Question, 'onBeforeNewQuestion'); if ($oNotification->isNotificationCancelled()) { throw new QuestionParserException('Sorry, we are unable to process your question at this time.'); } } catch (FilterException $e) { e('Got filter exteption: ' . $e->getFile() . ' ' . $e->getLine() . ' ' . $e->getMessage() . ' ' . $e->getTraceAsString()); throw new QuestionParserException($e->getMessage()); } /** * Do ensureIndexes() now and not before we are sure that we even going * to add a new question. */ $this->ensureIndexes(); $this->Question->insert(); $this->followQuestion(); $this->Registry->Dispatcher->post($this->Question, 'onNewQuestion'); return $this; }
/** * * Update the contents of body * with edited content * If this is a question do extra steps; * unhighlight (just in case that actual highlighed words * have been edited), then re-apply highlightWords() * just in case some of the new word that belong to * tags have been added * * @param string $body * * @return string html of new body * */ protected function makeBody($body) { /** * Must pass array('drop-proprietary-attributes' => false) * otherwise tidy removes rel="code" */ $aEditorConfig = $this->Registry->Ini->getSection('EDITOR'); $tidyConfig = $aEditorConfig['ENABLE_CODE_EDITOR'] ? array('drop-proprietary-attributes' => false) : null; $this->Body = Utf8String::factory($body)->tidy($tidyConfig)->safeHtml()->asHtml(); $Body = HTMLStringParser::factory($this->Body)->parseCodeTags()->linkify()->reload()->setNofollow(); if ($this->Resource instanceof \Lampcms\Question) { $Body->unhilight()->hilightWords($this->Resource['a_tags']); } $htmlBody = $Body->valueOf(); d('after HTMLStringParser: ' . $htmlBody); return $htmlBody; }