1<?php 2 3namespace dokuwiki\plugin\struct\types; 4 5use dokuwiki\plugin\struct\meta\QueryBuilder; 6use dokuwiki\plugin\struct\meta\QueryBuilderWhere; 7 8/** 9 * Class Page 10 * 11 * Represents a single page in the wiki. Will be linked in output. 12 * 13 * @package dokuwiki\plugin\struct\types 14 */ 15class Page extends AbstractMultiBaseType 16{ 17 protected $config = array( 18 'usetitles' => false, 19 'autocomplete' => array( 20 'mininput' => 2, 21 'maxresult' => 5, 22 'namespace' => '', 23 'postfix' => '', 24 ), 25 ); 26 27 /** 28 * Output the stored data 29 * 30 * @param string $value the value stored in the database - JSON when titles are used 31 * @param \Doku_Renderer $R the renderer currently used to render the data 32 * @param string $mode The mode the output is rendered in (eg. XHTML) 33 * @return bool true if $mode could be satisfied 34 */ 35 public function renderValue($value, \Doku_Renderer $R, $mode) 36 { 37 if ($this->config['usetitles']) { 38 list($id, $title) = \helper_plugin_struct::decodeJson($value); 39 } else { 40 $id = $value; 41 $title = $id; // cannot be empty or internallink() might hijack %pageid% and use headings 42 } 43 44 if (!$id) return true; 45 46 $R->internallink(":$id", $title); 47 return true; 48 } 49 50 /** 51 * Cleans the link 52 * 53 * @param string $rawvalue 54 * @return string 55 */ 56 public function validate($rawvalue) 57 { 58 list($page, $fragment) = array_pad(explode('#', $rawvalue, 2), 2, ''); 59 return cleanID($page) . (strlen(cleanID($fragment)) > 0 ? '#' . cleanID($fragment) : ''); 60 } 61 62 /** 63 * Autocompletion support for pages 64 * 65 * @return array 66 */ 67 public function handleAjax() 68 { 69 global $INPUT; 70 71 // check minimum length 72 $lookup = trim($INPUT->str('search')); 73 if (utf8_strlen($lookup) < $this->config['autocomplete']['mininput']) return array(); 74 75 // results wanted? 76 $max = $this->config['autocomplete']['maxresult']; 77 if ($max <= 0) return array(); 78 79 // lookup with namespace and postfix applied 80 $namespace = $this->config['autocomplete']['namespace']; 81 if ($namespace) { 82 // namespace may be relative, resolve in current context 83 $namespace .= ':foo'; // resolve expects pageID 84 resolve_pageid($INPUT->str('ns'), $namespace, $exists); 85 $namespace = getNS($namespace); 86 } 87 $postfix = $this->config['autocomplete']['postfix']; 88 if ($namespace) $lookup .= ' @' . $namespace; 89 90 $data = ft_pageLookup($lookup, true, $this->config['usetitles']); 91 if (!count($data)) return array(); 92 93 // this basically duplicates what we do in ajax_qsearch() 94 $result = array(); 95 $counter = 0; 96 foreach ($data as $id => $title) { 97 if ($this->config['usetitles']) { 98 $name = $title . ' (' . $id . ')'; 99 } else { 100 $ns = getNS($id); 101 if ($ns) { 102 $name = noNS($id) . ' (' . $ns . ')'; 103 } else { 104 $name = $id; 105 } 106 } 107 108 // check suffix 109 if ($postfix && substr($id, -1 * strlen($postfix)) != $postfix) { 110 continue; // page does not end in postfix, don't suggest it 111 } 112 113 $result[] = array( 114 'label' => $name, 115 'value' => $id 116 ); 117 118 $counter++; 119 if ($counter > $max) break; 120 } 121 122 return $result; 123 } 124 125 /** 126 * When using titles, we need ot join the titles table 127 * 128 * @param QueryBuilder $QB 129 * @param string $tablealias 130 * @param string $colname 131 * @param string $alias 132 */ 133 public function select(QueryBuilder $QB, $tablealias, $colname, $alias) 134 { 135 if (!$this->config['usetitles']) { 136 parent::select($QB, $tablealias, $colname, $alias); 137 return; 138 } 139 $rightalias = $QB->generateTableAlias(); 140 $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid"); 141 $QB->addSelectStatement("STRUCT_JSON($tablealias.$colname, $rightalias.title)", $alias); 142 } 143 144 /** 145 * When using titles, sort by them first 146 * 147 * @param QueryBuilder $QB 148 * @param string $tablealias 149 * @param string $colname 150 * @param string $order 151 */ 152 public function sort(QueryBuilder $QB, $tablealias, $colname, $order) 153 { 154 if (!$this->config['usetitles']) { 155 parent::sort($QB, $tablealias, $colname, $order); 156 return; 157 } 158 159 $rightalias = $QB->generateTableAlias(); 160 $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid"); 161 $QB->addOrderBy("$rightalias.title $order"); 162 $QB->addOrderBy("$tablealias.$colname $order"); 163 } 164 165 /** 166 * Return the pageid only 167 * 168 * @param string $value 169 * @return string 170 */ 171 public function rawValue($value) 172 { 173 if ($this->config['usetitles']) { 174 list($value) = \helper_plugin_struct::decodeJson($value); 175 } 176 return $value; 177 } 178 179 /** 180 * Return the title only 181 * 182 * @param string $value 183 * @return string 184 */ 185 public function displayValue($value) 186 { 187 if ($this->config['usetitles']) { 188 list($pageid, $value) = \helper_plugin_struct::decodeJson($value); 189 if (blank($value)) { 190 $value = $pageid; 191 } 192 } 193 return $value; 194 } 195 196 /** 197 * When using titles, we need to compare against the title table, too 198 * 199 * @param QueryBuilderWhere $add 200 * @param string $tablealias 201 * @param string $colname 202 * @param string $comp 203 * @param string $value 204 * @param string $op 205 */ 206 public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op) 207 { 208 if (!$this->config['usetitles']) { 209 parent::filter($add, $tablealias, $colname, $comp, $value, $op); 210 return; 211 } 212 213 $QB = $add->getQB(); 214 $rightalias = $QB->generateTableAlias(); 215 $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid"); 216 217 // compare against page and title 218 $sub = $add->where($op); 219 $pl = $QB->addValue($value); 220 $sub->whereOr("$tablealias.$colname $comp $pl"); 221 $pl = $QB->addValue($value); 222 $sub->whereOr("$rightalias.title $comp $pl"); 223 } 224} 225