Changeset 1070
- Timestamp:
- 11/14/09 18:02:23 (4 years ago)
- Location:
- taggingreloaded
- Files:
-
- 6 added
- 5 removed
- 18 modified
-
de.xml (modified) (2 diffs)
-
eventlistener.xml (modified) (1 diff)
-
files/acp (deleted)
-
files/js/TaggingReloaded.class.js (modified) (9 diffs)
-
files/lib/data (added)
-
files/lib/data/tag (added)
-
files/lib/data/tag/TagCloudWrapper.class.php (added)
-
files/lib/page/PublicSEORewriterTagging.class.php (deleted)
-
files/lib/page/TaggingPage.class.php (modified) (5 diffs)
-
files/lib/system/event/listener/TaggingReloadedMessageFormListener.class.php (modified) (9 diffs)
-
files/lib/system/event/listener/TaggingReloadedPageListener.class.php (modified) (8 diffs)
-
files/lib/util/TaggingReloadedUtil.class.php (modified) (1 diff)
-
install.sql (modified) (1 diff)
-
optionals/de.easy-coding.wbb.taggingreloaded/boardoptions.xml (deleted)
-
optionals/de.easy-coding.wbb.taggingreloaded/eventlistener.xml (modified) (1 diff)
-
optionals/de.easy-coding.wbb.taggingreloaded/files/lib/data (added)
-
optionals/de.easy-coding.wbb.taggingreloaded/files/lib/data/board (added)
-
optionals/de.easy-coding.wbb.taggingreloaded/files/lib/data/board/BoardListTaggingReloaded.class.php (added)
-
optionals/de.easy-coding.wbb.taggingreloaded/files/lib/system/cache/CacheBuilderTaggingReloadedBoards.class.php (modified) (3 diffs)
-
optionals/de.easy-coding.wbb.taggingreloaded/files/lib/system/event/listener/TaggingReloadedWBBMessageFormListener.class.php (modified) (3 diffs)
-
optionals/de.easy-coding.wbb.taggingreloaded/files/lib/system/event/listener/TaggingReloadedWBBPageListener.class.php (modified) (5 diffs)
-
optionals/de.easy-coding.wbb.taggingreloaded/package.xml (modified) (3 diffs)
-
optionals/de.easy-coding.wbb.taggingreloaded/update.sql (modified) (1 diff)
-
optionals/de.easy-coding.wcf.taggingreloaded.seo (deleted)
-
package.xml (modified) (1 diff)
-
templates/messageFormTaggingReloaded.tpl (modified) (3 diffs)
-
templates/taggingCloud.tpl (deleted)
-
templates/taggingCloudContainer.tpl (modified) (1 diff)
-
templates/taggingReloaded.tpl (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
taggingreloaded/de.xml
r1065 r1070 3 3 <language languagecode="de"> 4 4 <category name="wcf.taggingreloaded"> 5 <item name="wcf.taggingreloaded.topuser"><![CDATA[Top Benutzer]]></item> 6 <item name="wcf.taggingreloaded.expand"><![CDATA[WheelTags aktivieren]]></item> 5 7 <item name="wcf.taggingreloaded.tagging"><![CDATA[Tagging]]></item> 6 8 <item name="wcf.taggingreloaded.tags"><![CDATA[Tags]]></item> … … 13 15 <item name="wcf.taggingreloaded.add.instruction"><![CDATA[<b>Bedienung:</b> 14 16 <ul> 15 <li> FÃŒgen sie das Wort in die TagCloud hinzu, indem Sie nach nach der Eingabe auf den Buttn klicken.</li>16 <li> AnschlieÃend können Sie die Gewichtung Àndern, indem Sie das Mausrad ÃŒber dem Wort scrollen.</li>17 <li>Entfernen Sie Tags , indem Sie die Tags aus dem Kasten schieben.</li>17 <li>WheelTags landen in der Cloud, sobald Sie die Eingabe beendet haben.</li> 18 <li>In der Cloud können die Tags verschoben und die Gewichtung manipuliert werden. Nutzen Sie dazu das Mausrad ÃŒber dem Wort.</li> 19 <li>Entfernen Sie Tags wieder, indem Sie die Tags aus dem Kasten schieben.</li> 18 20 </ul>]]></item> 19 21 <item name="wcf.taggingreloaded.fetch"><![CDATA[Auto]]></item> -
taggingreloaded/eventlistener.xml
r50 r1070 18 18 <listenerclassfile>lib/system/event/listener/TaggingReloadedPageListener.class.php</listenerclassfile> 19 19 </eventlistener> 20 21 22 <eventlistener><!-- wcf: TaggedObjectsPage page //--> 23 <eventclassname>TaggedObjectsPage</eventclassname> 24 <eventname>assignVariables</eventname> 25 <listenerclassfile>lib/system/event/listener/TaggingReloadedPageListener.class.php</listenerclassfile> 26 </eventlistener> 27 <eventlistener> 28 <eventclassname>TaggedObjectsPage</eventclassname> 29 <eventname>readParameters</eventname> 30 <listenerclassfile>lib/system/event/listener/TaggingReloadedPageListener.class.php</listenerclassfile> 31 </eventlistener> 20 32 </import> 21 33 </data> -
taggingreloaded/files/js/TaggingReloaded.class.js
r1069 r1070 40 40 var posx = 0, posy = 0; 41 41 42 /** 43 * sets limits 44 * @param min -> minimum font size in percent 45 * @param max -> maximum font size in percent 42 // last type in 43 this.typed = 3; 44 this.typetimer = null; 45 46 /** 47 * set limits 48 * @param min integer minimum font size in percent 49 * @param max integer maximum font size in percent 46 50 */ 47 51 this.setLimits = function(min, max) { … … 52 56 /** 53 57 * registers input form.. every time stop sign is pressed, word is added to list 54 * @param ob 55 * @param nonincrease 58 * @param ob string 56 59 */ 57 60 this.register = function(ob) { 58 61 ob = document.getElementById(ob); 62 63 // register new event and keep existing events 59 64 this.callregister = ob.onkeyup; 60 tagging.formadd(ob);61 65 ob.onkeyup = function(e) { 66 tagging.typed = 3; 62 67 if(this.value.lastIndexOf(stopsign) > 0) { 63 68 return tagging.formadd(this, false); … … 66 71 tagging.callregister(e); 67 72 }; 73 74 // push input into bubble if you wait to long 75 tagging.typetimer = window.setInterval(function() { 76 if(tagging.typed-- == 0) { 77 tagging.formadd(ob, false); 78 } 79 }, 500); 80 81 // add tags from input field 82 tagging.formadd(ob); 68 83 }; 69 84 70 85 /** 71 86 * unregisters input form event listener.. 87 * @param ob string 72 88 */ 73 89 this.unregister = function(ob) { … … 75 91 ob.onkeyup = this.callregister; 76 92 93 // type timer 94 window.clearInterval(tagging.typetimer); 95 77 96 var x = []; 78 97 var elems = root.getElementsByTagName('div'); 79 98 for(var i=0; i<elems.length; i++) { 80 x.push(elems[i].firstChild.data) 81 } 99 x.push(elems[i].firstChild.data); 100 } 101 102 // transfer elements to input text field 82 103 ob.value = x.join(stopsign+" "); 83 104 }; … … 97 118 for(var j=0; j<elems.length; j++) { 98 119 if(elems[j] && elems[j].firstChild.data == val[i]) { 99 elems[j].firstChild.nextSibling.nextSibling.value = arith(elems[j].firstChild.nextSibling.nextSibling.value,+5); 100 elems[j].style.fontSize = elems[j].firstChild.nextSibling.nextSibling.value+'%'; 101 if(typeof nonhighlight === undefined) { 102 highlight(elems[j], timer.length); 120 var next = elems[j].firstChild.nextSibling.nextSibling; 121 next.value = arith(next.value, +5); 122 elems[j].style.fontSize = next.value+'%'; 123 if(typeof nonhighlight !== 'undefined') { 124 fade(elems[j], timer.length); 125 } 126 if(elems[j].style.display == 'none') { 127 this.place(elems[j]); 128 elems[j].style.display = 'block'; 103 129 } 104 130 val[i] = null; … … 106 132 } 107 133 if(val[i] != null) { 108 highlight(this.add(val[i], 100), timer.length);134 fade(this.add(val[i], 100, true), timer.length); 109 135 } 110 136 } 111 137 return false; 112 138 }; 139 140 /** 141 * move position 142 */ 143 this.place = function(div) { 144 div.style.left = Math.round(Math.random()*arith(root.offsetWidth)*0.8)+'px' 145 div.style.top = Math.round(Math.random()*arith(root.offsetHeight)*0.8)+'px'; 146 } 113 147 114 148 /** … … 118 152 * @return -> returs the generated div element (consisting hidden form data) 119 153 */ 120 this.add = function(val, size ) {154 this.add = function(val, size, visible) { 121 155 var div = document.createElement('div'); 122 div.onmouseover = function (event) { 156 div.onmouseover = function (event) { 123 157 scrollobject = this; 124 158 }; … … 130 164 }; 131 165 div.style.position = 'absolute'; 132 div.style.left = Math.round(Math.random()*arith(root.offsetWidth)*0.9)+'px'133 div.style.top = Math.round(Math.random()*arith(root.offsetHeight)*0.9)+'px';134 166 div.style.cursor = 'pointer'; 135 167 div.style.fontSize = size+'%'; 136 168 div.style.color = 'rgb(0,0,0)'; 137 169 div.appendChild(document.createTextNode(val)); 170 171 // place randomly 172 this.place(div); 173 174 // hide 175 if(!visible) { 176 div.style.display = 'none'; 177 } 138 178 139 179 var input = document.createElement('input'); … … 234 274 * @param i -> index of the interval timer array 235 275 */ 236 var highlight= function(ob, i) {276 var fade = function(ob, i) { 237 277 if(timer[i] == null) { 238 278 ob.style.color = 'rgb(255,0,0)'; 239 timer[i] = window.setInterval(function() { highlight(ob, i)}, 50); 279 timer[i] = window.setInterval(function() { 280 fade(ob, i); 281 }, 50); 240 282 } else { 241 283 var tmp = rgbdec(ob.style.color); -
taggingreloaded/files/lib/page/TaggingPage.class.php
r805 r1070 1 1 <?php 2 require_once(WCF_DIR.'lib/page/AbstractPage.class.php'); 3 require_once(WCF_DIR.'lib/page/PublicSEORewriterTagging.class.php'); 2 require_once(WCF_DIR.'lib/page/SortablePage.class.php'); 4 3 5 4 /** … … 10 9 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-3.0.html> 11 10 */ 12 class TaggingPage extends AbstractPage { 13 public $templateName = 'taggingReloaded'; 14 protected $rewriter; 15 protected $tag; 11 class TaggingPage extends SortablePage { 12 public $templateName = 'taggingReloaded'; 13 public $itemsPerPage = 10; 14 15 /** 16 * @var array<Tag> 17 */ 16 18 protected $tags = array(); 17 19 18 20 /** 19 * @see Page::readParameters()21 * @see MultipleLinkPage::countItems() 20 22 */ 21 public function readParameters() { 22 parent::readParameters(); 23 public function countItems() { 24 parent::countItems(); 25 $wrapper = new TagCloud(); 26 $this->tags = $wrapper->getTags(500); 23 27 24 if(isset($_GET['tag'])) $this->tag = $_GET['tag']; 25 26 $this->rewriter = new PublicSEORewriterTagging(); 28 return count($this->tags); 27 29 } 28 30 … … 33 35 parent::readData(); 34 36 35 if(empty($this->tag)) { 36 $minLifetime = 0; 37 $maxLifetime = 43200; // 12h 38 39 WCF::getCache()->addResource( 40 'taggingreloaded.wcf.taggingpage', 41 WCF_DIR.'cache/cache.taggingreloaded.wcf.taggingpage.php', 42 WCF_DIR.'lib/system/cache/CacheBuilderTaggingReloadedTagging.class.php', 43 $minLifetime, 44 $maxLifetime 45 ); 46 47 $this->tags = WCF::getCache()->get('taggingreloaded.wcf.taggingpage'); 48 } 37 $wrapper = new TagCloud(); 38 $tags = $wrapper->getTags(500); 39 40 $tags = TaggingReloadedUtil::sort($tags, 'counter', 'DESC'); 41 $tags = array_slice($tags, ($this->pageNo - 1) * $this->itemsPerPage, $this->itemsPerPage); 42 43 // slice 44 $this->tags = TaggingReloadedUtil::sort($tags, 'name'); 45 46 // and the users 47 $this->top_user = $this->getTopUser(); 49 48 } 50 49 … … 54 53 public function assignVariables() { 55 54 parent::assignVariables(); 56 57 if(empty($this->tag)) {58 $title = WCF::getLanguage()->get('wcf.header.menu.tagging');59 $headline = WCF::getLanguage()->get('wcf.taggingreloaded.tagging');60 $desc = WCF::getLanguage()->get('wcf.taggingreloaded.description');61 } else {62 $title = $this->tag . ' - ' . WCF::getLanguage()->get('wcf.header.menu.tagging');63 $headline = $this->tag;64 $desc = WCF::getLanguage()->get('wcf.taggingreloaded.tagging');65 66 WCF::getTPL()->append('additionalTaggingBreadCrumbs', '<li><a href="index.php?page=Tagging'.SID_ARG_2ND.'">'.67 '<img src="'.RELATIVE_WCF_DIR.'icon/tagging16.png" alt="" /> '.68 '<span>'.WCF::getLanguage()->get('wcf.header.menu.tagging').'</span></a> »</li>');69 }70 55 71 56 WCF::getTPL()->assign(array( 72 'title' => $title, 73 'headline' => $headline, 74 'desc' => $desc, 57 'allowSpidersToIndexThisPage' => true, 75 58 'tags' => $this->tags, 76 'allowSpidersToIndexThisPage' => true 77 )); 78 79 WCF::getTPL()->append('additionalTaggingContents', WCF::getTPL()->fetch('taggingCloud')); 80 WCF::getTPL()->append('specialStyles', '<link rel="stylesheet" type="text/css" href="'.RELATIVE_WCF_DIR.'style/taggingreloaded.css" />'); 59 'top_user' => $this->top_user, 60 )); 81 61 } 82 62 … … 85 65 */ 86 66 public function show() { 87 // switched from non-seo to seo?88 if(defined('SEO_ENABLE') && SEO_ENABLE && defined('SEO_REWRITE_TAGGING') && SEO_REWRITE_TAGGING && strpos($_SERVER['REQUEST_URI'], '&tag=') !== false) {89 $url = $this->rewriter->publicParseTagURLs($_GET['tag']);90 header("HTTP/1.1 301 Moved Permanently");91 header("location: $url");92 exit;93 }94 95 67 // set active header menu item 96 require_once(WCF_DIR.'lib/page/util/menu/ HeaderMenu.class.php');97 HeaderMenu::setActiveMenuItem('wcf.header.menu.tagging');68 require_once(WCF_DIR.'lib/page/util/menu/PageMenu.class.php'); 69 PageMenu::setActiveMenuItem('wcf.header.menu.tagging'); 98 70 99 71 parent::show(); 100 72 } 73 74 public function getTopUser($limit = 20) { 75 $sql = "SELECT user.*, 76 counter 77 FROM ( 78 SELECT userID, 79 COUNT(userID) AS counter 80 FROM wcf".WCF_N."_tagging3 81 GROUP BY userID 82 ORDER BY counter DESC 83 ) x 84 INNER JOIN wcf".WCF_N."_user user USING(userID) 85 LIMIT ".intval($limit); 86 $result = WCF::getDB()->sendQuery($sql); 87 88 $user = array(); 89 while ($row = WCF::getDB()->fetchArray($result)) { 90 $user[] = new UserProfile(null, $row); 91 } 92 return $user; 93 94 } 101 95 } 102 96 ?> -
taggingreloaded/files/lib/system/event/listener/TaggingReloadedMessageFormListener.class.php
r1069 r1070 2 2 // wcf imports 3 3 require_once(WCF_DIR.'lib/system/event/EventListener.class.php'); 4 require_once(WCF_DIR.'lib/data/tag/Tag.class.php'); 5 require_once(WCF_DIR.'lib/data/tag/TagEngine.class.php'); 6 7 // tagging3 imports 8 require_once(WCF_DIR.'lib/util/TaggingReloadedUtil.class.php'); 4 9 5 10 /** … … 14 19 protected $className; 15 20 protected $tags3 = array(); 16 protected $tags = ''; 21 22 /** 23 * @var Tag[] 24 */ 25 protected $existingTagsUser = array(); 26 27 /** 28 * @var Tag[] 29 */ 30 protected $existingTagsObject = array(); 17 31 18 32 /** … … 20 34 */ 21 35 public function execute($eventObj, $className, $eventName) { 36 37 // enabled? 38 if(!MODULE_TAGGING) { 39 return; 40 } 41 22 42 $this->eventObj = $eventObj; 23 43 $this->className = $className; … … 34 54 WCF::getTPL()->assign(array( 35 55 'tags3' => $this->tags3, 36 'tags' => $this-> tags56 'tags' => $this->eventObj->tags 37 57 )); 38 39 // button40 $tab = '<li id="taggingTab"><a onclick="tabbedPane.openTab(\'tagging\');">'.WCF::getLanguage()->get('wcf.taggingreloaded.tagging').'</a></li>';41 58 42 59 WCF::getTPL()->append('specialStyles', '<script type="text/javascript" src="'.RELATIVE_WCF_DIR.'js/TaggingReloaded.class.js"></script>'); … … 45 62 46 63 /** 47 * @see Form:: validate()64 * @see Form::readFormParameters() 48 65 */ 49 protected function validate() {50 66 protected function readFormParameters() { 67 51 68 // handle reloaded input 52 69 if (isset($_REQUEST['taggingname']) && isset($_REQUEST['taggingval'])) { … … 79 96 )); 80 97 } 81 82 // classic input 83 $this->eventObj->tags = $this->tags = TaggingUtil::buildString(array_keys($this->tags3)); 84 } 85 86 87 /** 88 * this is a reimplementation of TagEngine::deleteObjectTags with a slite modification to write to our table 89 */ 90 public function tagEngineDeleteObjectTags($userID, Tagged $object, $languageIDArray = array()) { 91 if (!count($languageIDArray)) $languageIDArray = array(0); 98 } 99 100 /** 101 * workaround to be called after readFormData from woltlab 102 * @see Form::validate() 103 */ 104 protected function validate () { 105 $this->eventObj->tags = TaggingUtil::buildString(array_keys($this->tags3)); 106 } 107 108 public function deleteFromTagging3($userID, $tags, Tagged $object, $languageIDArray) { 109 if(!count($tags)) return; 110 111 $ids = array(); 112 foreach($tags as $tag) { 113 $ids[] = $tag->tagID; 114 } 115 92 116 $sql = "DELETE FROM wcf".WCF_N."_tagging3 93 117 WHERE taggableID = ".$object->getTaggable()->getTaggableID()." 94 118 AND languageID IN (".implode(',', $languageIDArray).") 95 119 AND objectID = ".$object->getObjectID()." 96 AND userID = ".$userID; 120 AND tagID IN (".implode(",", $ids).") 121 AND userID = ".intval($userID); 97 122 WCF::getDB()->sendQuery($sql); 98 } 99 100 /** 101 * this is a reimplementation of TagEngine::addTags with a slite modification to write to our table 102 */ 103 public function tagEngineAddTags($userID, $tags, Tagged $object, $languageID = 0) { 104 // skip empty 105 if(!count($tags)) return; 106 107 error_log('save combined ('.count($tags).'): '.serialize($tags)); 108 109 // store original database 110 TagEngine::getInstance()->addTags(array_keys($tags), $object, $languageID); 123 $result = WCF::getDB()->sendQuery($sql); 124 error_log(__METHOD__.$sql); 125 } 126 127 /** 128 * this is a reimplemenation of TagEngine::deleteObjectTags with a slite modification. 129 * not all tags are deleted. just some tagIDs 130 */ 131 public function deleteFromSystem($tags, Tagged $object, $languageIDArray) { 132 if(!count($tags)) return; 133 134 $ids = array(); 135 foreach($tags as $tag) { 136 $ids[] = $tag->tagID; 137 } 138 139 $sql = "DELETE FROM wcf".WCF_N."_tag_to_object 140 WHERE taggableID = ".$object->getTaggable()->getTaggableID()." 141 AND languageID IN (".implode(',', $languageIDArray).") 142 AND objectID = ".$object->getObjectID()." 143 AND tagID IN (".implode(",", $ids).")"; 144 WCF::getDB()->sendQuery($sql); 145 $result = WCF::getDB()->sendQuery($sql); 146 error_log(__METHOD__.$sql); 147 } 148 149 public function addToSystem($tags, Tagged $object, $languageID) { 150 if(!count($tags)) return; 151 152 $tags = array_keys($tags); 153 error_log(__METHOD__.print_r($tags, 1)); 154 TagEngine::getInstance()->addTags($tags, $object, $languageID); 155 } 156 157 /** 158 * this is a reimplementation of TagEngine::addTags with a slite modification to write to our table and to update existing tables with weight 159 */ 160 public function addToTagging3($userID, $tags, Tagged $object, $languageID) { 161 if(!count($tags)) return; 111 162 112 163 // store tagging3 database 113 164 $tagIDs = array(); 114 165 foreach ($tags as $tag) { 115 $tagID = Tag::test($tag->name, $languageID);166 $tagID = $tag->tagID ? $tag->tagID : Tag::test($tag->name, $languageID); 116 167 if (!$tagID) $tagID = Tag::insert($tag->name, $languageID); 117 168 … … 120 171 } 121 172 122 $sql = " INSERTINTO wcf".WCF_N."_tagging3173 $sql = "REPLACE INTO wcf".WCF_N."_tagging3 123 174 (objectID, tagID, taggableID, languageID, userID, weight) 124 175 VALUES "; 125 176 foreach ($tagIDs as $tagID => $tag) { 126 $sql .= "(" . $object->getObjectID() . ", " . $tagID . ", " . $object->getTaggable()->getTaggableID() . ", ".$languageID.", ".$tag->userID.", ".$tag->weight."),"; 177 $sql .= "(" . $object->getObjectID() . ", " . $tagID . ", " . $object->getTaggable()->getTaggableID() . ", 178 ".$languageID.", ".$tag->userID.", ".$tag->weight."),"; 127 179 } 128 180 $sql = StringUtil::substring($sql, 0, StringUtil::length($sql) - 1); 129 181 $result = WCF::getDB()->sendQuery($sql); 182 error_log(__METHOD__.$sql); 183 } 184 185 /** 186 * save 187 */ 188 protected function tagging3Save($userID, $tagged, $languageID) { 189 $languageIDArray = array($languageID); 190 191 // remove old links 192 if(count($this->existingTagsUser) || count($this->existingTagsObject)) { 193 194 // tagging3: remove the link to the current user 195 $deleteFromTagging3 = TaggingReloadedUtil::diff($this->existingTagsUser, $this->tags3); 196 197 // woltlab system: when no other user has this tag, remove the link from tag to object 198 $deleteFromSystem = TaggingReloadedUtil::diff($this->existingTagsObject, $deleteFromTagging3); 199 200 // tagging3: ids are known.. delete is easy 201 $this->deleteFromTagging3($userID, $deleteFromTagging3, $tagged, $languageIDArray); 202 203 // woltlab system: ids are known.. delete is easy 204 $this->deleteFromSystem($deleteFromSystem, $tagged, $languageIDArray); 205 } 206 207 // tagging3: add user specific tags 208 $addToTagging3 = TaggingReloadedUtil::diff($this->tags3, $this->existingTagsUser); 209 210 // woltlab system: add link from tag to object 211 $addToSystem = TaggingReloadedUtil::diff($addToTagging3, $this->existingTagsObject); 212 213 // woltlab system: save 214 $this->addToSystem($addToSystem, $tagged, $languageID); 215 216 // tagging3: save 217 // attention: use $this->tags3 instead of the diff, because we want to update all weights 218 $this->addToTagging3($userID, $this->tags3, $tagged, $languageID); 219 } 220 221 /** 222 * load the existing tags of this user from database right before being saved 223 * in save method, we will watch for for this variable 224 */ 225 protected function tagging3Load($userID, $tagged, $languageID) { 226 $this->existingTagsUser = $this->getTagsByObject($userID, $tagged, $languageID, true); 227 $this->existingTagsObject = $this->getTagsByObject($userID, $tagged, $languageID, false); 130 228 } 131 229 … … 133 231 * @return Tag[] 134 232 */ 135 protected function getTagsByObject($userID, Tagged $tagged, $ threadID, $languageID, $even = true) {233 protected function getTagsByObject($userID, Tagged $tagged, $languageID, $even = true) { 136 234 $taggableID = $tagged->getTaggable()->getTaggableID(); 235 $objectID = $tagged->getObjectID(); 137 236 $sign = $even ? '=' : '!='; 138 237 139 238 $sql = "SELECT tag.*, 140 userID 239 userID, 141 240 weight 142 241 FROM ( … … 147 246 WHERE taggableID = ".intval($taggableID)." 148 247 AND languageID = ".intval($languageID)." 149 AND objectID = ".intval($ threadID)."248 AND objectID = ".intval($objectID)." 150 249 AND userID $sign ".intval($userID)." 151 250 ) x -
taggingreloaded/files/lib/system/event/listener/TaggingReloadedPageListener.class.php
r1065 r1070 3 3 require_once(WCF_DIR.'lib/system/event/EventListener.class.php'); 4 4 5 // tagging imports 6 require_once(WCF_DIR.'lib/util/TaggingReloadedUtil.class.php'); 7 8 // seo imports 9 require_once(WCF_DIR.'lib/page/PublicSEORewriterTagging.class.php'); 5 // taggin imports 6 require_once(WCF_DIR.'lib/data/tag/Tag.class.php'); 7 require_once(WCF_DIR.'lib/data/tag/TagEngine.class.php'); 8 require_once(WCF_DIR.'lib/data/tag/TagCloudWrapper.class.php'); 10 9 11 10 /** … … 17 16 */ 18 17 class TaggingReloadedPageListener implements EventListener { 19 protected static $constructed = false; 18 // base 19 protected $eventObj; 20 protected $className; 20 21 21 protected $userID=0; // params22 23 22 // data 24 23 protected $tags = array(); 25 24 26 protected $eventObj; 27 protected $className; 28 protected $rewriter; 25 // params 26 protected $userID = 0; 29 27 30 28 /** … … 32 30 */ 33 31 public function execute($eventObj, $className, $eventName) { 34 35 32 $this->eventObj = $eventObj; 36 33 $this->className = $className; 37 34 38 35 if(method_exists($this, $eventName)) { 39 $this->rewriter = new PublicSEORewriterTagging();40 36 $this->$eventName(); 41 37 } … … 43 39 44 40 /** 45 * reads by user41 * reads threads by user 46 42 */ 47 protected function queryTagsByUser() { 48 // order by weight and cut 49 $sql = "SELECT tag, 50 SUM(weight) AS weight 51 FROM wcf".WCF_N."_taggingreloaded wcf 52 WHERE userID = ".$this->userID." 53 GROUP BY tag 54 ORDER BY weight DESC 55 LIMIT 50"; 43 protected function queryTagsByUser($userID) { 44 $outer_limit = 50; 56 45 46 $sql = "SELECT tag.*, 47 userID, 48 counter 49 FROM ( 50 SELECT tagID, 51 userID, 52 SUM(weight) AS counter 53 FROM wcf".WCF_N."_tagging3 54 WHERE userID = ".intval($userID)." 55 56 GROUP BY tagID 57 ORDER BY counter DESC 58 ) x 59 INNER JOIN wcf".WCF_N."_tag tag USING(tagID) 60 LIMIT ".intval($outer_limit); 57 61 return $sql; 62 } 63 64 protected function fromQuery($sql) { 65 $result = WCF::getDB()->sendQuery($sql); 66 while ($row = WCF::getDB()->fetchArray($result)) { 67 $this->tags[$row['name']] = new Tag(null, $row); 68 } 69 70 // optional beautify 71 $wrapper = new TagCloudWrapper($this->tags); 72 $this->tags = $wrapper->getTags(); 58 73 } 59 74 … … 62 77 */ 63 78 protected function readData () { 64 if($this->userID) { 65 $sql = $this->queryTagsByUser(); 79 switch($this->className) { 80 case 'UserPage': 81 $userID = $this->eventObj->frame->getUser()->userID; 82 83 $this->fromQuery($this->queryTagsByUser($userID)); 84 break; 66 85 } 67 68 // break69 if(!isset($sql)) return;70 71 // order by tag72 $sql = "SELECT tag,weight FROM ($sql) A ORDER BY tag ASC";73 74 // query75 $result = WCF::getDB()->sendQuery($sql);76 while ($row = WCF::getDB()->fetchArray($result)) {77 $this->tags[$row['tag']] = array(78 'weight'=> $row['weight'],79 'color'=> 0,80 'size'=> 0,81 'url' => $this->rewriter->publicParseTagURLs($row['tag'])82 );83 }84 85 $this->tags = TaggingReloadedUtil::beautify($this->tags);86 86 } 87 87 88 88 /** 89 * @see Page::read Parameters()89 * @see Page::readData() 90 90 */ 91 91 protected function readParameters () { 92 if (isset($_GET['userID'])) $this->userID = intval($_GET['userID']); 92 switch($this->className) { 93 case 'TaggedObjectsPage': 94 $name = substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], '/')+1); 95 96 $sql = "SELECT tagID 97 FROM wcf".WCF_N."_tag 98 WHERE name = '".escapeString($name)."'"; 99 $result = WCF::getDB()->getFirstRow($sql); 100 if (isset($result['tagID'])) { 101 $_REQUEST['tagID'] = intval($result['tagID']); 102 } 103 break; 104 } 93 105 } 94 106 … … 96 108 * creates a new tagging category 97 109 */ 98 protected function assign Category() {110 protected function assignUser () { 99 111 $tpl = '<div class="userProfileContent"> 100 112 <div class="border"> 101 113 <div class="containerHead"> 102 <div class="containerIcon"><img src="'.RELATIVE_WCF_DIR.'icon/tagging24.png" alt="" /> </div> 103 <h3 class="containerContent">'.WCF::getLanguage()->get('wcf.user.option.category.tagging').'</h3> 114 <h3>'.WCF::getLanguage()->get('wcf.user.option.category.tagging').'</h3> 104 115 </div> 105 116 <div class="container-1"> … … 108 119 </div> 109 120 </div>'; 110 WCF::getTPL()->append('additional Contents3', $tpl);121 WCF::getTPL()->append('additionalBoxes1', $tpl); 111 122 112 123 } … … 116 127 */ 117 128 protected function assignVariables () { 129 WCF::getTPL()->assign('tags3', $this->tags); 118 130 if(count($this->tags) > 0) { 119 WCF::getTPL()->assign('tags', $this->tags);120 $this->assignCategory();121 131 WCF::getTPL()->append('specialStyles', '<link rel="stylesheet" type="text/css" href="'.RELATIVE_WCF_DIR.'style/taggingreloaded.css" />'); 132 } 133 134 switch($this->className) { 135 case 'UserPage': 136 $this->assignUser(); 137 break; 138 case 'TaggedObjectsPage': 139 case 'TaggingPage': 140 141 $hash = md5(PAGE_URL); 142 if(in_array($hash[0], range(1,5))) { 143 $text = 'powered by easy-coding'; 144 $url = 'http://www.easy-coding.de'; 145 } else if(in_array($hash[0], range(6,9))) { 146 $text = 'Wheeltags by easy-coding'; 147 $url = 'http://trac.easy-coding.de/trac/wcf/wiki/taggingreloaded'; 148 } else { 149 $text = 'easy-coding.de Projekt'; 150 $url = 'http://www.easy-coding.de/index.php?page=ProjectList'; 151 } 152 153 154 // powered by easy-coding 155 WCF::getTPL()->append('additionalFooterContents', '<div style="text-align:right;margin:10px"> 156 <a href="'.$url.'">'.$text.'</a> 157 </div>'); 158 159 break; 122 160 } 123 161 } -
taggingreloaded/files/lib/util/TaggingReloadedUtil.class.php
r1064 r1070 8 8 */ 9 9 class TaggingReloadedUtil { 10 protected static $systemTaggerUserID = 0; // unsigned int11 10 12 11 /** 13 * removes bbcodes and adds some tagging optimizations (just for tagging usage) 14 * @param text 15 * @return text 12 * returns difference between two lists full of tag objects 13 * 14 * @param array<Tag> $list1 15 * @param array<Tag> $list2 16 16 */ 17 public static function bbcode2text($text) { 18 // if there is bold text, then repeat the words 19 $text = preg_replace('/\[b\](.+)\[\/b\]/', '$1 $1', $text); 20 21 // make pseudo html 22 $text = str_replace(array('[',']'), array('<','>'), $text); 23 24 // strip this html 25 $text = strip_tags($text); 26 27 return $text; 17 public static function diff($list1, $list2) { 18 $diff = array_diff(array_keys($list1), array_keys($list2)); 19 foreach($list1 as $key => $val) { 20 if(!in_array($key, $diff)) { 21 unset($list1[$key]); 22 } 23 } 24 return $list1; 28 25 } 29 26 30 27 /** 31 * gets a text and returns a list of weighted tags 32 * @param text 33 * @param limit (optional) 34 * @return array 28 * begins with biggest value 35 29 */ 36 public static function text2tags($text, $limit=15) { 37 $tags = array(); 38 39 // find words beginning with capitals 40 // TODO: does this make any sense? just germany uses big capitals!? what about the umlauts? 41 preg_match_all('/([A-Z][\wÀöÌÃÃÃÃ]+)/e', $text, $words); 42 43 // lower them 44 $words = array_map(create_function('$a','return trim(strtolower($a));'), $words[1]); 45 46 // search weights 47 foreach($words as $word) { 48 $tags[$word] = array_key_exists($word, $tags) ? $tags[$word]+1 : 1; 30 public static function sort($result, $column = 'counter', $sort = 'ASC') { 31 uasort($result, "TaggingReloadedUtil::sort".ucfirst($column)); 32 if($sort == 'DESC') { 33 arsort($result); 49 34 } 50 51 // limit to words with minimum one occurent 52 $tags = array_filter($tags, create_function('$a','return $a > 1;')); 53 54 // sort by weight 55 arsort($tags); 56 57 // extend to beautify 58 array_walk($tags, create_function('&$val, $key','$val = array("weight"=>$val*100);')); 59 60 // we just allow a range between 100 and 250 61 $tags = self::beautify($tags, 100, 250); 62 63 // replace weights with sizes 64 array_walk($tags, create_function('&$val, $key','$val = $val["size"];')); 65 66 // cut it off - TODO: think of first cutting, then beautifying 67 $tags = array_slice($tags, 0, $limit); 68 69 return $tags; 35 return $result; 70 36 } 71 37 72 /** 73 * beautify tags with size and color 74 * 75 * @param tags 76 * @param minsize -> optional 77 * @param maxsize -> optional 78 * @return array 79 */ 80 public static function beautify($tags, $forced_minsize=null, $forced_maxsize=null) { 81 $minsize = $forced_minsize === null ? 75 : $forced_minsize; 82 $maxsize = $forced_maxsize === null ? 275 : $forced_maxsize; 83 $mincolor = 75; 84 $maxcolor = 255; 38 public static function sortCounter($a, $b) { 39 $key = 'counter'; 40 if ($a->$key == $b->$key) { 41 return 0; 42 } 43 return ($a->$key > $b->$key) ? 1 : -1; 44 } 85 45 86 $min = array_reduce($tags, create_function('$a, $b', 'return $a !== null && $a < $b["weight"] ? $a : $b["weight"];')); 87 $max = array_reduce($tags, create_function('$a, $b', 'return $a !== null && $a > $b["weight"] ? $a : $b["weight"];')); 88 89 if($min == $max) { 90 $max = $max + 1; 46 public static function sortName($a, $b) { 47 $key = 'name'; 48 if ($a->$key == $b->$key) { 49 return 0; 91 50 } 92 93 if($forced_minsize === null && $min < $minsize) { 94 $minsize = $min; 95 } 96 97 if($forced_maxsize === null && $max < $maxsize) { 98 $maxsize = $max; 99 } 100 101 $b = $maxsize -($max * ($maxsize-$minsize) / ($max-$min)); 102 $c = $maxcolor -($max * ($maxcolor-$mincolor) / ($max-$min)); 103 104 $tmp = array(); 105 foreach($tags as $key => $tag) { 106 $key = strtolower($key); 107 $size = ($tag['weight'] * ($maxsize-$minsize) / ($max-$min) )+$b; 108 $color = ($tag['weight'] * ($maxcolor-$mincolor) / ($max-$min) )+$c; 109 110 $tmp[$key] = $tag; 111 $tmp[$key]['weight'] = $tag['weight']; 112 $tmp[$key]['color'] = intval($color); 113 $tmp[$key]['size'] = intval($size); 114 } 115 return $tmp; 51 return ($a->$key > $b->$key) ? 1 : -1; 116 52 } 117 53 } -
taggingreloaded/install.sql
r1064 r1070 1 1 CREATE TABLE IF NOT EXISTS `wcf1_tagging3` ( 2 `objectID` int(10) NOT NULL DEFAULT '0',3 `tagID` int(10) NOT NULL DEFAULT '0',4 `taggableID` int(10) NOT NULL DEFAULT '0',5 `languageID` int(10) NOT NULL DEFAULT '0',6 `userID` int(10) NOT NULL DEFAULT '0',7 `weight` tinyint( 2)NOT NULL DEFAULT '0',2 `objectID` int(10) UNSIGNED NOT NULL DEFAULT '0', 3 `tagID` int(10) UNSIGNED NOT NULL DEFAULT '0', 4 `taggableID` int(10) UNSIGNED NOT NULL DEFAULT '0', 5 `languageID` int(10) UNSIGNED NOT NULL DEFAULT '0', 6 `userID` int(10) UNSIGNED NOT NULL DEFAULT '0', 7 `weight` tinyint(1) UNSIGNED NOT NULL DEFAULT '0', 8 8 PRIMARY KEY (`taggableID`,`languageID`,`objectID`,`userID`,`tagID`), 9 9 KEY `taggableID` (`taggableID`,`languageID`,`tagID`), -
taggingreloaded/optionals/de.easy-coding.wbb.taggingreloaded/eventlistener.xml
r1065 r1070 48 48 <eventlistener> 49 49 <eventclassname>ThreadAddForm</eventclassname> 50 <eventname>readFormParameters</eventname> 51 <inherit>1</inherit> 52 <listenerclassfile>lib/system/event/listener/TaggingReloadedWBBMessageFormListener.class.php</listenerclassfile> 53 </eventlistener> 54 <eventlistener> 55 <eventclassname>ThreadAddForm</eventclassname> 50 56 <eventname>assignVariables</eventname> 51 57 <inherit>1</inherit> -
taggingreloaded/optionals/de.easy-coding.wbb.taggingreloaded/files/lib/system/cache/CacheBuilderTaggingReloadedBoards.class.php
r805 r1070 2 2 // WCF include 3 3 require_once(WCF_DIR.'lib/system/cache/CacheBuilder.class.php'); 4 5 // Tagging imports 6 require_once(WCF_DIR.'lib/util/TaggingReloadedUtil.class.php'); 7 require_once(WBB_DIR.'lib/data/board/BoardListTaggingReloaded.class.php'); 8 9 // seo imports 10 require_once(WCF_DIR.'lib/page/PublicSEORewriterTagging.class.php'); 4 require_once(WCF_DIR.'lib/data/tag/Tag.class.php'); 5 require_once(WCF_DIR.'lib/data/tag/TagEngine.class.php'); 11 6 12 7 /** 13 * cache 8 * cache for boards 14 9 * 15 10 * @author Torben Brodt … … 18 13 */ 19 14 class CacheBuilderTaggingReloadedBoards implements CacheBuilder { 20 protected $rewriter; 15 /** 16 * @var integer 17 */ 18 protected $inner_limit = 1000; 19 20 21 /** 22 * @var integer 23 */ 24 protected $outer_limit = 100; 21 25 22 26 /** … … 24 28 */ 25 29 public function getData($cacheResource) { 26 $this->rewriter = new PublicSEORewriterTagging(); 27 $tmp = explode(".",$cacheResource['cache']); 28 $boardIDs = substr($cacheResource['cache'], strrpos($cacheResource['cache'],".")+1); 30 list($cache, $packageID, $languageIDs, $boardIDs) = explode('-', $cacheResource['cache']); 31 32 $tagged = new TaggedThread(null, array( 33 'taggable' => TagEngine::getInstance()->getTaggable('com.woltlab.wbb.thread') 34 )); 29 35 30 // order by weight and cut 31 $sql = "SELECT tag, 32 SUM(weight) AS weight 33 FROM wcf".WCF_N."_taggingreloaded wcf 34 NATURAL JOIN wbb".WBB_N."_taggingreloaded wbb 35 JOIN wbb".WBB_N."_post p 36 ON wbb.postID = p.postID 37 JOIN wbb".WBB_N."_thread t 38 ON p.threadID = t.threadID 39 WHERE t.boardID IN (".$boardIDs.") 40 GROUP BY tag 41 ORDER BY weight DESC 42 LIMIT 50"; 43 44 return $this->query($sql); 45 } 36 $taggableID = $tagged->getTaggable()->getTaggableID(); 46 37 47 /** 48 * 49 * @param sql 50 */ 51 protected function query($sql) { 52 $data = array(); 53 54 // order by tag 55 $sql = "SELECT tag,weight FROM ($sql) A ORDER BY tag ASC"; 38 $sql = "SELECT tag.*, 39 counter 40 FROM ( 41 SELECT tagID, 42 SUM(weight) AS counter 43 FROM ( 44 -- take the last threads 45 SELECT threadID AS objectID 46 FROM wbb".WBB_N."_thread 47 WHERE boardID IN (".$boardIDs.") 48 ORDER BY threadID DESC 49 LIMIT ".$this->inner_limit." 50 ) x 51 INNER JOIN wcf".WCF_N."_tagging3 USING(objectID) 52 WHERE taggableID = ".intval($taggableID)." 53 AND languageID IN (".$languageIDs.") 54 GROUP BY tagID 55 ORDER BY counter DESC 56 ) x 57 INNER JOIN wcf".WCF_N."_tag tag USING(tagID) 58 LIMIT ".intval($this->outer_limit); 56 59 57 // query60 $tags = array(); 58 61 $result = WCF::getDB()->sendQuery($sql); 59 62 while ($row = WCF::getDB()->fetchArray($result)) { 60 $data[$row['tag']] = array( 61 'weight'=> $row['weight'], 62 'color'=> 0, 63 'size'=> 0, 64 'url' => $this->rewriter->publicParseTagURLs($row['tag']) 65 66 ); 63 $tags[$row['name']] = new Tag(null, $row); 67 64 } 68 69 $data = TaggingReloadedUtil::beautify($data); 70 71 return $data; 65 return $tags; 72 66 } 73 67 } -
taggingreloaded/optionals/de.easy-coding.wbb.taggingreloaded/files/lib/system/event/listener/TaggingReloadedWBBMessageFormListener.class.php
r1067 r1070 1 1 <?php 2 // wcf imports3 require_once(WCF_DIR.'lib/system/event/EventListener.class.php');4 require_once(WCF_DIR.'lib/data/tag/Tag.class.php');5 require_once(WCF_DIR.'lib/data/tag/TagEngine.class.php');6 7 2 // wbb imports 8 require_once(WBB_DIR.'lib/ form/PostEditForm.class.php');3 require_once(WBB_DIR.'lib/data/board/Board.class.php'); 9 4 require_once(WBB_DIR.'lib/data/thread/TaggedThread.class.php'); 10 5 11 // tagging 3imports6 // tagging imports 12 7 require_once(WCF_DIR.'lib/util/TaggingReloadedUtil.class.php'); 13 require_once(WCF_DIR.'lib/system/event/listener/TaggingReloadedMessageFormListener.class.php'); 8 require_once(WCF_DIR.'lib/system/event/listener/TaggingReloadedPageListener.class.php'); 9 require_once(WBB_DIR.'lib/data/board/BoardListTaggingReloaded.class.php'); 10 11 // seo imports 12 require_once(WCF_DIR.'lib/page/PublicSEORewriterTagging.class.php'); 14 13 15 14 /** 16 * Displays the ta b for tagging15 * Displays the tags for threads/boards 17 16 * 18 17 * @author Torben Brodt … … 20 19 * @license GNU General Public License <http://opensource.org/licenses/gpl-3.0.html> 21 20 */ 22 class TaggingReloadedWBBMessageFormListener extends TaggingReloadedMessageFormListener { 21 class TaggingReloadedWBBPageListener extends TaggingReloadedPageListener { 22 23 23 /** 24 * @var Tag[]24 * reads threads by tag 25 25 */ 26 protected $existingTags = array(); 26 protected function queryTagsByThread(Tagged $tagged, $languageID) { 27 $taggableID = $tagged->getTaggable()->getTaggableID(); 28 $objectID = $tagged->getObjectID(); 29 30 $sql = "SELECT tag.*, 31 userID, 32 weight AS counter 33 FROM ( 34 SELECT tagID, 35 userID, 36 weight 37 FROM wcf".WCF_N."_tagging3 38 WHERE taggableID = ".intval($taggableID)." 39 AND languageID = ".intval($languageID)." 40 AND objectID = ".intval($objectID)." 41 ) x 42 INNER JOIN wcf".WCF_N."_tag tag USING(tagID) 43 "; 44 return $sql; 45 } 46 47 /** 48 * @see Page::readData() 49 */ 50 protected function readData () { 51 parent::readData(); 52 53 switch($this->className) { 54 case 'ThreadPage': 55 $threadID = $this->eventObj->thread->threadID; 56 $languageID = $this->eventObj->thread->languageID; 57 58 $tagged = new TaggedThread(null, array( 59 'threadID' => $threadID, 60 'taggable' => TagEngine::getInstance()->getTaggable('com.woltlab.wbb.thread') 61 )); 62 63 $this->fromQuery($this->queryTagsByThread($tagged, $languageID)); 64 break; 65 case 'BoardPage': 66 $boardID = $this->eventObj->boardID; 67 $languageID = $this->eventObj->languageID; 68 $languageIDArray = array($languageID); 69 70 $boardList = new BoardListTaggingReloaded($boardID); 71 $boardList->renderBoards(); 72 $this->readSubBoards($boardList->getSubBoards()); 73 $this->boards[] = $boardID; 74 75 $minLifetime = 0; 76 $maxLifetime = 43200; //12 h 77 78 $key = 'taggingreloaded.wbb.boards-'.PACKAGE_ID. 79 '-'.(implode(',', $languageIDArray)). 80 '-'.(implode(',', $this->boards)); 81 82 WCF::getCache()->addResource( 83 $key, 84 WBB_DIR.'cache/cache.taggingreloaded.boards-'.PACKAGE_ID. 85 '-'.StringUtil::getHash(implode(',', $languageIDArray)). 86 '-'.StringUtil::getHash(implode(',', $this->boards)).'.php', 87 WBB_DIR.'lib/system/cache/CacheBuilderTaggingReloadedBoards.class.php', 88 $minLifetime, 89 $maxLifetime 90 ); 91 92 $this->tags = WCF::getCache()->get($key); 93 break; 94 } 95 } 96 97 /** 98 * fetch recursive all child boardIDs 99 * @param arr 100 */ 101 protected function readSubBoards($arr) { 102 foreach($arr as $boardID => $next) { 103 $this->boards[] = $boardID; 104 $this->readSubBoards($next); 105 } 106 } 27 107 28 108 /** … … 30 110 */ 31 111 protected function assignVariables () { 32 if($this->className == 'ThreadAddForm') { 33 return; 112 parent::assignVariables(); 113 114 if(count($this->tags) > 0) { 115 WCF::getTPL()->append('additionalBoxes', WCF::getTPL()->fetch('taggingCloudContainer')); 34 116 } 35 36 switch ($this->className) {37 case 'PostEditForm':38 $threadID = $this->eventObj->post->threadID;39 $languageID = $this->eventObj->post->languageID;40 $userID = $this->eventObj->post->userID;41 break;42 default:43 $threadID = $this->eventObj->thread->threadID;44 $languageID = $this->eventObj->thread->languageID;45 $userID = WCF::getUser()->userID;46 break;47 }48 49 50 $tagged = new TaggedThread(null, array(51 'threadID' => $threadID,52 'taggable' => TagEngine::getInstance()->getTaggable('com.woltlab.wbb.thread')53 ));54 55 // load for reloaded style56 $this->tags3 = $this->getTagsByObject($userID, $tagged, $threadID, $languageID, true);57 58 // when editing "first post", woltlab fills tags array with all tags for this thread59 // anyway.. we divide tags by user, so override the woltlab array with tags by the user60 $this->eventObj->tags = $this->tags = TaggingUtil::buildString(array_keys($this->tags3));61 62 parent::assignVariables();63 64 // woltlab decided to only show the text input for the first post.. in other cases we have to show it65 if(MODULE_TAGGING && THREAD_ENABLE_TAGS && $this->eventObj->board->getPermission('canSetTags')) {66 // ThreadAddForm handled by wcf, posteditform > postaddform > threadaddform(x)67 if($this->className != 'ThreadAddForm' && ($this->eventObj instanceof PostAddForm || $this->eventObj->thread->firstPostID != $this->eventObj->postID)) {68 WCF::getTPL()->append('additionalInformationFields', WCF::getTPL()->fetch('tagAddBit'));69 }70 }71 }72 73 /**74 * @see Form::validate()75 */76 protected function validate () {77 parent::validate();78 if($this->className == 'ThreadAddForm') {79 return;80 }81 82 $threadID = $this->eventObj->thread->threadID;83 $firstPost = $this->eventObj instanceof PostAddForm ? false : $this->eventObj->thread->firstPostID == $this->eventObj->post->postID;84 $userID = $this->eventObj instanceof PostAddForm ? WCF::getUser()->userID : $this->eventObj->post->userID;85 $languageID = $this->eventObj->thread->languageID;86 87 // save tags88 $tagged = new TaggedThread(null, array(89 'threadID' => $threadID,90 'taggable' => TagEngine::getInstance()->getTaggable('com.woltlab.wbb.thread')91 ));92 93 // woltlab will delete all tags, not being sent within this request :(94 // my workaround is to load the existing tags from database right before being saved95 // and store the tags from this request in a separate variable96 // in save method, we will watch for usertags variable97 $this->existingTags = $this->getTagsByObject($userID, $tagged, $threadID, $languageID, false);98 }99 100 /**101 * @see Form::save()102 */103 protected function save () {104 // we don't want woltlab to save any tags105 // careful, overwrite tags variable before woltlab will save it106 $this->eventObj->tags = $this->tags = '';107 }108 109 /**110 * @see Form::saved()111 */112 protected function saved () {113 switch ($this->className) {114 case 'ThreadAddForm':115 $threadID = $this->eventObj->newThread->threadID;116 $firstPost = true;117 $userID = WCF::getUser()->userID;118 $languageID = $this->eventObj->newThread->languageID;119 break;120 default:121 $threadID = $this->eventObj->thread->threadID;122 $firstPost = $this->eventObj instanceof PostAddForm ? false : $this->eventObj->thread->firstPostID == $this->eventObj->post->postID;123 $userID = $this->eventObj instanceof PostAddForm ? WCF::getUser()->userID : $this->eventObj->post->userID;124 $languageID = $this->eventObj->thread->languageID;125 break;126 }127 128 // save tags129 $tagged = new TaggedThread(null, array(130 'threadID' => $threadID,131 'taggable' => TagEngine::getInstance()->getTaggable('com.woltlab.wbb.thread')132 ));133 134 $languageIDArray = array($languageID);135 136 // first post, woltlab cares about inserts137 if ($firstPost) {138 139 } else {140 // we will do the same, and remove all tags for this thread from original table141 TagEngine::getInstance()->deleteObjectTags($tagged, $languageIDArray);142 }143 144 // delete all tags added by this user to this thread from tagging3 table145 $this->tagEngineDeleteObjectTags($userID, $tagged, $languageIDArray);146 147 // restore existing tags to the database148 // and also the tags from this request149 // fill the tagging3 table to get the link with the user150 $tags = array_merge($this->existingTags, $this->tags3);151 $this->tagEngineAddTags($userID, $tags, $tagged, $languageID);152 117 } 153 118 } -
taggingreloaded/optionals/de.easy-coding.wbb.taggingreloaded/files/lib/system/event/listener/TaggingReloadedWBBPageListener.class.php
r1066 r1070 1 1 <?php 2 // wcf imports3 require_once(WCF_DIR.'lib/system/event/EventListener.class.php');4 require_once(WCF_DIR.'lib/data/tag/Tag.class.php');5 require_once(WCF_DIR.'lib/data/tag/TagEngine.class.php');6 7 2 // wbb imports 8 require_once(WBB_DIR.'lib/ form/PostEditForm.class.php');3 require_once(WBB_DIR.'lib/data/board/Board.class.php'); 9 4 require_once(WBB_DIR.'lib/data/thread/TaggedThread.class.php'); 10 5 11 // tagging 3imports6 // tagging imports 12 7 require_once(WCF_DIR.'lib/util/TaggingReloadedUtil.class.php'); 13 require_once(WCF_DIR.'lib/system/event/listener/TaggingReloadedMessageFormListener.class.php'); 8 require_once(WCF_DIR.'lib/system/event/listener/TaggingReloadedPageListener.class.php'); 9 require_once(WBB_DIR.'lib/data/board/BoardListTaggingReloaded.class.php'); 10 11 // seo imports 12 require_once(WCF_DIR.'lib/page/PublicSEORewriterTagging.class.php'); 14 13 15 14 /** 16 * Displays the ta b for tagging15 * Displays the tags for threads/boards 17 16 * 18 17 * @author Torben Brodt … … 20 19 * @license GNU General Public License <http://opensource.org/licenses/gpl-3.0.html> 21 20 */ 22 class TaggingReloadedWBBMessageFormListener extends TaggingReloadedMessageFormListener { 21 class TaggingReloadedWBBPageListener extends TaggingReloadedPageListener { 22 23 23 /** 24 * @var Tag[]24 * reads threads by tag 25 25 */ 26 protected $existingTags = array(); 27 28 /** 29 * @return Tag[] 30 */ 31 protected function getTagsByThread($userID, $threadID, $languageID, $even = true) { 32 $tagged = new TaggedThread(null, array( 33 'threadID' => $threadID, 34 'taggable' => TagEngine::getInstance()->getTaggable('com.woltlab.wbb.thread') 35 )); 36 26 protected function queryTagsByThread(Tagged $tagged, $languageID) { 37 27 $taggableID = $tagged->getTaggable()->getTaggableID(); 38 $ sign = $even ? '=' : '!=';28 $objectID = $tagged->getObjectID(); 39 29 40 30 $sql = "SELECT tag.*, 41 userID 42 weight 31 userID, 32 weight AS counter 43 33 FROM ( 44 34 SELECT tagID, … … 48 38 WHERE taggableID = ".intval($taggableID)." 49 39 AND languageID = ".intval($languageID)." 50 AND objectID = ".intval($threadID)." 51 AND userID $sign ".intval($userID)." 40 AND objectID = ".intval($objectID)." 52 41 ) x 53 42 INNER JOIN wcf".WCF_N."_tag tag USING(tagID) 54 43 "; 55 $result = WCF::getDB()->sendQuery($sql); 56 $tags = array(); 57 while ($row = WCF::getDB()->fetchArray($result)) { 58 $tags[$row['name']] = new Tag(null, $row); 59 } 60 return $tags; 44 return $sql; 61 45 } 62 46 … … 65 49 */ 66 50 protected function readData () { 67 if(!($this->eventObj instanceof PostEditForm)) { 68 return; 51 parent::readData(); 52 53 switch($this->className) { 54 case 'ThreadPage': 55 $threadID = $this->eventObj->thread->threadID; 56 $languageID = $this->eventObj->thread->languageID; 57 58 $tagged = new TaggedThread(null, array( 59 'threadID' => $threadID, 60 'taggable' => TagEngine::getInstance()->getTaggable('com.woltlab.wbb.thread') 61 )); 62 63 $this->fromQuery($this->queryTagsByThread($tagged, $languageID)); 64 break; 65 case 'BoardPage': 66 $boardID = $this->eventObj->boardID; 67 $languageID = $this->eventObj->languageID; 68 $languageIDArray = array($languageID); 69 70 $boardList = new BoardListTaggingReloaded($boardID); 71 $boardList->renderBoards(); 72 $this->readSubBoards($boardList->getSubBoards()); 73 $this->boards[] = $boardID; 74 75 $minLifetime = 0; 76 $maxLifetime = 43200; //12 h 77 78 $key = 'taggingreloaded.wbb.boards-'.PACKAGE_ID. 79 '-'.(implode(',', $languageIDArray)). 80 '-'.(implode(',', $this->boards)); 81 82 WCF::getCache()->addResource( 83 $key, 84 WBB_DIR.'cache/cache.taggingreloaded.boards-'.PACKAGE_ID. 85 '-'.StringUtil::getHash(implode(',', $languageIDArray)). 86 '-'.StringUtil::getHash(implode(',', $this->boards)).'.php', 87 WBB_DIR.'lib/system/cache/CacheBuilderTaggingReloadedBoards.class.php', 88 $minLifetime, 89 $maxLifetime 90 ); 91 92 $this->tags = WCF::getCache()->get($key); 93 break; 69 94 } 70 71 $threadID = $this->eventObj->post->threadID; 72 $languageID = $this->eventObj->post->languageID; 73 $userID = $this->eventObj->post->userID; 74 75 // load for reloaded style 76 $this->tags3 = $this->getTagsByThread($userID, $threadID, $languageID); 95 } 96 97 /** 98 * fetch recursive all child boardIDs 99 * @param arr 100 */ 101 protected function readSubBoards($arr) { 102 foreach($arr as $boardID => $next) { 103 $this->boards[] = $boardID; 104 $this->readSubBoards($next); 105 } 77 106 } 78 107 … … 81 110 */ 82 111 protected function assignVariables () { 83 // when editing "first post", woltlab fills tags array with all tags for this thread 84 // anyway.. we divide tags by user, so override the woltlab array with tags by the user 85 $this->eventObj->tags = TaggingUtil::buildString(array_keys($this->tags3)); 112 parent::assignVariables(); 86 113 87 parent::assignVariables(); 88 } 89 90 /** 91 * @see Form::validate() 92 */ 93 protected function validate () { 94 95 // handle reloaded input 96 if (isset($_REQUEST['taggingname']) && isset($_REQUEST['taggingval'])) { 97 $_REQUEST['taggingval'] = array_map(create_function('$a', 'return trim(str_replace(",","",$a));'), $_REQUEST['taggingval']); 98 $_REQUEST['taggingval'] = array_filter($_REQUEST['taggingval'], create_function('$a', 'return strlen($a)>1;')); 99 $this->tags3 = array_combine($_REQUEST['taggingname'], $_REQUEST['taggingval']); 114 if(count($this->tags) > 0) { 115 WCF::getTPL()->append('additionalBoxes', WCF::getTPL()->fetch('taggingCloudContainer')); 100 116 } 101 102 // handle classic input103 if (isset($_REQUEST['tags']) && !empty($_REQUEST['tags'])) {104 foreach(TaggingUtil::splitString($_REQUEST['tags']) as $tag) {105 if(strlen($tag) > 1) {106 if(array_key_exists($tag, $this->tags3)) {107 $this->tags3[$tag] *= 1.33; // increase with 33%108 } else {109 $this->tags3[$tag] = 100;110 }111 }112 }113 }114 115 // forgotten to press the add button?116 if (isset($_REQUEST['tags3']) && !empty($_REQUEST['tags3'])) {117 foreach(TaggingUtil::splitString($_REQUEST['tags3']) as $tag) {118 if(strlen($tag) > 1) {119 if(array_key_exists($tag, $this->tags3)) {120 $this->tags3[$tag] *= 1.33; // increase with 33%121 } else {122 $this->tags3[$tag] = 100;123 }124 }125 }126 }127 128 // conversion from post to Tag129 foreach($this->tags3 as $name => $weight) {130 $this->tags3[$name] = new Tag(null, array(131 'name' => $name,132 'weight' => $weight133 ));134 }135 136 // classic input137 $this->eventObj->tags = TaggingUtil::buildString(array_keys($this->tags3));138 139 if(!($this->eventObj instanceof PostEditForm)) {140 return;141 }142 143 $threadID = $this->eventObj->thread->threadID;144 $languageID = $this->eventObj->thread->languageID;145 146 // woltlab will delete all tags, not being sent within this request :(147 // my workaround is to load the existing tags from database right before being saved148 // and store the tags from this request in a separate variable149 // in save method, we will watch for usertags variable150 $this->existingTags = $this->getTagsByThread(null, $threadID, $languageID, false);151 }152 153 /**154 * @see Form::save()155 */156 protected function save () {157 // we don't want woltlab to save any tags158 // careful, overwrite tags variable before woltlab will save it159 $this->eventObj->tags = '';160 }161 162 /**163 * @see Form::saved()164 */165 protected function saved () {166 switch ($this->className) {167 case 'ThreadAddForm':168 $threadID = $this->eventObj->newThread->threadID;169 $firstPost = true;170 $userID = WCF::getUser()->userID;171 $languageID = $this->eventObj->newThread->languageID;172 break;173 default:174 $threadID = $this->eventObj->thread->threadID;175 $firstPost = $this->eventObj instanceof PostAddForm ? false : $this->eventObj->thread->firstPostID == $this->eventObj->post->postID;176 $userID = $this->eventObj instanceof PostAddForm ? WCF::getUser()->userID : $this->eventObj->post->userID;177 $languageID = $this->eventObj->thread->languageID;178 break;179 }180 181 // save tags182 $tagged = new TaggedThread(null, array(183 'threadID' => $threadID,184 'taggable' => TagEngine::getInstance()->getTaggable('com.woltlab.wbb.thread')185 ));186 187 $languageIDArray = array($languageID);188 189 // first post, woltlab cares about inserts190 if ($firstPost) {191 192 } else {193 // we will do the same, and remove all tags for this thread from original table194 TagEngine::getInstance()->deleteObjectTags($tagged, $languageIDArray);195 }196 197 // delete all tags added by this user to this thread from tagging3 table198 $this->tagEngineDeleteObjectTags($userID, $tagged, $languageIDArray);199 200 // restore existing tags to the database201 // and also the tags from this request202 // fill the tagging3 table to get the link with the user203 $tags = array_merge($this->existingTags, $this->tags3);204 $this->tagEngineAddTags($userID, $tags, $tagged, $languageID);205 }206 207 /**208 * this is a reimplementation of TagEngine::deleteObjectTags with a slite modification to write to our table209 */210 public function tagEngineDeleteObjectTags($userID, Tagged $object, $languageIDArray = array()) {211 if (!count($languageIDArray)) $languageIDArray = array(0);212 $sql = "DELETE FROM wcf".WCF_N."_tagging3213 WHERE taggableID = ".$object->getTaggable()->getTaggableID()."214 AND languageID IN (".implode(',', $languageIDArray).")215 AND objectID = ".$object->getObjectID()."216 AND userID = ".$userID;217 WCF::getDB()->sendQuery($sql);218 }219 220 /**221 * this is a reimplementation of TagEngine::addTags with a slite modification to write to our table222 */223 public function tagEngineAddTags($userID, $tags, Tagged $object, $languageID = 0) {224 // skip empty225 if(!count($tags)) return;226 227 // store original database228 TagEngine::getInstance()->addTags(array_keys($tags), $object, $languageID);229 230 // store tagging3 database231 $tagIDs = array();232 foreach ($tags as $tag) {233 $tagID = Tag::test($tag->name, $languageID);234 if (!$tagID) $tagID = Tag::insert($tag->name, $languageID);235 $tagIDs[$tagID] = $tag->weight;236 }237 238 $sql = "INSERT INTO wcf".WCF_N."_tagging3239 (objectID, tagID, taggableID, languageID, userID, weight)240 VALUES ";241 foreach ($tagIDs as $tagID => $weight) {242 $sql .= "(" . $object->getObjectID() . ", " . $tagID . ", " . $object->getTaggable()->getTaggableID() . ", ".$languageID.", ".$userID.", ".$weight."),";243 }244 $sql = StringUtil::substring($sql, 0, StringUtil::length($sql) - 1);245 $result = WCF::getDB()->sendQuery($sql);246 117 } 247 118 } -
taggingreloaded/optionals/de.easy-coding.wbb.taggingreloaded/package.xml
r1067 r1070 19 19 <requiredpackages> 20 20 <requiredpackage minversion="1.1.0 Beta 1">com.woltlab.wcf</requiredpackage> 21 <requiredpackage minversion=" 2.0.0">com.woltlab.wbb</requiredpackage>22 <requiredpackage minversion="0. 7.0">de.easy-coding.wcf.taggingreloaded</requiredpackage>21 <requiredpackage minversion="3.1.0 Beta 1">com.woltlab.wbb</requiredpackage> 22 <requiredpackage minversion="0.8.0 Beta 1">de.easy-coding.wcf.taggingreloaded</requiredpackage> 23 23 </requiredpackages> 24 24 … … 26 26 <files>files.tar</files> 27 27 <eventlistener>eventlistener.xml</eventlistener> 28 <boardoptions>boardoptions.xml</boardoptions>29 28 </instructions> 30 29 … … 33 32 <sql>update.sql</sql> 34 33 <eventlistener>eventlistener.xml</eventlistener> 35 <boardoptions>boardoptions.xml</boardoptions>36 34 </instructions> 37 35 </package> -
taggingreloaded/optionals/de.easy-coding.wbb.taggingreloaded/update.sql
r1064 r1070 1 ALTER TABLE wbb1_1_post DROP systemTagged; 2 DROP TABLE wbb1_1_taggingreloaded; 1 INSERT IGNORE INTO wcf1_tagging3 (taggableID, languageID, objectID, tagID, userID, weight) 2 SELECT 3 1 AS taggeableID, 4 0 AS languageID, 5 threadID AS objectID, 6 wcf1_tag.tagID AS tagID, 7 userID, 8 100 AS weight 9 FROM wcf1_tag 10 INNER JOIN wbb1_1_post USING(postID); 11 12 13 INSERT IGNORE INTO wcf1_tag (languageID, name) 14 SELECT 0 AS languageID, tag AS name FROM wcf1_taggingreloaded; 15 16 INSERT IGNORE INTO wcf1_tag_to_object (objectID, tagID, taggableID, time, languageID) 17 SELECT 18 threadID AS objectID, 19 wcf1_tag.tagID AS tagID, 20 1 AS taggeableID, 21 wbb1_1_post.time AS time, 22 0 AS languageID 23 FROM wbb1_1_taggingreloaded 24 INNER JOIN wcf1_taggingreloaded USING(taggingID) 25 INNER JOIN wbb1_1_post USING(postID) 26 INNER JOIN wcf1_tag ON wcf1_tag.languageID = 0 AND wcf1_tag.name = wcf1_taggingreloaded.tag; 27 28 INSERT IGNORE INTO wcf1_tagging3 (taggableID, languageID, objectID, tagID, userID, weight) 29 SELECT 30 1 AS taggeableID, 31 0 AS languageID, 32 threadID AS objectID, 33 wcf1_tag.tagID AS tagID, 34 wcf1_taggingreloaded.userID, 35 weight 36 FROM wbb1_1_taggingreloaded 37 INNER JOIN wcf1_taggingreloaded USING(taggingID) 38 INNER JOIN wbb1_1_post USING(postID) 39 INNER JOIN wcf1_tag ON wcf1_tag.languageID = 0 AND wcf1_tag.name = wcf1_taggingreloaded.tag; -
taggingreloaded/package.xml
r1065 r1070 20 20 <requiredpackages> 21 21 <requiredpackage minversion="1.1.0 Beta 1">com.woltlab.wcf</requiredpackage> 22 <requiredpackage minversion="1.0.4">com.woltlab.wcf.page.user.profile</requiredpackage> 22 <requiredpackage minversion="1.1.0 Beta 1">com.woltlab.wcf.tagging</requiredpackage> 23 <requiredpackage minversion="1.1.0 Beta 1">com.woltlab.wcf.data.message.search.tagging</requiredpackage> 24 <requiredpackage minversion="1.1.0 Beta 1">com.woltlab.wcf.page.user.profile</requiredpackage> 23 25 </requiredpackages> 24 26 -
taggingreloaded/templates/messageFormTaggingReloaded.tpl
r1069 r1070 1 {include file='tagAddBit' sandbox=false} 1 2 <div id="taggingReloadedExpand" style="display:none; position:absolute; top: 0px; right: 0px"> 2 3 <input type="button" value="{lang}wcf.taggingreloaded.expand{/lang}" onclick="return taggingReloadedToggle(this)" /> … … 12 13 <script type="text/javascript"> 13 14 //<!CDATA[[ 14 15 //tagging.formadd(this.form.tags);16 15 $('tags').parentNode.parentNode.style.position = 'relative'; 17 16 … … 40 39 var tagging = new TaggingReloaded('taggingBubble', 'taggingReloaded'); 41 40 tagging.setLimits(30,500); 41 42 {if $tags3|isset} 43 {foreach from=$tags3 item=tag} 44 tagging.add('{$tag->name}',{$tag->weight}, false); 45 {/foreach} 46 {/if} 42 47 //]]> 43 48 </script> -
taggingreloaded/templates/taggingCloudContainer.tpl
r50 r1070 1 {if $tags|count} 1 {if $tags3|count} 2 {assign var=tags value=$tags3} 2 3 <div class="container-1"> 3 4 <div class="containerIcon"><img src="{@RELATIVE_WCF_DIR}icon/tagging24.png" alt="" /></div> 4 5 <div class="containerContent"> 5 6 <h3>{lang}wcf.taggingreloaded.tagging{/lang}</h3> 6 {include file="tag gingCloud"}7 {include file="tagCloud"} 7 8 </div> 8 9 </div> -
taggingreloaded/templates/taggingReloaded.tpl
r767 r1070 1 1 {include file="documentHeader"} 2 2 <head> 3 <title>{$title} - {PAGE_TITLE}</title> 3 <title>{lang}wcf.header.menu.tagging{/lang} - {lang}{PAGE_TITLE}{/lang}</title> 4 {include file='headInclude' sandbox=false} 5 {include file='imageViewer'} 6 <script type="text/javascript" src="{@RELATIVE_WCF_DIR}js/MultiPagesLinks.class.js"></script> 7 </head> 8 <body{if $templateName|isset} id="tpl{$templateName|ucfirst}"{/if}> 9 {include file='header' sandbox=false} 4 10 5 {include file='headInclude' sandbox=false}6 </head>7 <body>8 {include file="header" sandbox=false}9 11 <div id="main"> 10 12 <ul class="breadCrumbs"> 11 13 <li><a href="index.php{@SID_ARG_1ST}"><img src="icon/indexS.png" alt="" /> <span>{PAGE_TITLE}</span></a> »</li> 12 {if $additionalTaggingBreadCrumbs|isset}{@$additionalTaggingBreadCrumbs}{/if}13 14 </ul> 14 15 15 16 <div class="mainHeadline" style="clear:none"> 16 <img src="{@RELATIVE_WCF_DIR}icon/tagging48.png" alt="" />17 <img src="{@RELATIVE_WCF_DIR}icon/tagging48.png" alt=""/> 17 18 <div class="headlineContainer"> 18 <h2> { $headline}</h2>19 <p>{$desc}</p>19 <h2> {lang}wcf.taggingreloaded.tagging{/lang}</h2> 20 {lang}wcf.taggingreloaded.description{/lang} 20 21 </div> 21 22 </div> 23 22 24 <div class="border"> 23 <div class="container-1" style="padding:10px"> 24 {if $additionalTaggingContents|isset || $additionalTaggingBoxes|isset} 25 {if $additionalTaggingContents|isset}{@$additionalTaggingContents}{/if} 26 {if $additionalTaggingBoxes|isset}{@$additionalTaggingBoxes}{/if} 27 {else} 28 {lang}wcf.taggingreloaded.notags{/lang} 29 {/if} 30 31 <div style="text-align:right"><a href="http://trac.easy-coding.de/trac/wcf/wiki/taggingreloaded" class="externalURL">{lang}wcf.taggingreloaded.copyright{/lang}</a></div> 32 </div> 25 <div class="layout-2 blog"> 26 <div class="columnContainer"> 27 <div class="container-1 column first"> 28 <div class="columnInner"> 29 <div class="contentBox"> 30 {include file="tagCloud"} 31 32 {pages link="index.php?page=Tagging&pageNo=%d"|concat:SID_ARG_2ND_NOT_ENCODED} 33 </div> 34 </div> 35 </div> 36 37 <div class="container-3 column second blogSidebar"> 38 <div class="columnInner"> 39 {if $additionalBoxes1|isset}{@$additionalBoxes1}{/if} 40 41 <div class="contentBox"> 42 <div class="border"> 43 <div class="containerHead"> 44 <h3>{lang}wcf.taggingreloaded.topuser{/lang}</h3> 45 </div> 46 47 <ul class="dataList"> 48 {foreach from=$top_user item=topUser} 49 <li class="{cycle values='container-1,container-2'}"> 50 <div class="containerIcon"> 51 <a href="index.php?page=User&userID={@$topUser->userID}{@SID_ARG_2ND}" title="{lang username=$topUser->username}wcf.user.viewProfile{/lang}"> 52 {if $topUser->getAvatar()} 53 {assign var=x value=$topUser->getAvatar()->setMaxSize(24, 24)} 54 {@$topUser->getAvatar()} 55 {else} 56 <img src="{@RELATIVE_WCF_DIR}images/avatars/avatar-default.png" alt="" style="width: 24px; height: 24px" /> 57 {/if} 58 </a> 59 </div> 60 <div class="containerContent"> 61 <h4><a href="index.php?page=User&userID={@$topUser->userID}{@SID_ARG_2ND}" title="{lang username=$topUser->username}wcf.user.viewProfile{/lang}">{@$topUser->username}</a></h4> 62 <p class="light smallFont">{@$topUser->counter}</p> 63 </div> 64 </li> 65 {/foreach} 66 </ul> 67 </div> 68 </div> 69 70 {if $additionalBoxes2|isset}{@$additionalBoxes2}{/if} 71 </div> 72 </div> 73 </div> 74 </div> 33 75 </div> 34 76 </div>
