/** * Find data in Mongo * and create array of $this->aData * * @return object $this */ protected function getData() { $aTokens = TitleTokenizer::factory($this->Request->getUTF8('q'))->getArrayCopy(); if (!empty($aTokens)) { d('looking for something'); try { $cur = $this->Registry->Mongo->QUESTIONS->find(array('a_title' => array('$all' => $aTokens), 'a_deleted' => null), array('_id', 'title', 'url', 'intro', 'hts', 'status', 'i_ans', 'ans_s'))->sort(array('status' => 1, 'i_ans' => -1))->limit(12); $this->aData = iterator_to_array($cur, false); d('$this->aData: ' . print_r($this->aData, 1)); } catch (\MongoException $e) { d('MongoException: ' . $e->getMessage() . ' aTokens was: ' . print_r($this->aTokens, 1)); } } 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; }
/** * * Process submitted form values * * edge cases with category: if category was required * when question was posted but now it's optional * then it's possible to change the category from selected one * to empty (no category) by simply selecting the "no category" * in the drop down menu. Also if category support was removed completely * by admin by setting CATEGORIES to empty in !config.ini * then submitted value will be empty - in this case we don't want to * update an existing category with an empty one. So the only way we can * accept the empty category is when CATEGORIES are optional and empty value is submitted * * * * * @return \Lampcms\Controllers\Editor */ protected function process() { $this->Registry->Dispatcher->post($this->Resource, 'onBeforeEdit'); $formVals = $this->Form->getSubmittedValues(); d('formVals: ' . json_encode($formVals)); $htmlBody = $this->makeBody($formVals['qbody']); $this->Resource[QuestionSchema::BODY] = $htmlBody; $this->Resource[QuestionSchema::WORDS_COUNT] = $this->Body->asPlainText()->getWordsCount(); /** * @important Don't attempt to edit the value of title * for the answer since it technically does not have the title * If we don't skip this step for Answer then title * of answer will be removed */ if ($this->Resource instanceof \Lampcms\Question) { $hash = hash('md5', strtolower($htmlBody . json_encode($this->Resource[QuestionSchema::TAGS_ARRAY]))); $origTitle = $this->Resource[QuestionSchema::TITLE]; $origCategoryId = $this->Resource[QuestionSchema::CATEGORY_ID]; d('$origTitle: ' . $origTitle . ' $origCategoryId: ' . $origCategoryId); $oTitle = $this->makeTitle($formVals['title']); $title = $oTitle->valueOf(); $categoryId = \array_key_exists('category', $formVals) ? $formVals['category'] : 0; $categoryId = (int) $categoryId; $this->Resource[QuestionSchema::TITLE] = $title; $this->Resource[QuestionSchema::URL] = $oTitle->toASCII()->makeLinkTitle()->valueOf(); $this->Resource[QuestionSchema::TITLE_ARRAY] = \Lampcms\TitleTokenizer::factory($oTitle)->getArrayCopy(); $this->Resource[QuestionSchema::INTRO] = $this->plainTextBody->truncate(150)->valueOf(); $this->Resource[QuestionSchema::BODY_HASH] = $hash; /** * * Need to update 'title' of all answers to this question * But first check to see if title has actually changed * * ONLY if Question status is POSTED (don't do anything for pending) * */ if ($this->Resource[QuestionSchema::RESOURCE_STATUS_ID] === QuestionSchema::POSTED) { if ($origTitle !== $title) { $this->updateAnswersTitle($title); } if ($origCategoryId !== $categoryId) { /** * If Submitted category ID is empty * then allow it ONLY if category support is optional ( value of CATEGORIES is 1 in !config.ini) */ if ($categoryId > 0 || $this->Registry->Ini->CATEGORIES == 1) { $this->Resource[QuestionSchema::CATEGORY_ID] = $categoryId; $this->updateCategoryCounter($origCategoryId, $categoryId); /** * Post onCategoryUpdate event * It will cause the CacheObserver to unset the cached categories html blocks */ $this->Registry->Dispatcher->post($this->Resource, 'onCategoryUpdate'); } } } } $this->Resource->setEdited($this->Registry->Viewer, \strip_tags($formVals['reason'])); $this->Resource->touch()->save(); $this->Registry->Dispatcher->post($this->Resource, 'onEdit'); return $this; }
/** * * Process submitted form values */ protected function process() { $this->Registry->Dispatcher->post($this->Resource, 'onBeforeEdit'); $formVals = $this->Form->getSubmittedValues(); d('formVals: ' . print_r($formVals, 1)); $this->Resource['b'] = $this->makeBody($formVals['qbody']); $this->Resource['i_words'] = $this->Body->asPlainText()->getWordsCount(); /** * @important Don't attempt to edit the value of title * for the answer since it technically does not have the title * If we don't skip this step for Answer then title * of answer will be removed */ if ($this->Resource instanceof \Lampcms\Question) { $oTitle = $this->makeTitle($formVals['title']); $title = $oTitle->valueOf(); $this->Resource['title'] = $title; $this->Resource['url'] = $oTitle->toASCII()->makeLinkTitle()->valueOf(); $this->Resource['a_title'] = \Lampcms\TitleTokenizer::factory($oTitle)->getArrayCopy(); /** * @todo * Need to update 'title' of all answers to this question * But first check to see if title has actually changed */ } $this->Resource->setEdited($this->Registry->Viewer, \strip_tags($formVals['reason'])); $this->Resource->touch()->save(); $this->Registry->Dispatcher->post($this->Resource, 'onEdit'); 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 cancels event * */ protected function makeQuestion() { $Ini = $this->Registry->Ini; $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 = $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 * */ $HtmlDoc = HTMLStringParser::stringFactory($Body)->parseCodeTags()->linkify()->importCDATA()->setNofollow()->hilightWords($aTags)->parseImages(); $aImages = $HtmlDoc->getImages(); $htmlBody = $HtmlDoc->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); $Poster = $this->Submitted->getUserObject(); $username = $Poster->getDisplayName(); $time = time(); /** * If NEW_POSTS_MODERATION in !config.ini is > 0 then * check if viewer requires new posts to be moderated */ $resourceStatus = $Ini->NEW_POSTS_MODERATION > 0 && $Poster->isOnProbation() ? Schema::PENDING : Schema::POSTED; /** * * @var array */ $aData = array(Schema::PRIMARY => $this->Registry->Resource->create('QUESTION'), Schema::TITLE => $oTitle->valueOf(), Schema::BODY => $htmlBody, Schema::BODY_HASH => $hash, Schema::INTRO => $this->Submitted->getBody()->asPlainText()->truncate(150)->valueOf(), Schema::URL => $this->Submitted->getTitle()->toASCII()->makeLinkTitle()->valueOf(), Schema::WORDS_COUNT => $this->Submitted->getBody()->asPlainText()->getWordsCount(), Schema::POSTER_ID => $uid, Schema::POSTER_USERNAME => $username, Schema::USER_PROFILE_URL => '<a href="' . $Poster->getProfileUrl() . '">' . $username . '</a>', Schema::AVATAR_URL => $Poster->getAvatarSrc(), Schema::UPVOTES_COUNT => 0, Schema::DOWNVOTES_COUNT => 0, Schema::VOTES_SCORE => 0, Schema::NUM_FAVORITES => 0, Schema::NUM_VIEWS => 0, Schema::CATEGORY_ID => $this->Submitted->getCategoryId(), Schema::TAGS_ARRAY => $aTags, Schema::TITLE_ARRAY => TitleTokenizer::factory($oTitle)->getArrayCopy(), Schema::STATUS => 'unans', Schema::TAGS_HTML => \tplQtags::loop($aTags, false), Schema::CREDITS => '', Schema::CREATED_TIMESTAMP => $time, Schema::TIME_STRING => date('F j, Y g:i a T'), Schema::LAST_MODIFIED_TIMESTAMP => $time, Schema::NUM_ANSWERS => 0, 'ans_s' => 's', 'v_s' => 's', 'f_s' => 's', Schema::IP_ADDRESS => $this->Submitted->getIP(), Schema::APP_NAME => $this->Submitted->getApp(), Schema::APP_ID => $this->Submitted->getAppId(), Schema::APP_LINK => $this->Submitted->getAppLink(), Schema::NUM_FOLLOWERS => 1, Schema::RESOURCE_STATUS_ID => $resourceStatus); if (!empty($aImages)) { $aData[Schema::UPLOADED_IMAGES] = $aImages; } /** * 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: ', $aExtraData); 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 (preferred 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 exception: ' . $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(); if ($resourceStatus === Schema::POSTED) { $this->updateCategory()->addTags()->addUnansweredTags()->addRelatedTags(); $this->Registry->Dispatcher->post($this->Question, 'onCategoryUpdate'); $this->Registry->Dispatcher->post($this->Question, 'onNewQuestion'); } elseif ($resourceStatus === Schema::PENDING) { $this->Registry->Dispatcher->post($this->Question, 'onNewPendingQuestion'); } return $this; }