forena-8.x-1.x-dev/src/FrxPlugin/Driver/DriverBase.php
src/FrxPlugin/Driver/DriverBase.php
<?php // $Id$ /** * @file * Class that defines default methods for access control in an DriverBase * */ namespace Drupal\forena\FrxPlugin\Driver; use Behat\Mink\Exception\Exception; use Drupal\forena\AppService; use Drupal\forena\File\DataFileSystem; use Drupal\forena\FrxAPI; use \SimpleXMLElement; use Symfony\Component\Yaml\Parser; abstract class DriverBase implements DriverInterface { use FrxAPI; // Dependency injection for drupal code. public $name; public $conf; public $block_path; public $comment_prefix; public $comment_suffix; public $block_ext; public $block_extensions; public $types; public $block_name; public $fileSvc; protected $te; public $debug = FALSE; public function __construct($name, $conf, DataFileSystem $fileSystem) { $this->conf = $conf; $this->fileSvc = $fileSystem; $this->comment_prefix = '--'; $this->block_ext = 'sql'; $this->block_extensions = array('inc', 'sql', 'xml'); $this->name = $name; $this->debug = @$conf['debug']; } /** * Implements the basic default security check of calling * an access method. * * @param string $arg * @return bool * True indicates allowed access. */ public function access($arg) { $obj_access = TRUE; $f = @$this->conf['access callback']; if ($arg) { if ($f && is_callable($f)) { $obj_access = $f($arg); } elseif (isset($this->conf['access block'])) { $block = @$this->conf['access block']; $path=''; if (isset($this->conf['access path'])) $path = $this->conf['access path']; $obj_access = $this->dataManager()->blockAccess($block, $path, $arg); } } return $obj_access; } protected function loadBlockFromFile($block_name) { $full_name = $this->name . '/' . $block_name; $php_class = $block_name; if ($this->fileSvc->exists($block_name . '.sql')) { $contents = $this->fileSvc->contents($block_name . '.sql'); $block = $this->parseSQLFile($contents); $block['type'] = 'sql'; } elseif ($this->fileSvc->exists($block_name . '.xml')) { $contents = $this->fileSvc->contents($block_name . '.xml'); $block = $this->parseXMLFile($contents); $block['type'] = 'xml'; } elseif ($this->fileSvc->exists($block_name . '.php')) { $php_file = $this->fileSvc->path($php_class . '.php'); include_once $php_file; if (class_exists($php_class)) { $o = new $php_class(); $block['type'] = 'php'; $block['access'] = @$o->access; $block['object'] = $o; if (method_exists($o, 'tokens' )) { $block['tokens'] = $o->tokens(); } elseif (isset($o->tokens)) { $block['tokens'] = $o->tokens; } else{ $block['tokens'] = array(); } } } else { return array(); } $block['locked']=1; return $block; } /** * Load blcok data from filesystem * @param $block_name * @return array * Block definition. */ function loadBlock($block_name, $include=FALSE) { if ($include) $this->block_name = $block_name; $block = $this->loadBlockFromFile($block_name); return $block; } /** * Load tokens from block source */ public function tokens($source) { $tokens = array(); // If we have a regular expression token parser, then get the tokens out of the block. if ($this->te) { $tokens = @$this->te->tokens($source); $tokens = array_diff($tokens, array('current_user')); //check tokens in the where clause } return $tokens; } /** * Return data based on block definition. * * @param array $block * Block definition. * @param bool|FALSE $raw_mode * True to reutrn raw record/states or data structures * @return string */ public function data(Array $block, $raw_mode=FALSE) { $xml = ''; $right = @$block['access']; if ($block && $this->access($right)) { if ($raw_mode) $block['options']['return_type'] = 'raw'; switch ($block['type']) { case 'sql': $xml = $this->sqlData($block['source'], @$block['options']); break; case 'xml': $xml = $this->xmlData($block['source']); break; case 'php': $data = $this->dataManager()->dataSvc->currentContextArray(); $xml = $this->phpData($block['object'], $data ); break; } } return $xml; } /** * @param $search * @param $data_blocks * @TODO: Determine whether we still need this. */ public function listDBBlocks($search, &$data_blocks) { $search = '%' . $search . '%'; $sql = 'SELECT * from {forena_data_blocks} WHERE repository=:repos AND block_name like :search '; $rs = db_query($sql, array(':repos' => $this->name, ':search' => $search )); foreach ($rs as $block) { $data_blocks[] = $block->block_name; } } /** * Find all the blocks matching a provided search string * * @param string $search * partial block names to search for * @param array $block_list * List of blocks to build * @param string $subdir * Subdirectory being examined. Used primarily for recursion. * @TODO: MOve toDat Manageer */ public function listDataBlocks($search, &$block_list, $subdir='') { $count=0; // First find files that match the search string $path = $this->fileSvc->includes[0] . '/'; if ($subdir) $path .= $subdir . '/'; $block_path = $path . '*' . $search . '*'; // Find sql files // @TODO: Refactor to use file service to list files $d = glob($block_path); if ($d) foreach ($d as $file_name) { // Split off the extention $p = strripos($file_name, '.'); if ($p!==FALSE) { $ext = substr($file_name, $p+1); $block_name = substr($file_name, 0, $p); } else { $ext = ''; $block_name = $file_name; } switch ($ext) { case 'inc': require_once $file_name; $class = str_replace($path, '', $block_name); $methods = get_class_methods($class); if ($methods) foreach ($methods as $method) { if ($method != 'tokens') { $block_list[] = $class . '.' . $method; } } break; default: if (array_search($ext, $this->block_extensions)!==FALSE) { $block_list[] = str_replace($apth . '/', '', $block_name); } } } $count++; // Find directories $d = glob($path . '*'); if ($d) foreach ($d as $dir_name) { if (is_dir($dir_name)) { $dir_name = str_replace($path . '/', '', $dir_name); $this->listDataBlocks($search, $block_list, $dir_name); } } // Date if (!$subdir && \Drupal::moduleHandler()->moduleExists('forena_query')) { $this->listDBBlocks($search, $block_list); } } /** * Parse XML File contents into contents. * @param $contents * @return array */ public function parseXMLFile($contents) { $comment = $this->comment_prefix; $trim = '->'; $lines = explode("\n", $contents); $cnt = count($lines); $access = ''; $i=0; $block = ''; $data = ''; while ($i<$cnt) { $l = trim($lines[$i], "\r"); @list($d, $c) = explode($comment, $l, 2); if ($trim) $c = trim($c, $trim); if ($c) { list($a, $o) = explode('=', $c, 2); $a = trim($a); if ($a && $o) { switch ($a) { case 'ACCESS': $access = trim($o); break; default: } } } if (strpos($l, $comment)!==0) { $data .= "$l\n"; } $i++; } return array('access' => $access, 'source' => $data, 'tokens' => $this->tokens($data)); } public function getSQLInclude($block_name) { //@TODO: allow relative block includes $block = $this->loadBlock($block_name, TRUE); if ($block && $block['type'] == 'sql') { return $block; } else { $this->app()->error("Include $block_name.sql not found"); return NULL; } } /** * Break the contents of a sql file down to its source. * @param $contents * @return array */ public function parseSQLFile($contents) { $comment = $this->comment_prefix; $trim = $this->comment_suffix; $lines = explode("\n", $contents); $cnt = count($lines); $access = ''; $i=0; $data = ''; $file = ''; $skip = FALSE; $in_info = FALSE; $found_case = FALSE; $info_text = ''; $tokens = array(); $options = array(); $switch = ''; while ($i<$cnt) { $l = trim($lines[$i], "\r"); @list($d, $c) = explode($comment, $l, 2); if ($trim) $c = trim($c, $trim); if ($c) { $c = trim($c); @list($a, $o) = explode('=', $c, 2); $a = trim($a); if (($a && $o) || $c == 'END' || $c == 'ELSE' || $c == 'INFO') { if ($c != 'INFO' ) { $in_info = false; } switch ($a) { case 'ACCESS': $access = trim($o); break; case 'SWITCH': $switch = trim($o); $found_case = FALSE; break; case 'CASE': $match = $this->te->replace($switch, TRUE) == $this->te->replace($o); if ($match) $found_case = TRUE; $skip = !$match; break; case 'IF': $skip = !$this->te->test(trim($o)); break; case 'END': $skip = FALSE; $switch = ''; break; case 'ELSE': $skip = $switch ? $found_case : !$skip; break; case 'INFO': $in_info = TRUE; break; case 'INCLUDE': if (!$skip) { $inc_block = $this->getSQLInclude(trim($o)); if ($inc_block) { $data .= $inc_block['source']; $tokens = array_merge($tokens, $inc_block['tokens']); } } break; } } if ($a != 'ACCESS') $file .= "$l\n"; } else { $file .= "$l\n"; } if ($in_info) { if (strpos($l, $comment)!==0 && $l) $info_text .= "$l\n"; } elseif (!$skip) { if (strpos($l, $comment)!==0 && $l) { $data .= "$l\n"; } } $i++; } $tokens = array_merge($tokens, $this->tokens($contents)); if ($info_text) { $parser = new Parser(); try { $options = $parser->parse($info_text); } catch (Exception $e) { $options = []; } } $block = array( 'source' => $data, 'file' => trim($file, " \n"), 'tokens' => $tokens, 'options' => $options, 'access' => $access); return $block; } /** * Implement static XML functioin * @param string $xmlData * XML Source data from block load * @return SimpleXMLElement * XML node */ public function xmlData($xmlData) { $xml =''; if (trim($xmlData)) { try { $xml = new SimpleXMLElement($xmlData); } catch (\Exception $e) { $this->app()->error("Error processing xml\n", $e->getMessage() . "\n" . $xmlData); } } return $xml; } public function phpData($o, $parameters) { $data = NULL; if (is_object($o) && is_callable(array($o, 'data'))) { $data = $o->data($parameters); } return $data; } /** * Build the SQL clause based on builder data. * @param $data * @return string * Where clause for SQL. */ public function buildFilterSQL($data) { $clause = ''; $op = $data['op']; $i=0; if ($data['filter']) foreach ($data['filter'] as $cond) { $i++; $conj = ($i==1) ? '' : $op . ' '; if (isset($cond['filter'])) { $clause .= $conj . ' (' . $this->buildFilterSQL($cond) . " )\n"; } else { switch ($cond['op']) { case 'IS NULL': case 'IS NOT NULL': $expr = $cond['field'] . ' ' . $cond['op']; break; default: $expr = $cond['field'] . ' ' . $cond['op'] . ' ' . $this->format($cond['value'], $cond['field'], array()); } $clause .= ' ' . $conj . $expr; } } return $clause; } /** * Perform generic type conversion based on attributes. * @param string $key * The token key for parameter replacement. * @param string $value * The value of the parameter * @return mixed * Value of the parameter. */ public function parmConvert($key, $value) { if (isset($this->types[$key]) && $this->types[$key]) { if ($value === NULL || $value ==='') { $value = NULL; } else { switch (strtolower($this->types[$key])) { case 'date': $time = @new \DateTime($value); if ($time) { $value = date_format($time, 'Y-m-d H:i:s'); } else { $value = NULL; } break; case 'unixtime': $time = @new \DateTime($value); if ($time) { $value = $time->getTimestamp(); } else { $value = NULL; } break; case 'numeric': case 'float': $value = (float)$value; break; case 'int': case 'integer': $value = (int)$value; break; case 'array': $value = (array)$value; break; } } } return $value; } /** * Method to return an array of tables that start with the string * indicated in $str * @param string $str * Partial table Name to search. * @return array * Array of tables to search. : */ public function searchTables($str) { return array(); } public function searchTableColumns($table, $str) { return array(); } public function searchTablesSQL() { switch ($this->db_type) { CASE 'mysql': $sql = "SHOW TABLES LIKE :str"; break; CASE 'postgres': CASE 'postgresql': CASE 'pgsql': $sql = "SELECT tablename from ( SELECT schemaname, tablename FROM pg_catalog.pg_tables UNION SELECT schemaname, viewname from pg_catalog.pg_views) v where schemaname NOT IN ('pg_catalog', 'information_schema') and tablename like :str order by 1"; break; CASE 'oracle': CASE 'oci': $sql = "SELECT object_name FROM all_objects where object_type in ('TABLE','VIEW') AND owner not in ('SYS') AND object_name LIKE :str"; break; CASE 'mssql': $sql = "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' and table_name like :str"; break; CASE 'sqlite': $sql = 'SELECT name FROM sqlite_master WHERE name like :str'; break; default: $this->app()->error($this->app()->t('Unknown database type: %s', array('%s' => $this->db_type)),'error'); } return $sql; } public function searchTableColumnsSQL() { switch ($this->db_type) { CASE 'mysql': $sql = "select column_name from information_schema.COLUMNS where table_schema = :database AND table_name = :table AND column_name like :str"; break; CASE 'postgres': CASE 'postgresql': CASE 'pgsql': $sql = "SELECT column_name from information_schema.columns WHERE table_catalog = :database AND table_name = :table AND column_name like :str order by 1"; break; CASE 'oracle': CASE 'oci': $sql = "SELECT column_name FROM all_tab_columns where table_name = :table_name AND column_name LIKE :str"; break; CASE 'mssql': $sql = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = :table and column_name like :str"; break; CASE 'sqlite': $sql = 'PRAGMA table_info(:table)'; break; default: $this->app()->error($this->app()->t('Unknown database type: %s', array('%s' => $this->db_type)),'error'); } return $sql; } }