1<?php 2 3namespace dokuwiki\plugin\struct\meta; 4 5/** 6 * Creates the SQL query using the QueryBuilder 7 * 8 * @internal This class is used by the Search class and should probably never be used directly 9 */ 10class SearchSQLBuilder 11{ 12 13 /** @var QueryBuilder */ 14 protected $qb; 15 16 /** 17 * SearchSQLBuilder constructor. 18 */ 19 public function __construct() 20 { 21 $this->qb = new QueryBuilder(); 22 } 23 24 /** 25 * Add the schemas to the query 26 * 27 * @param Schema[] $schemas Schema names to query 28 */ 29 public function addSchemas($schemas) 30 { 31 // basic tables 32 $first_table = ''; 33 foreach ($schemas as $schema) { 34 $datatable = 'data_' . $schema->getTable(); 35 if ($first_table) { 36 // follow up tables 37 $this->qb->addLeftJoin($first_table, $datatable, $datatable, "$first_table.pid = $datatable.pid"); 38 } else { 39 // first table 40 $this->qb->addTable($datatable); 41 42 // add conditional page clauses if pid has a value 43 $subAnd = $this->qb->filters()->whereSubAnd(); 44 $subAnd->whereAnd("$datatable.pid = ''"); 45 $subOr = $subAnd->whereSubOr(); 46 $subOr->whereAnd("GETACCESSLEVEL($datatable.pid) > 0"); 47 $subOr->whereAnd("PAGEEXISTS($datatable.pid) = 1"); 48 $subOr->whereAnd('(ASSIGNED = 1 OR ASSIGNED IS NULL)'); 49 50 // add conditional schema assignment check 51 $this->qb->addLeftJoin( 52 $datatable, 53 'schema_assignments', 54 '', 55 "$datatable.pid != '' 56 AND $datatable.pid = schema_assignments.pid 57 AND schema_assignments.tbl = '{$schema->getTable()}'" 58 ); 59 60 $this->qb->addSelectColumn($datatable, 'rid'); 61 $this->qb->addSelectColumn($datatable, 'pid', 'PID'); 62 $this->qb->addSelectColumn($datatable, 'rev'); 63 $this->qb->addSelectColumn('schema_assignments', 'assigned', 'ASSIGNED'); 64 $this->qb->addGroupByColumn($datatable, 'pid'); 65 $this->qb->addGroupByColumn($datatable, 'rid'); 66 67 $first_table = $datatable; 68 } 69 // phpcs:ignore 70 $this->qb->filters()->whereAnd("( (IS_PUBLISHER($datatable.pid) AND $datatable.latest = 1) OR (IS_PUBLISHER($datatable.pid) !=1 AND $datatable.published = 1) )"); 71 } 72 } 73 74 /** 75 * Add the columns to select, handling multis 76 * 77 * @param Column[] $columns 78 */ 79 public function addColumns($columns) 80 { 81 $sep = Search::CONCAT_SEPARATOR; 82 $n = 0; 83 foreach ($columns as $col) { 84 $CN = 'C' . $n++; 85 86 if ($col->isMulti()) { 87 $datatable = "data_{$col->getTable()}"; 88 $multitable = "multi_{$col->getTable()}"; 89 $MN = $this->qb->generateTableAlias('M'); 90 91 $this->qb->addLeftJoin( 92 $datatable, 93 $multitable, 94 $MN, 95 "$datatable.pid = $MN.pid AND $datatable.rid = $MN.rid AND 96 $datatable.rev = $MN.rev AND 97 $MN.colref = {$col->getColref()}" 98 ); 99 100 $col->getType()->select($this->qb, $MN, 'value', $CN); 101 $sel = $this->qb->getSelectStatement($CN); 102 $this->qb->addSelectStatement("GROUP_CONCAT_DISTINCT($sel, '$sep')", $CN); 103 } else { 104 $col->getType()->select($this->qb, 'data_' . $col->getTable(), $col->getColName(), $CN); 105 $this->qb->addGroupByStatement($CN); 106 } 107 } 108 } 109 110 /** 111 * Add the filters to the query 112 * 113 * All given filters are added to their own subclause. 114 * 115 * @param array[] $filters 116 */ 117 public function addFilters($filters) 118 { 119 if (!$filters) return; // ignore empty filters 120 121 $subClause = $this->qb->filters()->where('AND'); 122 123 foreach ($filters as $filter) { 124 /** @var Column $col */ 125 list($col, $value, $comp, $op) = $filter; 126 127 $datatable = "data_{$col->getTable()}"; 128 $multitable = "multi_{$col->getTable()}"; 129 130 /** @var $col Column */ 131 if ($col->isMulti()) { 132 $MN = $this->qb->generateTableAlias('MN'); 133 134 $this->qb->addLeftJoin( 135 $datatable, 136 $multitable, 137 $MN, 138 "$datatable.pid = $MN.pid AND $datatable.rid = $MN.rid AND 139 $datatable.rev = $MN.rev AND 140 $MN.colref = {$col->getColref()}" 141 ); 142 $coltbl = $MN; 143 $colnam = 'value'; 144 } else { 145 $coltbl = $datatable; 146 $colnam = $col->getColName(); 147 } 148 149 $col->getType()->filter($subClause, $coltbl, $colnam, $comp, $value, $op); // type based filter 150 } 151 } 152 153 /** 154 * Add the sort by clauses to the query 155 * 156 * We always sort by the single val column which contains a copy of the first value of the multi column 157 * 158 * @param array[] $sorts 159 */ 160 public function addSorts($sorts) 161 { 162 foreach ($sorts as $sort) { 163 list($col, $asc, $nc) = $sort; 164 /** @var $col Column */ 165 $colname = $col->getColName(false); 166 if ($nc) $colname .= ' COLLATE NOCASE'; 167 $col->getType()->sort($this->qb, 'data_' . $col->getTable(), $colname, $asc ? 'ASC' : 'DESC'); 168 } 169 } 170 171 /** 172 * Get the SQL query and parameters 173 * 174 * @return array ($sql, $params) 175 */ 176 public function getSQL() 177 { 178 return $this->qb->getSQL(); 179 } 180} 181