Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #230 from cosmocode/feature/cloudAggregation
Add cloud aggregation similar to data-plugin's datacloud
- Loading branch information
Showing
13 changed files
with
578 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
<?php | ||
|
||
namespace dokuwiki\plugin\struct\meta; | ||
|
||
class AggregationCloud { | ||
|
||
/** | ||
* @var string the page id of the page this is rendered to | ||
*/ | ||
protected $id; | ||
|
||
/** | ||
* @var string the Type of renderer used | ||
*/ | ||
protected $mode; | ||
|
||
/** | ||
* @var \Doku_Renderer the DokuWiki renderer used to create the output | ||
*/ | ||
protected $renderer; | ||
|
||
/** | ||
* @var SearchConfig the configured search - gives access to columns etc. | ||
*/ | ||
protected $searchConfig; | ||
|
||
/** | ||
* @var Column[] the list of columns to be displayed | ||
*/ | ||
protected $columns; | ||
|
||
/** | ||
* @var Value[][] the search result | ||
*/ | ||
protected $result; | ||
|
||
/** | ||
* @var int number of all results | ||
*/ | ||
protected $resultCount; | ||
|
||
/** | ||
* Initialize the Aggregation renderer and executes the search | ||
* | ||
* You need to call @see render() on the resulting object. | ||
* | ||
* @param string $id | ||
* @param string $mode | ||
* @param \Doku_Renderer $renderer | ||
* @param SearchConfig $searchConfig | ||
*/ | ||
public function __construct($id, $mode, \Doku_Renderer $renderer, SearchCloud $searchConfig) { | ||
$this->id = $id; | ||
$this->mode = $mode; | ||
$this->renderer = $renderer; | ||
$this->searchConfig = $searchConfig; | ||
$this->data = $searchConfig->getConf(); | ||
$this->columns = $searchConfig->getColumns(); | ||
$this->result = $this->searchConfig->execute(); | ||
$this->resultCount = $this->searchConfig->getCount(); | ||
|
||
$this->max = $this->result[0]['count']; | ||
$this->min = end($this->result)['count']; | ||
} | ||
|
||
/** | ||
* Create the cloud on the renderer | ||
*/ | ||
public function render() { | ||
|
||
$this->sortResults(); | ||
|
||
$this->startScope(); | ||
$this->startList(); | ||
foreach ($this->result as $result) { | ||
$this->renderTag($result); | ||
} | ||
$this->finishList(); | ||
$this->finishScope(); | ||
return; | ||
} | ||
|
||
/** | ||
* Adds additional info to document and renderer in XHTML mode | ||
* | ||
* @see finishScope() | ||
*/ | ||
protected function startScope() { | ||
// wrapping div | ||
if($this->mode != 'xhtml') return; | ||
$this->renderer->doc .= "<div class=\"structcloud\">"; | ||
} | ||
|
||
/** | ||
* Closes the table and anything opened in startScope() | ||
* | ||
* @see startScope() | ||
*/ | ||
protected function finishScope() { | ||
// wrapping div | ||
if($this->mode != 'xhtml') return; | ||
$this->renderer->doc .= '</div>'; | ||
} | ||
|
||
/** | ||
* Render a tag of the cloud | ||
* | ||
* @param ['tag' => Value, 'count' => int] $result | ||
*/ | ||
protected function renderTag($result) { | ||
/** | ||
* @var Value $value | ||
*/ | ||
$value = $result['tag']; | ||
$count = $result['count']; | ||
if ($value->isEmpty()) { | ||
return; | ||
} | ||
|
||
$type = strtolower($value->getColumn()->getType()->getClass()); | ||
$weight = $this->getWeight($count, $this->min, $this->max); | ||
|
||
if (!empty($this->data['target'])) { | ||
$target = $this->data['target']; | ||
} else { | ||
global $INFO; | ||
$target = $INFO['id']; | ||
} | ||
|
||
$tagValue = $value->getDisplayValue(); | ||
if (is_array($tagValue)) { | ||
$tagValue = $tagValue[0]; | ||
} | ||
$key = $value->getColumn()->getFullQualifiedLabel() . '*~'; | ||
$filter = SearchConfigParameters::$PARAM_FILTER . "[$key]=" . urlencode($tagValue); | ||
|
||
$this->renderer->listitem_open(1); | ||
$this->renderer->listcontent_open(); | ||
|
||
if($this->mode == 'xhtml') { | ||
$this->renderer->doc .= "<div style='font-size:$weight%' data-count='$count' class='cloudtag struct_$type'>"; | ||
} | ||
|
||
$value->renderAsTagCloudLink($this->renderer, $this->mode, $target, $filter, $weight); | ||
|
||
if($this->mode == 'xhtml') { | ||
$this->renderer->doc .= '</div>'; | ||
} | ||
|
||
$this->renderer->listcontent_close(); | ||
$this->renderer->listitem_close(); | ||
} | ||
|
||
/** | ||
* This interpolates the weight between 70 and 150 based on $min, $max and $current | ||
* | ||
* @param int $current | ||
* @param int $min | ||
* @param int $max | ||
* @return int | ||
*/ | ||
protected function getWeight($current, $min, $max) { | ||
if ($min == $max) { | ||
return 100; | ||
} | ||
return round(($current - $min)/($max - $min) * 80 + 70); | ||
} | ||
|
||
/** | ||
* Sort the list of results | ||
*/ | ||
protected function sortResults() { | ||
usort($this->result, function ($a, $b) { | ||
$asort = $a['tag']->getColumn()->getType()->getSortString($a['tag']); | ||
$bsort = $b['tag']->getColumn()->getType()->getSortString($b['tag']); | ||
if ($asort < $bsort) { | ||
return -1; | ||
} | ||
if ($asort > $bsort) { | ||
return 1; | ||
} | ||
return 0; | ||
}); | ||
} | ||
|
||
protected function startList() { | ||
$this->renderer->listu_open(); | ||
} | ||
|
||
protected function finishList() { | ||
$this->renderer->listu_close(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<?php | ||
|
||
namespace dokuwiki\plugin\struct\meta; | ||
|
||
|
||
/** | ||
* Class SearchCloud | ||
* | ||
* The same as @see SearchConfig, but executed a search that is not pid-focused | ||
* | ||
* @package dokuwiki\plugin\struct\meta | ||
*/ | ||
class SearchCloud extends SearchConfig { | ||
|
||
protected $limit = ''; | ||
|
||
|
||
/** | ||
* Transform the set search parameters into a statement | ||
* | ||
* @return array ($sql, $opts) The SQL and parameters to execute | ||
*/ | ||
public function getSQL() { | ||
if(!$this->columns) throw new StructException('nocolname'); | ||
|
||
$QB = new QueryBuilder(); | ||
reset($this->schemas); | ||
$schema = current($this->schemas); | ||
$datatable = 'data_' . $schema->getTable(); | ||
if(!$schema->isLookup()) { | ||
$QB->addTable('schema_assignments'); | ||
$QB->filters()->whereAnd("$datatable.pid = schema_assignments.pid"); | ||
$QB->filters()->whereAnd("schema_assignments.tbl = '{$schema->getTable()}'"); | ||
$QB->filters()->whereAnd("schema_assignments.assigned = 1"); | ||
$QB->filters()->whereAnd("GETACCESSLEVEL($datatable.pid) > 0"); | ||
$QB->filters()->whereAnd("PAGEEXISTS($datatable.pid) = 1"); | ||
} | ||
$QB->addTable($datatable); | ||
$QB->filters()->whereAnd("$datatable.latest = 1"); | ||
|
||
$col = $this->columns[0]; | ||
if($col->isMulti()) { | ||
$multitable = "multi_{$col->getTable()}"; | ||
$MN = $QB->generateTableAlias('M'); | ||
|
||
$QB->addLeftJoin( | ||
$datatable, | ||
$multitable, | ||
$MN, | ||
"$datatable.pid = $MN.pid AND | ||
$datatable.rev = $MN.rev AND | ||
$MN.colref = {$col->getColref()}" | ||
); | ||
|
||
$col->getType()->select($QB, $MN, 'value', 'tag'); | ||
$colname = $MN . '.value'; | ||
} else { | ||
$col->getType()->select($QB, $datatable, $col->getColName(), 'tag'); | ||
$colname = $datatable . '.' . $col->getColName(); | ||
} | ||
$QB->addSelectStatement("COUNT($colname)", 'count'); | ||
$QB->addGroupByStatement('tag'); | ||
$QB->addOrderBy('count DESC'); | ||
|
||
list($sql, $opts) = $QB->getSQL(); | ||
return [$sql . $this->limit, $opts]; | ||
} | ||
|
||
/** | ||
* We do not have pagination in clouds, so we can work with a limit within SQL | ||
* | ||
* @param int $limit | ||
*/ | ||
public function setLimit($limit) { | ||
$this->limit = " LIMIT $limit"; | ||
} | ||
|
||
/** | ||
* Execute this search and return the result | ||
* | ||
* The result is a two dimensional array of Value()s. | ||
* | ||
* @return Value[][] | ||
*/ | ||
public function execute() { | ||
list($sql, $opts) = $this->getSQL(); | ||
|
||
/** @var \PDOStatement $res */ | ||
$res = $this->sqlite->query($sql, $opts); | ||
if($res === false) throw new StructException("SQL execution failed for\n\n$sql"); | ||
|
||
$result = []; | ||
$rows = $this->sqlite->res2arr($res); | ||
|
||
foreach ($rows as $row) { | ||
if (!empty($this->config['min']) && $this->config['min'] > $row['count']) { | ||
break; | ||
} | ||
|
||
$row['tag'] = new Value($this->columns[0], $row['tag']); | ||
$result[] = $row; | ||
} | ||
|
||
$this->sqlite->res_close($res); | ||
$this->count = count($result); | ||
return $result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.