12e564e06SAndreas Gohr<?php 22e564e06SAndreas Gohr 32e564e06SAndreas Gohr/** 42e564e06SAndreas Gohr * DokuWiki Plugin dbquery (Helper Component) 52e564e06SAndreas Gohr * 62e564e06SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 72e564e06SAndreas Gohr * @author Andreas Gohr <dokuwiki@cosmocode.de> 82e564e06SAndreas Gohr */ 92e564e06SAndreas Gohrclass helper_plugin_dbquery extends dokuwiki\Extension\Plugin 102e564e06SAndreas Gohr{ 111a014136SAndreas Gohr /** @var PDO[] do not access directly, use getPDO instead */ 121a014136SAndreas Gohr protected $pdo = []; 132e564e06SAndreas Gohr 142e564e06SAndreas Gohr /** 152e564e06SAndreas Gohr * @param string $name Page name of the query 166adcc9adSAndreas Gohr * @return array 172e564e06SAndreas Gohr * @throws \Exception 182e564e06SAndreas Gohr */ 19c64c1748SAndreas Gohr public function loadDataFromPage($name) 202e564e06SAndreas Gohr { 212e564e06SAndreas Gohr 222e564e06SAndreas Gohr $name = cleanID($name); 232e564e06SAndreas Gohr $id = $this->getConf('namespace') . ':' . $name; 242e564e06SAndreas Gohr if (!page_exists($id)) throw new \Exception("No query named '$name' found"); 252e564e06SAndreas Gohr 262e564e06SAndreas Gohr $doc = p_cached_output(wikiFN($id), 'dbquery'); 272e564e06SAndreas Gohr 286adcc9adSAndreas Gohr return json_decode($doc, true); 292e564e06SAndreas Gohr } 302e564e06SAndreas Gohr 312e564e06SAndreas Gohr /** 321a014136SAndreas Gohr * Return the PDO object and cache it for the request 331a014136SAndreas Gohr * 341a014136SAndreas Gohr * Connections data can be null to use the info from the config 351a014136SAndreas Gohr * 361a014136SAndreas Gohr * @param string|null $dsn 371a014136SAndreas Gohr * @param string|null $user 381a014136SAndreas Gohr * @param string|null $pass 396648d9d9SAndreas Gohr * @return PDO 406648d9d9SAndreas Gohr */ 411a014136SAndreas Gohr public function getPDO($dsn = null, $user = null, $pass = null) 426648d9d9SAndreas Gohr { 431a014136SAndreas Gohr $dsn = $dsn ?: $this->getConf('dsn'); 441a014136SAndreas Gohr $user = $user ?: $this->getConf('user'); 451a014136SAndreas Gohr $pass = $pass ?: conf_decodeString($this->getConf('pass')); 461a014136SAndreas Gohr $conid = md5($dsn . $user . $pass); 471a014136SAndreas Gohr 481a014136SAndreas Gohr if (isset($this->pdo[$conid])) return $this->pdo[$conid]; 491a014136SAndreas Gohr 506648d9d9SAndreas Gohr $opts = [ 516648d9d9SAndreas Gohr PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // always fetch as array 526648d9d9SAndreas Gohr PDO::ATTR_EMULATE_PREPARES => true, // emulating prepares allows us to reuse param names 536648d9d9SAndreas Gohr PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // we want exceptions, not error codes 546648d9d9SAndreas Gohr ]; 556648d9d9SAndreas Gohr 561a014136SAndreas Gohr $this->pdo[$conid] = new PDO($dsn, $user, $pass, $opts); 571a014136SAndreas Gohr return $this->pdo[$conid]; 586648d9d9SAndreas Gohr } 596648d9d9SAndreas Gohr 606648d9d9SAndreas Gohr /** 612e564e06SAndreas Gohr * Opens a database connection, executes the query and returns the result 622e564e06SAndreas Gohr * 632e564e06SAndreas Gohr * @param string $query 642e564e06SAndreas Gohr * @return array 652e564e06SAndreas Gohr * @throws \PDOException 66584c7810SAndreas Gohr * @throws Exception 672e564e06SAndreas Gohr */ 68*3113520cSAndreas Gohr public function executeQuery($query, $dsnalias = null) 692e564e06SAndreas Gohr { 70584c7810SAndreas Gohr if (!preg_match('/^select /i', trim($query))) { 71584c7810SAndreas Gohr throw new \Exception('For security reasons only SELECT statements are allowed in dbquery'); 72584c7810SAndreas Gohr } 73584c7810SAndreas Gohr 74*3113520cSAndreas Gohr [$dsn, $user, $pass] = $this->getDSN($dsnalias); 75*3113520cSAndreas Gohr $pdo = $this->getPDO($dsn, $user, $pass); 76be0b6729SAndreas Gohr $params = $this->gatherVariables(); 77be0b6729SAndreas Gohr $sth = $this->prepareStatement($pdo, $query, $params); 78be0b6729SAndreas Gohr $sth->execute(); 792e564e06SAndreas Gohr $data = $sth->fetchAll(PDO::FETCH_ASSOC); 802e564e06SAndreas Gohr $sth->closeCursor(); 812e564e06SAndreas Gohr 822e564e06SAndreas Gohr return $data; 832e564e06SAndreas Gohr } 84be0b6729SAndreas Gohr 85be0b6729SAndreas Gohr /** 86be0b6729SAndreas Gohr * Generate a prepared statement with bound parameters 87be0b6729SAndreas Gohr * 88be0b6729SAndreas Gohr * @param PDO $pdo 89be0b6729SAndreas Gohr * @param string $sql 906648d9d9SAndreas Gohr * @param array $parameters 91be0b6729SAndreas Gohr * @return PDOStatement 92be0b6729SAndreas Gohr */ 93be0b6729SAndreas Gohr public function prepareStatement(\PDO $pdo, $sql, $parameters) 94be0b6729SAndreas Gohr { 956648d9d9SAndreas Gohr // prepare the groups 966648d9d9SAndreas Gohr $cnt = 0; 976648d9d9SAndreas Gohr $groupids = []; 986648d9d9SAndreas Gohr foreach ($parameters[':groups'] as $group) { 996648d9d9SAndreas Gohr $id = 'group' . $cnt++; 1006648d9d9SAndreas Gohr $parameters[$id] = $group; 1016648d9d9SAndreas Gohr $groupids[] = ":$id"; 1026648d9d9SAndreas Gohr } 1036648d9d9SAndreas Gohr unset($parameters[':groups']); 1046648d9d9SAndreas Gohr $sql = str_replace(':groups', join(',', $groupids), $sql); 105be0b6729SAndreas Gohr 1066648d9d9SAndreas Gohr $sth = $pdo->prepare($sql); 107be0b6729SAndreas Gohr foreach ($parameters as $key => $val) { 108be0b6729SAndreas Gohr if (is_array($val)) continue; 109be0b6729SAndreas Gohr if (is_object($val)) continue; 110be0b6729SAndreas Gohr if (strpos($sql, $key) === false) continue; // skip if parameter is missing 111be0b6729SAndreas Gohr 112be0b6729SAndreas Gohr if (is_int($val)) { 113be0b6729SAndreas Gohr $sth->bindValue($key, $val, PDO::PARAM_INT); 114be0b6729SAndreas Gohr } else { 115be0b6729SAndreas Gohr $sth->bindValue($key, $val); 116be0b6729SAndreas Gohr } 117be0b6729SAndreas Gohr } 118be0b6729SAndreas Gohr 119be0b6729SAndreas Gohr return $sth; 120be0b6729SAndreas Gohr } 121be0b6729SAndreas Gohr 122be0b6729SAndreas Gohr /** 123be0b6729SAndreas Gohr * Get the standard replacement variables 124be0b6729SAndreas Gohr * 125be0b6729SAndreas Gohr * @return array 126be0b6729SAndreas Gohr */ 127be0b6729SAndreas Gohr public function gatherVariables() 128be0b6729SAndreas Gohr { 129be0b6729SAndreas Gohr global $USERINFO; 130be0b6729SAndreas Gohr global $INFO; 131be0b6729SAndreas Gohr global $INPUT; 132be0b6729SAndreas Gohr 133be0b6729SAndreas Gohr return [ 134be0b6729SAndreas Gohr ':user' => $INPUT->server->str('REMOTE_USER'), 1354515310dSAndreas Gohr ':mail' => $USERINFO['mail'] ?? '', 1364515310dSAndreas Gohr ':groups' => $USERINFO['grps'] ?? [], 1376648d9d9SAndreas Gohr ':id' => ':' . $INFO['id'], 1386648d9d9SAndreas Gohr ':page' => noNS($INFO['id']), 1396648d9d9SAndreas Gohr ':ns' => ':' . getNS($INFO['id']), 140be0b6729SAndreas Gohr ]; 141be0b6729SAndreas Gohr } 142*3113520cSAndreas Gohr 143*3113520cSAndreas Gohr /** 144*3113520cSAndreas Gohr * Get the DSN, user and pass for a given alias 145*3113520cSAndreas Gohr * 146*3113520cSAndreas Gohr * @param string|null $alias null for default 147*3113520cSAndreas Gohr * @return [string, string, string] DSN, user, pass 148*3113520cSAndreas Gohr * @throws Exception 149*3113520cSAndreas Gohr */ 150*3113520cSAndreas Gohr public function getDSN($alias = null) 151*3113520cSAndreas Gohr { 152*3113520cSAndreas Gohr static $aliases = null; 153*3113520cSAndreas Gohr if ($aliases === null) { 154*3113520cSAndreas Gohr $aliases = $this->getDsnAliases( 155*3113520cSAndreas Gohr $this->getConf('dsn'), 156*3113520cSAndreas Gohr $this->getConf('user'), 157*3113520cSAndreas Gohr $this->getConf('pass') 158*3113520cSAndreas Gohr ); 159*3113520cSAndreas Gohr } 160*3113520cSAndreas Gohr 161*3113520cSAndreas Gohr if ($aliases === []) throw new \Exception('No DSN aliases defined'); 162*3113520cSAndreas Gohr 163*3113520cSAndreas Gohr if ($alias === null || !isset($aliases[$alias])) { 164*3113520cSAndreas Gohr $alias = '_'; 165*3113520cSAndreas Gohr } 166*3113520cSAndreas Gohr 167*3113520cSAndreas Gohr return [$aliases[$alias]['dsn'], $aliases[$alias]['user'], $aliases[$alias]['pass']]; 168*3113520cSAndreas Gohr } 169*3113520cSAndreas Gohr 170*3113520cSAndreas Gohr /** 171*3113520cSAndreas Gohr * Load and parse the DSN configuration 172*3113520cSAndreas Gohr * 173*3113520cSAndreas Gohr * @param string $config 174*3113520cSAndreas Gohr * @param string $defaultuser 175*3113520cSAndreas Gohr * @param string $defaultpass 176*3113520cSAndreas Gohr * @return array 177*3113520cSAndreas Gohr */ 178*3113520cSAndreas Gohr protected function getDsnAliases($config, $defaultuser, $defaultpass) 179*3113520cSAndreas Gohr { 180*3113520cSAndreas Gohr $aliases = []; 181*3113520cSAndreas Gohr $lines = explode("\n", $config); 182*3113520cSAndreas Gohr foreach ($lines as $line) { 183*3113520cSAndreas Gohr $line = trim($line); 184*3113520cSAndreas Gohr if (!$line) continue; 185*3113520cSAndreas Gohr $parts = preg_split('/\s+/', $line, 4); 186*3113520cSAndreas Gohr 187*3113520cSAndreas Gohr if (count($parts) > 1) { 188*3113520cSAndreas Gohr $aliases[$parts[0]] = [ 189*3113520cSAndreas Gohr 'dsn' => $parts[1], 190*3113520cSAndreas Gohr 'user' => $parts[2] ?? $defaultuser, 191*3113520cSAndreas Gohr 'pass' => $parts[3] ?? $defaultpass 192*3113520cSAndreas Gohr ]; 193*3113520cSAndreas Gohr } else { 194*3113520cSAndreas Gohr $parts = ['', $parts[0]]; 195*3113520cSAndreas Gohr } 196*3113520cSAndreas Gohr 197*3113520cSAndreas Gohr if (!isset($aliases['_'])) { 198*3113520cSAndreas Gohr $aliases['_'] = [ 199*3113520cSAndreas Gohr 'dsn' => $parts[1], 200*3113520cSAndreas Gohr 'user' => $parts[2] ?? $defaultuser, 201*3113520cSAndreas Gohr 'pass' => $parts[3] ?? $defaultpass 202*3113520cSAndreas Gohr ]; 203*3113520cSAndreas Gohr } 204*3113520cSAndreas Gohr } 205*3113520cSAndreas Gohr return $aliases; 206*3113520cSAndreas Gohr } 2072e564e06SAndreas Gohr} 208