/** * Get array of up to 30 * similar questions, create html block from * these questions and save in Question * under the sim_q key * * @param \Lampcms\Question $Question * * @throws \Exception * @internal param bool $ret indicates that this is a retry * and prevents against retrying calling itself * more than once * * @return object $this */ public function getSimilarQuestions(\Lampcms\Question $Question) { if (!extension_loaded('pdo_mysql')) { d('pdo or pdo_mysql not loaded skipping parsing of similar items'); return $this; } $qid = (int) $this->Question['_id']; $term = $Question['title']; $html = ''; $aRes = array(); $sql = "SELECT\r\n\t\t\t\tqid, \r\n\t\t\t\ttitle, \r\n\t\t\t\turl, \r\n\t\t\t\tintro\r\n\t\t\t\tFROM question_title\r\n\t\t\t\tWHERE \r\n\t\t\t\tqid != :qid\r\n \t\t\t\tAND " . self::BY_TITLE . "\r\n\t\t\t\tLIMIT 30"; d('$sql: ' . $sql); try { $sth = $this->Registry->Db->makePrepared($sql); $sth->bindParam(':qid', $qid, \PDO::PARAM_INT); $sth->bindParam(':subj', $term, \PDO::PARAM_STR); $sth->execute(); $aRes = $sth->fetchAll(); d('found ' . count($aRes) . ' similar questions ' . print_r($aRes, 1)); if (!empty($aRes)) { $html = \tplSimquestions::loop($aRes); $s = '<div id="sim_questions" class="similars">' . $html . '</div>'; d('html: ' . $s); $Question->offsetSet('sim_q', $s); $Question->save(); } } catch (\Exception $e) { $err = 'Exception: ' . get_class($e) . ' Unable to select mysql because: ' . $e->getMessage() . ' Err Code: ' . $e->getCode() . ' trace: ' . $e->getTraceAsString(); d('mysql error: ' . $err); if ('42S02' === $e->getCode()) { if (true === TitleTagsTable::create($this->Registry)) { return $this; } else { throw $e; } } else { throw $e; } } return $this; }
/** * When question is edited in any way we will * run this method to also update * the index. * * @param \Lampcms\Question $Question $Question * * @throws \Exception * @return \Lampcms\Modules\Search\IndexerMySQL */ public function updateQuestion(\Lampcms\Question $Question) { if (!extension_loaded('pdo_mysql')) { d('pdo_mysql not loaded '); return $this; } $res = false; $qid = $Question->offsetGet('_id'); $title = $Question->offsetGet('title'); $url = $Question->offsetGet('url'); $intro = $Question->offsetGet('intro'); $username = $Question['username']; $ulink = $Question['ulink']; $avatar = $Question['avtr']; $tags_html = $Question['tags_html']; $body = $Question['body']; d($qid . ' title: ' . $title . ' url: ' . $url . ' intro: ' . $intro); $sql = 'UPDATE question_title SET title = :qtitle, q_body = :qbody, url = :qurl, intro = :qintro, username = :username, userlink = :userlink, avtr = :avatar, tags_html = :tags_html WHERE qid = :qid'; try { $sth = $this->Registry->Db->makePrepared($sql); $sth->bindParam(':qid', $qid, \PDO::PARAM_INT); $sth->bindParam(':qtitle', $title, \PDO::PARAM_STR); $sth->bindParam(':qbody', $body, \PDO::PARAM_STR); $sth->bindParam(':qurl', $url, \PDO::PARAM_STR); $sth->bindParam(':qintro', $intro, \PDO::PARAM_STR); $sth->bindParam(':tags_html', $tags_html, \PDO::PARAM_STR); $sth->bindParam(':username', $username, \PDO::PARAM_STR); $sth->bindParam(':userlink', $ulink, \PDO::PARAM_STR); $sth->bindParam(':avatar', $avatar, \PDO::PARAM_STR); $res = $sth->execute(); } catch (\Exception $e) { $err = 'Exception: ' . get_class($e) . ' Unable to insert into mysql because: ' . $e->getMessage() . ' Err Code: ' . $e->getCode() . ' trace: ' . $e->getTraceAsString(); d('mysql error: ' . $err); if ('42S02' === $e->getCode()) { if (true === TitleTagsTable::create($this->Registry)) { $this->indexTitle($Question); } } else { throw $e; } } d('res: ' . $res); return $this; }
/** * Get html div with answers for this one question, * sorted according to param passed in request * * Enclose result in <div> and add pagination to bottom * of div if there is any pagination necessary! * * @todo add skip() and limit() to cursor * in order to use pagination. * * @param Question $Question * * @param string $result desired format of result. Possible * options are: html, array or json object * * * * @throws Exception * @return string html block */ public function getAnswers(Question $Question, $result = 'html') { $qid = $Question[Schema::PRIMARY]; $url = $Question['url']; $pageID = $this->Registry->Router->getPageID(); d('url: ' . $url . ' $pageID: ' . $pageID); $this->pagetPath = $Question->getUrl() . '/'; $urlParts = $this->Registry->Ini->getSection('URI_PARTS'); $cond = $this->Registry->Router->getSegment(3, 's', $urlParts['SORT_RECENT']); d('cond: ' . $cond); $noComments = false === (bool) $this->Registry->Ini->MAX_COMMENTS; d('no comments: ' . $noComments); $aFields = $noComments || false === (bool) $this->Registry->Ini->SHOW_COMMENTS ? array('comments' => 0) : array(); /** * Extra security validation, * IMPORTANT because we should never use * anything in Mongo methods directly from * user input */ if (!in_array($cond, array($urlParts['SORT_RECENT'], $urlParts['SORT_BEST'], $urlParts['SORT_OLDEST']))) { throw new Exception('Invalid value of param "cond" was: ' . $cond); } $where = array(Schema::QUESTION_ID => $qid); /** * Important as of version 0.2 no longer using i_del_ts * as indicator of deleted status - instead using i_status */ if (!$this->Registry->Viewer->isModerator()) { d('not moderator. Get only questions with status 1'); $where[Schema::RESOURCE_STATUS_ID] = Schema::POSTED; } else { $where[Schema::RESOURCE_STATUS_ID] < Schema::DELETED; } switch ($cond) { case $urlParts['SORT_RECENT']: /** * Accepted answer will always be the first one, * then most recently modified */ $sort = array(Schema::IS_ACCEPTED => -1, Schema::LAST_MODIFIED_TIMESTAMP => -1); break; case $urlParts['SORT_OLDEST']: $sort = array(Schema::CREATED_TIMESTAMP => 1); break; case $urlParts['SORT_BEST']: default: /** * Accepted answer will be first * then most highly voted */ $sort = array(Schema::IS_ACCEPTED => -1, Schema::VOTES_SCORE => -1); } $cursor = $this->Registry->Mongo->ANSWERS->find($where, $aFields); d('$cursor: ' . gettype($cursor)); $cursor->sort($sort); $oPager = Paginator::factory($this->Registry); $oPager->paginate($cursor, $this->Registry->Ini->PER_PAGE_ANSWERS, array('path' => $this->pagetPath . $cond, 'append' => false, 'currentPage' => $pageID)); $pagerLinks = $oPager->getLinks(); $ownerId = $Question[Schema::POSTER_ID]; $showLink = $ownerId > 0 && ($this->Registry->Viewer->isModerator() || $ownerId == $this->Registry->Viewer->getUid()); $noComments = $noComments ? ' nocomments' : ''; $func = function (&$a) use($showLink, $noComments) { /** * Don't show Accept link for * already accepted answer */ if (!$a['accepted']) { if ($showLink) { $a['accept_link'] = '<a class="accept ttt" title="@@Click to accept this as best answer@@" href="{_WEB_ROOT_}/{_accept_}/' . $a['_id'] . '">@@Accept@@</a>'; } } else { $a['accepted'] = '<img src="{_IMAGE_SITE_}{_DIR_}/images/accepted.png" alt="@@Best answer@@" class="ttt" title="@@Owner of the question accepted this as best answer@@">'; } $a['nocomments'] = $noComments; $a['edited'] = '@@Edited@@'; }; /** * Create div with answers, append pagination links * to bottom and return the whole div block */ $answers = \tplAnswer::loop($cursor, true, $func) . $pagerLinks; return $answers; }
/** * @depends testSetBestAnswer * */ public function testSetLatestAnswer() { $User = new MockUser($this->Question->getRegistry()); $Answer = new MockAnswer($this->Question->getRegistry()); $this->Question->setLatestAnswer($User, $Answer); $a = $this->Question['a_latest']; $this->assertTrue(is_array($a[0])); $this->assertEquals(1, count($a)); $this->assertEquals('<a href="/users/26/ladada">John D Doe</a>', $a['0']['u']); $this->assertEquals(513, $a['0']['id']); $Answer['_id'] = 999; $User['username'] = '******'; $User['_id'] = 999999; $Answer->setSaved(); $User->setSaved(); $this->Question->setLatestAnswer($User, $Answer); $a = $this->Question['a_latest']; $this->assertTrue(is_array($a[0])); $this->assertEquals(2, count($a)); $this->assertEquals('<a href="/users/999999/Dude">John D Doe</a>', $a['0']['u']); $this->assertEquals(999, $a['0']['id']); $this->assertEquals('<a href="/users/26/ladada">John D Doe</a>', $a['1']['u']); $this->assertEquals(513, $a['1']['id']); $this->Question->insert(); $Question = new Question($this->Question->getRegistry()); $Question->by_id(510); $a = $Question['a_latest']; $this->assertEquals(999, $a['0']['id']); }
/** * When question is deleted * then decrease i_qcount but do it in such a way * that it cannot go below zero * Also if deleted question happends to be the one in the 'a_latest' * then also remove a_latest as well as set i_qid to 0 * * @todo if a_latest is removed we really * need to replace it with another question * that is the "new latest" which would be one * posted before this one in the same category * * @param \Lampcms\Question $Q * * @return $this */ public function removeQuestion(\Lampcms\Question $Q) { $id = $Q->getCategoryId(); if ($id > 0) { $qid = $Q->getResourceId(); $this->Mongo->CATEGORY->update(array('id' => $id, 'i_qcount' => array('$gt' => 0)), array('$inc' => array('i_qcount' => -1))); $this->Mongo->CATEGORY->update(array('id' => $id, 'i_qid' => $qid), array('$set' => array('i_qid' => 0, 'a_latest' => null))); } return $this; }
/** * When question is deleted * then decrease i_qcount but do it in such a way * that it cannot go below zero * Also if deleted question happends to be the one in the 'a_latest' * then also remove a_latest as well as set i_qid to 0 * * @todo if a_latest is removed we really * need to replace it with another question * that is the "new latest" which would be one * posted before this one in the same category * * @param \Lampcms\Question $Q * * @return $this */ public function removeQuestion(\Lampcms\Question $Q) { $id = $Q->getCategoryId(); $qid = $Q->getResourceId(); $this->removeQuestionById($qid, $id); return $this; }