<title>Suche</title>
</content>
</page>
+ <page identifier="com.woltlab.wcf.TagSearch">
+ <pageType>system</pageType>
+ <controller>wcf\form\TagSearchForm</controller>
+ <name language="de">Suche nach Tags</name>
+ <name language="en">Search by Tags</name>
+ <permissions>user.tag.canViewTag</permissions>
+ <content language="en">
+ <title>Search by Tags</title>
+ </content>
+ <content language="de">
+ <title>Suche nach Tags</title>
+ </content>
+ </page>
<page identifier="com.woltlab.wcf.Settings">
<pageType>system</pageType>
<controller>wcf\form\SettingsForm</controller>
--- /dev/null
+{capture assign='pageTitle'}{lang}wcf.tagging.combinedTaggedObjects.{@$objectType}{/lang}{if $pageNo > 1} - {lang}wcf.page.pageNo{/lang}{/if}{/capture}
+
+{capture assign='contentHeader'}
+ <header class="contentHeader">
+ <div class="contentHeaderTitle">
+ <h1 class="contentTitle">{lang}wcf.tagging.combinedTaggedObjects.{@$objectType}{/lang}</h1>
+ <ul class="tagList" style="margin-top: 10px">
+ {foreach from=$combinedTags item=tag}
+ <li><a href="{link controller='Tagged' object=$tag}objectType={@$objectType}{/link}" class="tag jsTooltip" title="{lang}wcf.tagging.taggedObjects.{@$objectType}{/lang}">{$tag->name}</a></li>
+ {/foreach}
+ </ul>
+ </div>
+ </header>
+{/capture}
+
+{capture assign='linkParameters'}{implode from=$combinedTags item=tag glue='&'}tagIDs[]={@$tag->tagID}{/implode}{/capture}
+
+{capture assign='headContent'}
+ {if $pageNo < $pages}
+ <link rel="next" href="{link controller='CombinedTagged'}{@$linkParameters}&objectType={@$objectType}&pageNo={@$pageNo+1}{/link}">
+ {/if}
+ {if $pageNo > 1}
+ <link rel="prev" href="{link controller='CombinedTagged'}{@$linkParameters}&objectType={@$objectType}{if $pageNo > 2}&pageNo={@$pageNo-1}{/if}{/link}">
+ {/if}
+ <link rel="canonical" href="{link controller='CombinedTagged'}{@$linkParameters}&objectType={@$objectType}{if $pageNo > 1}&pageNo={@$pageNo}{/if}{/link}">
+{/capture}
+
+{capture assign='sidebarLeft'}
+ <section class="box" data-static-box-identifier="com.woltlab.wcf.TaggedMenu">
+ <h2 class="boxTitle">{lang}wcf.tagging.objectTypes{/lang}</h2>
+
+ <nav class="boxContent">
+ <ul class="boxMenu">
+ {foreach from=$availableObjectTypes item=availableObjectType}
+ <li{if $objectType == $availableObjectType->objectType} class="active"{/if}><a class="boxMenuLink" href="{link controller='CombinedTagged'}{@$linkParameters}&objectType={@$availableObjectType->objectType}{/link}">{lang}wcf.tagging.objectType.{@$availableObjectType->objectType}{/lang}</a></li>
+ {/foreach}
+ </ul>
+ </nav>
+ </section>
+
+ <section class="box" data-static-box-identifier="com.woltlab.wcf.TaggedTagCloud">
+ <h2 class="boxTitle">{lang}wcf.tagging.tags{/lang}</h2>
+
+ <div class="boxContent">
+ {include file='tagCloudBox' taggableObjectType=$objectType}
+ </div>
+ </section>
+{/capture}
+
+{include file='header'}
+
+{hascontent}
+ <div class="paginationTop">
+ {content}{pages print=true assign=pagesLinks controller='CombinedTagged' link="$linkParameters&objectType=$objectType&pageNo=%d"}{/content}
+ </div>
+{/hascontent}
+
+{if $items}
+ {include file=$resultListTemplateName application=$resultListApplication}
+{else}
+ <p class="info">{lang}wcf.tagging.taggedObjects.noResults{/lang}</p>
+{/if}
+
+<footer class="contentFooter">
+ {hascontent}
+ <div class="paginationBottom">
+ {content}{@$pagesLinks}{/content}
+ </div>
+ {/hascontent}
+
+ {hascontent}
+ <nav class="contentFooterNavigation">
+ <ul>
+ {content}{event name='contentFooterNavigation'}{/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</footer>
+
+{include file='footer'}
<nav class="tabMenu">
<ul>
<li class="active"><a href="{link controller='Search'}{/link}">{lang}wcf.search.type.keywords{/lang}</a></li>
- <li><a href="{link controller='TagSearch'}{/link}">{lang}wcf.search.type.tags{/lang}</a></li>
+ {if $__wcf->session->getPermission('user.tag.canViewTag')}<li><a href="{link controller='TagSearch'}{/link}">{lang}wcf.search.type.tags{/lang}</a></li>{/if}
{event name='tabMenuTabs'}
</ul>
/**
* Creates a new CategoryArticleList object.
*
- * @param Tag|Tag[] $tag
+ * @param Tag|Tag[] $tags
*/
- public function __construct($tag) {
+ public function __construct($tags) {
parent::__construct();
$this->sqlOrderBy = 'article.time ' . ARTICLE_SORT_ORDER;
- $innerSql = TagEngine::getInstance()->getSqlForObjectsByTags('com.woltlab.wcf.article', $tag);
- $this->getConditionBuilder()->add("article.articleID IN (SELECT articleID FROM wcf".WCF_N."_article_content WHERE articleContentID IN (".$innerSql['sql']."))", [$innerSql['parameters']]);
+ $tagIDs = TagEngine::getInstance()->getTagIDs($tags);
+ $this->getConditionBuilder()->add("article.articleID IN (
+ SELECT articleID FROM wcf".WCF_N."_article_content WHERE articleContentID IN (
+ SELECT objectID
+ FROM wcf".WCF_N."_tag_to_object
+ WHERE objectTypeID = ?
+ AND tagID IN (?)
+ GROUP BY objectID
+ HAVING COUNT(objectID) = ?
+ )
+ )", [
+ TagEngine::getInstance()->getObjectTypeID('com.woltlab.wcf.article'),
+ $tagIDs,
+ count($tagIDs)
+ ]);
}
}
* Shows the tag search form.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Form
* @since 3.2
*/
public $languageID;
+ /**
+ * @inheritDoc
+ */
+ public $neededPermissions = ['user.tag.canViewTag'];
+
/**
* @var TagCloud
*/
public function save() {
parent::save();
+ $tagIDs = '';
+ foreach ($this->tags as $tag) {
+ if (!empty($tagIDs)) $tagIDs .= '&';
+ $tagIDs .= 'tagIDs[]=' . $tag->tagID;
+ }
+
HeaderUtil::redirect(
- LinkHandler::getInstance()->getLink('CombinedTagged', ['tagIDs' => array_keys($this->tags)]),
+ LinkHandler::getInstance()->getLink('CombinedTagged', [], $tagIDs),
true,
true
);
use wcf\data\tag\TagList;
use wcf\system\exception\IllegalLinkException;
use wcf\system\exception\PermissionDeniedException;
+use wcf\system\tagging\ICombinedTaggable;
use wcf\system\tagging\TypedTagCloud;
use wcf\system\WCF;
use wcf\util\ArrayUtil;
use wcf\util\StringUtil;
/**
- * Shows the a list of tagged objects.
+ * Shows the a list of objects matching a combination of tags.
*
- * @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package WoltLabSuite\Core\Page
+ * @author Alexander Ebert
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Page
*/
-class CombinedTaggedPage extends TaggedPage {
+class CombinedTaggedPage extends MultipleLinkPage {
+ /**
+ * @var ObjectType[]
+ */
+ public $availableObjectTypes = [];
+
+ /**
+ * @inheritDoc
+ */
+ public $neededModules = ['MODULE_TAGGING'];
+
+ /**
+ * @inheritDoc
+ */
+ public $neededPermissions = ['user.tag.canViewTag'];
+
+ /**
+ * @var ObjectType
+ */
+ public $objectType;
+
+ /**
+ * @var ICombinedTaggable
+ */
+ public $processor;
+
+ /**
+ * @var Tag[]
+ */
public $tags = [];
+
+ /**
+ * @var int[]
+ */
public $tagIDs = [];
+ /**
+ * @var TypedTagCloud
+ */
+ public $tagCloud;
+
/**
* @inheritDoc
*/
public function readParameters() {
- MultipleLinkPage::readParameters();
+ parent::readParameters();
if (isset($_GET['tagIDs']) && is_array($this->tagIDs)) $this->tagIDs = ArrayUtil::toIntegerArray($_GET['tagIDs']);
if (empty($this->tagIDs)) {
if (!$objectType->validateOptions() || !$objectType->validatePermissions()) {
unset($this->availableObjectTypes[$key]);
}
+
+ if (!$objectType->getProcessor() instanceof ICombinedTaggable) {
+ unset($this->availableObjectTypes[$key]);
+ }
}
if (empty($this->availableObjectTypes)) {
// No object type provided, use the first object type.
$this->objectType = reset($this->availableObjectTypes);
}
+
+ $this->processor = $this->objectType->getProcessor();
}
/**
* @inheritDoc
*/
protected function initObjectList() {
- $this->objectList = $this->objectType->getProcessor()->getObjectListFor($this->tags);
+ $this->objectList = $this->processor->getObjectListFor($this->tags);
}
/**
parent::assignVariables();
WCF::getTPL()->assign([
- 'tag' => $this->tag,
+ 'combinedTags' => $this->tags,
'tags' => $this->tagCloud->getTags(100),
'availableObjectTypes' => $this->availableObjectTypes,
'objectType' => $this->objectType->objectType,
- 'resultListTemplateName' => $this->objectType->getProcessor()->getTemplateName(),
- 'resultListApplication' => $this->objectType->getProcessor()->getApplication()
+ 'resultListTemplateName' => $this->processor->getTemplateName(),
+ 'resultListApplication' => $this->processor->getApplication()
]);
if (count($this->objectList) === 0) {
* Abstract implementation of a taggable with support for searches with multiple tags.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Tagging
* @since 3.2
return key($languageIDs);
}
+ /**
+ * @param Tag[] $tags
+ * @return int[]
+ */
+ public function getTagIDs($tags) {
+ return array_map(function($tag) {
+ return $tag->tagID;
+ }, $tags);
+ }
+
/**
* Generates the inner SQL statement to fetch object ids that have all listed
* tags assigned to them.
* @return array
* @since 3.2
*/
- public function getSqlForObjectsByTags($objectType, array $tags) {
+ public function getSubselectForObjectsByTags($objectType, array $tags) {
$parameters = [$this->getObjectTypeID($objectType)];
- $tagIDs = implode(',', array_map(function(Tag $tag) {
+ $tagIDs = implode(',', array_map(function(Tag $tag) use (&$parameters) {
$parameters[] = $tag->tagID;
return '?';
];
}
+ public function setJoinCondition($objectType, array $tags, PreparedStatementConditionBuilder $conditions) {
+ $conditions->add('tag_to_object.objectTypeID = ?', [$this->getObjectTypeID($objectType)]);
+
+ $tagIDs = [];
+ foreach ($tags as $tag) {
+ $tagIDs[] = $tag->tagID;
+ }
+ $conditions->add('tag_to_object.tagID IN (?)', [$tagIDs]);
+ }
+
+ public function getSqlGroupAndHaving(array $tags) {
+ return 'GROUP BY tag_to_object.objectID HAVING COUNT(tag_to_object.objectID) = ' . count($tags);
+ }
+
/**
* Returns the matching tags by name.
*
<item name="wcf.search.type.com.woltlab.wcf.article"><![CDATA[Artikel]]></item>
<item name="wcf.search.object.com.woltlab.wcf.page"><![CDATA[Seite]]></item>
<item name="wcf.search.type.com.woltlab.wcf.page"><![CDATA[Seiten]]></item>
+ <item name="wcf.search.type.keywords"><![CDATA[Suche nach Begriffen]]></item>
+ <item name="wcf.search.type.tags"><![CDATA[Suche nach Tags]]></item>
+ <item name="wcf.search.type.tags.popular"><![CDATA[Beliebte Tags]]></item>
</category>
<category name="wcf.style">
<item name="wcf.style.changeStyle"><![CDATA[Stil ändern]]></item>
<item name="wcf.style.colorPicker.button.apply"><![CDATA[Übernehmen]]></item>
</category>
<category name="wcf.tagging">
+ <item name="wcf.tagging.combinedTaggedObjects"><![CDATA[{implode from=$combinedTags item=tag glue=', '}„{$tag->name}“{/implode}]]></item>
+ <item name="wcf.tagging.combinedTaggedObjects.com.woltlab.wcf.article"><![CDATA[Artikel mit den Tags]]></item>
<item name="wcf.tagging.tags"><![CDATA[Tags]]></item>
<item name="wcf.tagging.tags.add"><![CDATA[Tags]]></item>
<item name="wcf.tagging.tags.description"><![CDATA[Mehrere Tags müssen durch ein Komma getrennt werden.]]></item>
<item name="wcf.search.type.com.woltlab.wcf.article"><![CDATA[Articles]]></item>
<item name="wcf.search.object.com.woltlab.wcf.page"><![CDATA[Page]]></item>
<item name="wcf.search.type.com.woltlab.wcf.page"><![CDATA[Pages]]></item>
+ <item name="wcf.search.type.keywords"><![CDATA[Search by Terms]]></item>
+ <item name="wcf.search.type.tags"><![CDATA[Search by Tags]]></item>
+ <item name="wcf.search.type.tags.popular"><![CDATA[Popular Tags]]></item>
</category>
<category name="wcf.style">
<item name="wcf.style.changeStyle"><![CDATA[Change Style]]></item>
<item name="wcf.style.colorPicker.button.apply"><![CDATA[Apply]]></item>
</category>
<category name="wcf.tagging">
+ <item name="wcf.tagging.combinedTaggedObjects"><![CDATA[{implode from=$combinedTags item=tag glue=', '}“{$tag->name}”{/implode}]]></item>
+ <item name="wcf.tagging.combinedTaggedObjects.com.woltlab.wcf.article"><![CDATA[Articles Tagged with]]></item>
<item name="wcf.tagging.tags"><![CDATA[Tags]]></item>
<item name="wcf.tagging.tags.add"><![CDATA[Tags]]></item>
<item name="wcf.tagging.tags.description"><![CDATA[Separate multiple tags with a comma.]]></item>
<item name="wcf.tagging.objectTypes"><![CDATA[Content]]></item>
<item name="wcf.tagging.taggedObjects.noResults"><![CDATA[No items matched this tag.]]></item>
<item name="wcf.tagging.objectType.com.woltlab.wcf.article"><![CDATA[Articles]]></item>
- <item name="wcf.tagging.taggedObjects.com.woltlab.wcf.article"><![CDATA[Articles Tagged With “{$tag->name}”]]></item>
+ <item name="wcf.tagging.taggedObjects.com.woltlab.wcf.article"><![CDATA[Articles Tagged with “{$tag->name}”]]></item>
</category>
<category name="wcf.user">
<item name="wcf.user.confirmEmail"><![CDATA[Confirm Email]]></item>