1<?php 2 3namespace dokuwiki\plugin\struct\types; 4 5use dokuwiki\plugin\struct\meta\Column; 6use dokuwiki\plugin\struct\meta\QueryBuilder; 7use dokuwiki\plugin\struct\meta\QueryBuilderWhere; 8use dokuwiki\plugin\struct\meta\SearchConfigParameters; 9use dokuwiki\Utf8\PhpString; 10 11class Tag extends AbstractMultiBaseType 12{ 13 protected $config = [ 14 'page' => '', 15 'autocomplete' => [ 16 'mininput' => 2, 17 'maxresult' => 5 18 ] 19 ]; 20 21 /** 22 * @param int|string $value 23 * @param \Doku_Renderer $R 24 * @param string $mode 25 * @return bool 26 */ 27 public function renderValue($value, \Doku_Renderer $R, $mode) 28 { 29 $context = $this->getContext(); 30 $filter = SearchConfigParameters::$PARAM_FILTER . 31 '[' . $context->getTable() . '.' . $context->getLabel() . '*~]=' . $value; 32 33 $page = trim($this->config['page']); 34 if (!$page) $page = cleanID($context->getLabel()); 35 36 $R->internallink($page . '?' . $filter, $value); 37 return true; 38 } 39 40 /** 41 * Autocomplete from existing tags 42 * 43 * @return array 44 */ 45 public function handleAjax() 46 { 47 global $INPUT; 48 49 // check minimum length 50 $lookup = trim($INPUT->str('search')); 51 if (PhpString::strlen($lookup) < $this->config['autocomplete']['mininput']) return []; 52 53 // results wanted? 54 $max = $this->config['autocomplete']['maxresult']; 55 if ($max <= 0) return []; 56 57 $context = $this->getContext(); 58 $sql = $this->buildSQLFromContext($context); 59 $opt = ["%$lookup%"]; 60 61 /** @var \helper_plugin_struct_db $hlp */ 62 $hlp = plugin_load('helper', 'struct_db'); 63 $sqlite = $hlp->getDB(); 64 $rows = $sqlite->queryAll($sql, $opt); 65 66 $result = []; 67 foreach ($rows as $row) { 68 $result[] = [ 69 'label' => $row['value'], 70 'value' => $row['value'] 71 ]; 72 } 73 74 return $result; 75 } 76 77 /** 78 * Create the sql to query the database for tags to do autocompletion 79 * 80 * This method both handles multi columns and page schemas that need access checking 81 * 82 * @param Column $context 83 * 84 * @return string The sql with a single "?" placeholde for the search value 85 */ 86 protected function buildSQLFromContext(Column $context) 87 { 88 $sql = ''; 89 if ($context->isMulti()) { 90 /** @noinspection SqlResolve */ 91 $sql .= "SELECT DISTINCT value 92 FROM multi_{$context->getTable()} AS M, data_{$context->getTable()} AS D 93 WHERE M.pid = D.pid 94 AND M.rev = D.rev 95 AND M.colref = {$context->getColref()}\n"; 96 } else { 97 /** @noinspection SqlResolve */ 98 $sql .= "SELECT DISTINCT col{$context->getColref()} AS value 99 FROM data_{$context->getTable()} AS D 100 WHERE 1 = 1\n"; 101 } 102 103 $sql .= "AND ( D.pid = '' OR ("; 104 $sql .= "PAGEEXISTS(D.pid) = 1\n"; 105 $sql .= "AND GETACCESSLEVEL(D.pid) > 0\n"; 106 $sql .= ")) "; 107 108 $sql .= "AND D.latest = 1\n"; 109 $sql .= "AND value LIKE ?\n"; 110 $sql .= 'ORDER BY value'; 111 112 return $sql; 113 } 114 115 /** 116 * Normalize tags before comparing 117 * 118 * @param QueryBuilder $QB 119 * @param string $tablealias 120 * @param string $colname 121 * @param string $comp 122 * @param string|string[] $value 123 * @param string $op 124 */ 125 public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op) 126 { 127 /** @var QueryBuilderWhere $add Where additionional queries are added to */ 128 if (is_array($value)) { 129 $add = $add->where($op); // sub where group 130 $op = 'OR'; 131 } 132 foreach ((array)$value as $item) { 133 $pl = $add->getQB()->addValue($item); 134 $add->where($op, "LOWER(REPLACE($tablealias.$colname, ' ', '')) $comp LOWER(REPLACE($pl, ' ', ''))"); 135 } 136 } 137} 138