diff --git a/lib/Lampcms/Answer.php b/lib/Lampcms/Answer.php index 51d81ae..101d160 100644 --- a/lib/Lampcms/Answer.php +++ b/lib/Lampcms/Answer.php @@ -86,8 +86,7 @@ public function getResourceTypeId(){ * @return object $this */ public function setAccepted(){ - $this->offsetSet('accepted', true); - $this->touch(); + parent::offsetSet('accepted', true); return $this; } @@ -99,8 +98,7 @@ public function setAccepted(){ * @return object $this */ public function unsetAccepted(){ - $this->offsetSet('accepted', false); - $this->touch(); + parent::offsetSet('accepted', false); return $this; } @@ -150,8 +148,8 @@ public function getUsername(){ */ public function setDeleted(User $user, $reason = null){ if(0 === $this->getDeletedTime()){ - $this->offsetSet('i_del_ts', time()); - $this->offsetSet('a_deleted', + parent::offsetSet('i_del_ts', time()); + parent::offsetSet('a_deleted', array( 'username' => $user->getDisplayName(), 'i_uid' => $user->getUid(), @@ -160,8 +158,6 @@ public function setDeleted(User $user, $reason = null){ 'hts' => date('F j, Y g:i a T') ) ); - - $this->touch(); } return $this; @@ -191,9 +187,7 @@ public function setEdited(User $user, $reason = ''){ 'reason' => $reason, 'hts' => date('F j, Y g:i a T')); - $this->offsetSet('a_edited', $aEdited); - - $this->touch(); + parent::offsetSet('a_edited', $aEdited); return $this; } @@ -246,14 +240,14 @@ public function addUpVote($inc = 1){ $score = (int)$this->offsetGet('i_votes'); $total = ($score + $inc); - $this->offsetSet('i_up', max(0, ($tmp + $inc)) ); - $this->offsetSet('i_votes', $total ); + parent::offsetSet('i_up', max(0, ($tmp + $inc)) ); + parent::offsetSet('i_votes', $total ); /** * Plural extension handling */ $v_s = (1 === abs($total) ) ? '' : 's'; - $this->offsetSet('v_s', $v_s); + parent::offsetSet('v_s', $v_s); return $this; } @@ -273,14 +267,14 @@ public function addDownVote($inc = 1){ $score = (int)$this->offsetGet('i_votes'); $total = ($score - $inc); - $this->offsetSet('i_down', max(0, ($tmp + $inc)) ); - $this->offsetSet('i_votes', $total); + parent::offsetSet('i_down', max(0, ($tmp + $inc)) ); + parent::offsetSet('i_votes', $total); /** * Plural extension handling */ $v_s = (1 === abs($total) ) ? '' : 's'; - $this->offsetSet('v_s', $v_s); + parent::offsetSet('v_s', $v_s); return $this; } @@ -387,11 +381,11 @@ public function addComment(CommentParser $oComment){ * because we don't need them here */ $aComment = $oComment->getArrayCopy(); - $aComment = array_intersect_key($aComment, array_flip($aKeys)); + $aComment = \array_intersect_key($aComment, array_flip($aKeys)); $aComments[] = $aComment; - $this->offsetSet('comments', $aComments); + $this->offsetSet('a_comments', $aComments); $this->increaseCommentsCount(); return $this; @@ -412,16 +406,29 @@ public function getCommentsCount(){ /** * - * Enter description here ... + * Increase value of i_comments by 1 + * The i_comments is a counter + * + * @return object $this */ - public function increaseCommentsCount(){ + public function increaseCommentsCount($count = 1){ + if(!is_int($count)){ + throw new \InvalidArgumentException('$count must be integer. was: '.gettype($count)); + } + /** * Now increase comments count */ $commentsCount = $this->getCommentsCount(); d('$commentsCount '.$commentsCount); - $this->offsetSet('i_comments', ($commentsCount + 1) ); + /** + * Must use parent::offsetSet because + * $this->offsetSet will point back to this + * method and enter infinite loop untill + * we run out of memory + */ + parent::offsetSet('i_comments', ($commentsCount + $count) ); return $this; } @@ -439,30 +446,30 @@ public function increaseCommentsCount(){ */ public function deleteComment($id){ - if(!$this->checkOffset('comments')){ + if(0 === $this->getCommentsCount()){ e('This question does not have any comments'); return $this; } - $aComments = $this->offsetGet('comments'); + $aComments = $this->offsetGet('a_comments'); for($i = 0; $ioffsetUnset('comments'); + $this->offsetUnset('a_comments'); } else { - $this->offsetSet('comments', $aComments); + $this->offsetSet('a_comments', $aComments); } - $this->offsetSet('i_comments', $newCount ); + $this->increaseCommentsCount(-1); return $this; } @@ -475,7 +482,8 @@ public function deleteComment($id){ * */ public function getComments(){ - return $this->getFallback('comments', array()); + + return $this->offsetGet('a_comments'); } @@ -502,4 +510,42 @@ public function getQuestionOwnerId(){ return (int)$this->offsetGet('i_quid'); } + + /** + * This method prevents setting some + * values directly + * + * (non-PHPdoc) + * @see ArrayObject::offsetSet() + */ + public function offsetSet($index, $newval){ + switch($index){ + case 'accepted': + throw new DevException('value of accepted cannot be set directly. Use setAccepted() or unsetAccepted() methods'); + break; + + case 'i_comments': + throw new DevException('value of i_comments cannot be set directly. Use increaseCommentsCount() method'); + break; + + case 'i_down': + case 'i_up': + case 'i_votes': + throw new DevException('value of '.$index.' keys cannot be set directly. Use addDownVote or addUpVote to add votes'); + break; + + case 'a_deleted': + case 'i_del_ts': + throw new DevException('value of '.$index.' cannot be set directly. Must use setDeleted() method for that'); + break; + + case 'a_edited': + throw new DevException('value of a_edited cannot be set directly. Must use setEdited() method for that'); + break; + + default: + parent::offsetSet($index, $newval); + } + } + } diff --git a/lib/Lampcms/AnswerParser.php b/lib/Lampcms/AnswerParser.php index ffba068..a14a961 100644 --- a/lib/Lampcms/AnswerParser.php +++ b/lib/Lampcms/AnswerParser.php @@ -193,7 +193,7 @@ protected function makeAnswer(){ $uid = $this->oSubmittedAnswer->getUserObject()->getUid(); $qid = $this->oSubmittedAnswer->getQid(); - $hash = hash('md5', strtolower($htmlBody.$qid)); + $hash = hash('md5', \mb_strtolower($htmlBody.$qid)); /** * @@ -364,7 +364,8 @@ protected function updateQuestion(){ $this->oQuestion->updateAnswerCount() ->addContributor($oUser) - ->setLastAnswerer($oUser); + ->setLatestAnswer($oUser, $this->oAnswer) + ->touch(); return $this; } @@ -377,8 +378,6 @@ protected function updateQuestion(){ * @return object $this */ protected function followQuestion(){ - d('cp'); - $oFollowManager = new FollowManager($this->oRegistry); $oFollowManager->followQuestion($this->oRegistry->Viewer, $this->oQuestion); diff --git a/lib/Lampcms/CacheHeaders.php b/lib/Lampcms/CacheHeaders.php index 23562cf..4355b7e 100644 --- a/lib/Lampcms/CacheHeaders.php +++ b/lib/Lampcms/CacheHeaders.php @@ -122,7 +122,7 @@ public static function processCacheHeaders($etag = null, $lastModified = null, $ * may be notified * */ - if(headers_sent($file, $line)){ + if(\headers_sent($file, $line)){ e('LampcmsError Headers have already been sent in file '.$file. ' on line '.$line); return true; diff --git a/lib/Lampcms/Controllers/Answer.php b/lib/Lampcms/Controllers/Answer.php index 4e00ede..658cee6 100644 --- a/lib/Lampcms/Controllers/Answer.php +++ b/lib/Lampcms/Controllers/Answer.php @@ -112,8 +112,8 @@ protected function process(){ $oAdapter = new AnswerParser($this->oRegistry); try{ $oAnswer = $oAdapter->parse(new SubmittedAnswerWWW($this->oRegistry, $formVals)); - d('cp created new question'); - d('ans id: '.$oAnswer->_id); + d('cp created new question: '.print_r($oAnswer->getArrayCopy(), 1)); + d('ans id: '.$oAnswer->getResourceId()); /** * In case of ajax we need to send out a diff --git a/lib/Lampcms/Controllers/Delete.php b/lib/Lampcms/Controllers/Delete.php index eb8a7c5..3b3e2a6 100644 --- a/lib/Lampcms/Controllers/Delete.php +++ b/lib/Lampcms/Controllers/Delete.php @@ -215,17 +215,15 @@ protected function updateQuestion(){ $oQuestion = new \Lampcms\Question($this->oRegistry); $oQuestion->by_id($this->oResource['i_qid']); - $oQuestion->updateAnswerCount(-1); + $oQuestion->removeAnswer($this->oResource); if((true === $this->oResource['accepted'])){ d('this was an accepted answer'); - $this->oResource['accepted'] = false; - $oQuestion->offsetUnset('i_sel_ans'); + $this->oResource->unsetAccepted(); } - $oQuestion->removeContributor($this->oResource['i_uid']) - ->touch()->save(); + $oQuestion->touch()->save(); } return $this; @@ -313,7 +311,8 @@ protected function setDeleted(){ */ $this->updateTags(); $this->removeFromIndex(); - $this->oResource->setDeleted($this->oRegistry->Viewer, $this->oRequest['note']); + $this->oResource->setDeleted($this->oRegistry->Viewer, $this->oRequest['note']) + ->touch(); d('new resource data: '.print_r($this->oResource->getArrayCopy(), 1)); @@ -360,7 +359,7 @@ protected function updateTags(){ } } else { $oQuestion = new \Lampcms\Question($this->oRegistry); - $oQuestion->by_id($this->oResource['i_qid']); + $oQuestion->by_id($this->oResource->getQuestionId()); d('tags: ' . print_r($oQuestion['a_tags'], 1)); } diff --git a/lib/Lampcms/Controllers/Editor.php b/lib/Lampcms/Controllers/Editor.php index 1504543..2922b18 100644 --- a/lib/Lampcms/Controllers/Editor.php +++ b/lib/Lampcms/Controllers/Editor.php @@ -148,7 +148,7 @@ protected function process(){ } $this->oResource->setEdited($this->oRegistry->Viewer, \strip_tags($formVals['reason'])); - $this->oResource->save(); + $this->oResource->touch()->save(); $this->oRegistry->Dispatcher->post($this->oResource, 'onEdit'); diff --git a/lib/Lampcms/Controllers/Retag.php b/lib/Lampcms/Controllers/Retag.php index 4a545e0..5f30448 100644 --- a/lib/Lampcms/Controllers/Retag.php +++ b/lib/Lampcms/Controllers/Retag.php @@ -292,7 +292,8 @@ protected function addNewTags(){ */ protected function updateQuestion(){ - $this->oQuestion->retag($this->oRegistry->Viewer, $this->aSubmitted)->save(); + $this->oQuestion->retag($this->oRegistry->Viewer, $this->aSubmitted) + ->save(); return $this; } diff --git a/lib/Lampcms/Controllers/Search.php b/lib/Lampcms/Controllers/Search.php index 156cc89..046c5cc 100644 --- a/lib/Lampcms/Controllers/Search.php +++ b/lib/Lampcms/Controllers/Search.php @@ -75,6 +75,8 @@ class Search extends WebPage * @var bool */ protected $notAjaxPaginatable = true; + + //protected $bRequirePost = true; /** * (non-PHPdoc) @@ -87,7 +89,7 @@ protected function main(){ * $_GET as underlying array, and php * already decodes $_GET or $_POST vars */ - $this->term = $this->oRequest['q']; + $this->term = $this->oRegistry->Request->getUTF8('q')->stripTags(); $this->aPageVars['qheader'] = '

Search results for: '.$this->term.'

'; $this->aPageVars['title'] = 'Questions matching ''.$this->term.'''; @@ -95,7 +97,7 @@ protected function main(){ $this->oSearch = SearchFactory::factory($this->oRegistry); - $this->oSearch->search(); + $this->oSearch->search($this->term); $this->makeTopTabs() ->makeInfo() @@ -103,7 +105,7 @@ protected function main(){ } protected function makeTopTabs(){ - d('cp'); + $tabs = Urhere::factory($this->oRegistry)->get('tplToptabs', 'questions'); $this->aPageVars['topTabs'] = $tabs; diff --git a/lib/Lampcms/Controllers/Shred.php b/lib/Lampcms/Controllers/Shred.php index e9ee385..a78c498 100644 --- a/lib/Lampcms/Controllers/Shred.php +++ b/lib/Lampcms/Controllers/Shred.php @@ -54,7 +54,8 @@ use \Lampcms\WebPage; use \Lampcms\Request; -use \Lampcms\Responder; +use \Lampcms\Responder; +use \Lampcms\Answer; class Shred extends WebPage { @@ -115,7 +116,7 @@ protected function excludeAdmin(){ return $this; } - + /** * This is important as it will cause * the removal cached value of 'recent questions' @@ -197,23 +198,23 @@ protected function deleteAnswers(){ if($cur && ($cur->count() > 0)){ foreach($cur as $a){ - + $oQuestion = new \Lampcms\Question($this->oRegistry); try{ - $oQuestion->by_id((int)$a['i_qid']); - $oQuestion->updateAnswerCount(-1); - - if((true === $a['accepted'])){ - d('this was an accepted answer'); - $oQuestion->offsetUnset('i_sel_ans'); - } - + $oQuestion->by_id((int)$oAnswer->getQuestionId()); + $oAnswer = new Answer($this->oRegistry, $a); + $oQuestion->removeAnswer($oAnswer); $oQuestion->save(); + /** + * setSaved() because we don't need auto-save feature + * to save each answer + * since all answers will be removed at end of this method + */ + $oAnswer->setSaved(); } catch(\MongoException $e){ d('Question not found by _id: '.$a['i_qid']); } - - + if(!empty($a['cc'])){ $this->aCountries[] = $a['cc']; } diff --git a/lib/Lampcms/Controllers/Vote.php b/lib/Lampcms/Controllers/Vote.php index b79a393..9572373 100644 --- a/lib/Lampcms/Controllers/Vote.php +++ b/lib/Lampcms/Controllers/Vote.php @@ -268,8 +268,12 @@ protected function setOwnerReputation(){ * Now need to calculate points * */ - \Lampcms\User::factory($this->oRegistry)->by_id($uid)->setReputation($this->calculatePoints()); - + try{ + \Lampcms\User::factory($this->oRegistry)->by_id($uid)->setReputation($this->calculatePoints()); + } catch(\Exception $e){ + e($e->getMessage().' in file: '.$e->getFile().' on line: '.$e->getLine()); + } + return $this; } @@ -307,9 +311,9 @@ protected function calculatePoints(){ */ protected function increaseVoteCount(){ if('up' === $this->voteType){ - $this->oResource->addUpVote($this->inc); + $this->oResource->addUpVote($this->inc)->touch(true); } else { - $this->oResource->addDownVote($this->inc); + $this->oResource->addDownVote($this->inc)->touch(true); } return $this; diff --git a/lib/Lampcms/Cookie.php b/lib/Lampcms/Cookie.php index 17559d4..c8bfd29 100644 --- a/lib/Lampcms/Cookie.php +++ b/lib/Lampcms/Cookie.php @@ -85,7 +85,7 @@ public static function sendLoginCookie($intUserId, $strSID, $cookieName = 'uid') * Google Friend Connect or Facebook connect */ - $salt = (defined('MOCK_COOKIE_SALT')) ? constant('MOCK_COOKIE_SALT') : COOKIE_SALT; + $salt = COOKIE_SALT; $cookieSid = \hash('sha256', $intUserId.$salt); $cookie = \http_build_query(array('uid' => $intUserId, 's' => $cookieSid)); diff --git a/lib/Lampcms/CookieAuth.php b/lib/Lampcms/CookieAuth.php index 289e650..450ef0d 100644 --- a/lib/Lampcms/CookieAuth.php +++ b/lib/Lampcms/CookieAuth.php @@ -96,11 +96,9 @@ public function authByCookie(){ $this->logLoginError($this->uid, $this->sid, true, null, 'cookie'); throw new CookieAuthException('wrong sid '.print_r($oUser, 1)); - } return $oUser; - } @@ -160,7 +158,7 @@ protected function checkRequiredCookies(){ * must be equal to 's' value * if any of these steps fails, throw Exception * - * @throws LampcmsCookieAuthException if cookie string + * @throws \Lampcms\CookieAuthException if cookie string * does not parse or does not validate * * @return object $this @@ -191,7 +189,7 @@ protected function validateCookieSalt(){ */ $this->uid = (int)$this->uid; - $salt = (defined('MOCK_COOKIE_SALT')) ? constant('MOCK_COOKIE_SALT') : COOKIE_SALT; + $salt = COOKIE_SALT; d('cookie salt: '.$salt); if($a['s'] !== \hash('sha256', $this->uid.$salt)){ diff --git a/lib/Lampcms/DB.php b/lib/Lampcms/DB.php index 1027538..243141a 100644 --- a/lib/Lampcms/DB.php +++ b/lib/Lampcms/DB.php @@ -208,11 +208,6 @@ public function __clone(){ protected function makeDsn(){ $this->aDB = $this->oIni->getSection('DB'); - if (null === $this->aDB) { - - throw new IniException('section "DB" does not exist in aIni'); - } - if (!isset($this->aDB['Database_username']) || !isset($this->aDB['Database_password'])) { throw new IniException('Database_username OR Database_password not set'); @@ -235,25 +230,42 @@ protected function getDSN(){ if ( empty($this->aDB['Database_name']) || empty($this->aDB['Database_host']) || empty ($this->aDB['Database_type'])) { - throw new IniException('Cannot create dsn because some required dns params are missing: '. - print_r($this->aDB, true)); + throw new IniException('Cannot create dsn because some required dns params are missing: '.print_r($this->aDB, true)); } - $dbhost = strtolower($this->aDB['Database_host']); + /** + * LAMPCMS_TEST is the name we use in Unit Tests + * If the actual name is also LAMPCMS_TEST then + * Unit tests will destroy actual database during + * tests. This should not be allowed! + */ + if('LAMPCMS_TEST' === \trim($this->aDB['Database_name']) ){ + throw new DevException('Reserved name! You cannot name your database '.$this->aDB['Database_name'].' Please set different value of Database_name is !config.ini'); + } + + $dbhost = \strtolower($this->aDB['Database_host']); + + /** + * Always try to use defined LAMPCMS_MYSQL_DB + * This is useful in Unit testing so we can + * define value for test database and not + * use live database! + * + * @var string + */ + $dbname = (defined('LAMPCMS_MYSQL_DB')) ? LAMPCMS_MYSQL_DB : $this->aDB['Database_name']; - $ret = strtolower($this->aDB['Database_type']).':host='.$dbhost; + $ret = \strtolower($this->aDB['Database_type']).':host='.$dbhost; if ('localhost' !== $dbhost) { - if ( empty ($this->aDB['TCP_Port_number'])) { throw new IniException('If Database_host is not "localhost" then "TCP_Port_number" MUST be defined'); } $ret .= ';port='.$this->aDB['TCP_Port_number']; - } - $ret .= ';dbname='.$this->aDB['Database_name']; + $ret .= ';dbname='.$dbname; return $ret; diff --git a/lib/Lampcms/Interfaces/All.php b/lib/Lampcms/Interfaces/All.php index b98fa3d..fb45f1f 100644 --- a/lib/Lampcms/Interfaces/All.php +++ b/lib/Lampcms/Interfaces/All.php @@ -131,7 +131,7 @@ public function delete($name); * somewhere for debugging purposes. */ public function set($name, $val, $ttl = 63072000, $sDomain = null); - + /** * Function for setting or deleting login cookie * the value of the s cookie is an md5 hash of user password @@ -258,8 +258,8 @@ interface RoleInterface * @return string */ public function getRoleId(); - - + + public function setRoleId($role); } @@ -613,10 +613,10 @@ public function getTumblrBlogUrl(); * * @return string value to be used as 'group' param * in WRITE API call - * + * */ public function getTumblrBlogId(); - + public function setTumblrBlogs(array $blogs); } @@ -667,12 +667,12 @@ public function getBloggerBlogs(); /** * Get title of default blog - * + * */ public function getBloggerBlogTitle(); /** - * + * * Get url of default blog */ public function getBloggerBlogUrl(); @@ -681,14 +681,14 @@ public function getBloggerBlogUrl(); /** * @return string value to be used as '' param * in WRITE API call - * + * */ public function getBloggerBlogId(); - + /** * Set value of 'blogs' under the 'blogger' element - * - * + * + * * @param array $blogs array of all blogs * user has on Blogger. Each element is an array * with 3 keys: id, url, title @@ -733,10 +733,7 @@ public function getBody(); } /** - * @todo - * add setLastAnswerer() - * - * @author admin + * @author Dmitri Snytkine * */ interface Question extends Post @@ -807,7 +804,27 @@ public function setBestAnswer(\Lampcms\Answer $oAnswer); public function updateAnswerCount($int = 1); + /** + * + * Adds the small array with link to last poster + * and time of last post and id of last answer + * to the a_latest element of the Question + * + * @param \Lampcms\User $oUser + * @param \Lampcms\Answer $oAnswer + */ + public function setLatestAnswer(\Lampcms\User $oUser, \Lampcms\Answer $oAnswer); + /** + * Method to run when an answer is delete + * Deleting an Answer affects several values + * in Question like count of answers, status of question etc. + * even more so if that answer + * was also a "accepted" answer + * + * @param \Lampcms\Answer $oAnswer + */ + public function removeAnswer(\Lampcms\Answer $oAnswer); } /** diff --git a/lib/Lampcms/Modules/Observers/EmailNotifier.php b/lib/Lampcms/Modules/Observers/EmailNotifier.php index f2013fa..446a7b4 100644 --- a/lib/Lampcms/Modules/Observers/EmailNotifier.php +++ b/lib/Lampcms/Modules/Observers/EmailNotifier.php @@ -621,11 +621,20 @@ protected function notifyQuestionFollowers($qid = null, $excludeUid = 0){ if($qid){ $oQuestion = new \Lampcms\Question($this->oRegistry); - $oQuestion->by_id((int)$qid); + try{ + $oQuestion->by_id((int)$qid); + } catch(\Exception $e){ + e($e->getMessage().' in file: '.$e->getFile().' on line: '.$e->getLine()); + $oQuestion = null; + } } else { $oQuestion = $this->oQuestion; } + if(null === $oQuestion){ + return $this; + } + $updateType = ('onNewAnswer' === $this->eventName) ? 'answer' : 'comment'; $subj = sprintf(static::$QUESTION_FOLLOW_SUBJ, $updateType); d('cp'); @@ -670,7 +679,6 @@ protected function notifyQuestionFollowers($qid = null, $excludeUid = 0){ */ if(false !== $key = array_search($viewerID, $aFollowers)){ array_splice($aFollowers, $key, 1); - } if(!empty($excludeUid)){ diff --git a/lib/Lampcms/Modules/Search/IndexerMySQL.php b/lib/Lampcms/Modules/Search/IndexerMySQL.php index ce28a59..3d25176 100644 --- a/lib/Lampcms/Modules/Search/IndexerMySQL.php +++ b/lib/Lampcms/Modules/Search/IndexerMySQL.php @@ -116,10 +116,9 @@ public function indexQuestion(\Lampcms\Question $oQuestion){ $username = $oQuestion['username']; $ulink = $oQuestion['ulink']; $avatar = $oQuestion['avtr']; - $tags_c = $oQuestion['tags_c']; $tags_html = $oQuestion['tags_html']; - d($qid.' title: '. $title. ' url: '. $url.' intro: '.$intro.' tags_c: '.$tags_c); + d($qid.' title: '. $title. ' url: '. $url.' intro: '.$intro); $sql = 'INSERT INTO question_title ( @@ -132,7 +131,6 @@ public function indexQuestion(\Lampcms\Question $oQuestion){ username, userlink, avtr, - tags_c, tags_html) VALUES ( :qid, @@ -144,7 +142,6 @@ public function indexQuestion(\Lampcms\Question $oQuestion){ :username, :userlink, :avatar, - :tags_c, :tags_html)'; @@ -155,7 +152,6 @@ public function indexQuestion(\Lampcms\Question $oQuestion){ $sth->bindParam(':qbody', $body, \PDO::PARAM_STR); $sth->bindParam(':qurl', $url, \PDO::PARAM_STR); $sth->bindParam(':qintro', $intro, \PDO::PARAM_STR); - $sth->bindParam(':tags_c', $tags_c, \PDO::PARAM_STR); $sth->bindParam(':tags_html', $tags_html, \PDO::PARAM_STR); $sth->bindParam(':uid', $uid, \PDO::PARAM_INT); $sth->bindParam(':username', $username, \PDO::PARAM_STR); @@ -238,11 +234,10 @@ public function updateQuestion(\Lampcms\Question $oQuestion){ $username = $oQuestion['username']; $ulink = $oQuestion['ulink']; $avatar = $oQuestion['avtr']; - $tags_c = $oQuestion['tags_c']; $tags_html = $oQuestion['tags_html']; $body = $oQuestion['body']; - d($qid.' title: '. $title. ' url: '. $url.' intro: '.$intro.' tags_c: '.$tags_c); + d($qid.' title: '. $title. ' url: '. $url.' intro: '.$intro); $sql = 'UPDATE question_title SET @@ -253,7 +248,6 @@ public function updateQuestion(\Lampcms\Question $oQuestion){ username = :username, userlink = :userlink, avtr = :avatar, - tags_c = :tags_c, tags_html = :tags_html WHERE qid = :qid'; @@ -265,7 +259,6 @@ public function updateQuestion(\Lampcms\Question $oQuestion){ $sth->bindParam(':qbody', $body, \PDO::PARAM_STR); $sth->bindParam(':qurl', $url, \PDO::PARAM_STR); $sth->bindParam(':qintro', $intro, \PDO::PARAM_STR); - $sth->bindParam(':tags_c', $tags_c, \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); diff --git a/lib/Lampcms/Modules/Search/MySQL.php b/lib/Lampcms/Modules/Search/MySQL.php index 3b2a7f6..ed8257e 100644 --- a/lib/Lampcms/Modules/Search/MySQL.php +++ b/lib/Lampcms/Modules/Search/MySQL.php @@ -112,7 +112,7 @@ public function __construct(Registry $oRegistry){ public function search($term = null){ - $this->term = (!empty($term)) ? $term : $this->oRegistry->Request['q']; + $this->term = (!empty($term)) ? $term : $this->oRegistry->Request->getUTF8('q')->stripTags(); $this->getCondition() ->getCount() @@ -154,7 +154,7 @@ protected function getCount(){ d('mysql error: '.$err); if('42S02' === $e->getCode()){ - if(true === \Lampcms\TitleTagsTable::create($this->oRegistry)){ + if(true === TitleTagsTable::create($this->oRegistry)){ return $this; } else { @@ -211,7 +211,6 @@ protected function getResults(){ DATE_FORMAT(ts, '%%M %%e, %%Y %%l:%%i %%p') as hts, username, avtr, - tags_c, tags_html FROM question_title WHERE %s @@ -360,7 +359,7 @@ public function getSimilarQuestions(\Lampcms\Question $oQuestion){ d('mysql error: '.$err); if('42S02' === $e->getCode()){ - if(true === \Lampcms\TitleTagsTable::create($this->oRegistry)){ + if(true === TitleTagsTable::create($this->oRegistry)){ return $this; } else { diff --git a/lib/Lampcms/TitleTagsTable.php b/lib/Lampcms/Modules/Search/TitleTagsTable.php similarity index 95% rename from lib/Lampcms/TitleTagsTable.php rename to lib/Lampcms/Modules/Search/TitleTagsTable.php index fd84e39..bd46dd9 100644 --- a/lib/Lampcms/TitleTagsTable.php +++ b/lib/Lampcms/Modules/Search/TitleTagsTable.php @@ -49,7 +49,7 @@ * */ -namespace Lampcms; +namespace Lampcms\Modules\Search; /** * @@ -74,7 +74,6 @@ class TitleTagsTable `username` varchar(50) NOT NULL, `userlink` varchar(60) NOT NULL COMMENT \'path to user profile, usually looks like this: /users/123/someuser\', `avtr` text NOT NULL COMMENT \'path to user avatar at time of posting\', - `tags_c` varchar(100) NOT NULL, `tags_html` text NOT NULL, UNIQUE KEY `qid` (`qid`), KEY `uid` (`uid`), @@ -83,7 +82,7 @@ class TitleTagsTable ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT=\'Table used for full text indexing of question title\'; '; - public static function create(Registry $oRegistry){ + public static function create(\Lampcms\Registry $oRegistry){ d('Table "question_title" not found going to create it now'); $res = $oRegistry->Db->exec(self::SQL); d('res: '.$res); diff --git a/lib/Lampcms/Mongo.php b/lib/Lampcms/Mongo.php index 321fbe6..69f0658 100644 --- a/lib/Lampcms/Mongo.php +++ b/lib/Lampcms/Mongo.php @@ -56,7 +56,7 @@ /** * Wrapped class for working with * php's MongoDB classes - * + * * @author Dmitri Snytkine * */ @@ -101,7 +101,7 @@ public function __construct(Ini $oIni){ if(!\extension_loaded('mongo')){ exit('PHP mongo extension not loaded. Exiting'); } - + $aOptions = array('connect' => true); $aConfig = $oIni->getSection('MONGO'); d('$aConfig: '.print_r($aConfig, 1)); @@ -111,7 +111,7 @@ public function __construct(Ini $oIni){ * For Unit testing we define * MONGO_DBNAME to be LAMPCMS_TEST * so that actual database not used during testing - * + * */ $this->dbname = (defined('MONGO_DBNAME')) ? constant('MONGO_DBNAME') : $aConfig['db']; @@ -150,7 +150,7 @@ public function setDbName($name){ if(!is_string($name)){ throw new \InvalidArgumentException('$name must be a string. Was: '.gettype($name)); } - + $this->dbname = $name; return $this; @@ -295,9 +295,23 @@ public function getDb(){ * @return object of type MongoCollection */ public function getCollection($collName){ + if(!is_string($collName)){ + throw new \InvalidArgumentException('Param $collName must be a string. was: '.gettype($collName)); + } + return $this->conn->selectCollection($this->dbname, $collName); } + /** + * Alias of getCollection() + * This is the same name as in php's MongoDB class + * + * @param string $collName + */ + public function selectCollection($collName){ + return $this->getCollection($collName); + } + /** * Magic getter to simplify selecting collection diff --git a/lib/Lampcms/MongoCache.php b/lib/Lampcms/MongoCache.php index a118280..e7d4754 100644 --- a/lib/Lampcms/MongoCache.php +++ b/lib/Lampcms/MongoCache.php @@ -90,7 +90,7 @@ class MongoCache implements Interfaces\Cache /** * Flag indicates to compress data * this will save storage space if - * values are fairly longs strings + * values are fairly long strings * and if value is close to 4MB (limit of mongo document size) * then you should use this option * @@ -227,8 +227,7 @@ public function getIds() { * this equals to flushing cache completely * all keys/values are gone after this call */ - public function flush() - { + public function flush(){ $dropped = $this->_collection->drop(); d('dropped: '.print_r($dropped, 1)); @@ -247,8 +246,7 @@ public function flush() * @return mixed null if not found * or array */ - public function getRawData($key) - { + public function getRawData($key){ $ret = $this->_collection->findOne(array('_id' => $key)); return (empty($ret)) ? null : $ret; diff --git a/lib/Lampcms/MongoDoc.php b/lib/Lampcms/MongoDoc.php index d6f9bb2..04ce224 100644 --- a/lib/Lampcms/MongoDoc.php +++ b/lib/Lampcms/MongoDoc.php @@ -60,7 +60,7 @@ * @author Dmitri Snytkine implements \Serializable * */ -class MongoDoc extends ArrayDefaults implements \Serializable +class MongoDoc extends LampcmsArray implements \Serializable { /** * Object of type Registry @@ -171,8 +171,8 @@ class MongoDoc extends ArrayDefaults implements \Serializable * * @return object of this class OR class extending this class */ - public static function factory(Registry $oRegistry, $collectionName = null, array $a = array(), $default = ''){ - $o = new static($oRegistry, $collectionName, $a, $default); + public static function factory(Registry $oRegistry, $collectionName = null, array $a = array(), $default = null){ + $o = new static($oRegistry, $collectionName, $a); return $o; } @@ -189,8 +189,8 @@ public static function factory(Registry $oRegistry, $collectionName = null, arra * set it to null or false, whatever you want to use for a default (fallback) * value of any array key */ - public function __construct(Registry $oRegistry, $collectionName = null, array $a = array(), $default = ''){ - parent::__construct($a, $default); + public function __construct(Registry $oRegistry, $collectionName = null, array $a = array()){ + parent::__construct($a); $this->oRegistry = $oRegistry; $this->collectionName = $collectionName; $this->md5 = \md5(\serialize($a)); @@ -230,9 +230,11 @@ public function getMinAutoIncrement(){ * @see ArrayDefaults::offsetGet() */ public function offsetGet($name){ - $ret = parent::offsetGet($name); - - $prefix = substr($name, 0, 2); + //$ret = parent::offsetGet($name); // old way, when this was ArrayDefaults object - not anymore! + $ret = !$this->offsetExists($name) ? null : parent::offsetGet($name); + + d(' looking for '.$name.' getting: '.var_export($ret, true)); + $prefix = \substr($name, 0, 2); switch($prefix){ case 'i_': $ret = (int)$ret; @@ -572,7 +574,7 @@ public function addArray(array $a){ */ public function insert(){ - if(!$this->checkOffset($this->keyColumn) && $this->minAutoIncrement){ + if(!$this->offsetExists($this->keyColumn) && $this->minAutoIncrement){ $_id = $this->getRegistry()->Incrementor->nextValue($this->collectionName, $this->minAutoIncrement); d('setting value of _id to '.$_id); $this->offsetSet('_id', $_id); @@ -766,8 +768,7 @@ public function serialize(){ 'collectionName' => $this->collectionName, 'md5' => $this->md5, 'bSaved' => $this->bSaved, - 'keyColumn' => $this->keyColumn, - 'defaultValue' => $this->defaultValue); + 'keyColumn' => $this->keyColumn); unset($this->oRegistry); @@ -783,7 +784,6 @@ public function unserialize($serialized){ $a = unserialize($serialized); $this->exchangeArray($a['array']); $this->collectionName = $a['collectionName']; - $this->defaultValue = $a['defaultValue']; $this->bSaved = $a['bSaved']; $this->keyColumn = $a['keyColumn']; $this->md5 = $a['md5']; diff --git a/lib/Lampcms/MongoIncrementor.php b/lib/Lampcms/MongoIncrementor.php index 9d57dd9..66f97c0 100644 --- a/lib/Lampcms/MongoIncrementor.php +++ b/lib/Lampcms/MongoIncrementor.php @@ -107,8 +107,7 @@ public function __construct(Mongo $oMongo){ * * @return int value of next id for the collection */ - public function nextValue($collName, $initialId = 0, $try = 1) - { + public function nextValue($collName, $initialId = 0, $try = 1){ if( $try > 100 ){ throw new \RuntimeException('Unable to get nextID for collection '.$collName.' after 100 tries'); diff --git a/lib/Lampcms/Object.php b/lib/Lampcms/Object.php index d724d35..1c1fb98 100644 --- a/lib/Lampcms/Object.php +++ b/lib/Lampcms/Object.php @@ -329,6 +329,15 @@ public function getClass(){ public function __toString(){ return 'object of type: '.$this->getClass().' hashCode: '.$this->hashCode(); } + + /** + * Getter for $this->oRegistry + * + * @return object of type Registry + */ + public function getRegistry(){ + return $this->oRegistry; + } } @@ -543,11 +552,19 @@ public function __isset($name){ * since we can no longer rely on the * offsetExists() in this object, * we are asking a parent object + * + * It ONLY works properly + * with this object and does not work + * in sub-class because in sub-class parent + * becomes THIS class and calling offsetExists + * on THIS class always returns true! + * + * Ideally this function should be eliminated * * @param string $name * @return bool */ - public function checkOffset($name){ + public final function checkOffset($name){ return parent::offsetExists($name); } @@ -611,41 +628,6 @@ public function resetDefaultValue(){ } - /** - * If the key $key does not actually exists in - * the array, then return the value passed as - * second argument , if second param is not given ,then returns - * value of $key - * - * otherwise return the value of $key - * - * @param string $key - * @param mixed $default - * @return mixed - */ - public function getFallback($key, $default = null){ - if (parent::offsetExists($key)) { - - return parent::offsetGet($key); - } - - return ( null !== $default) ? $default : $key; - } - - - /** - * - * Get value of $key previously converting - * $key to lower case - * - * @param string $key - * @param string $default - */ - public function getFallbackLc($key, $default = null){ - return $this->getFallback(\mb_strtolower($key), $default); - } - - /** * This method lets you get undefined array keys as * object properties diff --git a/lib/Lampcms/Question.php b/lib/Lampcms/Question.php index 6e69eb6..09f4b3e 100644 --- a/lib/Lampcms/Question.php +++ b/lib/Lampcms/Question.php @@ -175,7 +175,7 @@ public function getUrl($short = false){ return ($short) ? $url : $url.$this->offsetGet('url'); } - + /** * (non-PHPdoc) * @see Lampcms\Interfaces.Post::getBody() @@ -184,7 +184,7 @@ public function getBody(){ return $this->offsetGet('b'); } - + /** * (non-PHPdoc) * @see Lampcms\Interfaces.Post::getTitle() @@ -193,7 +193,7 @@ public function getTitle(){ return $this->offsetGet('title'); } - + /** * (non-PHPdoc) * @see Lampcms\Interfaces.Post::getSeoUrl() @@ -201,17 +201,16 @@ public function getTitle(){ public function getSeoUrl(){ return $this->offsetGet('url'); } - - + + /** - * Should return false if NOT closed - * otherwise either true or timestamp - * of when it was closed + * Test to see if question is closed. If it is closed + * then returns array of data that contains + * Username, reason and time of when question was + * closed * - * @todo change this to just return offsetGet('i_closed) - * it will return 0 if i_closed is not present - * because if the new way we going to handle non-existent - * offsets that start with 'i_' or end with 'id') + * @return mixed false if not closed | array of a_closed + * if is closed */ public function isClosed(){ $a = $this->offsetGet('a_closed'); @@ -240,8 +239,8 @@ public function getAnswerCount(){ */ public function setClosed(User $closer, $reason = null){ - if(!$this->checkOffset('a_closed')){ - $this->offsetSet('a_closed', array( + if(!$this->offsetExists('a_closed')){ + parent::offsetSet('a_closed', array( 'username' => $closer->getDisplayName(), 'i_uid' => $closer->getUid(), 'av' => $closer->getAvatarSrc(), @@ -270,8 +269,8 @@ public function setClosed(User $closer, $reason = null){ */ public function setDeleted(User $user, $reason = null){ if(0 === $this->getDeletedTime()){ - $this->offsetSet('i_del_ts', time()); - $this->offsetSet('a_deleted', + parent::offsetSet('i_del_ts', time()); + parent::offsetSet('a_deleted', array( 'username' => $user->getDisplayName(), 'i_uid' => $user->getUid(), @@ -282,8 +281,6 @@ public function setDeleted(User $user, $reason = null){ ); } - $this->touch(); - return $this; } @@ -311,9 +308,7 @@ public function setEdited(User $user, $reason = ''){ 'reason' => $reason, 'hts' => date('F j, Y g:i a T')); - $this->offsetSet('a_edited', $aEdited); - - $this->touch(); + parent::offsetSet('a_edited', $aEdited); return $this; } @@ -332,8 +327,8 @@ public function setEdited(User $user, $reason = ''){ */ public function retag(User $user, array $tags){ - $this->offsetSet('a_tags', $tags); - $this->offsetSet('tags_html', \tplQtags::loop($tags, false)); + parent::offsetSet('a_tags', $tags); + parent::offsetSet('tags_html', \tplQtags::loop($tags, false)); $b = $this->offsetGet('b'); d('b: '.$b); @@ -343,7 +338,7 @@ public function retag(User $user, array $tags){ $this->offsetSet('b', $body); - $this->setEdited($user, 'Retagged'); + $this->setEdited($user, 'Retagged')->touch(); return $this; } @@ -365,13 +360,13 @@ public function retag(User $user, array $tags){ */ public function setBestAnswer(Answer $oAnswer){ d('about to set status to accptd'); - $this->offsetSet('i_sel_ans', $oAnswer->getResourceId()); - $this->offsetSet('i_sel_uid', $oAnswer->getOwnerId()); + parent::offsetSet('i_sel_ans', $oAnswer->getResourceId()); + parent::offsetSet('i_sel_uid', $oAnswer->getOwnerId()); /** * Now set the Answer object's accepted status to true */ - $oAnswer->setAccepted(); + $oAnswer->setAccepted()->touch(); /** * If Question is still not 'answered', means @@ -405,10 +400,12 @@ public function setBestAnswer(Answer $oAnswer){ * @param int $inc */ public function updateAnswerCount($inc = 1){ + if(!\is_int($inc)){ + throw new \InvalidArgumentException('Param $inc must be an integer. was: '.gettype($inc)); + } + $iAns = $this->offsetGet('i_ans'); d('$iAns '.$iAns ); - $newCount = max(0, ($iAns + $inc)); - d('$newCount: '.$newCount); /** * Set new value of i_ans but make sure @@ -418,7 +415,10 @@ public function updateAnswerCount($inc = 1){ * is possible when we need to decrease answer count, * that's why we need this guard here. */ - $this->offsetSet('i_ans', $newCount); + $newCount = max(0, ($iAns + $inc)); + d('$newCount: '.$newCount); + + parent::offsetSet('i_ans', $newCount); /** * Change the status to answrd @@ -427,10 +427,10 @@ public function updateAnswerCount($inc = 1){ * of div to not be red, but it still does not * make the question 'answered' */ - if($newCount > 0){ - $this->offsetSet('status', 'answrd'); - } else { - $this->offsetSet('status', 'unans'); + if($newCount < 1){ + parent::offsetSet('status', 'unans'); + } elseif('unans' === $this->offsetGet('status')){ + parent::offsetSet('status', 'answrd'); } /** @@ -438,13 +438,11 @@ public function updateAnswerCount($inc = 1){ * a_s (plural suffix) to 's' */ if(1 !== ($newCount)){ - $this->offsetSet('ans_s', 's'); + parent::offsetSet('ans_s', 's'); } else { - $this->offsetSet('ans_s', ''); + parent::offsetSet('ans_s', ''); } - $this->touch(); - return $this; } @@ -485,6 +483,10 @@ public function touch($etagOnly = false){ * @return object $this */ public function increaseViews(\Lampcms\User $Viewer, $inc = 1){ + if(!\is_int($inc)){ + throw new \InvalidArgumentException('Param $inc must be an integer. was: '.gettype($inc)); + } + /** * @todo Don't count question owner view * For this we must be able to get Viewer from Registry @@ -494,9 +496,9 @@ public function increaseViews(\Lampcms\User $Viewer, $inc = 1){ $viewerId = $Viewer->getUid(); /** - * If guest, then don't check for dups - * @todo this will be a problem if we at least don't check - * for same session_id or ip address + * If guest, then there + * will be a problem if we at least don't check + * for same session_id */ $viewerId = (0 === $viewerId) ? session_id() : $viewerId; @@ -515,7 +517,7 @@ public function increaseViews(\Lampcms\User $Viewer, $inc = 1){ /** * If this is the first view, we will cheat a little * and set the views to 2 - * There will not be just 1 view, and this way we don't + * There will never be just 1 view, and this way we don't * have to worry about the plural suffix */ if(0 === $iViews && (1 === $inc)) { @@ -527,7 +529,7 @@ public function increaseViews(\Lampcms\User $Viewer, $inc = 1){ $qid = (int)$this->offsetGet('_id'); try{ $collViews->insert(array('qid' => $qid, 'uid' => $viewerId, 'i_ts' => time()), array('safe' => true)); - $this->offsetSet('i_views', ($iViews + (int)$inc) ); + parent::offsetSet('i_views', ($iViews + (int)$inc) ); /** * If new value is NOT 1 then set @@ -562,14 +564,14 @@ public function addUpVote($inc = 1){ $score = (int)$this->offsetGet('i_votes'); $total = ($score + $inc); - $this->offsetSet('i_up', max(0, ($tmp + $inc)) ); - $this->offsetSet('i_votes', $total ); + parent::offsetSet('i_up', max(0, ($tmp + $inc)) ); + parent::offsetSet('i_votes', $total ); /** * Plural extension handling */ $v_s = (1 === abs($total) ) ? '' : 's'; - $this->offsetSet('v_s', $v_s); + parent::offsetSet('v_s', $v_s); return $this; } @@ -589,17 +591,17 @@ public function addDownVote($inc = 1){ $score = (int)$this->offsetGet('i_votes'); $total = ($score - $inc); - $this->offsetSet('i_down', max(0, ($tmp + $inc)) ); + parent::offsetSet('i_down', max(0, ($tmp + $inc)) ); /** * Question can have negative score, so we allow it! */ - $this->offsetSet('i_votes', $total ); + parent::offsetSet('i_votes', $total ); /** * Plural extension handling */ $v_s = (1 === abs($total) ) ? '' : 's'; - $this->offsetSet('v_s', $v_s); + parent::offsetSet('v_s', $v_s); return $this; } @@ -665,7 +667,7 @@ public function addComment(CommentParser $oComment){ $aComments[] = $aComment; - $this->offsetSet('comments', $aComments); + $this->offsetSet('a_comments', $aComments); $this->increaseCommentsCount(); /** @@ -696,14 +698,17 @@ public function getCommentsCount(){ * Increase value of i_commets by 1 * @return object $this */ - public function increaseCommentsCount(){ + public function increaseCommentsCount($count = 1){ + if(!is_int($count)){ + throw new \InvalidArgumentException('$count must be integer. was: '.gettype($count)); + } /** * Now increase comments count */ $commentsCount = $this->getCommentsCount(); d('$commentsCount '.$commentsCount); - $this->offsetSet('i_comments', ($commentsCount + 1) ); + parent::offsetSet('i_comments', ($commentsCount + $count) ); return $this; } @@ -721,13 +726,13 @@ public function increaseCommentsCount(){ */ public function deleteComment($id){ - if(!$this->checkOffset('comments')){ - e('This question does not have any comments'); + if(0 === $this->getCommentsCount()){ + d('This question does not have any comments'); return $this; } - $aComments = $this->offsetGet('comments'); + $aComments = $this->offsetGet('a_comments'); for($i = 0; $ioffsetUnset('comments'); + $this->offsetUnset('a_comments'); } else { - $this->offsetSet('comments', $aComments); + $this->offsetSet('a_comments', $aComments); } - $this->offsetSet('i_comments', $newCount ); + parent::offsetSet('i_comments', $newCount ); return $this; } @@ -757,18 +762,25 @@ public function deleteComment($id){ * has made an answer or a comment * to a question * + * Contributors array is not unique, + * it can have more than one entry for + * the same user if user contributed multiple + * times. This way we can remove just one record + * and user is still considered a contributor + * as long as the same user has contributed other items + * * @param mixed int | object $oUser object of type User */ public function addContributor($User){ - if(!is_int($User) && (!is_object($User) || !($User instanceof User))){ + if(!\is_int($User) && (!\is_object($User) || !($User instanceof User))){ throw new \InvalidArgumentException('Value of $User can be only int or instance of User class. it was: '.var_export($User, true)); } - $uid = (is_int($User)) ? $User : $User->getUid(); - $a = $this->getFallback('a_uids', array()); + $uid = (\is_int($User)) ? $User : $User->getUid(); + $a = $this->offsetGet('a_uids'); $a[] = $uid; - $this->offsetSet('a_uids', $a); + parent::offsetSet('a_uids', $a); return $this; } @@ -793,12 +805,12 @@ public function removeContributor($User){ } $changed = false; - $uid = (is_int($User)) ? $User : $User->getUid(); - $a = $this->getFallback('a_uids', array()); + $uid = (\is_int($User)) ? $User : $User->getUid(); + $a = $this->offsetGet('a_uids'); for($i = 0; $ioffsetGet('a_latest'); + $a = array( + 'u' => ''.$oUser->getDisplayName().'', + 't' => date('F j, Y g:i a T', $oAnswer->getLastModified()), + 'id' => $oAnswer->getResourceId() + ); + + /** + * Latest answer data goes + * to top of array + */ + \array_unshift($aLatest, $a); - $this->offsetSet('lp_u', ''.$oUser->getDisplayName().''); - $this->offsetSet('lp_t', date('F j, Y g:i a T')); + $this->offsetSet('a_latest', $aLatest); + + return $this; + } + + + /** + * Removes one element from a_latest array + * that represents answer passed in param. + * + * If that array had only one element + * then also unset the whole 'a_latest' key + * from this object + * + * @param object $oAnswer object of type Answer + * + * @return object $this + */ + public function removeAnswer(Answer $oAnswer){ + $id = $oAnswer->getResourceId(); + $aLatest = $this->offsetGet('a_latest'); + + for($i = 0; $i < count($aLatest); $i += 1){ + if(!empty($aLatest[$i]) && ($id === $aLatest[$i]['id'])){ + \array_splice($aLatest, $i, 1); + break; + } + } + + if( 0 === count($aLatest)){ + $this->offsetUnset('a_latest'); + } else { + parent::offsetSet('a_latest', $aLatest); + } + + /** + * If removed Answer was also a "accepted" answer + * then change status to just "answrd" here + * + * The updateAnswerCount(-1) method + * may then change the status to "unans" + * if it's determined that this was + * the only answer + * + * Also need to add this question to + * UNANSWERED_TAGS again because now + * this question is technically unanswered again + */ + if((true === $oAnswer['accepted']) && + ($id === $this->offsetGet('i_sel_ans')) + ){ + parent::offsetSet('status', 'answrd'); + $this->offsetUnset('i_sel_ans'); + $this->offsetUnset('i_sel_uid'); + UnansweredTags::factory($this->oRegistry)->set($this); + } + + $this->updateAnswerCount(-1) + ->removeContributor($oAnswer->getOwnerId()); + + $this->touch(false); return $this; } @@ -899,7 +982,7 @@ public function setLastAnswerer(User $oUser){ * */ public function getComments(){ - return $this->getFallback('comments', array()); + return $this->offsetGet('a_comments'); } @@ -921,4 +1004,59 @@ public function getQuestionOwnerId(){ public function getUsername(){ return $this->offsetGet('username'); } + + /** + * This method prevents setting some + * values directly + * + * (non-PHPdoc) + * @see ArrayObject::offsetSet() + */ + public function offsetSet($index, $newval){ + switch($index){ + + case 'i_comments': + throw new DevException('value of i_comments cannot be set directly. Use increaseCommentsCount() method'); + break; + + case 'i_down': + case 'i_up': + case 'i_votes': + throw new DevException('value of '.$index.' keys cannot be set directly. Use addDownVote or addUpVote to add votes'); + break; + + case 'a_deleted': + case 'i_del_ts': + throw new DevException('value of '.$index.' cannot be set directly. Must use setDeleted() method for that'); + break; + + case 'i_ans': + throw new DevException('value of i_ans cannot be set directly. Use updateAnswerCount() method'); + break; + + case 'i_views': + throw new DevException('value of i_ans cannot be set directly. Use increaseViews() method'); + break; + + case 'a_edited': + throw new DevException('value of a_edited cannot be set directly. Must use setEdited() method for that'); + break; + + case 'a_closed': + throw new DevException('value of a_closed cannot be set directly. Must use setClosed() method for that'); + break; + + /*case 'a_latest': + throw new DevException('value of a_latest cannot be set directly. Must use setLatestAnswer() method for that'); + break;*/ + + case 'i_sel_uid': + case 'i_sel_ans': + throw new DevException('value of '.$index.' cannot be set directly. Must use setBestAnswer() method for that'); + break; + + default: + parent::offsetSet($index, $newval); + } + } } diff --git a/lib/Lampcms/RegBlock.php b/lib/Lampcms/RegBlock.php index c40bea5..b4952fd 100644 --- a/lib/Lampcms/RegBlock.php +++ b/lib/Lampcms/RegBlock.php @@ -190,7 +190,7 @@ protected function setUsernameVars() $this->aUsername = array('Username', $this->oViewer->username, 'Username will appear alongside your posts'); d('$this->aUsername: '.print_r($this->aUsername, 1)); - $this->aVars['username'] = $this->oViewer->username; + $this->aVars['username'] = $this->oViewer['username']; return $this; } diff --git a/lib/Lampcms/Relatedtags.php b/lib/Lampcms/Relatedtags.php index 84c4d01..d9a78b7 100644 --- a/lib/Lampcms/Relatedtags.php +++ b/lib/Lampcms/Relatedtags.php @@ -59,8 +59,6 @@ * in the same question. For example: mysql, zend, PDO * are normally related to 'php' * - * @todo get this from Registry, as singleton - * * @author Dmitri Snytkine * */ diff --git a/lib/Lampcms/Request.php b/lib/Lampcms/Request.php index 68f3929..519be58 100644 --- a/lib/Lampcms/Request.php +++ b/lib/Lampcms/Request.php @@ -94,7 +94,7 @@ class Request extends LampcmsArray implements Interfaces\LampcmsObject /** * Array of UTF8 objects * - * @var array + * @var array of objects of type Utf8String */ protected $aUTF8 = array(); @@ -309,35 +309,35 @@ protected function getFiltered($name){ d('getting filtered for '.$name); - if(!array_key_exists($name, $this->aFiltered)){ + if(!\array_key_exists($name, $this->aFiltered)){ d('cp not yet in $this->aFiltered'); $val = parent::offsetGet($name); if('a' === $name && !empty($val)){ $expression = '/^[[:alpha:]\-]{1,20}$/'; - if(!filter_var($val, FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => $expression)))){ + if(!\filter_var($val, FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => $expression)))){ throw new \InvalidArgumentException('Invalid value of "a" it can only contain letters and a hyphen and be limited to 20 characters in total was: '.\htmlentities($val)); } $ret = $val; } elseif( - ('i_' === substr(strtolower($name), 0, 2)) || - ('id' === substr(strtolower($name), -2, 2))){ + ('i_' === \substr(\strtolower($name), 0, 2)) || + ('id' === \substr(\strtolower($name), -2, 2))){ /** * FILTER_VALIDATE_INT * does not seem to accept 0 as a valid int! * this sucks, so instead going to use is_numeric */ - if(!is_numeric($val) || ($val < 0) || ($val > 99999999999)){ + if(!\is_numeric($val) || ($val < 0) || ($val > 99999999999)){ throw new \InvalidArgumentException('Invalid value of "'.$name.'". It can only be a number between 0 and 99999999999 was: '.\htmlentities($val)); } $ret = (int)$val; - } elseif('_hex' === substr(strtolower($name), -4, 4)){ + } elseif('_hex' === substr(\strtolower($name), -4, 4)){ $expression = '/^[0-9A-F]{6}$/'; if(!filter_var($val, FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => $expression)))){ throw new \InvalidArgumentException('Invalid value of '.$name.' it can only be a hex number. Was: '.\htmlentities($val)); @@ -345,7 +345,7 @@ protected function getFiltered($name){ $ret = $val; - } elseif('flag' === substr(strtolower($name), -4, 4)){ + } elseif('flag' === \substr(\strtolower($name), -4, 4)){ /** * FILTER_VALIDATE_BOOLEAN will not work here @@ -392,7 +392,6 @@ protected function getFiltered($name){ public function getUTF8($name, $default = null){ if(empty($this->aUTF8[$name])){ $res = $this->get($name, 's', $default); - $ret = Utf8String::factory($res); $this->aUTF8[$name] = $ret; } @@ -435,12 +434,12 @@ public static function getRequestMethod(){ * Get value of specific request header * * @param string $strHeader - * @return mixed string value of header or false + * @return mixed string value of header or null * if header not found */ public final static function getHttpHeader($strHeader){ - $strKey = 'HTTP_'.strtoupper(str_replace('-', '_', $strHeader)); + $strKey = 'HTTP_'.\strtoupper(\str_replace('-', '_', $strHeader)); if (!empty($_SERVER[$strKey])) { return $_SERVER[$strKey]; @@ -452,15 +451,16 @@ public final static function getHttpHeader($strHeader){ * */ if (\function_exists('apache_request_headers')) { - $strHeader = (str_replace(" ", "-", (ucwords(str_replace("-", " ", strtolower($strHeader)))))); - $arrHeaders = apache_request_headers(); + $strHeader = (\str_replace(" ", "-", (\ucwords(\str_replace("-", " ", \strtolower($strHeader)))))); + $arrHeaders = \apache_request_headers(); + if (!empty($arrHeaders[$strHeader])) { return $arrHeaders[$strHeader]; } } - return false; + return null; } @@ -483,7 +483,7 @@ public static final function isAjax(){ return true; } - self::$ajax = (strtoupper((string)self::getHttpHeader('X-REQUESTED-WITH')) === 'XMLHTTPREQUEST'); + self::$ajax = (\strtoupper((string)self::getHttpHeader('X-REQUESTED-WITH')) === 'XMLHTTPREQUEST'); return self::$ajax; } diff --git a/lib/Lampcms/Responder.php b/lib/Lampcms/Responder.php index 48c926a..b389168 100644 --- a/lib/Lampcms/Responder.php +++ b/lib/Lampcms/Responder.php @@ -234,5 +234,5 @@ public static function makeUrlFromLocation(){ return $sUrl; } - -} \ No newline at end of file + +} diff --git a/lib/Lampcms/SearchFactory.php b/lib/Lampcms/SearchFactory.php index 103f5ef..fad7e8f 100644 --- a/lib/Lampcms/SearchFactory.php +++ b/lib/Lampcms/SearchFactory.php @@ -84,7 +84,6 @@ public static function factory(Registry $oRegistry){ throw new \Lampcms\DevException('Search provider class '.get_class($o).' does not implement Search interface'); } - return $o; } } diff --git a/lib/Lampcms/SimilarItems.php b/lib/Lampcms/SimilarItems.php deleted file mode 100644 index 6bf867c..0000000 --- a/lib/Lampcms/SimilarItems.php +++ /dev/null @@ -1,218 +0,0 @@ -LampCMS" - * The location of the link is not important, it can be in the footer of the page - * but it must not be hidden by style attibutes - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * This product includes GeoLite data created by MaxMind, - * available from http://www.maxmind.com/ - * - * - * @author Dmitri Snytkine - * @copyright 2005-2011 (or current year) ExamNotes.net inc. - * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3 - * @link http://www.lampcms.com Lampcms.com project - * @version Release: @package_version@ - * - * - */ - - -namespace Lampcms; - -use \Lampcms\Interfaces\Search; -/** - * Creates html of 'similar mailing list items' - * for one new question - * - * This can be run onNewQuestion, so it will be run AFTER - * question has already been inserted in MONGO, and - * after creating similar items html it will re-save - * the object to mongo. - * - * This way the function can run as registered shutdown function - * so script can return output to browser and then update question - * AFTER user gets the result back. Since there will be a couple - * of seconds delay during meta redirect, user will most likely - * See the question complete with the similar items by the time - * browser actually redirects user to the question. - * - * @todo remove this, we now use search provider - * instantiated via SearchFactory class - * - * @author Dmitri Snytkine - * - */ -class SimilarItems -{ - /*protected $title; - - protected $qid = 0; - - protected $oQuestion; - - protected $threadID = 1;*/ - - - /*public function __construct(Registry $oRegistry){ - $this->oRegistry = $oRegistry; - }*/ - - public static function factory(Registry $oRegistry){ - - if(extension_loaded('pdo_mysql')){ - $o = new \Lampcms\Modules\Search\MySQL($oRegistry); - } - - - - if(!isset($o)){ - throw new \Lampcms\DevException('Search feature is not implemented because no search providers are defined'); - } - - if(!($o instanceof Search)){ - throw new \Lampcms\DevException('Search provider class '.get_class($o).' does not implement Search interface'); - } - - - return $o; - } - - /** - * This method is called during parsing - * of new question, in which case 2 separate - * fields are added to oQuestion object: - * one for similar threads and one for similar questions - * - * @todo remove this - * - * @param Question $oQuestion - */ - public function parse(Question $oQuestion){ - if(!extension_loaded('pdo_mysql')){ - d('pdo or pdo_mysql not loaded skipping parsing of similar items'); - return $this; - } - - $this->oQuestion = $oQuestion; - $this->title = $oQuestion['title']; - $this->qid = $oQuestion['_id']; - d('$this->title: '.$this->title); - - $this->getSimilarQuestions(); - } - - - /** - * Get array of up to 30 - * similar questions, create html block from - * these questions and save in oQuestion - * under the sim_q key - * - * @param bool $ret indicats that this is a retry - * and prevents against retrying calling itself - * more than once - * - * @todo remove this - * - * @return object $this - * - */ - protected function getSimilarQuestions($limit = 30, $ret = false){ - - $qid = (int)$this->oQuestion['_id']; - $html = ''; - $aRes = array(); - - $sql = "SELECT * - FROM question_title - WHERE - qid != :qid - AND - MATCH (q_title) - AGAINST (:subj) - LIMIT $limit"; - - d('$sql: '.$sql); - try{ - $sth = $this->oRegistry->Db->makePrepared($sql); - $sth->bindParam(':qid', $this->qid, PDO::PARAM_INT); - $sth->bindParam(':subj', $this->title, PDO::PARAM_STR); - $sth->execute(); - $aRes = $sth->fetchAll(); - } 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() && !$ret){ - if(true === TitleTagsTable::create($this->oRegistry)){ - /** - * If Table was just created, no need to retry the select - * because we know table is empty now, so just - * return - */ - return $this; - } else { - throw $e; - } - } else { - throw $e; - } - } - - d('found '.count($aRes).' similar questions '.print_r($aRes, 1)); - - if(!empty($aRes)){ - if($ret){ - $html = \tplSimquestions2::loop($aRes); - } else { - $html = \tplSimquestions::loop($aRes); - } - } - - if($ret){ - d('returning html for similar questions: '.$html); - return $html; - } - - if(!empty($aRes)){ - $s = '
'.$html.'
'; - $this->oQuestion->offsetSet('sim_q', $s); - } - - return $this; - } - -} diff --git a/lib/Lampcms/String.php b/lib/Lampcms/String.php index 9b2ce0a..1e2e4fc 100644 --- a/lib/Lampcms/String.php +++ b/lib/Lampcms/String.php @@ -412,7 +412,7 @@ public static function makeSid($len = 48){ * @return string md5 hash of VERSION + $pwd */ public static function hashPassword($pwd){ - $salt = (defined('MOCK_SALT')) ? MOCK_SALT : LAMPCMS_SALT; + $salt = LAMPCMS_SALT; return \hash('sha256', $salt.$pwd); } diff --git a/lib/Lampcms/Stub.php b/lib/Lampcms/Stub.php index 22dd37f..406216c 100644 --- a/lib/Lampcms/Stub.php +++ b/lib/Lampcms/Stub.php @@ -63,5 +63,4 @@ public function __call($method, $args){ return ''; } - } diff --git a/lib/Lampcms/Tokenizer.php b/lib/Lampcms/Tokenizer.php index d5ef2ad..c009930 100644 --- a/lib/Lampcms/Tokenizer.php +++ b/lib/Lampcms/Tokenizer.php @@ -88,9 +88,9 @@ public function tokenize($string){ $a = explode(' ', $string); $ret = array(); foreach($a as $token){ - $keyword = strtolower(trim($token)); + $keyword = \mb_strtolower(trim($token)); - if(strlen($keyword) > 2 && !in_array($keyword, $this->aStopwords)){ + if(\mb_strlen($keyword) > 2 && !in_array($keyword, $this->aStopwords)){ $ret[] = $keyword; } } diff --git a/lib/Lampcms/User.php b/lib/Lampcms/User.php index e3ab7d7..74d5f64 100644 --- a/lib/Lampcms/User.php +++ b/lib/Lampcms/User.php @@ -57,8 +57,7 @@ * * @author Dmitri Snytkine * - */ -use Lampcms\Interfaces\Answer; + */ class User extends MongoDoc implements Interfaces\RoleInterface, Interfaces\User, @@ -88,19 +87,6 @@ class User extends MongoDoc implements Interfaces\RoleInterface, protected $avtrSrc; - /** - * - * Array of resolved permission values - * used for memoization of the hasPermission() method - * values are boolean, keys are permission name - * Not included in serialization, so it's lost - * between page views. - * - * @var array - */ - protected $aPermissions = array(); - - /** * Factory method * @@ -133,14 +119,14 @@ public function __get($name){ /** - * Getter for userID (value of USER.id) + * Getter for userID (value of USERS._id) * - * @return int value of userid (value of USER.id) + * @return int value of userid (value of USERS._id) */ public function getUid(){ d('$this->keyColumn: '.$this->keyColumn); - if (true !== $this->checkOffset($this->keyColumn)) { + if (true !== $this->offsetExists($this->keyColumn)) { d('cp no key column '.$this->keyColumn); return 0; @@ -186,7 +172,6 @@ public function isGuest(){ /** * Check if user is moderator, which * includes all types of moderator or admin - * or root * * @return bool true if moderator, falst otherwise */ @@ -200,6 +185,7 @@ public function isModerator(){ /** * Get full name of user * by concatinating first name, middle name, last name + * * @return string full name */ public function getFullName(){ @@ -223,16 +209,7 @@ public function getDisplayName(){ * is not considered empty. */ $ret = \trim($ret); - if(!empty($ret)){ - d('returning full name: '.$ret); - - return $ret; - } - - $ret = $this->offsetGet('username'); - d('returning full name: '.$ret); - - return $ret; + return (!empty($ret)) ? $ret : $this->offsetGet('username'); } @@ -251,7 +228,6 @@ public function __set($name, $val){ * @return string the HTML code for image src */ public function getAvatarImgSrc($sSize = 'medium', $noCache = false){ - d('cp'); $strAvatar = 'avatar'; return $strAvatar; @@ -354,14 +330,10 @@ public function setRoleId($role){ if(!\is_string($role)){ throw new \InvalidArgumentException('$role must be a string. was: '.gettype($role)); } - d('cp'); $a = $this->getRegistry()->Acl->getRegisteredRoles(); - d('cp'); if(!\array_key_exists($role, $a)){ - d('cp'); throw new \Lampcms\DevException('The $role name: '.$role.' is not one of the roles in the acl.ini file'); } - d('cp'); /** * IMPORTANT: do not make a mistake @@ -467,7 +439,7 @@ public function revokeFacebookConnect(){ * Instead of offsetUnset we do * offsetSet and set to null * This is necessary in case user - * does not have these keys yet, + * does not have this key yet, * in which case offsetUnset will raise error */ $this->offsetSet('fb_token', null); diff --git a/lib/Lampcms/UserAuth.php b/lib/Lampcms/UserAuth.php index c147988..afba5a8 100644 --- a/lib/Lampcms/UserAuth.php +++ b/lib/Lampcms/UserAuth.php @@ -59,6 +59,15 @@ * as well as during authenticating * from external programs like from * the nntp server or email server, etc. + * + * @todo remove preCheckLogin and move all + * to getUser() or at least to validateLogin() + * or just move some validators to preCheckLogin() + * + * @todo make sure that record is added to EMAILS from join + * by Facebook as well as when doint step2 in Twitter/GFC connect + * + * */ class UserAuth extends LampcmsObject { diff --git a/lib/Lampcms/UserTags.php b/lib/Lampcms/UserTags.php index 2f3edb7..519500c 100644 --- a/lib/Lampcms/UserTags.php +++ b/lib/Lampcms/UserTags.php @@ -101,7 +101,7 @@ public function addTags($uid, \Lampcms\Question $oQuestion){ * Extra precaution to filter out * empty values */ - $aTags = array_filter($aTags); + $aTags = \array_filter($aTags); d('$aTags: '.var_export($aTags, true)); $coll = $this->oRegistry->Mongo->getCollection(self::USER_TAGS); diff --git a/lib/Lampcms/UserTwitter.php b/lib/Lampcms/UserTwitter.php index 1b8acf7..7fa1003 100644 --- a/lib/Lampcms/UserTwitter.php +++ b/lib/Lampcms/UserTwitter.php @@ -65,8 +65,7 @@ class UserTwitter extends UserExternal * @return string the HTML code for image src which actually * includes 2 images - an avatar and a tiny facebook icon. */ - public function getAvatarImgSrc($sSize = 'medium', $boolNoCache = false) - { + public function getAvatarImgSrc($sSize = 'medium', $boolNoCache = false){ $strAvatar = parent::getAvatarImgSrc($sSize, $boolNoCache); $strAvatar .= 'Twitter User'; diff --git a/lib/Lampcms/Utf8String.php b/lib/Lampcms/Utf8String.php index f50087d..e7b43d9 100644 --- a/lib/Lampcms/Utf8String.php +++ b/lib/Lampcms/Utf8String.php @@ -153,7 +153,7 @@ public static function factory($string, $charset = null, $isClean = false) * If $charset is not supplied we will try to guess it * */ - $charset = (empty($charset)) ? strtolower(self::guessCharset($string)) : strtolower($charset); + $charset = (empty($charset)) ? \strtolower(self::guessCharset($string)) : \strtolower($charset); d('charset: '.$charset); //d('$className: '.$className); @@ -210,7 +210,7 @@ public static function guessCharset($string, $charsetHint = ''){ throw new \RuntimeException('Unable to detect charset encoding because mbstring extension is not available and a string is not in UTF-8'); } - + $cs = false; $charsetHint = strtoupper($charsetHint); if('US-ASCII' === $charsetHint){ @@ -267,6 +267,7 @@ public static function sanitizeUtf8($utf8string, $bIsAscii = false) d('cp $bIsAscii '.$bIsAscii); if(true !== self::validateUtf8($utf8string)){ + d('cp'); /** * Now that we know that string is not a valid utf-8 what do we do? @@ -279,8 +280,7 @@ public static function sanitizeUtf8($utf8string, $bIsAscii = false) * */ try{ - d('cp'); - $charset = strtolower(self::guessCharset($utf8string)); + $charset = \strtolower(self::guessCharset($utf8string)); } catch(\Exception $e){ d('unable to guess charset'); /** @@ -294,11 +294,10 @@ public static function sanitizeUtf8($utf8string, $bIsAscii = false) d('cp'); if('utf8' !== $charset){ - d('cp'); - + $utf8string = self::convertToUtf8($utf8string, $charset); } else { - d('cp'); + $utf8string = self::recodeUtf8($utf8string); } } catch (\Exception $e){ @@ -309,7 +308,6 @@ public static function sanitizeUtf8($utf8string, $bIsAscii = false) } if($bIsAscii){ - return self::sanitizeAscii($utf8string); } @@ -367,7 +365,7 @@ public static function validateUtf8($utf8string){ * @return mixed array | false if the input string * isn't a valid UTF-8 octet sequence. */ - public static function utf8ToUnicode(&$str) + public static function isUtf8(&$str) { d('cp'); @@ -379,47 +377,47 @@ public static function utf8ToUnicode(&$str) //$out = array(); - $len = strlen($str); + $len = \strlen($str); d('$len: '.$len); for($i = 0; $i < $len; $i++) { - //$oLogger->log('cp $i '.$i); + $in = ord($str[$i]); - //$oLogger->log('cp'); + if (0 == $mState) { - // $oLogger->log('cp'); + // When mState is zero we expect either a US-ASCII character or a // multi-octet sequence. if (0 == (0x80 & ($in))) { - //$oLogger->log('cp'); + // US-ASCII, pass straight through. //$out[] = $in; $mBytes = 1; } else if (0xC0 == (0xE0 & ($in))) { - //$oLogger->log('cp'); + // First octet of 2 octet sequence $mUcs4 = ($in); $mUcs4 = ($mUcs4 & 0x1F) << 6; $mState = 1; $mBytes = 2; - //$oLogger->log('cp'); + } else if (0xE0 == (0xF0 & ($in))) { - //$oLogger->log('cp'); + // First octet of 3 octet sequence $mUcs4 = ($in); $mUcs4 = ($mUcs4 & 0x0F) << 12; $mState = 2; $mBytes = 3; - //$oLogger->log('cp'); + } else if (0xF0 == (0xF8 & ($in))) { - //$oLogger->log('cp'); + // First octet of 4 octet sequence $mUcs4 = ($in); $mUcs4 = ($mUcs4 & 0x07) << 18; $mState = 3; $mBytes = 4; - //$oLogger->log('cp'); + } else if (0xF8 == (0xFC & ($in))) { - //$oLogger->log('cp'); + /* First octet of 5 octet sequence. * * This is illegal because the encoded codepoint must be either @@ -432,7 +430,7 @@ public static function utf8ToUnicode(&$str) $mUcs4 = ($mUcs4 & 0x03) << 24; $mState = 4; $mBytes = 5; - //$oLogger->log('cp'); + } else if (0xFC == (0xFE & ($in))) { d('cp'); // First octet of 6 octet sequence, see comments for 5 octet sequence. @@ -440,7 +438,7 @@ public static function utf8ToUnicode(&$str) $mUcs4 = ($mUcs4 & 1) << 30; $mState = 5; $mBytes = 6; - //$oLogger->log('cp'); + } else { d('cp'); /* Current octet is neither in the US-ASCII range nor a legal first @@ -449,7 +447,7 @@ public static function utf8ToUnicode(&$str) return false; } } else { - //$oLogger->log('cp'); + // When mState is non-zero, we expect a continuation of the multi-octet // sequence if (0x80 == (0xC0 & ($in))) { @@ -501,6 +499,118 @@ public static function utf8ToUnicode(&$str) } + public static function utf8ToUnicode(&$str) + { + $mState = 0; // cached expected number of octets after the current octet + // until the beginning of the next UTF8 character sequence + $mUcs4 = 0; // cached Unicode character + $mBytes = 1; // cached expected number of octets in the current sequence + + $out = array(); + + $len = strlen($str); + for($i = 0; $i < $len; $i++) { + $in = ord($str{$i}); + if (0 == $mState) { + // When mState is zero we expect either a US-ASCII character or a + // multi-octet sequence. + if (0 == (0x80 & ($in))) { + // US-ASCII, pass straight through. + $out[] = $in; + $mBytes = 1; + } else if (0xC0 == (0xE0 & ($in))) { + // First octet of 2 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x1F) << 6; + $mState = 1; + $mBytes = 2; + } else if (0xE0 == (0xF0 & ($in))) { + // First octet of 3 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x0F) << 12; + $mState = 2; + $mBytes = 3; + } else if (0xF0 == (0xF8 & ($in))) { + // First octet of 4 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x07) << 18; + $mState = 3; + $mBytes = 4; + } else if (0xF8 == (0xFC & ($in))) { + /* First octet of 5 octet sequence. + * + * This is illegal because the encoded codepoint must be either + * (a) not the shortest form or + * (b) outside the Unicode range of 0-0x10FFFF. + * Rather than trying to resynchronize, we will carry on until the end + * of the sequence and let the later error handling code catch it. + */ + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x03) << 24; + $mState = 4; + $mBytes = 5; + } else if (0xFC == (0xFE & ($in))) { + // First octet of 6 octet sequence, see comments for 5 octet sequence. + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 1) << 30; + $mState = 5; + $mBytes = 6; + } else { + /* Current octet is neither in the US-ASCII range nor a legal first + * octet of a multi-octet sequence. + */ + return false; + } + } else { + // When mState is non-zero, we expect a continuation of the multi-octet + // sequence + if (0x80 == (0xC0 & ($in))) { + // Legal continuation. + $shift = ($mState - 1) * 6; + $tmp = $in; + $tmp = ($tmp & 0x0000003F) << $shift; + $mUcs4 |= $tmp; + + if (0 == --$mState) { + /* End of the multi-octet sequence. mUcs4 now contains the final + * Unicode codepoint to be output + * + * Check for illegal sequences and codepoints. + */ + + // From Unicode 3.1, non-shortest form is illegal + if (((2 == $mBytes) && ($mUcs4 < 0x0080)) || + ((3 == $mBytes) && ($mUcs4 < 0x0800)) || + ((4 == $mBytes) && ($mUcs4 < 0x10000)) || + (4 < $mBytes) || + // From Unicode 3.2, surrogate characters are illegal + (($mUcs4 & 0xFFFFF800) == 0xD800) || + // Codepoints outside the Unicode range are illegal + ($mUcs4 > 0x10FFFF)) { + return false; + } + if (0xFEFF != $mUcs4) { + // BOM is legal but we don't want to output it + $out[] = $mUcs4; + } + //initialize UTF8 cache + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + } + } else { + /* ((0xC0 & (*in) != 0x80) && (mState != 0)) + * + * Incomplete multi-octet sequence. + */ + return false; + } + } + } + return $out; + } + + /** * Converts from utf8 to utf8 but with * an option to ignore errors. @@ -515,18 +625,19 @@ public static function utf8ToUnicode(&$str) */ public static function recodeUtf8($utf8string){ - if(!function_exists('iconv') && !function_exists('mb_convert_encoding')){ + if(!\function_exists('iconv') && !\function_exists('mb_convert_encoding')){ throw new \RuntimeException('Cannot use this method because iconv OR mb_convert_encoding functions is not available'); } /** * IMPORTANT * lower the error reporting here - * in order to suppress warnings and stuff like that + * in order to suppress warnings * because iconv will generate a warning if it detects * illegal char */ - if(function_exists('iconv')){ + if(\function_exists('iconv')){ + $ER = error_reporting(1); /** * We are not going to do this: setlocale(LC_ALL, 'en_US.UTF8'); @@ -534,7 +645,7 @@ public static function recodeUtf8($utf8string){ * and by the way, //TRANSLIT//IGNORE does not really work, * its a myth that they work fine together */ - $ret = iconv("UTF-8", "UTF-8//IGNORE", $utf8string); + $ret = \iconv("UTF-8", "UTF-8//IGNORE", $utf8string); error_reporting($ER); return $ret; @@ -546,8 +657,8 @@ public static function recodeUtf8($utf8string){ * meaning when character is not available in target charset * the substitute will be used */ - mb_substitute_character("none"); - $ret = mb_convert_encoding($utf8string, "UTF-8", "UTF-8"); + \mb_substitute_character("none"); + $ret = \mb_convert_encoding($utf8string, "UTF-8", "UTF-8"); /** * Restore error_reporting back to what id was @@ -559,7 +670,8 @@ public static function recodeUtf8($utf8string){ /** - * strips low bytes except for \r, tab and \n + * strips low bytes except for return (\r), + * tab and newline (\n) * * @param string $string utf8 string * @return string string with low bytes removed @@ -703,7 +815,7 @@ public function length(){ */ public function getWordsCount(){ - return preg_match_all(self::WORD_COUNT_MASK, $this->string, $matches); + return \preg_match_all(self::WORD_COUNT_MASK, $this->string, $matches); } @@ -728,7 +840,7 @@ public function truncate($max, $link = ''){ $numwords = 0; foreach ($words as $word) { - if ((mb_strlen($newstring) + 1 + \mb_strlen($word)) < $max) { + if ((\mb_strlen($newstring) + 1 + \mb_strlen($word)) < $max) { $newstring .= ' '.$word; ++$numwords; } else { @@ -740,8 +852,7 @@ public function truncate($max, $link = ''){ /** * Adds utf-8 Ellipses (3 dots) * This is better than manually adding 3 dots - * because this adds just one char! - * + * because this adds just one char * */ $newstring .= "\xE2\x80\xA6".$link; @@ -826,11 +937,11 @@ public function wordWrap($width = 75, $break = "\n", $cut = false) * @return string a string with first letter upercased, the rest lowercase */ public static function utf8_ucfirst($utf8string){ - $string = \mb_strtolower($utf8string); + $string = \mb_strtolower($utf8string, 'UTF-8'); - $first = \mb_strtoupper(\mb_substr($string, 0, 1)); + $first = \mb_strtoupper(\mb_substr($string, 0, 1, 'UTF-8'), 'UTF-8'); - return $first.\mb_substr($string, 1, \mb_strlen($string)); + return $first.\mb_substr($string, 1, \mb_strlen($string, 'UTF-8'), 'UTF-8'); } @@ -852,10 +963,10 @@ public function ucwords(){ $words = explode(' ', $this->string); $ret = ''; foreach($words as $word){ - $ret .= self::utf8_ucfirst($word); + $ret .= self::utf8_ucfirst($word).' '; } - return $this->handleReturn($ret); + return $this->handleReturn(\trim($ret)); } diff --git a/lib/Lampcms/Validate.php b/lib/Lampcms/Validate.php index e70785e..aa339d5 100644 --- a/lib/Lampcms/Validate.php +++ b/lib/Lampcms/Validate.php @@ -67,8 +67,7 @@ class Validate * * @throws Lampcms\Exception on error */ - public static function validateToken() - { + public static function validateToken(){ $token = $_REQUEST['token']; if(empty($token)){ throw new Exception('Form token not found'); @@ -85,6 +84,7 @@ public static function validateToken() return true; } + /** * Validates a string so that it can contain only * alphanumeric and hyphens '-' but hyphens @@ -106,100 +106,16 @@ public static function validateToken() * * @param string $string */ - public static function username($string) - { + public static function username($string){ d('$string: '.$string); - - - $ret = (0 !== preg_match('/([a-zA-Z0-9@])([a-zA-Z0-9\-]{1,18})([a-zA-Z0-9])$/A', $string, $m)); + $ret = (0 !== \preg_match('/([a-zA-Z0-9@])([a-zA-Z0-9\-]{1,18})([a-zA-Z0-9])$/A', $string, $m)); d('ret '.$ret); return $ret; } - - /** - * Tests a value for a specific type - * - * @param mixed $val any type of value like object, string, boolean, resource, etc. - * @param string $strType type of value that $var must be in order to satisfy the test - * @param string $strFunction function or method name that called this validator - * @return true - * @throws LampcmsDevException is variable type does not match the test condition - * also throws exception is test condition is not supported by the php is_ test - * or if either $val or $strType are empty - */ - public final static function validateType($val = null, $strType = null, $strFunction = 'function not defined') - { - - $arrTypes = array('array', - 'bool', - 'float', - 'int', - 'integer', - 'null', - 'numeric', - 'object', - 'resource', - 'string', - 'unicode', - 'buffer', - 'scalar'); - if ( empty ($val)) { - throw new Exception('empty_param', array('"$val"', $strFunction, $strFunction)); - } - if ( empty ($strType)) { - throw new DevException('empty_param', array('"$strType"', __METHOD__, $strFunction)); - } - - /** - * Special case: for a resource we can validate - * the resource type - * - * In this case the strType should be an array with - * value being a string: name of resource type - * and a key must just be 'resource' - */ - - if(is_array($strType)){ - d('$strType array: '.print_r($strType, true)); - - if(count($strType) !== 1){ - throw new DevException('in $strType is array it MUST have just one element'); - } - - $aType = $strType; - - foreach($aType as $name => $restype){ - $strType = (string)$name; - $resourceType = (string)$restype; - } - } - - $strType = strtolower($strType); - d('$strType: '.$strType); - - if (!in_array($strType, $arrTypes)) { - - throw new DevException($strType.' is not one of the allowed values'); - } - $strFn = 'is_'.$strType; - if (true !== $strFn ($val)) { - - throw new \InvalidArgumentException('wrong_type', array($strType, gettype($val), $strFunction)); - } - - if(isset($resourceType)){ - if($resourceType !== $actualType = get_resource_type($val)){ - d( 'looking for type: '.$resourceType.'$actualType: '.$actualType); - throw new DevException('Invalid resource type. Expected resource of type: '.$resourceType. ' got: '.$actualType); - } - } - - return true; - } - + /** * Verifies that password contains * at least one letter and at least one number @@ -209,8 +125,7 @@ public final static function validateType($val = null, $strType = null, $strFunc * * @return bool true if validation passes, false otherwise */ - public static function enforcePwd($pwd) - { + public static function enforcePwd($pwd){ $res = preg_match('/[a-zA-Z]+/', $pwd, $matches); $res2 = preg_match('/\d+/', $pwd); @@ -228,7 +143,7 @@ public static function enforcePwd($pwd) public static function email($email){ - if (false === filter_var($email, FILTER_VALIDATE_EMAIL)) { + if (false === \filter_var($email, FILTER_VALIDATE_EMAIL)) { return false; } @@ -237,16 +152,15 @@ public static function email($email){ $domain = $a[1]; d('domain: '.$domain); - return (true === checkdnsrr($domain, 'MX') || true === checkdnsrr($domain, 'A')); + return (true === \checkdnsrr($domain, 'MX') || true === \checkdnsrr($domain, 'A')); } /** * Validate string DOB (Date of Birth) - * that supposed to follow this date format: YYYY/MM/DD + * It supposed to follow this date format: YYYY/MM/DD * - * - * @param strubg $string + * @param string $string * * @return bool true if string is in valid YYYY/MM/DD format * and the actual values of each part of string make sense @@ -343,7 +257,7 @@ public final static function type($val, $type){ } } - $type = strtolower($type); + $type = \strtolower($type); d('$type: '.$type); if (!in_array($type, $arrTypes)) { diff --git a/lib/Lampcms/WebPage.php b/lib/Lampcms/WebPage.php index a4b156c..430cb24 100644 --- a/lib/Lampcms/WebPage.php +++ b/lib/Lampcms/WebPage.php @@ -282,9 +282,7 @@ public function __construct(Registry $oRegistry, Request $oRequest = null){ $this->checkLoginStatus() ->checkAccessPermission() ->main(); - } catch(Exception $e) { - $this->handleException($e); } } @@ -920,7 +918,7 @@ public function handleException(\Lampcms\Exception $le){ $this->httpCode = 404; } - if(!($le instanceof AuthException)){ + if(!($le instanceof AuthException) && !($le instanceof MustLoginException)){ e('Exception caught in: '.$le->getFile().' on line: '.$le->getLine().' '.$le->getMessage()); } diff --git a/phpunit.xml b/phpunit.xml index 2850f2f..87093bf 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -4,9 +4,24 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" syntaxCheck="false" bootstrap="tests/bootstrap.php"> + - ./tests/ + tests/StringTest.php + tests/Utf8StringTest.php + tests/LampcmsObjectTest.php + tests/LampcmsArrayTest.php + tests/ArrayDefaultsTest.php + tests/IniTest.php + tests/MongoTest.php + tests/MongoIncrementorTest.php + tests/MongoDocTest.php + tests/ResourceTest.php + tests/RequestTest.php + tests/RegistryTest.php + tests/UserTest.php + tests/QuestionTest.php + tests/AnswerTest.php diff --git a/tests/AnswerTest.php b/tests/AnswerTest.php new file mode 100644 index 0000000..b8723b4 --- /dev/null +++ b/tests/AnswerTest.php @@ -0,0 +1,286 @@ +LampCMS" + * The location of the link is not important, it can be in the footer of the page + * but it must not be hidden by style attibutes + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This product includes GeoLite data created by MaxMind, + * available from http://www.maxmind.com/ + * + * + * @author Dmitri Snytkine + * @copyright 2005-2011 (or current year) ExamNotes.net inc. + * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3 + * @link http://www.lampcms.com Lampcms.com project + * @version Release: @package_version@ + * + * + */ + + +namespace Lampcms; +require_once 'bootstrap.php'; + +require_once 'Fixtures/MockAnswer.php'; +require_once 'Fixtures/MockUser.php'; + +class AnswerTest extends LampcmsUnitTestCase +{ + + protected $oAnswer; + + public function setUp(){ + $this->oAnswer = new \Lampcms\MockAnswer(new Registry()); + } + + + public function testScore(){ + $s = $this->oAnswer->getScore(); + + $this->assertEquals(1, $s); + } + + /** + * + * @depends testScore + */ + public function testAddVote(){ + + $this->oAnswer->addDownVote(); + + $this->assertEquals(0, $this->oAnswer->getScore()); + $this->assertEquals('s', $this->oAnswer['v_s']); + + $this->oAnswer->addUpVote(); + $this->assertEquals('', $this->oAnswer['v_s']); + + $this->oAnswer->addUpVote() + ->addUpVote(); + + $this->assertEquals(3, $this->oAnswer->getScore()); + $this->assertEquals('s', $this->oAnswer['v_s']); + } + + + public function testGetCommentsCount(){ + $this->assertEquals(2, $this->oAnswer->getCommentsCount()); + } + + public function testGetOwnerId(){ + $this->assertTrue(3 === $this->oAnswer->getOwnerId()); + } + + public function testGetQuestionOwnerId(){ + $this->assertTrue(3 === $this->oAnswer->getQuestionOwnerId()); + } + + public function testGetResourceTypeId(){ + $this->assertEquals('ANSWER', $this->oAnswer->getResourceTypeId()); + } + + public function testGetQuestionId(){ + $this->assertEquals(510, $this->oAnswer->getQuestionId()); + } + + + public function testGetLastModified(){ + $this->assertEquals(1305712931, $this->oAnswer->getLastModified()); + } + + public function testGetUsername(){ + $this->assertEquals('user1', $this->oAnswer->getUsername()); + } + + public function testGetRegistry(){ + $this->assertInstanceOf('\Lampcms\Registry', $this->oAnswer->getRegistry()); + } + + public function testTouch(){ + $this->oAnswer->touch(); + $this->assertTrue((time() - $this->oAnswer->getLastModified()) < 2); + } + + /** + * + * @depends testGetCommentsCount + */ + public function testGetComments(){ + $a = $this->oAnswer->getComments(); + $this->assertTrue(516 === $a[0]['_id']); + $this->assertTrue(517 === $a[1]['_id']); + } + + + public function testGetTitle(){ + $this->assertEquals('Mock Stub Post', $this->oAnswer->getTitle()); + } + + public function testGetBody(){ + $this->assertEquals('Text of Mock Answer
Mock test
', $this->oAnswer->getBody()); + } + + /** + * @depends testGetRegistry + * + */ + public function testGetUrl(){ + + $Registry = $this->oAnswer->getRegistry(); + + $siteUrl = $Registry->Ini->SITE_URL; + $url = $this->oAnswer->getUrl(); + $shortUrl = $this->oAnswer->getUrl(true); + + $this->assertEquals('/q510/#ans513', \substr($url, strlen($siteUrl))); + $this->assertEquals('/q510/#ans513', \substr($shortUrl, strlen($siteUrl))); + } + + + public function testGetVotesArray(){ + + $this->assertEquals(array('up' => 1, 'down' => 0, 'score' => 1), $this->oAnswer->getVotesArray()); + } + + + public function testIncreaseCommentsCount(){ + $val = $this->oAnswer->getCommentsCount(); + $this->oAnswer->increaseCommentsCount(); + $this->assertTrue($this->oAnswer['i_comments'] === ($val + 1) ); + } + + + public function testDecreaseCommentsCount(){ + $val = $this->oAnswer->getCommentsCount(); + $this->oAnswer->increaseCommentsCount(-1); + $this->assertTrue($this->oAnswer['i_comments'] === 1 ); + } + + public function testSetAccepted(){ + $this->assertFalse($this->oAnswer['accepted'] === true); + $this->oAnswer->setAccepted(); + $this->assertTrue($this->oAnswer['accepted'] === true); + } + + /** + * @depends testSetAccepted + * + */ + public function testUnsetAccepted(){ + + $this->oAnswer->setAccepted(); + $this->oAnswer->unsetAccepted(); + $this->assertFalse($this->oAnswer['accepted'] === true); + } + + + public function testSetEdited(){ + $oUser = new MockUser($this->oAnswer->getRegistry()); + $this->oAnswer->setEdited($oUser, 'test of editing'); + $a = $this->oAnswer['a_edited']; + $this->assertTrue(is_array($a)); + $this->assertTrue(count($a) > 1); + + $aEdited = end($a); + + + $this->assertTrue(is_array($aEdited)); + $this->assertEquals('John D Doe', $aEdited['username']); + $this->assertEquals('test of editing', $aEdited['reason']); + $this->assertEquals(26, $aEdited['i_uid']); + } + + + public function testSetDeleted(){ + $oUser = new MockUser($this->oAnswer->getRegistry()); + $this->oAnswer->setDeleted($oUser, 'test of deleting'); + + $a = $this->oAnswer['a_deleted']; + + $this->assertTrue(is_array($a)); + $this->assertEquals(count($a), 5); + $this->assertEquals('John D Doe', $a['username']); + $this->assertEquals('test of deleting', $a['reason']); + $this->assertEquals(26, $a['i_uid']); + $this->assertTrue(($this->oAnswer['i_del_ts']) > 1000); + } + + + public function testOffsetSetAccepted(){ + try{ + $this->oAnswer['accepted'] = true; + } catch (DevException $e){ + return; + } + + $this->fail('DevException expected when trying to set "accepted" key directy'); + } + + + + public function testSave(){ + $oRegistry = new Registry(); + $id = $this->oAnswer->getResourceId(); + + $this->oAnswer->insert(); + $a = $oRegistry->Mongo->ANSWERS->findOne(array('_id' => $id)); + $this->assertTrue(is_array($a) && count($a) > 0); + } + + + public function testAutoInsert(){ + $this->persistDB = true; + $oRegistry = new Registry(); + $oAnswer = new Answer($oRegistry); + $oAnswer['b'] = 'test body'; + $oAnswer['title'] = 'test title'; + $oAnswer['i_uid'] = 99999999; + + return; + } + + /** + * @depends testAutoInsert + * + */ + public function testAutoInserted(){ + + $oRegistry = new Registry(); + $a = $oRegistry->Mongo->ANSWERS->findOne(array('i_uid' => 99999999)); + $this->assertTrue(!empty($a)); + + return; + } + +} diff --git a/tests/ArrayDefaultsTest.php b/tests/ArrayDefaultsTest.php index a53c30b..a5bdd55 100644 --- a/tests/ArrayDefaultsTest.php +++ b/tests/ArrayDefaultsTest.php @@ -82,18 +82,6 @@ public function testDefaultVal(){ $this->assertTrue( $this->default === $this->o['badkey']); } - public function testGetFallback(){ - $this->assertTrue( 'abcde' === $this->o->getFallback('badkey', 'abcde')); - } - - public function testGetFallbackValueExists(){ - $this->assertTrue( 2 === $this->o->getFallback('two', 'abcde')); - } - - public function testGetFallbackLc(){ - $this->assertTrue( 2 === $this->o->getFallbackLc('TWO', 'abcde')); - } - public function testSerialization(){ $this->o->setDefaultValue('abcde'); $s = \serialize($this->o); diff --git a/tests/Fixtures/MockAnswer.php b/tests/Fixtures/MockAnswer.php new file mode 100644 index 0000000..2c38990 --- /dev/null +++ b/tests/Fixtures/MockAnswer.php @@ -0,0 +1,75 @@ +LampCMS" + * The location of the link is not important, it can be in the footer of the page + * but it must not be hidden by style attibutes + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This product includes GeoLite data created by MaxMind, + * available from http://www.maxmind.com/ + * + * + * @author Dmitri Snytkine + * @copyright 2005-2011 (or current year) ExamNotes.net inc. + * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3 + * @link http://www.lampcms.com Lampcms.com project + * @version Release: @package_version@ + * + * + */ + + +namespace Lampcms; + +class MockAnswer extends Answer +{ + protected $JSON_ENCODED = '{"_id":513,"i_qid":510,"i_uid":3,"i_quid":3,"title":"Mock Stub Post","hash":"327d4e34c496435248e6145dca59065d","username":"user1","ulink":"user1<\/a>","avtr":"http:\/\/127.0.0.5:88\/w\/img\/avatar\/sqr\/3.jpg","i_words":6,"i_up":1,"i_down":0,"i_votes":1,"b":"Text of Mock Answer
Mock test<\/strong>
<\/span>","i_ts":1305401334,"i_lm_ts":1305712931,"hts":"May 14, 2011 2:28 pm CDT","v_s":"s","accepted":false,"ip":"127.0.0.1","app":"web","cc":null,"cn":null,"reg":null,"city":null,"zip":null,"lat":null,"lon":null,"a_comments":[{"_id":516,"b":"comment one","username":"user1","i_uid":3,"i_prnt":0,"ts":"Tue, 17 May 2011 20:17:57 -0500","t":"May 17 \'11 at 20:17","avtr":"http:\/\/127.0.0.5:88\/w\/img\/avatar\/sqr\/3.jpg","b_owner":true},{"_id":517,"b":"comment two","username":"user1","i_uid":3,"i_prnt":0,"ts":"Tue, 17 May 2011 20:18:10 -0500","t":"May 17 \'11 at 20:18","avtr":"http:\/\/127.0.0.5:88\/w\/img\/avatar\/sqr\/3.jpg","b_owner":true}],"i_comments":2,"a_edited":[{"username":"user1","i_uid":3,"av":"http:\/\/127.0.0.5:88\/w\/img\/avatar\/sqr\/3.jpg","reason":"for mock test","hts":"May 18, 2011 5:02 am CDT"}]}'; + + public function __construct(Registry $oRegistry, array $a = null){ + /** + * 3 Lines below to change the database + * that's going to be used to the test database + * + * This in theory is not necessary because boostrap.php + * is used in all tests and it defines MONGO_DBNAME as LAMPCMS_TEST + * but this is just an extra precaution + */ + $aMongo = $oRegistry->Ini->getSection('MONGO'); + $aMongo['db'] = 'LAMPCMS_TEST'; + $oRegistry->Ini->setSection('MONGO', $aMongo); + + $a = \json_decode($this->JSON_ENCODED, true); + parent::__construct($oRegistry, $a); + } +} diff --git a/tests/Fixtures/MockQuestion.php b/tests/Fixtures/MockQuestion.php index 55e9239..1e207b2 100644 --- a/tests/Fixtures/MockQuestion.php +++ b/tests/Fixtures/MockQuestion.php @@ -56,9 +56,9 @@ class MockQuestion extends Question { - const JSON_ENCODED = '{"_id":510,"title":"Mock Stub Post","b":"This is a simple mock<\/em> question<\/span>","hash":"ae2cabbd5072f31fe927ce6d0f52bf6a","intro":" This is a simple mock question","url":"Mock-Stub-Post","i_words":6,"i_uid":3,"username":"user1","ulink":"
user1<\/a>","avtr":"http:\/\/127.0.0.5:88\/w\/img\/avatar\/sqr\/3.jpg","i_up":0,"i_down":0,"i_votes":0,"i_favs":0,"i_views":0,"a_tags":["mock","stub","tag"],"a_title":["mock","stub","post"],"status":"answrd","tags_html":"mock<\/a> stub<\/a> tag<\/a> ","credits":"","i_ts":1305395213,"hts":"May 14, 2011 12:46 pm CDT","i_lm_ts":1305401334,"i_ans":1,"ans_s":"","v_s":"s","f_s":"s","ip":"127.0.0.1","app":"web","i_flwrs":1,"cc":null,"cn":null,"reg":null,"city":null,"zip":null,"lat":null,"lon":null,"a_flwrs":[3],"sim_q":"
another attempt to post + in tag<\/a>
April 9, 2011<\/span>
<\/div>
Testing of banned ip post<\/a>
<\/span>
<\/div>
my new test post links<\/a>
April 8, 2011<\/span>
<\/div>
test post with new TagIndexer and stuff<\/a>
April 4, 2011<\/span>
<\/div>
test of follow by email on user post<\/a>
March 24, 2011<\/span>
<\/div>
and yet another test post to test indexer<\/a>
March 6, 2011<\/span>
<\/div><\/div>","comments":[{"_id":511,"b":"First comment","username":"user1","i_uid":3,"i_prnt":0,"ts":"Sat, 14 May 2011 14:28:05 -0500","t":"May 14 \'11 at 14:28","avtr":"http:\/\/127.0.0.5:88\/w\/img\/avatar\/sqr\/3.jpg","b_owner":true},{"_id":512,"b":"Second comment","username":"user1","i_uid":3,"i_prnt":0,"ts":"Sat, 14 May 2011 14:28:15 -0500","t":"May 14 \'11 at 14:28","avtr":"http:\/\/127.0.0.5:88\/w\/img\/avatar\/sqr\/3.jpg","b_owner":true}],"i_comments":3,"a_uids":[3,3,3],"i_etag":1305401334,"lp_u":"
user1<\/a>","lp_t":"May 14, 2011 2:28 pm CDT"}'; + protected $JSON_ENCODED = '{"_id":510,"title":"Mock Stub Post","b":"This is a simple mock<\/em> question<\/span>","hash":"ae2cabbd5072f31fe927ce6d0f52bf6a","intro":" This is a simple mock question","url":"Mock-Stub-Post","i_words":6,"i_uid":3,"username":"user1","ulink":"user1<\/a>","avtr":"http:\/\/127.0.0.5:88\/w\/img\/avatar\/sqr\/3.jpg","i_up":0,"i_down":0,"i_votes":0,"i_favs":0,"i_views":0,"a_tags":["mock","stub","tag"],"a_title":["mock","stub","post"],"status":"answrd","tags_html":"mock<\/a> stub<\/a> tag<\/a> ","credits":"","i_ts":1305395213,"hts":"May 14, 2011 12:46 pm CDT","i_lm_ts":1305401334,"i_ans":1,"ans_s":"","v_s":"s","f_s":"s","ip":"127.0.0.1","app":"web","i_flwrs":1,"cc":null,"cn":null,"reg":null,"city":null,"zip":null,"lat":null,"lon":null,"a_flwrs":[3],"sim_q":"
another attempt to post + in tag<\/a>
April 9, 2011<\/span>
<\/div>
Testing of banned ip post<\/a>
<\/span>
<\/div>
my new test post links<\/a>
April 8, 2011<\/span>
<\/div>
test post with new TagIndexer and stuff<\/a>
April 4, 2011<\/span>
<\/div>
test of follow by email on user post<\/a>
March 24, 2011<\/span>
<\/div>
and yet another test post to test indexer<\/a>
March 6, 2011<\/span>
<\/div><\/div>","a_comments":[{"_id":511,"b":"First comment","username":"user1","i_uid":3,"i_prnt":0,"ts":"Sat, 14 May 2011 14:28:05 -0500","t":"May 14 \'11 at 14:28","avtr":"http:\/\/127.0.0.5:88\/w\/img\/avatar\/sqr\/3.jpg","b_owner":true},{"_id":512,"b":"Second comment","username":"user1","i_uid":3,"i_prnt":0,"ts":"Sat, 14 May 2011 14:28:15 -0500","t":"May 14 \'11 at 14:28","avtr":"http:\/\/127.0.0.5:88\/w\/img\/avatar\/sqr\/3.jpg","b_owner":true}],"i_comments":3,"a_uids":[3,3,3],"i_etag":1305401334,"lp_u":"
user1<\/a>","lp_t":"May 14, 2011 2:28 pm CDT"}'; - public function __construct(Registry $oRegistry){ + public function __construct(Registry $oRegistry, array $a = null){ /** * 3 Lines below to change the database @@ -72,7 +72,7 @@ public function __construct(Registry $oRegistry){ $aMongo['db'] = 'LAMPCMS_TEST'; $oRegistry->Ini->setSection('MONGO', $aMongo); - $a = \json_decode(self::JSON_ENCODED, true); + $a = \json_decode($this->JSON_ENCODED, true); parent::__construct($oRegistry, $a); } diff --git a/tests/Fixtures/MockUser.php b/tests/Fixtures/MockUser.php index dc70e08..5556e71 100644 --- a/tests/Fixtures/MockUser.php +++ b/tests/Fixtures/MockUser.php @@ -56,10 +56,10 @@ class MockUser extends User { const PASS = 'abc12345'; - const JSON_ENCODED = '{"username":"ladada","username_lc":"ladada","email":"ladada123@mailinator.com","rs":"1297206315.3591az0q3KyOT1cp96s6ulBwtzuYz81sDo35G","role":"registered","tz":"Atlantic\/Azores","pwd":"c84a41b784503c9b4e766f4af56968d68e99c6edd3a069d7baada054da55576d","i_reg_ts":1305227187,"date_reg":"Thu, 12 May 2011 14:06:27 -0500","i_fv":1297206315,"lang":"en","locale":"en_US","i_rep":1,"cc":"US","country":"United States","state":"PA","city":"Stroudsburg","zip":"18301","_id":26,"i_ts_login":1305469732,"i_lm_ts":1305469733,"a_f_u":[3],"i_f_u":1,"a_f_t":["stub","mongodb"],"i_f_t":2,"fn":"John","mn":"D","ln":"Doe","url":"http:\/\/www.lampcms.com","dob":"1990\/1\/3","gender":"M","description":"I am the test user","avatar":"1A.jpg"}'; + protected $JSON_ENCODED = '{"username":"ladada","username_lc":"ladada","email":"ladada123@mailinator.com","rs":"1297206315.3591az0q3KyOT1cp96s6ulBwtzuYz81sDo35G","role":"registered","tz":"Atlantic\/Azores","pwd":"c84a41b784503c9b4e766f4af56968d68e99c6edd3a069d7baada054da55576d","i_reg_ts":1305227187,"date_reg":"Thu, 12 May 2011 14:06:27 -0500","i_fv":1297206315,"lang":"en","locale":"en_US","i_rep":1,"cc":"US","country":"United States","state":"PA","city":"Stroudsburg","zip":"18301","_id":26,"i_ts_login":1305469732,"i_lm_ts":1305469733,"a_f_u":[3],"i_f_u":1,"a_f_t":["stub","mongodb"],"i_f_t":2,"fn":"John","mn":"D","ln":"Doe","url":"http:\/\/www.lampcms.com","dob":"1990\/1\/3","gender":"M","description":"I am the test user","avatar":"1A.jpg"}'; public function __construct(Registry $oRegistry, $collectionName = null, array $a = array(), $default = ''){ - $a = json_decode(self::JSON_ENCODED, true); + $a = json_decode($this->JSON_ENCODED, true); parent::__construct($oRegistry, 'USERS', $a); } @@ -70,4 +70,3 @@ public static function factory(Registry $oRegistry, array $a = array()){ return $o; } } - diff --git a/tests/Fixtures/tplQtags.php b/tests/Fixtures/tplQtags.php new file mode 100644 index 0000000..ab05759 --- /dev/null +++ b/tests/Fixtures/tplQtags.php @@ -0,0 +1,72 @@ +LampCMS" + * The location of the link is not important, it can be in the footer of the page + * but it must not be hidden by style attibutes + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This product includes GeoLite data created by MaxMind, + * available from http://www.maxmind.com/ + * + * + * @author Dmitri Snytkine + * @copyright 2005-2011 (or current year) ExamNotes.net inc. + * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3 + * @link http://www.lampcms.com Lampcms.com project + * @version Release: @package_version@ + * + * + */ + + + +class tplQtags extends \Lampcms\Template\Template +{ + /** + * This is important! + * Since tags can be any combination of chars, even + * brackets and + sign, we should always urlencode tag's value + * for the link! + * + * @param array $a + */ + protected static function func(&$a){ + $a[] = urlencode($a[0]); + } + + protected static $vars = array(0 => ''); + + protected static $tpl = '%1$s '; + +} diff --git a/tests/LampcmsArrayTest.php b/tests/LampcmsArrayTest.php index 5de09e2..6c7eb29 100644 --- a/tests/LampcmsArrayTest.php +++ b/tests/LampcmsArrayTest.php @@ -72,11 +72,11 @@ public function testIsset(){ $good2 = isset($this->o['two']); if(true !== $good || true !== $good2){ - $this->fail('checkOffset failed to find good key'); + $this->fail('offsetExists() failed to find good key'); } $bad = isset($this->o['stuff']); if(false !== $bad){ - $this->fail('checkOffset reported non-existant value as exists'); + $this->fail('offsetExists() reported non-existant value as exists'); } } diff --git a/tests/LampcmsObjectTest.php b/tests/LampcmsObjectTest.php new file mode 100644 index 0000000..93803f4 --- /dev/null +++ b/tests/LampcmsObjectTest.php @@ -0,0 +1,85 @@ +LampCMS" + * The location of the link is not important, it can be in the footer of the page + * but it must not be hidden by style attibutes + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This product includes GeoLite data created by MaxMind, + * available from http://www.maxmind.com/ + * + * + * @author Dmitri Snytkine + * @copyright 2005-2011 (or current year) ExamNotes.net inc. + * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3 + * @link http://www.lampcms.com Lampcms.com project + * @version Release: @package_version@ + * + * + */ + + +namespace Lampcms; +require_once 'bootstrap.php'; + +class LampcmsObjectTest extends \PHPUnit_Framework_TestCase +{ + protected $o; + + + /** + * We want to override parent setUp + * because we want to use new instance of Registry + * and not use getInstance() here + * (non-PHPdoc) + * @see Lampcms.LampcmsUnitTestCase::setUp() + */ + public function setUp(){ + $this->o = new LampcmsObject(new Registry()); + } + + + public function testHashCode(){ + $this->assertTrue(is_string($this->o->hashCode())); + } + + public function testGetClass(){ + $this->assertEquals('Lampcms\LampcmsObject', $this->o->getClass()); + } + + + public function testToString(){ + $this->assertContains('object of type: Lampcms\LampcmsObject hashCode:', (string)$this->o); + } +} \ No newline at end of file diff --git a/tests/LampcmsUnitTestCase.php b/tests/LampcmsUnitTestCase.php index c4504f2..fe4cb65 100644 --- a/tests/LampcmsUnitTestCase.php +++ b/tests/LampcmsUnitTestCase.php @@ -78,6 +78,20 @@ class LampcmsUnitTestCase extends \PHPUnit_Framework_TestCase */ protected $aCollections = array(); + /** + * If this flag is set to true + * from inside test method + * then database now dropped + * after a test method, allowing + * the data created in one test method + * to persist untill another test method + * is called that does not have + * this flag set. + * + * @var bool + */ + protected $persistDB = false; + /** * Collection name used in this test @@ -89,28 +103,30 @@ class LampcmsUnitTestCase extends \PHPUnit_Framework_TestCase public function __destruct(){ - - $oRegistry = new Registry(); - try{ - $Ini = $oRegistry->Ini; - $aConfig = $Ini->getSection('MONGO'); - $realDbname = $aConfig['db']; - $oMongo = new Mongo($Ini); - } catch(\Exception $e){ - - return; - } - /** - * Being Careful - if for some reason - * the dbname used for this MongoInstance - * is our actual database then don't do anything - * - */ - if($realDbname !== $oMongo->getDbName()){ - $oMongo->getDb()->drop(); - } else { - echo 'Not going to drop db. This is the actual DB!'; + if(!$this->persistDB){ + $oRegistry = new Registry(); + try{ + $Ini = $oRegistry->Ini; + $aConfig = $Ini->getSection('MONGO'); + $realDbname = $aConfig['db']; + $oMongo = new Mongo($Ini); + } catch(\Exception $e){ + + return; + } + + /** + * Being Careful - if for some reason + * the dbname used for this MongoInstance + * is our actual database then don't do anything + * + */ + if($realDbname !== $oMongo->getDbName()){ + $oMongo->getDb()->drop(); + } else { + echo 'Not going to drop db. This is the actual DB!'; + } } } diff --git a/tests/MongoIncrementorTest.php b/tests/MongoIncrementorTest.php new file mode 100644 index 0000000..71110c2 --- /dev/null +++ b/tests/MongoIncrementorTest.php @@ -0,0 +1,102 @@ +LampCMS" + * The location of the link is not important, it can be in the footer of the page + * but it must not be hidden by style attibutes + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This product includes GeoLite data created by MaxMind, + * available from http://www.maxmind.com/ + * + * + * @author Dmitri Snytkine + * @copyright 2005-2011 (or current year) ExamNotes.net inc. + * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3 + * @link http://www.lampcms.com Lampcms.com project + * @version Release: @package_version@ + * + * + */ + + + +namespace Lampcms; +require_once 'bootstrap.php'; + + +/** + * Run after UserTest, AnswerTest, CommentTest + * + */ +class MongoIncrementorTest extends LampcmsUnitTestCase +{ + protected $oMongo; + + public function setUp(){ + $oRegistry = new Registry(); + $this->oMongo = new Mongo($oRegistry->Ini); + } + + public function getInput(){ + return array( + array('RESOURCE'), + array('QUESTIONS'), + array('ANSWERS'), + array('TEST') + ); + } + + /** + * @dataProvider getInput + * + */ + public function testGetNextValue($collName){ + + $oIncrementor = new MongoIncrementor($this->oMongo); + + $this->assertEquals(1, $oIncrementor->nextValue($collName)); + $this->assertEquals(2, $oIncrementor->nextValue($collName)); + $this->assertEquals(3, $oIncrementor->nextValue($collName)); + } + + + + + public function testGetNextValueStartAt100(){ + $oIncrementor = new MongoIncrementor($this->oMongo); + + $this->assertEquals(101, $oIncrementor->nextValue('COMMENTS', 100)); + $this->assertEquals(102, $oIncrementor->nextValue('COMMENTS', 100)); + } +} diff --git a/tests/QuestionTest.php b/tests/QuestionTest.php index fbcdaf3..32ea32e 100644 --- a/tests/QuestionTest.php +++ b/tests/QuestionTest.php @@ -53,8 +53,10 @@ namespace Lampcms; require_once 'bootstrap.php'; -require 'Fixtures/MockQuestion.php'; - +require_once 'Fixtures/MockQuestion.php'; +require_once 'Fixtures/MockUser.php'; +require_once 'Fixtures/MockAnswer.php'; +require_once 'Fixtures/tplQtags.php'; use Lampcms\Question; @@ -144,8 +146,7 @@ public function testGetAnswerCount(){ * @depends testGetAnswerCount */ public function testIncreaseAnswerCountTwice(){ - $this->oQuestion->updateAnswerCount() - ->updateAnswerCount(); + $this->oQuestion->updateAnswerCount()->updateAnswerCount(); $this->assertTrue(3 === $this->oQuestion->getAnswerCount()); } @@ -180,8 +181,8 @@ public function testGetEtag(){ */ public function testTouch(){ $this->oQuestion->touch(); - $this->assertTrue((time() - $this->oQuestion->getEtag()) < 1); - $this->assertTrue((time() - $this->oQuestion->getLastModified()) < 1); + $this->assertTrue((time() - $this->oQuestion->getEtag()) < 2); + $this->assertTrue((time() - $this->oQuestion->getLastModified()) < 2); } @@ -217,6 +218,27 @@ public function testSeoUrl(){ $this->assertEquals('Mock-Stub-Post', $this->oQuestion->getSeoUrl()); } + public function testGetRegistry(){ + $this->assertInstanceOf('\Lampcms\Registry', $this->oQuestion->getRegistry()); + } + + + /** + * @depends testGetRegistry + * + */ + public function testGetUrl(){ + + $Registry = $this->oQuestion->getRegistry(); + + $siteUrl = $Registry->Ini->SITE_URL; + $url = $this->oQuestion->getUrl(); + $shortUrl = $this->oQuestion->getUrl(true); + + $this->assertEquals('/q510/Mock-Stub-Post', \substr($url, strlen($siteUrl))); + $this->assertEquals('/q510/', \substr($shortUrl, strlen($siteUrl))); + } + /** @@ -228,12 +250,291 @@ public function testDeleteCommentComments(){ $this->assertTrue(1 === $this->oQuestion->getCommentsCount()); } - public function testSave(){ - $oRegistry = new Registry(); - $id = $this->oQuestion->getQuestionId(); + + public function testAddContributor(){ + $this->oQuestion->addContributor(5); + $a1 = $this->oQuestion['a_uids']; + $this->assertTrue(in_array(5, $a1)); + $this->assertFalse(in_array(26, $a1)); + $this->oQuestion->addContributor(new MockUser($this->oQuestion->getRegistry())); + $a2 = $this->oQuestion['a_uids']; + $this->assertTrue(in_array(26, $a2)); + + } + + + /** + * @depends testAddContributor + * + */ + public function testRemoveContributor(){ + $this->oQuestion->addContributor(new MockUser($this->oQuestion->getRegistry())); + $this->oQuestion->removeContributor(new MockUser($this->oQuestion->getRegistry())); + $a2 = $this->oQuestion['a_uids']; + $this->assertFalse(in_array(26, $a2)); + } + + + public function testSetBestAnswer(){ + $Answer = new MockAnswer($this->oQuestion->getRegistry()); + $this->oQuestion->setBestAnswer($Answer); + + $this->assertEquals(513, $this->oQuestion['i_sel_ans']); + $this->assertEquals(3, $this->oQuestion['i_sel_uid']); + $this->assertEquals('accptd', $this->oQuestion['status']); + $this->assertTrue( (time() - $this->oQuestion['i_etag']) < 2 ); + $this->assertTrue($Answer['accepted'] === true); + $this->assertTrue((time() - $Answer->getLastModified()) < 2); + } + + + + + public function testSetDeleted(){ + $oUser = new MockUser($this->oQuestion->getRegistry()); + $this->oQuestion->setDeleted($oUser, 'test of deleting'); + + $a = $this->oQuestion['a_deleted']; + + $this->assertTrue(is_array($a)); + $this->assertEquals(count($a), 5); + $this->assertEquals('John D Doe', $a['username']); + $this->assertEquals('test of deleting', $a['reason']); + $this->assertEquals(26, $a['i_uid']); + $this->assertTrue(( time() - $this->oQuestion['i_del_ts']) < 2); + } + + /** + * @depends testSetDeleted + * + */ + public function testGetDeletedTime(){ + $this->assertEquals(0, $this->oQuestion->getDeletedTime()); + $oUser = new MockUser($this->oQuestion->getRegistry()); + $this->oQuestion->setDeleted($oUser, 'test of deleting'); + $this->assertTrue(( time() - $this->oQuestion->getDeletedTime()) < 2); + } + + + public function testSetEdited(){ + $oUser = new MockUser($this->oQuestion->getRegistry()); + $this->oQuestion->setEdited($oUser, 'test of editing'); + $a = $this->oQuestion['a_edited']; + $this->assertTrue(is_array($a)); + $this->assertTrue(count($a) > 0); + + $aEdited = end($a); + $this->assertTrue(is_array($aEdited)); + $this->assertEquals('John D Doe', $aEdited['username']); + $this->assertEquals('test of editing', $aEdited['reason']); + $this->assertEquals(26, $aEdited['i_uid']); + $this->assertEquals(26, $aEdited['i_uid']); + } + + + /** + * @depends testIsClosed + * + */ + public function testSetClosed(){ + $oUser = new MockUser($this->oQuestion->getRegistry()); + $this->oQuestion->setClosed($oUser, 'test of closed'); + $a = $this->oQuestion['a_closed']; + $this->assertTrue(is_array($a)); + + $this->assertEquals(count($a), 5); + $this->assertEquals('John D Doe', $a['username']); + $this->assertEquals('test of closed', $a['reason']); + $this->assertEquals(26, $a['i_uid']); + $this->assertEquals($a['av'], $oUser->getAvatarSrc()); + $this->assertSame($a, ($this->oQuestion->isClosed()) ); + } + + + /** + * @depends testGetAnswerCount + * + */ + public function testUpdateAnswerCount(){ + $this->oQuestion->updateAnswerCount(-1); + $this->assertEquals(0, $this->oQuestion->getAnswerCount()); + $this->assertEquals('unans', $this->oQuestion['status']); + $this->assertEquals('s', $this->oQuestion['ans_s']); + + $this->oQuestion->updateAnswerCount(-2); + $this->assertEquals(0, $this->oQuestion->getAnswerCount()); + $this->assertEquals('unans', $this->oQuestion['status']); + $this->assertEquals('s', $this->oQuestion['ans_s']); + + $this->oQuestion->updateAnswerCount(); + $this->assertEquals(1, $this->oQuestion->getAnswerCount()); + $this->assertEquals('answrd', $this->oQuestion['status']); + $this->assertEquals('', $this->oQuestion['ans_s']); + + $this->oQuestion->updateAnswerCount(1); + $this->assertEquals(2, $this->oQuestion->getAnswerCount()); + $this->assertEquals('answrd', $this->oQuestion['status']); + $this->assertEquals('s', $this->oQuestion['ans_s']); + } + + + public function testIncreaseViews(){ + $oUser = new MockUser($this->oQuestion->getRegistry()); + $this->oQuestion->increaseViews($oUser); + $this->assertEquals(2, $this->oQuestion['i_views']); + $this->oQuestion->increaseViews($oUser); + $this->assertEquals(2, $this->oQuestion['i_views']); + + $oUser['_id'] = 7; + $this->oQuestion->increaseViews($oUser); + $this->assertEquals(3, $this->oQuestion['i_views']); + /** + * Test when Viewer is owner of question + * in which case view should not count + */ + $oUser['_id'] = 3; + $this->oQuestion->increaseViews($oUser); + $this->assertEquals(3, $this->oQuestion['i_views']); + + /** + * Test when Viewer is guest (_id is 0) + * View should count + * in which case view should not count + */ + $oUser['_id'] = 0; + $this->oQuestion->increaseViews($oUser); + $this->assertEquals(4, $this->oQuestion['i_views']); + } + + + /** + * @depends testSetBestAnswer + * + */ + public function testSetLatestAnswer(){ + + $oUser = new MockUser($this->oQuestion->getRegistry()); + $oAnswer = new MockAnswer($this->oQuestion->getRegistry()); + + $this->oQuestion->setLatestAnswer($oUser, $oAnswer); + $a = $this->oQuestion['a_latest']; + $this->assertTrue(is_array($a[0])); + $this->assertEquals(1, count($a)); + $this->assertEquals('John D Doe', $a['0']['u']); + $this->assertEquals(513, $a['0']['id']); + + $oAnswer['_id'] = 999; + $oUser['username'] = 'Dude'; + $oUser['_id'] = 999999; + $oAnswer->setSaved(); + $oUser->setSaved(); + + $this->oQuestion->setLatestAnswer($oUser, $oAnswer); + $a = $this->oQuestion['a_latest']; + $this->assertTrue(is_array($a[0])); + $this->assertEquals(2, count($a)); + $this->assertEquals('John D Doe', $a['0']['u']); + $this->assertEquals(999, $a['0']['id']); + $this->assertEquals('John D Doe', $a['1']['u']); + $this->assertEquals(513, $a['1']['id']); $this->oQuestion->insert(); - $a = $oRegistry->Mongo->QUESTIONS->findOne(array('_id' => $id)); - $this->assertTrue(is_array($a) && count($a) > 0); + + $oQuestion = new Question($this->oQuestion->getRegistry()); + $oQuestion->by_id(510); + + $a = $oQuestion['a_latest']; + $this->assertEquals(999, $a['0']['id']); + } + + + /** + * @depends testSetLatestAnswer + * + */ + public function testRemoveAnswer(){ + + /** + * Mock question has i_ans set to 1, we need to reset it + * to 0 for this test + */ + $this->oQuestion->updateAnswerCount(-1); + + $oUser = new MockUser($this->oQuestion->getRegistry()); + $oAnswer = new MockAnswer($this->oQuestion->getRegistry()); + $oAnswer2 = new MockAnswer($this->oQuestion->getRegistry()); + $oAnswer2['_id'] = 999; + + $this->oQuestion->setLatestAnswer($oUser, $oAnswer)->updateAnswerCount(); + $this->oQuestion->setBestAnswer($oAnswer); + + $this->oQuestion->setLatestAnswer($oUser, $oAnswer2)->updateAnswerCount(); + $a = $this->oQuestion['a_latest']; + $this->assertEquals(999, $a['0']['id']); + $this->assertEquals('accptd', $this->oQuestion['status']); + $this->assertEquals(3, $this->oQuestion['i_sel_uid']); + $this->assertEquals(513, $this->oQuestion['i_sel_ans']); + + /** + * Remove answer that was set as best + * answer should remove accptd status + * and change it to answrd + * it should also unset keys i_sel_uid and i_sel_ans + */ + $this->oQuestion->removeAnswer($oAnswer); + $a = $this->oQuestion['a_latest']; + $this->assertEquals(999, $a['0']['id']); + $this->assertEquals('answrd', $this->oQuestion['status']); + $this->assertFalse($this->oQuestion->offsetExists('i_sel_uid')); + $this->assertFalse($this->oQuestion->offsetExists('i_sel_ans')); + + /** + * Removing second answer should + * completely remove the a_latest key + * and reset status to unans since there + * are no answeres now + */ + $this->oQuestion->removeAnswer($oAnswer2); + $this->assertEquals('unans', $this->oQuestion['status']); + $this->assertFalse($this->oQuestion->offsetExists('a_latest')); + + } + + + /** + * @depends testSetEdited + * + */ + public function testRetag(){ + $oUser = new MockUser($this->oQuestion->getRegistry()); + $this->oQuestion->retag($oUser, array('brown', 'fox')); + + $tags = $this->oQuestion['a_tags']; + $tagsHtml = $this->oQuestion['tags_html']; + $body = $this->oQuestion['b']; + + $this->assertEquals(array('brown', 'fox'), $tags); + $this->assertContains('brown fox', $tagsHtml); + $this->assertEquals('This is a simple mock question', $body); + + $this->oQuestion->retag($oUser, array('mock', 'simple')); + + $tags = $this->oQuestion['a_tags']; + $tagsHtml = $this->oQuestion['tags_html']; + $body = $this->oQuestion['b']; + + $this->assertEquals(array('mock', 'simple'), $tags); + $this->assertContains('mock simple', $tagsHtml); + $this->assertEquals('This is a simple mock question', $body); + + $a = $this->oQuestion['a_edited']; + $this->assertTrue(is_array($a)); + $this->assertTrue(count($a) > 0); + + $aEdited = end($a); + $this->assertTrue(is_array($aEdited)); + $this->assertEquals('John D Doe', $aEdited['username']); + $this->assertEquals('Retagged', $aEdited['reason']); + } } diff --git a/tests/RegistryTest.php b/tests/RegistryTest.php index 44e97da..928f7bb 100644 --- a/tests/RegistryTest.php +++ b/tests/RegistryTest.php @@ -51,7 +51,6 @@ namespace Lampcms; - require_once 'bootstrap.php'; /** diff --git a/tests/RequestTest.php b/tests/RequestTest.php new file mode 100644 index 0000000..af4ab60 --- /dev/null +++ b/tests/RequestTest.php @@ -0,0 +1,201 @@ +LampCMS" + * The location of the link is not important, it can be in the footer of the page + * but it must not be hidden by style attibutes + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This product includes GeoLite data created by MaxMind, + * available from http://www.maxmind.com/ + * + * + * @author Dmitri Snytkine + * @copyright 2005-2011 (or current year) ExamNotes.net inc. + * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3 + * @link http://www.lampcms.com Lampcms.com project + * @version Release: @package_version@ + * + * + */ + + +namespace Lampcms; +require_once 'bootstrap.php'; + +class LampcmsRequestTest extends LampcmsUnitTestCase +{ + + + public function setUp(){ + if(!isset($_SERVER)){ + $_SERVER = array(); + } + + if(!isset($_REQUEST)){ + $_REQUEST = array(); + } + } + + public function testGetRequestMethod(){ + + $this->assertEquals(null, Request::getRequestMethod()); + + $_SERVER['REQUEST_METHOD'] = 'get'; + $this->assertEquals('GET', Request::getRequestMethod()); + + $_SERVER['REQUEST_METHOD'] = 'post'; + $this->assertEquals('POST', Request::getRequestMethod()); + + } + + + public function testGetIP(){ + $this->assertEquals('127.0.0.2', Request::getIP()); + + $_SERVER['REMOTE_ADDR'] = '71.207.145.21'; + $this->assertEquals('71.207.145.21', Request::getIP()); + } + + + public function testGetUserAgent(){ + $this->assertEquals(null, Request::getUserAgent()); + + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla 5'; + $this->assertEquals('Mozilla 5', Request::getUserAgent()); + } + + + public function testGetHttpHeader(){ + $this->assertEquals(null , Request::getHttpHeader('X-REQUESTED-WITH')); + + $_SERVER['HTTP_ACCEPT_CHARSET'] = 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'; + $this->assertEquals('ISO-8859-1,utf-8;q=0.7,*;q=0.7' , Request::getHttpHeader('Accept-Charset')); + } + + public function testGetHttpHeaderUseApacheHeaders(){ + if(defined('HAS_APACHE_REQUEST_HEADERS')){ + $this->markTestSkipped( + 'This test is probably run from browser, skipping' + ); + } + + $this->assertEquals('ISO-8859-1,utf-8;q=0.7,*;q=0.7' , Request::getHttpHeader('Accept-Charset')); + $this->assertEquals('gzip,deflate' , Request::getHttpHeader('Accept-encoding')); + $this->assertEquals('gzip,deflate' , Request::getHttpHeader('ACCEPT-ENCODING')); + $this->assertNotEquals('gzip,deflate' , Request::getHttpHeader('ACCEPT_ENCODING')); + } + + + public function testGetUTF8(){ + $Request = new Request(array('name' => 'bob')); + $this->assertInstanceOf('\Lampcms\Utf8String', $Request->getUTF8('name')); + $this->assertInstanceOf('\Lampcms\Utf8String', $Request->getUTF8('stuff')); + $this->assertEquals('bob', (string)$Request->getUTF8('name')); + } + + public function testGetDefaultVal(){ + $Request = new Request(array('name' => 'bob')); + + $this->assertSame('stuff', $Request->get('bad', 's', 'stuff')); + $this->assertSame(5, $Request->get('bad', 'i', 5)); + } + + public function testGetDefaultUTF8Val(){ + $Request = new Request(array('name' => 'bob')); + + $this->assertSame('stuff', (string)$Request->getUTF8('bad', 'stuff')); + } + + + public function testRequiredNotPresent(){ + $Request = new Request(array('name' => 'bob')); + $Request->setRequired(array('fruit')); + try{ + $Request->checkRequired(); + } catch (\LogicException $e){ + return; + } + + $this->fail('Expected \LogicException when required param not found in Request'); + } + + public function testRequiredPresent(){ + $Request = new Request(array('name' => 'bob')); + $Request->setRequired(array('name')); + $Request->checkRequired(); + } + + + public function testBadValueOf(){ + $Request = new Request(array('a' => 'abc234')); + try{ + $val = $Request['a']; + } catch(\InvalidArgumentException $e){ + return; + } + + $this->fail('InvalidArgumentException expected when value of "a" param in not all-letters'); + } + + + public function testDefaultPageID(){ + $Request = new Request(array('script' => 'ts')); + $this->assertSame(1, $Request['pageID']); + } + + public function testBadParamName(){ + $Request = new Request(array('script' => 'ts')); + $val = $Request['']; + $this->assertEquals('ts', $val); + } + + + public function testParamDoesNotExist(){ + $Request = new Request(array('script' => 'ts')); + try{ + $val = $Request['fruit']; + } catch(\Lampcms\DevException $e){ + return; + } + + $this->fail('\Lampcms\DevException expected when param does not exist'); + } + + + public function testFactory(){ + $Request = Request::factory(); + $this->assertInstanceOf('\Lampcms\Request', $Request); + } + +} diff --git a/tests/ResourceTest.php b/tests/ResourceTest.php new file mode 100644 index 0000000..a64352c --- /dev/null +++ b/tests/ResourceTest.php @@ -0,0 +1,89 @@ +LampCMS" + * The location of the link is not important, it can be in the footer of the page + * but it must not be hidden by style attibutes + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This product includes GeoLite data created by MaxMind, + * available from http://www.maxmind.com/ + * + * + * @author Dmitri Snytkine + * @copyright 2005-2011 (or current year) ExamNotes.net inc. + * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3 + * @link http://www.lampcms.com Lampcms.com project + * @version Release: @package_version@ + * + * + */ + + +namespace Lampcms; +require_once 'bootstrap.php'; + +class LampcmsResourceTest extends LampcmsUnitTestCase +{ + + public function setUp(){ + $this->oRegistry = new Registry(); + } + + public function testConstructor(){ + $Resource = new Resource($this->oRegistry); + $this->assertInstanceOf('\Lampcms\Resource', $Resource); + } + + /** + * @depends testConstructor + * + */ + public function testFactory(){ + $Resource = Resource::factory($this->oRegistry); + $this->assertInstanceOf('\Lampcms\Resource', $Resource); + } + + /** + * @depends testFactory + * + */ + public function testCreate(){ + $id = Resource::factory($this->oRegistry)->create('MYTEST'); + $this->assertTrue(is_int($id)); + $a = $this->oRegistry->Mongo->RESOURCE->findOne(array('_id' => $id)); + $this->assertEquals($id, $a['_id']); + $this->assertEquals('MYTEST', $a['res_type']); + } + +} diff --git a/tests/UserTest.php b/tests/UserTest.php index 57e910b..d7fa6eb 100644 --- a/tests/UserTest.php +++ b/tests/UserTest.php @@ -53,7 +53,7 @@ namespace Lampcms; require_once 'bootstrap.php'; -require 'Fixtures/MockUser.php'; +require_once 'Fixtures/MockUser.php'; use Lampcms\User; diff --git a/tests/Utf8StringTest.php b/tests/Utf8StringTest.php new file mode 100644 index 0000000..48e1209 --- /dev/null +++ b/tests/Utf8StringTest.php @@ -0,0 +1,173 @@ +LampCMS" + * The location of the link is not important, it can be in the footer of the page + * but it must not be hidden by style attibutes + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This product includes GeoLite data created by MaxMind, + * available from http://www.maxmind.com/ + * + * + * @author Dmitri Snytkine + * @copyright 2005-2011 (or current year) ExamNotes.net inc. + * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3 + * @link http://www.lampcms.com Lampcms.com project + * @version Release: @package_version@ + * + * + */ + + +namespace Lampcms; +require_once 'bootstrap.php'; + +/** + * @todo test with some bad invalid chars to NOT validate + * @todo fix string with bad chars + * @todo remove BOM from string + * + */ +class Utf8StringTest extends LampcmsUnitTestCase +{ + const UTF8_RUSSIAN = "\xD0\xBE\xD1\x82\xD0\xBA\xD0\xB0\xD0\xB7\xD0\xB0\xD0\xBB\xD0\xB0\xD1\x81\xD1\x8C\x20\xD0\xBE\xD1\x82\x20\xD1\x80\xD0\xB0\xD0\xB7\xD1\x80\xD0\xB0\xD0\xB1\xD0\xBE\xD1\x82\xD0\xBA\xD0\xB8"; + + const UTF8_RUSSIAN_UP = "\xD0\x9E\xD0\xA2\xD0\x9A\xD0\x90\xD0\x97\xD0\x90\xD0\x9B\xD0\x90\xD0\xA1\xD0\xAC\x20\xD0\x9E\xD0\xA2\x20\xD0\xA0\xD0\x90\xD0\x97\xD0\xA0\xD0\x90\xD0\x91\xD0\x9E\xD0\xA2\xD0\x9A\xD0\x98"; + + const UTF8_CHINISE = "\xE6\x8B\x9B\xE6\x8A\x95\xE6\xA0\x87"; + + const UTF8_ART = "\xE2\x80\xA0\xE2\x99\x9A"; // Dagger, Crown + + const BAD_STRING = "Invalid 3-octet char \xe2\x28\xa1 here"; + + public function getUtf8String(){ + return array( + array(self::UTF8_CHINISE), + array(self::UTF8_RUSSIAN), + array(self::UTF8_RUSSIAN_UP), + array(self::UTF8_ART) + ); + } + + public function getInvalidUtf8String(){ + return array( + array("Invalid char \xe9 here"), + array("Invalid 2-octet char \xa0\xa1 here"), + array("Invalid 3-octet char \xe2\x28\xa1 here"), + array("Invalid 4-octet char \xf0\x28\x8c\xbc here") + ); + } + + /** + * @dataProvider getUtf8String + * + */ + public function testValidateUtf8($s){ + $this->assertTrue(Utf8String::validateUtf8($s)); + } + + /** + * @dataProvider getInvalidUtf8String + * + */ + public function testValidateInvalidUtf8($s){ + $this->assertFalse(Utf8String::validateUtf8($s)); + } + + public function testToUpperCase(){ + $upper = Utf8string::factory(self::UTF8_RUSSIAN)->toUpperCase()->valueOf(); + $this->assertEquals( self::UTF8_RUSSIAN_UP, $upper); + } + + public function testToLowercase(){ + $upper = Utf8string::factory(self::UTF8_RUSSIAN_UP )->toLowerCase()->valueOf(); + $this->assertEquals( self::UTF8_RUSSIAN, $upper); + } + + public function testLength(){ + $this->assertEquals(24, Utf8String::factory(self::UTF8_RUSSIAN)->length()); + $this->assertEquals(24, Utf8String::factory(self::UTF8_RUSSIAN_UP)->length()); + } + + + public function testSubstr(){ + $o = Utf8String::factory(self::UTF8_RUSSIAN, 'utf-8', true); + $sub = (string)$o->substr(4, 3); + + $o2 = Utf8String::factory(self::UTF8_RUSSIAN_UP, 'utf-8', true); + $sub2 = (string)$o2->substr(4, 3); + + $this->assertEquals("\xD0\xB7\xD0\xB0\xD0\xBB", $sub); + $this->assertEquals("\xD0\x97\xD0\x90\xD0\x9B", $sub2); + } + + + public function testUcWords(){ + $o = Utf8String::factory(self::UTF8_RUSSIAN, 'utf-8', true); + $uc = "\xD0\x9E\xD1\x82\xD0\xBA\xD0\xB0\xD0\xB7\xD0\xB0\xD0\xBB\xD0\xB0\xD1\x81\xD1\x8C\x20\xD0\x9E\xD1\x82\x20\xD0\xA0\xD0\xB0\xD0\xB7\xD1\x80\xD0\xB0\xD0\xB1\xD0\xBE\xD1\x82\xD0\xBA\xD0\xB8"; + $ucf = (string)$o->ucwords(); + $this->assertEquals($uc, $ucf); + } + + + public function testUcFirst(){ + $o = Utf8String::factory(self::UTF8_RUSSIAN, 'utf-8', true); + $ucf = (string)$o->ucfirst(); + $uc = "\xD0\x9E\xD1\x82\xD0\xBA\xD0\xB0\xD0\xB7\xD0\xB0\xD0\xBB\xD0\xB0\xD1\x81\xD1\x8C\x20\xD0\xBE\xD1\x82\x20\xD1\x80\xD0\xB0\xD0\xB7\xD1\x80\xD0\xB0\xD0\xB1\xD0\xBE\xD1\x82\xD0\xBA\xD0\xB8"; + $this->assertEquals($uc, $ucf); + } + + public function testGetWordsCount(){ + $o = Utf8String::factory(self::UTF8_RUSSIAN); + $this->assertEquals(3, $o->getWordsCount()); + } + + + public function testStripLow(){ + $s = "String with tab and \nNew Line1\nNew Line2"; + $this->assertEquals($s, Utf8String::stripLow($s)); + } + + public function testSanitizeString(){ + + } + + public function testSanitizeAscii(){ + + } + + public function testRecodeUtf8(){ + + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index f16e02f..bb94ebc 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -49,6 +49,58 @@ * */ + +/** + * Order of tests: + * LampcmsObject X + * LampcmsArray X + * ArrayDefaults X + * String X + * Utf8String + * Dom\Document + * String\HTMLString + * String\HTMLStringParser + * Ini X + * Mongo X + * Incrementor X + * MongoDoc X + * Resource X + * Request X + * Responder <- use @outputBuffering true + * Registry X + * Dispatcher + * HtmlSafe + * Template + * + * User X + * Answer X + * UnansweredTags + * UserTags + * RelatedTags + * + * Tokenizer + * TagsTokenizer + * TitleTokenizer + * Question X + * + * SubmittedAnswerWWW + * SubmittedQuestionWWW + * SubmittedCommentWWW + * AnswerParser + * QuestionParser + * + * Validate + * Acl + * Base + * UserAuth + * WebPage + * + * + * Then + * loop over dirs but exclude Template, Dom and String from test suite + * since these has been tested already, and also + * exclude Interfaces + */ /** * Define constants that * are normally defined in !inc.php @@ -56,8 +108,8 @@ * */ define('LAMPCMS_DEBUG', false); -define('MOCK_SALT', 'abcde'); -define('MOCK_COOKIE_SALT', 'abcde'); +define('LAMPCMS_SALT', 'abcde'); +define('COOKIE_SALT', 'abcde'); define('DEFAULT_LANG', 'en'); define('COOKIE_DOMAIN', '' ); @@ -66,7 +118,7 @@ define('AVATAR_IMG_SITE', 'http://img.lampcms.com'); /** * This is very important! - * Use test database! + * Use test databases! * If this step if overlooked then * the actual database will be used during * test, and can override some actual data! @@ -76,6 +128,7 @@ * overriding actual data here and there */ define('MONGO_DBNAME', 'LAMPCMS_TEST'); +define('LAMPCMS_MYSQL_DB', 'LAMPCMS_TEST'); /** @@ -85,12 +138,12 @@ * point value to the root dir of your project * This is my value: C:\eclipse\workspace\QA * .. - * - * To Run individual tests in Eclipse set - * External Tools Configuration > Working Directory + * + * To Run individual tests in Eclipse set + * External Tools Configuration > Working Directory * to location of this dir (/tests), this is my config: * ${workspace_loc:/QA/tests} - * + * * To run test suite it's different, I setup second "External Tool" * called it TestSuite * and there I don't have Environment variable at all, @@ -145,6 +198,17 @@ function d($message){} function e($message){} -require LAMPCMS_PATH.DIRECTORY_SEPARATOR.'autoload.php'; +if(!function_exists('apache_request_headers')){ + function apache_request_headers(){ + $JSON_ENCODED_HEADERS = '{"Host":"127.0.0.5","User-Agent":"Mozilla\/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.9) Gecko\/20100824 Firefox\/3.6.9 GTB7.1","Accept":"text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8","Accept-Language":"en-us,en;q=0.5","Accept-Encoding":"gzip,deflate","Accept-Charset":"ISO-8859-1,utf-8;q=0.7,*;q=0.7","Keep-Alive":"115","Connection":"keep-alive","Cookie":"sid=1297206315.3591az0q354fEdcp96s6ulBwtQcGf81sDo35G; PHPSESSID=roqvm4i871og6u3msurp4d5qh0","Cache-Control":"max-age=0"}'; + $a = json_decode($JSON_ENCODED_HEADERS, true); + return $a; + } +} else { + define('HAS_APACHE_REQUEST_HEADERS', true); +} + +require LAMPCMS_PATH.DIRECTORY_SEPARATOR.'autoload.php'; +session_start(); diff --git a/www/index.php b/www/index.php index 03493df..3e93813 100644 --- a/www/index.php +++ b/www/index.php @@ -84,21 +84,22 @@ } catch(\Exception $e) { header("HTTP/1.0 500 Exception"); try { - $strHtml = \Lampcms\Responder::makeErrorPage('Error: '.Lampcms\Exception::formatException($e)); - $extra = (isset($_SERVER)) ? ' $_SERVER: '.print_r($_SERVER, 1) : ' no extra'; + $sHtml = \Lampcms\Responder::makeErrorPage('Error: '.Lampcms\Exception::formatException($e)); + $extra = (isset($_SERVER)) ? ' $_SERVER: '.print_r($_SERVER, 1) : ' no server'; + $extra .= 'file: '.$e->getFile(). ' line: '.$e->getLine().' trace: '.$e->getTraceAsString(); if(strlen(trim(constant('DEVELOPER_EMAIL'))) > 1){ - @mail(DEVELOPER_EMAIL, 'Error in index.php', $strHtml.$extra); + @mail(DEVELOPER_EMAIL, '500 Error in index.php', $sHtml.$extra); } - echo $strHtml; + echo $sHtml; fastcgi_finish_request(); }catch(\Exception $e2) { - $strHtml = \Lampcms\Responder::makeErrorPage('Exception: '.$e2->getMessage()."\nIn file:".$e2->getFile()."\nLine: ".$e2->getLine()); + $sHtml = \Lampcms\Responder::makeErrorPage('Exception: '.$e2->getMessage()."\nIn file:".$e2->getFile()."\nLine: ".$e2->getLine()); $extra = (isset($_SERVER)) ? ' $_SERVER: '.print_r($_SERVER, 1) : ' no extra'; if(strlen(trim(constant('DEVELOPER_EMAIL'))) > 1){ - @mail(DEVELOPER_EMAIL, 'Error in index.php on line '.__LINE__, $strHtml.$extra); + @mail(DEVELOPER_EMAIL, 'Error in index.php on line '.__LINE__, $sHtml.$extra); } - echo $strHtml; + echo $sHtml; fastcgi_finish_request(); } } diff --git a/www/style/1/www/tplQrecent.php b/www/style/1/www/tplQrecent.php index 2feeb44..256f3e5 100644 --- a/www/style/1/www/tplQrecent.php +++ b/www/style/1/www/tplQrecent.php @@ -38,19 +38,25 @@ class tplQrecent extends Lampcms\Template\Template { - + protected static function func(&$a){ if(!empty($a['a_closed'])){ $a['closed'] = ' closed'; } - - if(!empty($a['lp_u'])){ + + /*if(!empty($a['lp_u'])){ $reltime = \Lampcms\TimeAgo::format(new \DateTime($a['lp_t'])); $a['last_poster'] = '
Latest answer by: '.$a['lp_u'].'
'.$reltime.'
'; + }*/ + + if(!empty($a['a_latest'])){ + $reltime = \Lampcms\TimeAgo::format(new \DateTime($a['a_latest'][0]['t'])); + $a['last_poster'] = '
Latest answer by: '.$a['a_latest'][0]['u'].'
+ '.$reltime.'
'; } } - + protected static $vars = array( '_id' => '0', //1 'i_votes' => '0', //2 @@ -105,7 +111,6 @@ protected static function func(&$a){
%6$s
%9$s
-
%20$s %13$s
@@ -114,12 +119,10 @@ protected static function func(&$a){
%23$s
- -
'; -} \ No newline at end of file +} diff --git a/www/style/1/www/tplSearchForm.php b/www/style/1/www/tplSearchForm.php index d55906e..1fc6585 100644 --- a/www/style/1/www/tplSearchForm.php +++ b/www/style/1/www/tplSearchForm.php @@ -49,27 +49,27 @@ * */ - + class tplSearchForm extends \Lampcms\Template\Template { - - + + protected static function func(&$a){ - if(!empty($_REQUEST['q'])){ - $a['q'] = strip_tags($_REQUEST['q']); + if(!empty($_GET['q'])){ + $a['q'] = \filter_input(INPUT_GET, 'q', FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW); } } - + protected static $vars = array( 'q' => '' ); - + protected static $tpl = ' - '; - + } \ No newline at end of file diff --git a/www/style/1/www/tplSimquestions.php b/www/style/1/www/tplSimquestions.php index 15526c0..6eee031 100644 --- a/www/style/1/www/tplSimquestions.php +++ b/www/style/1/www/tplSimquestions.php @@ -54,6 +54,10 @@ class tplSimquestions extends Lampcms\Template\Template { + protected static function func(&$a){ + $a['intro'] = trim($a['intro']); + } + protected static $vars = array( 'qid' => '', 'url' => '', @@ -62,4 +66,4 @@ class tplSimquestions extends Lampcms\Template\Template 'hts' => ''); protected static $tpl = '
%4$s
%5$s
'; -} \ No newline at end of file +} diff --git a/www/update_comments.php b/www/update_comments.php new file mode 100644 index 0000000..ceda1d7 --- /dev/null +++ b/www/update_comments.php @@ -0,0 +1,80 @@ +LampCMS" + * The location of the link is not important, it can be in the footer of the page + * but it must not be hidden by style attibutes + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This product includes GeoLite data created by MaxMind, + * available from http://www.maxmind.com/ + * + * + * @author Dmitri Snytkine + * @copyright 2005-2011 (or current year) ExamNotes.net inc. + * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE (LGPL) version 3 + * @link http://www.lampcms.com Lampcms.com project + * @version Release: @package_version@ + * + * + */ + +/** + * Run this script once from browser + * ONLY if you upgrading from previous version + * and your Q&A site already has some "comments" + * + */ +include '../!inc.php'; +session_start(); + +use Lampcms\Registry; + +function update($collection = 'QUESTIONS'){ + global $oRegistry; + $coll = $oRegistry->Mongo->getCollection($collection); + $cur = $coll->find(array('comments' => array('$ne' => null)), array('_id', 'comments')); + echo 'Found: '.$cur->count(); + if($cur->count() > 0){ + foreach($cur as $row){ + $row['a_comments'] = $row['comments']; + + $coll->update(array('_id' => $row['_id']), array('$set' => array('a_comments' => $row['comments']), '$unset' => array('comments' => 1) ) ); //, '$unset' => array('comments' => 1) + } + } +} + +update(); +update('ANSWERS'); + +