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