*/
// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();
require_once(DOKU_PLUGIN . 'syntax.php');
require_once(DOKU_INC . 'inc/infoutils.php');
/**
* This is the base class for all syntax classes, providing some general stuff
*/
class helper_plugin_dataau extends DokuWiki_Plugin {
/**
* @var helper_plugin_sqlite initialized via _getDb()
*/
protected $db = null;
/**
* @var array stores the alias definitions
*/
protected $aliases = null;
/**
* @var array stores custom key localizations
*/
protected $locs = array();
/**
* Constructor
*
* Loads custom translations
*/
public function __construct() {
$this->loadLocalizedLabels();
}
private function loadLocalizedLabels() {
$lang = array();
$path = DOKU_CONF . '/lang/en/dataau-plugin.php';
if(file_exists($path)) include($path);
$path = DOKU_CONF . '/lang/' . $this->determineLang() . '/dataau-plugin.php';
if(file_exists($path)) include($path);
foreach($lang as $key => $val) {
$lang[utf8_strtolower($key)] = $val;
}
$this->locs = $lang;
}
/**
* Return language code
*
* @return mixed
*/
protected function determineLang() {
/** @var helper_plugin_translation $trans */
$trans = plugin_load('helper', 'translation');
if($trans) {
$value = $trans->getLangPart(getID());
if($value) return $value;
}
global $conf;
return $conf['lang'];
}
/**
* Simple function to check if the database is ready to use
*
* @return bool
*/
public function ready() {
return (bool) $this->_getDB();
}
/**
* load the sqlite helper
*
* @return helper_plugin_sqlite|false plugin or false if failed
*/
function _getDB() {
if($this->db === null) {
$this->db = plugin_load('helper', 'sqlite');
if($this->db === null) {
msg('The dataau plugin needs the sqlite plugin', -1);
return false;
}
if(!$this->db->init('dataau', dirname(__FILE__) . '/db/')) {
$this->db = null;
return false;
}
$this->db->create_function('DATARESOLVE', array($this, '_resolveData'), 2);
}
return $this->db;
}
/**
* Makes sure the given data fits with the given type
*
* @param string $value
* @param string|array $type
* @return string
*/
function _cleanData($value, $type) {
$value = trim($value);
if(!$value AND $value !== '0') {
return '';
}
if(is_array($type)) {
if(isset($type['enum']) && !preg_match('/(^|,\s*)' . preg_quote_cb($value) . '($|\s*,)/', $type['enum'])) {
return '';
}
$type = $type['type'];
}
switch($type) {
case 'dt':
if(preg_match('/^(\d\d?)-(\d\d?)-(\d\d\d\d)$/', $value, $m)) {
return sprintf('%02d-%02d-%d', $m[1], $m[2], $m[3]);
}
if($value === '%now%') {
return $value;
}
return '';
case 'url':
if(!preg_match('!^[a-z]+://!i', $value)) {
$value = 'http://' . $value;
}
return $value;
case 'mail':
$email = '';
$name = '';
$parts = preg_split('/\s+/', $value);
do {
$part = array_shift($parts);
if(!$email && mail_isvalid($part)) {
$email = strtolower($part);
continue;
}
$name .= $part . ' ';
} while($part);
return trim($email . ' ' . $name);
case 'page':
case 'nspage':
return cleanID($value);
default:
return $value;
}
}
/**
* Add pre and postfixs to the given value
*
* $type may be an column array with pre and postfixes
*
* @param string|array $type
* @param string $val
* @param string $pre
* @param string $post
* @return string
*/
function _addPrePostFixes($type, $val, $pre = '', $post = '') {
if(is_array($type)) {
if(isset($type['prefix'])) {
$pre = $type['prefix'];
}
if(isset($type['postfix'])) {
$post = $type['postfix'];
}
}
$val = $pre . $val . $post;
$val = $this->replacePlaceholders($val);
return $val;
}
/**
* Resolve a value according to its column settings
*
* This function is registered as a SQL function named DATARESOLVE
*
* @param string $value
* @param string $colname
* @return string
*/
function _resolveData($value, $colname) {
// resolve pre and postfixes
$column = $this->_column($colname);
$value = $this->_addPrePostFixes($column['type'], $value);
// for pages, resolve title
$type = $column['type'];
if(is_array($type)) {
$type = $type['type'];
}
if($type == 'title' || ($type == 'page' && useHeading('content'))) {
$id = $value;
if($type == 'title') {
list($id,) = explode('|', $value, 2);
}
//DATARESOLVE is only used with the 'LIKE' comparator, so concatenate the different strings is fine.
$value .= ' ' . p_get_first_heading($id);
}
return $value;
}
public function ensureAbsoluteId($id) {
if (substr($id,0,1) !== ':') {
$id = ':' . $id;
}
return $id;
}
/**
* Return XHTML formated data, depending on column type
*
* @param array $column
* @param string $value
* @param Doku_Renderer_xhtml $R
* @return string
*/
function _formatData($column, $value, Doku_Renderer_xhtml $R) {
global $conf;
$vals = explode("\n", $value);
$outs = array();
//multivalued line from db result for pageid and wiki has only in first value the ID
$storedID = '';
foreach($vals as $val) {
$val = trim($val);
if($val == '') continue;
$type = $column['type'];
if(is_array($type)) {
$type = $type['type'];
}
switch($type) {
case 'page':
$val = $this->_addPrePostFixes($column['type'], $val);
$val = $this->ensureAbsoluteId($val);
$outs[] = $R->internallink($val, null, null, true);
break;
case 'title':
list($id, $title) = explode('|', $val, 2);
$id = $this->_addPrePostFixes($column['type'], $id);
$id = $this->ensureAbsoluteId($id);
$outs[] = $R->internallink($id, $title, null, true);
break;
case 'pageid':
list($id, $title) = explode('|', $val, 2);
//use ID from first value of the multivalued line
if($title == null) {
$title = $id;
if(!empty($storedID)) {
$id = $storedID;
}
} else {
$storedID = $id;
}
$id = $this->_addPrePostFixes($column['type'], $id);
$outs[] = $R->internallink($id, $title, null, true);
break;
case 'nspage':
// no prefix/postfix here
$val = ':' . $column['key'] . ":$val";
$outs[] = $R->internallink($val, null, null, true);
break;
case 'mail':
list($id, $title) = explode(' ', $val, 2);
$id = $this->_addPrePostFixes($column['type'], $id);
$id = obfuscate(hsc($id));
if(!$title) {
$title = $id;
} else {
$title = hsc($title);
}
if($conf['mailguard'] == 'visible') {
$id = rawurlencode($id);
}
$outs[] = '' . $title . '';
break;
case 'url':
$val = $this->_addPrePostFixes($column['type'], $val);
$outs[] = $this->external_link($val, false, 'urlextern');
break;
case 'tag':
// per default use keyname as target page, but prefix on aliases
if(!is_array($column['type'])) {
$target = $column['key'] . ':';
} else {
$target = $this->_addPrePostFixes($column['type'], '');
}
$outs[] = '' . hsc($val) . '';
break;
case 'timestamp':
$outs[] = dformat($val);
break;
case 'wiki':
global $ID;
$oldid = $ID;
list($ID, $dataau) = explode('|', $val, 2);
//use ID from first value of the multivalued line
if($dataau == null) {
$dataau = $ID;
$ID = $storedID;
} else {
$storedID = $ID;
}
$dataau = $this->_addPrePostFixes($column['type'], $dataau);
// Trim document_{start,end}, p_{open,close} from instructions
$allinstructions = p_get_instructions($dataau);
$wraps = 1;
if(isset($allinstructions[1]) && $allinstructions[1][0] == 'p_open') {
$wraps ++;
}
$instructions = array_slice($allinstructions, $wraps, -$wraps);
$outs[] = p_render('xhtml', $instructions, $byref_ignore);
$ID = $oldid;
break;
default:
$val = $this->_addPrePostFixes($column['type'], $val);
//type '_img' or '_img'
if(substr($type, 0, 3) == 'img') {
$width = (int) substr($type, 3);
if(!$width) {
$width = $this->getConf('image_width');
}
list($mediaid, $title) = explode('|', $val, 2);
if($title === null) {
$title = $column['key'] . ': ' . basename(str_replace(':', '/', $mediaid));
} else {
$title = trim($title);
}
if(media_isexternal($val)) {
$html = $R->externalmedia($mediaid, $title, $align = null, $width, $height = null, $cache = null, $linking = 'direct', true);
} else {
$html = $R->internalmedia($mediaid, $title, $align = null, $width, $height = null, $cache = null, $linking = 'direct', true);
}
if(strpos($html, 'mediafile') === false) {
$html = str_replace('href', 'rel="lightbox" href', $html);
}
$outs[] = $html;
} else {
$outs[] = hsc($val);
}
}
}
return join(', ', $outs);
}
/**
* Split a column name into its parts
*
* @param string $col column name
* @returns array with key, type, ismulti, title, opt
*/
function _column($col) {
preg_match('/^([^_]*)(?:_(.*))?((? $col,
'multi' => ($matches[3] === 's'),
'key' => utf8_strtolower($matches[1]),
'origkey' => $matches[1], //similar to key, but stores upper case
'title' => $matches[1],
'type' => utf8_strtolower($matches[2])
);
// fix title for special columns
static $specials = array(
'%title%' => array('page', 'title'),
'%pageid%' => array('title', 'page'),
'%class%' => array('class'),
'%lastmod%' => array('lastmod', 'timestamp')
);
if(isset($specials[$column['title']])) {
$s = $specials[$column['title']];
$column['title'] = $this->getLang($s[0]);
if($column['type'] === '' && isset($s[1])) {
$column['type'] = $s[1];
}
}
// check if the type is some alias
$aliases = $this->_aliases();
if(isset($aliases[$column['type']])) {
$column['origtype'] = $column['type'];
$column['type'] = $aliases[$column['type']];
}
// use custom localization for keys
if(isset($this->locs[$column['key']])) {
$column['title'] = $this->locs[$column['key']];
}
return $column;
}
/**
* Load defined type aliases
*
* @return array
*/
function _aliases() {
if(!is_null($this->aliases)) return $this->aliases;
$sqlite = $this->_getDB();
if(!$sqlite) return array();
$this->aliases = array();
$res = $sqlite->query("SELECT * FROM aliases");
$rows = $sqlite->res2arr($res);
foreach($rows as $row) {
$name = $row['name'];
unset($row['name']);
$this->aliases[$name] = array_filter(array_map('trim', $row));
if(!isset($this->aliases[$name]['type'])) {
$this->aliases[$name]['type'] = '';
}
}
return $this->aliases;
}
/**
* Parse a filter line into an array
*
* @param $filterline
* @return array|bool - array on success, false on error
*/
function _parse_filter($filterline) {
//split filterline on comparator
if(preg_match('/^(.*?)([\*=<>!~]{1,2})(.*)$/', $filterline, $matches)) {
$column = $this->_column(trim($matches[1]));
$com = $matches[2];
$aliasses = array(
'<>' => '!=', '=!' => '!=', '~!' => '!~',
'==' => '=', '~=' => '~', '=~' => '~'
);
if(isset($aliasses[$com])) {
$com = $aliasses[$com];
} elseif(!preg_match('/(!?[=~])|([<>]=?)|(\*~)/', $com)) {
msg('Failed to parse comparison "' . hsc($com) . '"', -1);
return false;
}
$val = trim($matches[3]);
if($com == '~~') {
$com = 'IN(';
}
if(strpos($com, '~') !== false) {
if($com === '*~') {
$val = '*' . $val . '*';
$com = '~';
}
$val = str_replace('*', '%', $val);
if($com == '!~') {
$com = 'NOT LIKE';
} else {
$com = 'LIKE';
}
} else {
// Clean if there are no asterisks I could kill
$val = $this->_cleanData($val, $column['type']);
}
$sqlite = $this->_getDB();
if(!$sqlite) return false;
$val = $sqlite->escape_string($val); //pre escape
if($com == 'IN(') {
$val = explode(',', $val);
$val = array_map('trim', $val);
$val = implode("','", $val);
}
return array(
'key' => $column['key'],
'value' => $val,
'compare' => $com,
'colname' => $column['colname'],
'type' => $column['type']
);
}
msg('Failed to parse filter "' . hsc($filterline) . '"', -1);
return false;
}
/**
* Replace placeholders in sql
*/
function _replacePlaceholdersInSQL(&$dataau) {
global $USERINFO;
// allow current user name in filter:
$dataau['sql'] = str_replace('%user%', $_SERVER['REMOTE_USER'], $dataau['sql']);
$dataau['sql'] = str_replace('%groups%', implode("','", (array) $USERINFO['grps']), $dataau['sql']);
// allow current date in filter:
$dataau['sql'] = str_replace('%now%', dformat(null, '%d-%m-%Y'), $dataau['sql']);
// language filter
$dataau['sql'] = $this->makeTranslationReplacement($dataau['sql']);
}
/**
* Replace translation related placeholders in given string
*
* @param string $data
* @return string
*/
public function makeTranslationReplacement($dataau) {
global $conf;
global $ID;
$patterns[] = '%lang%';
if(isset($conf['lang_before_translation'])) {
$values[] = $conf['lang_before_translation'];
} else {
$values[] = $conf['lang'];
}
// if translation plugin available, get current translation (empty for default lang)
$patterns[] = '%trans%';
/** @var helper_plugin_translation $trans */
$trans = plugin_load('helper', 'translation');
if($trans) {
$local = $trans->getLangPart($ID);
if($local === '') {
$local = $conf['lang'];
}
$values[] = $local;
} else {
$values[] = '';
}
return str_replace($patterns, $values, $dataau);
}
/**
* Get filters given in the request via GET or POST
*
* @return array
*/
function _get_filters() {
$filters = array();
if(!isset($_REQUEST['dataflt'])) {
$flt = array();
} elseif(!is_array($_REQUEST['dataflt'])) {
$flt = (array) $_REQUEST['dataflt'];
} else {
$flt = $_REQUEST['dataflt'];
}
foreach($flt as $key => $line) {
// we also take the column and filtertype in the key:
if(!is_numeric($key)) {
$line = $key . $line;
}
$f = $this->_parse_filter($line);
if(is_array($f)) {
$f['logic'] = 'AND';
$filters[] = $f;
}
}
return $filters;
}
/**
* prepare an array to be passed through buildURLparams()
*
* @param string $name keyname
* @param string|array $array value or key-value pairs
* @return array
*/
function _a2ua($name, $array) {
$urlarray = array();
foreach((array) $array as $key => $val) {
$urlarray[$name . '[' . $key . ']'] = $val;
}
return $urlarray;
}
/**
* get current URL parameters
*
* @param bool $returnURLparams
* @return array with dataflt, datasrt and dataofs parameters
*/
function _get_current_param($returnURLparams = true) {
$cur_params = array();
if(isset($_REQUEST['dataflt'])) {
$cur_params = $this->_a2ua('dataflt', $_REQUEST['dataflt']);
}
if(isset($_REQUEST['dataausrt'])) {
$cur_params['dataausrt'] = $_REQUEST['dataausrt'];
}
if(isset($_REQUEST['dataauofs'])) {
$cur_params['dataauofs'] = $_REQUEST['dataauofs'];
}
//combine key and value
if(!$returnURLparams) {
$flat_param = array();
foreach($cur_params as $key => $val) {
$flat_param[] = $key . $val;
}
$cur_params = $flat_param;
}
return $cur_params;
}
/**
* Get url parameters, remove all filters for given column and add filter for desired tag
*
* @param array $column
* @param string $tag
* @return array of url parameters
*/
function _getTagUrlparam($column, $tag) {
$param = array();
if(isset($_REQUEST['dataflt'])) {
$param = (array) $_REQUEST['dataflt'];
//remove all filters equal to column
foreach($param as $key => $flt) {
if(!is_numeric($key)) {
$flt = $key . $flt;
}
$filter = $this->_parse_filter($flt);
if($filter['key'] == $column['key']) {
unset($param[$key]);
}
}
}
$param[] = $column['key'] . "_=$tag";
$param = $this->_a2ua('dataflt', $param);
if(isset($_REQUEST['dataausrt'])) {
$param['dataausrt'] = $_REQUEST['dataausrt'];
}
if(isset($_REQUEST['dataauofs'])) {
$param['dataauofs'] = $_REQUEST['dataauofs'];
}
return $param;
}
/**
* Perform replacements on the output values
*
* @param string $value
* @return string
*/
private function replacePlaceholders($value) {
return $this->makeTranslationReplacement($value);
}
}